aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java83
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java51
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java221
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java129
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderSolver.java202
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java15
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java35
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java65
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java249
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java13
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java11
20 files changed, 1130 insertions, 28 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 71623ea7..1aa97526 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -8,6 +8,7 @@ import de.hysky.skyblocker.skyblock.*;
import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra;
import de.hysky.skyblocker.skyblock.dungeon.*;
import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder.Boulder;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.CreeperBeams;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonBlaze;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.TicTacToe;
@@ -17,6 +18,7 @@ import de.hysky.skyblocker.skyblock.dungeon.secrets.SecretsTracker;
import de.hysky.skyblocker.skyblock.dwarven.CrystalsHud;
import de.hysky.skyblocker.skyblock.dwarven.CrystalsLocationsManager;
import de.hysky.skyblocker.skyblock.dwarven.DwarvenHud;
+import de.hysky.skyblocker.skyblock.end.TheEnd;
import de.hysky.skyblocker.skyblock.end.BeaconHighlighter;
import de.hysky.skyblocker.skyblock.item.*;
import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview;
@@ -121,6 +123,7 @@ public class SkyblockerMod implements ClientModInitializer {
FireFreezeStaffTimer.init();
GuardianHealth.init();
TheRift.init();
+ TheEnd.init();
SearchOverManager.init();
TitleContainer.init();
ScreenMaster.init();
@@ -135,6 +138,7 @@ public class SkyblockerMod implements ClientModInitializer {
SpecialEffects.init();
ItemProtection.init();
CreeperBeams.init();
+ Boulder.init();
ItemRarityBackgrounds.init();
MuseumItemCache.init();
SecretsTracker.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java
new file mode 100644
index 00000000..c33e2f54
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java
@@ -0,0 +1,83 @@
+package de.hysky.skyblocker.config;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
+import it.unimi.dsi.fastutil.ints.IntIntPair;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.Text;
+
+import java.awt.*;
+
+public abstract class HudConfigScreen extends Screen {
+ private final Widget widget;
+ private final Screen parent;
+
+ private int hudX = 0;
+ private int hudY = 0;
+ public HudConfigScreen(Text title, Widget widget, Screen parent) {
+ super(title);
+ this.widget = widget;
+ this.parent = parent;
+
+ int[] posFromConfig = getPosFromConfig(SkyblockerConfigManager.get());
+ hudX = posFromConfig[0];
+ hudY = posFromConfig[1];
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ super.render(context, mouseX, mouseY, delta);
+ renderBackground(context, mouseX, mouseY, delta);
+ renderWidget(context, hudX, hudY);
+ context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, height / 2, Color.GRAY.getRGB());
+ }
+
+ @Override
+ public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
+ IntIntPair dims = getDimensions();
+ if (RenderHelper.pointIsInArea(mouseX, mouseY, hudX, hudY, hudX + dims.leftInt(), hudY + dims.rightInt()) && button == 0) {
+ hudX = (int) Math.max(Math.min(mouseX - (double) dims.leftInt() / 2, this.width - dims.leftInt()), 0);
+ hudY = (int) Math.max(Math.min(mouseY - (double) dims.rightInt() / 2, this.height - dims.rightInt()), 0);
+ }
+ return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (button == 1) {
+ IntIntPair dims = getDimensions();
+ hudX = this.width / 2 - dims.leftInt();
+ hudY = this.height / 2 - dims.rightInt();
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ abstract protected int[] getPosFromConfig(SkyblockerConfig config);
+
+ protected IntIntPair getDimensions() {
+ return new IntIntImmutablePair(widget.getHeight(), widget.getWidth());
+ }
+
+ @Override
+ public void close() {
+ SkyblockerConfig skyblockerConfig = SkyblockerConfigManager.get();
+ savePos(skyblockerConfig, hudX, hudY);
+ SkyblockerConfigManager.save();
+
+ client.setScreen(parent);
+ }
+
+ /**
+ * This method should save the passed position to the config
+ * <p>
+ * NOTE: The parent class will call {@link SkyblockerConfigManager#save()} right after this method
+ * @param configManager the config so you don't have to get it
+ * @param x x
+ * @param y y
+ */
+ abstract protected void savePos(SkyblockerConfig configManager, int x, int y);
+
+ abstract protected void renderWidget(DrawContext context, int x, int y);
+}
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 175c3bdf..b5ddcf5d 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -175,6 +175,9 @@ public class SkyblockerConfig {
public boolean dontStripSkinAlphaValues = true;
@SerialEntry
+ public boolean dungeonQuality = true;
+
+ @SerialEntry
public boolean visitorHelper = true;
@SerialEntry
@@ -652,6 +655,9 @@ public class SkyblockerConfig {
public Rift rift = new Rift();
@SerialEntry
+ public TheEnd end = new TheEnd();
+
+ @SerialEntry
public SpidersDen spidersDen = new SpidersDen();
@SerialEntry
@@ -714,6 +720,9 @@ public class SkyblockerConfig {
public boolean solveWaterboard = true;
@SerialEntry
+ public boolean solveBoulder = true;
+
+ @SerialEntry
public boolean fireFreezeStaffTimer = true;
@SerialEntry
@@ -1042,6 +1051,24 @@ public class SkyblockerConfig {
public int mcGrubberStacks = 0;
}
+ public static class TheEnd {
+
+ @SerialEntry
+ public boolean hudEnabled = true;
+
+ @SerialEntry
+ public boolean enableBackground = true;
+
+ @SerialEntry
+ public boolean waypoint = true;
+
+ @SerialEntry
+ public int x = 10;
+
+ @SerialEntry
+ public int y = 10;
+ }
+
public static class SpidersDen {
@SerialEntry
public Relics relics = new Relics();
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
index 3b685f9a..5eb9a066 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -409,6 +409,14 @@ public class DungeonsCategory {
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveBoulder"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveBoulder.@Tooltip")))
+ .binding(defaults.locations.dungeons.solveBoulder,
+ () -> config.locations.dungeons.solveBoulder,
+ newValue -> config.locations.dungeons.solveBoulder = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.fireFreezeStaffTimer"))
.description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.fireFreezeStaffTimer.@Tooltip")))
.binding(defaults.locations.dungeons.fireFreezeStaffTimer,
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
index bb333f79..8a7d832c 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
@@ -79,6 +79,14 @@ public class GeneralCategory {
.flag(OptionFlag.ASSET_RELOAD)
.build())
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.general.dungeonQuality"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.dungeonQuality.@Tooltip")))
+ .binding(defaults.general.dungeonQuality,
+ () -> config.general.dungeonQuality,
+ newValue -> config.general.dungeonQuality = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.general.visitorHelper"))
.binding(defaults.general.visitorHelper,
() -> config.general.visitorHelper,
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java
index 9bdcf2e9..75c83a9b 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java
@@ -2,11 +2,11 @@ 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.OptionDescription;
-import dev.isxander.yacl3.api.OptionGroup;
+import de.hysky.skyblocker.skyblock.end.EndHudConfigScreen;
+import de.hysky.skyblocker.skyblock.end.TheEnd;
+import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder;
+import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
public class LocationsCategory {
@@ -79,6 +79,49 @@ public class LocationsCategory {
.build())
.build())
+ // The end
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end"))
+ .collapsed(false)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.hudEnabled"))
+ .binding(defaults.locations.end.hudEnabled,
+ () -> config.locations.end.hudEnabled,
+ newValue -> config.locations.end.hudEnabled = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enableBackground")) // Reusing that string cuz sure
+ .binding(defaults.locations.end.enableBackground,
+ () -> config.locations.end.enableBackground,
+ newValue -> config.locations.end.enableBackground = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.waypoint"))
+ .binding(defaults.locations.end.waypoint,
+ () -> config.locations.end.waypoint,
+ newValue -> config.locations.end.waypoint = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.screen"))
+ .text(Text.translatable("text.skyblocker.open")) // Reusing again lol
+ .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new EndHudConfigScreen(screen)))
+ .build())
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.resetName"))
+ .text(Text.translatable("text.autoconfig.skyblocker.option.locations.end.resetText"))
+ .action((screen, opt) -> {
+ TheEnd.zealotsKilled = 0;
+ TheEnd.zealotsSinceLastEye = 0;
+ TheEnd.eyes = 0;
+ })
+ .build())
+ .build()
+
+ )
+
//Spider's Den
.group(OptionGroup.createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.spidersDen"))
diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
index 1f56fe34..4c414212 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
@@ -4,6 +4,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import de.hysky.skyblocker.skyblock.FishingHelper;
+import de.hysky.skyblocker.skyblock.end.TheEnd;
import de.hysky.skyblocker.skyblock.dungeon.DungeonScore;
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.skyblock.end.BeaconHighlighter;
@@ -52,12 +53,6 @@ public abstract class ClientPlayNetworkHandlerMixin {
return !Utils.isOnHypixel();
}
- @ModifyExpressionValue(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;"))
- private Entity skyblocker$onEntityDeath(Entity entity, @Local(argsOnly = true) EntityStatusS2CPacket packet) {
- if (packet.getStatus() == EntityStatuses.PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES) DungeonScore.handleEntityDeath(entity);
- return entity;
- }
-
@WrapWithCondition(method = "onPlayerList", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
private boolean skyblocker$cancelPlayerListWarning(Logger instance, String format, Object arg1, Object arg2) {
return !Utils.isOnHypixel();
@@ -87,4 +82,13 @@ public abstract class ClientPlayNetworkHandlerMixin {
private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) {
MythologicalRitual.onParticle(packet);
}
+
+ @ModifyExpressionValue(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;"))
+ private Entity skyblocker$onEntityDeath(Entity entity, @Local(argsOnly = true) EntityStatusS2CPacket packet) {
+ if (packet.getStatus() == EntityStatuses.PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES) {
+ DungeonScore.handleEntityDeath(entity);
+ TheEnd.onEntityDeath(entity);
+ }
+ return entity;
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java
new file mode 100644
index 00000000..878c8d0a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java
@@ -0,0 +1,221 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonPuzzle;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.Room;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import de.hysky.skyblocker.utils.render.title.Title;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.BlockView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Boulder extends DungeonPuzzle {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Boulder.class.getName());
+ private static final Boulder INSTANCE = new Boulder();
+ private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents();
+ private static final float[] ORANGE_COLOR_COMPONENTS = DyeColor.ORANGE.getColorComponents();
+ private static final int BASE_Y = 65;
+ static Vec3d[] linePoints;
+ static Box boundingBox;
+
+ private Boulder() {
+ super("boulder", "boxes-room");
+ }
+
+ public static void init() {
+ }
+
+ @Override
+ public void tick(MinecraftClient client) {
+
+ if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.solveBoulder || client.world == null || !DungeonManager.isCurrentRoomMatched()) {
+ return;
+ }
+
+ Room room = DungeonManager.getCurrentRoom();
+
+ BlockPos chestPos = new BlockPos(15, BASE_Y, 29);
+ BlockPos start = new BlockPos(25, BASE_Y, 25);
+ BlockPos end = new BlockPos(5, BASE_Y, 8);
+ // Create a target BoulderObject for the puzzle
+ BoulderObject target = new BoulderObject(chestPos.getX(), chestPos.getX(), chestPos.getZ(), "T");
+ // Create a BoulderBoard representing the puzzle's grid
+ BoulderBoard board = new BoulderBoard(8, 7, target);
+
+ // Populate the BoulderBoard grid with BoulderObjects based on block types in the room
+ int column = 1;
+ for (int z = start.getZ(); z > end.getZ(); z--) {
+ int row = 0;
+ for (int x = start.getX(); x > end.getX(); x--) {
+ if (Math.abs(start.getX() - x) % 3 == 1 && Math.abs(start.getZ() - z) % 3 == 1) {
+ String blockType = getBlockType(client.world, x, BASE_Y, z);
+ board.placeObject(column, row, new BoulderObject(x, BASE_Y, z, blockType));
+ row++;
+ }
+ }
+ if (row == board.getWidth()) {
+ column++;
+ }
+ }
+
+ // Generate initial game states for the A* solver
+ char[][] boardArray = board.getBoardCharArray();
+ List<BoulderSolver.GameState> initialStates = Arrays.asList(
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 0),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 1),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 2),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 3),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 4),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 5),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 6)
+ );
+
+ // Solve the puzzle using the A* algorithm
+ List<int[]> solution = BoulderSolver.aStarSolve(initialStates);
+
+ if (solution != null) {
+ linePoints = new Vec3d[solution.size()];
+ int index = 0;
+ // Convert solution coordinates to Vec3d points for rendering
+ for (int[] coord : solution) {
+ int x = coord[0];
+ int y = coord[1];
+ // Convert relative coordinates to actual coordinates
+ linePoints[index++] = Vec3d.ofCenter(room.relativeToActual(board.getObject3DPosition(x, y)));
+ }
+
+ BlockPos button = null;
+ if (linePoints != null && linePoints.length > 0) {
+ // Check for buttons along the path of the solution
+ for (int i = 0; i < linePoints.length - 1; i++) {
+ Vec3d point1 = linePoints[i];
+ Vec3d point2 = linePoints[i + 1];
+ button = checkForButtonBlocksOnLine(client.world, point1, point2);
+ if (button != null) {
+ // If a button is found, calculate its bounding box
+ boundingBox = getBlockBoundingBox(client.world, button);
+ break;
+ }
+ }
+ if (button == null){
+ // If no button is found along the path the puzzle is solved; reset the puzzle
+ reset();
+ }
+ }
+ } else {
+ // If no solution is found, display a title message and reset the puzzle
+ Title title = new Title("skyblocker.dungeons.puzzle.boulder.noSolution", Formatting.GREEN);
+ RenderHelper.displayInTitleContainerAndPlaySound(title, 15);
+ reset();
+ }
+ }
+
+ /**
+ * Retrieves the type of block at the specified position in the world.
+ * If the block is Birch or Jungle plank, it will return "B"; otherwise, it will return ".".
+ *
+ * @param world The client world.
+ * @param x The x-coordinate of the block.
+ * @param y The y-coordinate of the block.
+ * @param z The z-coordinate of the block.
+ * @return The type of block at the specified position.
+ */
+ public static String getBlockType(ClientWorld world, int x, int y, int z) {
+ Block block = world.getBlockState(DungeonManager.getCurrentRoom().relativeToActual(new BlockPos(x, y, z))).getBlock();
+ return (block == Blocks.BIRCH_PLANKS || block == Blocks.JUNGLE_PLANKS) ? "B" : ".";
+ }
+
+ /**
+ * Checks for blocks along the line between two points in the world.
+ * Returns the position of a block if it found a button on the line, if any.
+ *
+ * @param world The client world.
+ * @param point1 The starting point of the line.
+ * @param point2 The ending point of the line.
+ * @return The position of the block found on the line, or null if no block is found.
+ */
+ private static BlockPos checkForButtonBlocksOnLine(ClientWorld world, Vec3d point1, Vec3d point2) {
+ double x1 = point1.getX();
+ double y1 = point1.getY() + 1;
+ double z1 = point1.getZ();
+
+ double x2 = point2.getX();
+ double y2 = point2.getY() + 1;
+ double z2 = point2.getZ();
+
+ int steps = (int) Math.max(Math.abs(x2 - x1), Math.max(Math.abs(y2 - y1), Math.abs(z2 - z1)));
+
+ double xStep = (x2 - x1) / steps;
+ double yStep = (y2 - y1) / steps;
+ double zStep = (z2 - z1) / steps;
+
+
+ for (int step = 0; step <= steps; step++) {
+ double currentX = x1 + step * xStep;
+ double currentY = y1 + step * yStep;
+ double currentZ = z1 + step * zStep;
+
+ BlockPos blockPos = BlockPos.ofFloored(currentX, currentY, currentZ);
+ Block block = world.getBlockState(blockPos).getBlock();
+
+ if (block == Blocks.STONE_BUTTON) {
+ return blockPos;
+ }
+
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the bounding box of a block in the world.
+ *
+ * @param world The client world.
+ * @param pos The position of the block.
+ * @return The bounding box of the block.
+ */
+ public static Box getBlockBoundingBox(BlockView world, BlockPos pos) {
+ BlockState blockState = world.getBlockState(pos);
+ return blockState.getOutlineShape(world, pos).getBoundingBox().offset(pos);
+ }
+
+ @Override
+ public void render(WorldRenderContext context) {
+ if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.solveBoulder || !DungeonManager.isCurrentRoomMatched())
+ return;
+ float alpha = 1.0f;
+ float lineWidth = 5.0f;
+
+ if (linePoints != null && linePoints.length > 0) {
+ for (int i = 0; i < linePoints.length - 1; i++) {
+ Vec3d startPoint = linePoints[i];
+ Vec3d endPoint = linePoints[i + 1];
+ RenderHelper.renderLinesFromPoints(context, new Vec3d[]{startPoint, endPoint}, ORANGE_COLOR_COMPONENTS, alpha, lineWidth, true);
+ }
+ if (boundingBox != null) {
+ RenderHelper.renderOutline(context, boundingBox, RED_COLOR_COMPONENTS, 5, false);
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ linePoints = null;
+ boundingBox = null;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java
new file mode 100644
index 00000000..d29097e4
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java
@@ -0,0 +1,129 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder;
+
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
+
+/**
+ * Represents the game board for the Boulder puzzle, managing the grid of BoulderObjects.
+ * This class handles operations such as placing objects on the board, retrieving objects,
+ * and generating a character representation of the game board.
+ */
+public class BoulderBoard {
+ private final int height;
+ private final int width;
+ private final BoulderObject[][] grid;
+
+ /**
+ * Constructs a BoulderBoard with the specified height, width, and target BoulderObject.
+ *
+ * @param height The height of the board.
+ * @param width The width of the board.
+ * @param target The target BoulderObject that needs to be reached to solve the puzzle.
+ */
+ public BoulderBoard(int height, int width, BoulderObject target) {
+ this.height = height;
+ this.width = width;
+ this.grid = new BoulderObject[height][width];
+
+ int offsetX = target.x() - 23;
+ int y = 65;
+
+ for (int z = 0; z < width; z++) {
+ if (z == width / 2) {
+ grid[0][z] = target;
+ } else {
+ grid[0][z] = new BoulderObject(offsetX, y, z, "B");
+ }
+ grid[height - 1][z] = new BoulderObject(24 - (3 * z), y, 6, "P");
+ }
+ }
+
+ /**
+ * Retrieves the BoulderObject at the specified position on the board.
+ *
+ * @param x The x-coordinate of the position.
+ * @param y The y-coordinate of the position.
+ * @return The BoulderObject at the specified position, or null if no object is present.
+ */
+ public BoulderObject getObjectAtPosition(int x, int y) {
+ return isValidPosition(x, y) ? grid[x][y] : null;
+ }
+
+ /**
+ * Retrieves the 3D position of the BoulderObject at the specified position on the board.
+ *
+ * @param x The x-coordinate of the position.
+ * @param y The y-coordinate of the position.
+ * @return The BlockPos representing the 3D position of the BoulderObject,
+ * or null if no object is present at the specified position.
+ */
+ public BlockPos getObject3DPosition(int x, int y) {
+ BoulderObject object = getObjectAtPosition(x, y);
+ return (object != null) ? object.get3DPosition().offset(Direction.Axis.Y, -1) : null;
+ }
+
+ /**
+ * Places a BoulderObject at the specified position on the board.
+ *
+ * @param x The x-coordinate of the position.
+ * @param y The y-coordinate of the position.
+ * @param object The BoulderObject to place on the board.
+ */
+ public void placeObject(int x, int y, BoulderObject object) {
+ grid[x][y] = object;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Checks whether the specified position is valid within the bounds of the game board.
+ *
+ * @param x The x-coordinate of the position to check.
+ * @param y The y-coordinate of the position to check.
+ * @return {@code true} if the position is valid within the bounds of the board, {@code false} otherwise.
+ */
+ private boolean isValidPosition(int x, int y) {
+ return x >= 0 && y >= 0 && x < height && y < width;
+ }
+
+ /**
+ * Generates a character array representation of the game board.
+ * Each character represents a type of BoulderObject or an empty space.
+ *
+ * @return A 2D character array representing the game board.
+ */
+ public char[][] getBoardCharArray() {
+ char[][] boardCharArray = new char[height][width];
+ for (int x = 0; x < height; x++) {
+ for (int y = 0; y < width; y++) {
+ BoulderObject boulderObject = grid[x][y];
+ boardCharArray[x][y] = (boulderObject != null) ? boulderObject.type().charAt(0) : '.';
+ }
+ }
+ return boardCharArray;
+ }
+
+ /**
+ * Prints the current state of the game board to the console.
+ * Each character represents a type of BoulderObject or an empty space.
+ */
+ public String boardToString() {
+ StringBuilder sb = new StringBuilder();
+ for (int x = 0; x < height; x++) {
+ for (int y = 0; y < width; y++) {
+ BoulderObject boulderObject = grid[x][y];
+ String displayChar = (boulderObject != null) ? boulderObject.type() : ".";
+ sb.append(displayChar);
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java
new file mode 100644
index 00000000..645dd456
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java
@@ -0,0 +1,9 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder;
+
+import net.minecraft.util.math.BlockPos;
+
+public record BoulderObject(int x, int y, int z, String type) {
+ public BlockPos get3DPosition() {
+ return new BlockPos(x, y, z);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/bo