Skip to content

Commit

Permalink
Merge pull request #849 from olim88/treasure-chest-highlighter
Browse files Browse the repository at this point in the history
Treasure Chest Highlighter for crystal hollows
  • Loading branch information
kevinthegreat1 authored Aug 4, 2024
2 parents 9b4ca8c + 2037089 commit bb356d2
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/main/java/de/hysky/skyblocker/SkyblockerMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public void onInitializeClient() {
LowerSensitivity.init();
CrystalsLocationsManager.init();
WishingCompassSolver.init();
CrystalsChestHighlighter.init();
MetalDetector.init();
ChatMessageListener.init();
Shortcuts.init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@
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) {
Expand Down Expand Up @@ -111,7 +110,23 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import dev.isxander.yacl3.config.v2.api.SerialEntry;

import java.awt.*;

public class MiningConfig {
@SerialEntry
public boolean enableDrillFuel = true;
Expand Down Expand Up @@ -67,6 +69,12 @@ public static class CrystalHollows {

@SerialEntry
public boolean nucleusWaypoints = false;

@SerialEntry
public boolean chestHighlighter = true;

@SerialEntry
public Color chestHighlightColor = new Color(0, 0, 255, 128);
}

public static class CrystalsHud {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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;
Expand Down Expand Up @@ -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))
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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;
Expand All @@ -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);

}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
14 changes: 13 additions & 1 deletion src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,24 @@ private static void renderBeaconBeam(WorldRenderContext context, BlockPos pos, f
* 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();
Tessellator tessellator = RenderSystem.renderThreadTesselator();

RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram);
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
RenderSystem.enableBlend();
RenderSystem.lineWidth(lineWidth);
RenderSystem.disableCull();
RenderSystem.enableDepthTest();
Expand All @@ -135,12 +146,13 @@ public static void renderOutline(WorldRenderContext context, Box box, float[] co
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);
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/assets/skyblocker/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit bb356d2

Please sign in to comment.