diff options
Diffstat (limited to 'src/main/java')
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 |
