diff options
Diffstat (limited to 'src')
8 files changed, 233 insertions, 8 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index b1d25a01..d5b8204f 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -134,6 +134,7 @@ public class SkyblockerMod implements ClientModInitializer { LowerSensitivity.init(); CrystalsLocationsManager.init(); WishingCompassSolver.init(); + CrystalsChestHighlighter.init(); MetalDetector.init(); ChatMessageListener.init(); Shortcuts.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java index 796a6105..74be78cb 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java @@ -4,17 +4,16 @@ import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.MiningConfig; import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudConfigScreen; -import dev.isxander.yacl3.api.ButtonOption; -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.OptionGroup; import de.hysky.skyblocker.skyblock.dwarven.DwarvenHudConfigScreen; +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.controller.ColorControllerBuilder; import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder; import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; +import java.awt.*; + public class MiningCategory { public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig config) { @@ -111,7 +110,23 @@ public class MiningCategory { newValue -> config.mining.crystalHollows.nucleusWaypoints = newValue) .controller(ConfigUtils::createBooleanController) .build()) - .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.mining.crystalHollows.chestHighlighter")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.crystalHollows.chestHighlighter.@Tooltip"))) + .binding(defaults.mining.crystalHollows.chestHighlighter, + () -> config.mining.crystalHollows.chestHighlighter, + newValue -> config.mining.crystalHollows.chestHighlighter = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Color>createBuilder() + .name(Text.translatable("skyblocker.config.mining.crystalHollows.chestHighlighter.color")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.crystalHollows.chestHighlighter.color.@Tooltip"))) + .binding(defaults.mining.crystalHollows.chestHighlightColor, + () -> config.mining.crystalHollows.chestHighlightColor, + newValue -> config.mining.crystalHollows.chestHighlightColor = newValue) + .controller(v -> ColorControllerBuilder.create(v).allowAlpha(true)) + .build()) + .build()) //Crystal Hollows Map .group(OptionGroup.createBuilder() diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java index dcf70f24..5236f1eb 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java @@ -2,6 +2,8 @@ package de.hysky.skyblocker.config.configs; import dev.isxander.yacl3.config.v2.api.SerialEntry; +import java.awt.*; + public class MiningConfig { @SerialEntry public boolean enableDrillFuel = true; @@ -67,6 +69,12 @@ public class MiningConfig { @SerialEntry public boolean nucleusWaypoints = false; + + @SerialEntry + public boolean chestHighlighter = true; + + @SerialEntry + public Color chestHighlightColor = new Color(0, 0, 255, 128); } public static class CrystalsHud { diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index c0cd8b7b..4e263015 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -10,6 +10,7 @@ import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.dwarven.WishingCompassSolver; +import de.hysky.skyblocker.skyblock.dwarven.CrystalsChestHighlighter; import de.hysky.skyblocker.skyblock.end.BeaconHighlighter; import de.hysky.skyblocker.skyblock.end.EnderNodes; import de.hysky.skyblocker.skyblock.end.TheEnd; @@ -79,6 +80,7 @@ public abstract class ClientPlayNetworkHandlerMixin { @Inject(method = "onPlaySound", at = @At("RETURN")) private void skyblocker$onPlaySound(PlaySoundS2CPacket packet, CallbackInfo ci) { FishingHelper.onSound(packet); + CrystalsChestHighlighter.onSound(packet); } @WrapWithCondition(method = "warnOnUnknownPayload", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false)) @@ -100,6 +102,7 @@ public abstract class ClientPlayNetworkHandlerMixin { private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) { MythologicalRitual.onParticle(packet); DojoManager.onParticle(packet); + CrystalsChestHighlighter.onParticle(packet); EnderNodes.onParticle(packet); WishingCompassSolver.onParticle(packet); } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java index a2d7887b..fdc199e8 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.mixins; import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; import de.hysky.skyblocker.skyblock.dungeon.device.SimonSays; +import de.hysky.skyblocker.skyblock.dwarven.CrystalsChestHighlighter; import de.hysky.skyblocker.utils.Utils; import net.minecraft.block.BlockState; import net.minecraft.client.world.ClientWorld; @@ -25,7 +26,10 @@ public class ClientWorldMixin { if (Utils.isInCrimson()) { DojoManager.onBlockUpdate(pos.toImmutable(), state); } - + else if (Utils.isInCrystalHollows()) { + CrystalsChestHighlighter.onBlockUpdate(pos.toImmutable(), state); + } SimonSays.onBlockUpdate(pos, state); + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java new file mode 100644 index 00000000..85d17e5f --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java @@ -0,0 +1,178 @@ +package de.hysky.skyblocker.skyblock.dwarven; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.render.RenderHelper; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +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.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.network.packet.s2c.play.ParticleS2CPacket; +import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.text.Text; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; + +import java.util.ArrayList; +import java.util.List; + +public class CrystalsChestHighlighter { + + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final String CHEST_SPAWN_MESSAGE = "You uncovered a treasure chest!"; + private static final long MAX_PARTICLE_LIFE_TIME = 250; + private static final Vec3d LOCK_HIGHLIGHT_SIZE = new Vec3d(0.1, 0.1, 0.1); + + private static int waitingForChest = 0; + private static final List<BlockPos> activeChests = new ArrayList<>(); + private static final Object2LongOpenHashMap<Vec3d> activeParticles = new Object2LongOpenHashMap<>(); + private static int currentLockCount = 0; + private static int neededLockCount = 0; + + public static void init() { + ClientReceiveMessageEvents.GAME.register(CrystalsChestHighlighter::extractLocationFromMessage); + WorldRenderEvents.AFTER_TRANSLUCENT.register(CrystalsChestHighlighter::render); + ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset()); + } + + private static void reset() { + waitingForChest = 0; + activeChests.clear(); + activeParticles.clear(); + currentLockCount = 0; + } + + private static void extractLocationFromMessage(Text text, boolean b) { + if (!Utils.isInCrystalHollows() || !SkyblockerConfigManager.get().mining.crystalHollows.chestHighlighter) { + return; + } + //if a chest is spawned add chest to look for + if (text.getString().matches(CHEST_SPAWN_MESSAGE)) { + waitingForChest += 1; + } + } + + /** + * When a block is updated in the crystal hollows if looking for a chest see if it's a chest and if so add to active. or remove active chests from where air is placed + * + * @param pos location of block update + * @param state the new state of the block + */ + public static void onBlockUpdate(BlockPos pos, BlockState state) { + if (!SkyblockerConfigManager.get().mining.crystalHollows.chestHighlighter || CLIENT.player == null) { + return; + } + if (waitingForChest > 0 && state.isOf(Blocks.CHEST)) { + //make sure it is not too far from the player (more than 10 blocks away) + if (pos.getSquaredDistance(CLIENT.player.getPos()) > 100) { + return; + } + activeChests.add(pos); + currentLockCount = 0; + waitingForChest -= 1; + } else if (state.isAir() && activeChests.contains(pos)) { + currentLockCount = 0; + activeChests.remove(pos); + } + } + + /** + * When a particle is spawned add that particle to active particles if correct for lock picking + * + * @param packet particle spawn packet + */ + public static void onParticle(ParticleS2CPacket packet) { + if (!Utils.isInCrystalHollows() || !SkyblockerConfigManager.get().mining.crystalHollows.chestHighlighter) { + return; + } + if (ParticleTypes.CRIT.equals(packet.getParameters().getType())) { + activeParticles.put(new Vec3d(packet.getX(), packet.getY(), packet.getZ()), System.currentTimeMillis()); + } + } + + /** + * Updates {@link CrystalsChestHighlighter#currentLockCount} and clears {@link CrystalsChestHighlighter#activeParticles} based on lock pick related sound events. + * + * @param packet sound packet + */ + public static void onSound(PlaySoundS2CPacket packet) { + if (!Utils.isInCrystalHollows() || !SkyblockerConfigManager.get().mining.crystalHollows.chestHighlighter) { + return; + } + String path = packet.getSound().value().getId().getPath(); + //lock picked sound + if (path.equals("entity.experience_orb.pickup") && packet.getPitch() == 1) { + currentLockCount += 1; + activeParticles.clear(); + //lock pick fail sound + } else if (path.equals("entity.villager.no")) { + currentLockCount = 0; + activeParticles.clear(); + //lock pick finish sound + } else if (path.equals("block.chest.open")) { + //set the needed lock count to the current, so we know how many locks a chest has + neededLockCount = currentLockCount; + activeParticles.clear(); + } + } + + /** + * If enabled, renders a box around active treasure chests, taking the color from the config. + * Additionally, calculates and displaces the highlight to indicate lock-picking spots on chests. + * Finally, renders text showing how many lock picks the player has done + * + * @param context context + */ + private static void render(WorldRenderContext context) { + if (!Utils.isInCrystalHollows() || !SkyblockerConfigManager.get().mining.crystalHollows.chestHighlighter) { + return; + } + //render chest outline + float[] color = SkyblockerConfigManager.get().mining.crystalHollows.chestHighlightColor.getComponents(new float[]{0, 0, 0, 0}); + for (BlockPos chest : activeChests) { + RenderHelper.renderOutline(context, Box.of(chest.toCenterPos().subtract(0, 0.0625, 0), 0.885, 0.885, 0.885), color, color[3], 3, false); + } + + //render lock picking if player is looking at chest that is in the active chests list + if (CLIENT.player == null) { + return; + } + HitResult target = CLIENT.crosshairTarget; + if (target instanceof BlockHitResult blockHitResult && activeChests.contains(blockHitResult.getBlockPos())) { + Vec3d chestPos = blockHitResult.getBlockPos().toCenterPos(); + + if (!activeParticles.isEmpty()) { + //the player is looking at a chest use active particle to highlight correct spot + Vec3d highlightSpot = Vec3d.ZERO; + + //if to old remove particle + activeParticles.entrySet().removeIf(e -> System.currentTimeMillis() - e.getValue() > MAX_PARTICLE_LIFE_TIME); + + //add up all particle within range of active block + for (Vec3d particlePos : activeParticles.keySet()) { + if (particlePos.squaredDistanceTo(chestPos) <= 0.75) { + highlightSpot = highlightSpot.add(particlePos); + } + } + + //render the spot + highlightSpot = highlightSpot.multiply((double) 1 / activeParticles.size()).subtract(LOCK_HIGHLIGHT_SIZE.multiply(0.5)); + RenderHelper.renderFilled(context, highlightSpot, LOCK_HIGHLIGHT_SIZE, color, color[3], true); + } + + //render total text if needed is more than 0 + if (neededLockCount <= 0) { + return; + } + RenderHelper.renderText(context, Text.literal(Math.min(currentLockCount, neededLockCount) + "/" + neededLockCount).withColor(SkyblockerConfigManager.get().mining.crystalHollows.chestHighlightColor.getRGB()), chestPos, true); + } + } +}
\ No newline at end of file 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 20d8157a..ad5811f1 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java @@ -119,6 +119,16 @@ public class RenderHelper { * This does not use renderer since renderer draws outline using debug lines with a fixed width. */ public static void renderOutline(WorldRenderContext context, Box box, float[] colorComponents, float lineWidth, boolean throughWalls) { + renderOutline(context, box, colorComponents, 1f, lineWidth, throughWalls); + } + + /** + * Renders the outline of a box with the specified color components and line width. + * This does not use renderer since renderer draws outline using debug lines with a fixed width. + * + * @param alpha the transparency of the lines for the box + */ + public static void renderOutline(WorldRenderContext context, Box box, float[] colorComponents, float alpha, float lineWidth, boolean throughWalls) { if (FrustumUtils.isVisible(box)) { MatrixStack matrices = context.matrixStack(); Vec3d camera = context.camera().getPos(); @@ -126,6 +136,7 @@ public class RenderHelper { RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram); RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + RenderSystem.enableBlend(); RenderSystem.lineWidth(lineWidth); RenderSystem.disableCull(); RenderSystem.enableDepthTest(); @@ -135,12 +146,13 @@ public class RenderHelper { matrices.translate(-camera.getX(), -camera.getY(), -camera.getZ()); BufferBuilder buffer = tessellator.begin(DrawMode.LINES, VertexFormats.LINES); - WorldRenderer.drawBox(matrices, buffer, box, colorComponents[0], colorComponents[1], colorComponents[2], 1f); + WorldRenderer.drawBox(matrices, buffer, box, colorComponents[0], colorComponents[1], colorComponents[2], alpha); BufferRenderer.drawWithGlobalProgram(buffer.end()); matrices.pop(); RenderSystem.lineWidth(1f); RenderSystem.enableCull(); + RenderSystem.disableBlend(); RenderSystem.disableDepthTest(); RenderSystem.depthFunc(GL11.GL_LEQUAL); } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 72b89464..3707bab9 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -486,6 +486,10 @@ "skyblocker.config.mining.crystalHollows.metalDetectorHelper.@Tooltip": "Helper for the metal detector puzzle in the Mines of Divan.", "skyblocker.config.mining.crystalHollows.nucleusWaypoints": "Nucleus Waypoints", "skyblocker.config.mining.crystalHollows.nucleusWaypoints.@Tooltip": "Show waypoints to the Nucleus in the Crystal Hollows.", + "skyblocker.config.mining.crystalHollows.chestHighlighter": "Treasure Chest Highlighter", + "skyblocker.config.mining.crystalHollows.chestHighlighter.@Tooltip": "Highlight found treasure chests and lock pick locations when powder mining.", + "skyblocker.config.mining.crystalHollows.chestHighlighter.color": "Chest Highlight Color", + "skyblocker.config.mining.crystalHollows.chestHighlighter.color.@Tooltip": "What color the treasure chests / lock pick should be highlighted.", "skyblocker.config.mining.crystalsHud": "Crystal Hollows Map", "skyblocker.config.mining.crystalsHud.enabled": "Enabled", |
