diff options
Diffstat (limited to 'src/main')
24 files changed, 789 insertions, 108 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index fc72ea3f..edd4241e 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -10,25 +10,23 @@ import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra; import de.hysky.skyblocker.skyblock.chat.ChatRulesHandler; import de.hysky.skyblocker.skyblock.dungeon.*; import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; +import de.hysky.skyblocker.skyblock.dungeon.puzzle.*; 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; import de.hysky.skyblocker.skyblock.dungeon.puzzle.waterboard.Waterboard; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; 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.end.TheEnd; import de.hysky.skyblocker.skyblock.item.*; import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.quicknav.QuickNav; import de.hysky.skyblocker.skyblock.rift.TheRift; -import de.hysky.skyblocker.skyblock.searchOverlay.SearchOverManager; +import de.hysky.skyblocker.skyblock.searchoverlay.SearchOverManager; import de.hysky.skyblocker.skyblock.shortcut.Shortcuts; import de.hysky.skyblocker.skyblock.special.SpecialEffects; import de.hysky.skyblocker.skyblock.tabhud.TabHud; @@ -121,6 +119,8 @@ public class SkyblockerMod implements ClientModInitializer { DungeonManager.init(); DungeonBlaze.init(); Waterboard.init(); + Silverfish.init(); + IceFill.init(); DungeonScore.init(); PartyFinderScreen.initClass(); ChestValue.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index ea1f7d43..4a1fe614 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -723,6 +723,12 @@ public class SkyblockerConfig { public boolean solveBoulder = true; @SerialEntry + public boolean solveIceFill = true; + + @SerialEntry + public boolean solveSilverfish = true; + + @SerialEntry public boolean fireFreezeStaffTimer = true; @SerialEntry 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 5eb9a066..3ebd5d76 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java @@ -417,6 +417,20 @@ public class DungeonsCategory { .controller(ConfigUtils::createBooleanController) .build()) .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveIceFill")) + .binding(defaults.locations.dungeons.solveIceFill, + () -> config.locations.dungeons.solveIceFill, + newValue -> config.locations.dungeons.solveIceFill = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveSilverfish")) + .binding(defaults.locations.dungeons.solveSilverfish, + () -> config.locations.dungeons.solveSilverfish, + newValue -> config.locations.dungeons.solveSilverfish = 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/mixin/ClientPlayerEntityMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java index 2a4c38a7..ceda9ed4 100644 --- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java @@ -6,8 +6,8 @@ import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; import de.hysky.skyblocker.skyblock.item.HotbarSlotLock; import de.hysky.skyblocker.skyblock.item.ItemProtection; import de.hysky.skyblocker.skyblock.rift.HealingMelonIndicator; -import de.hysky.skyblocker.skyblock.searchOverlay.OverlayScreen; -import de.hysky.skyblocker.skyblock.searchOverlay.SearchOverManager; +import de.hysky.skyblocker.skyblock.searchoverlay.OverlayScreen; +import de.hysky.skyblocker.skyblock.searchoverlay.SearchOverManager; import de.hysky.skyblocker.utils.Utils; import net.minecraft.block.entity.SignBlockEntity; import net.minecraft.client.MinecraftClient; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/Kuudra.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/Kuudra.java index 033a919d..5bc98894 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/Kuudra.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/Kuudra.java @@ -10,7 +10,6 @@ import net.minecraft.text.Text; import net.minecraft.util.Formatting; public class Kuudra { - public static final String LOCATION = "kuudra"; static KuudraPhase phase = KuudraPhase.OTHER; public static void init() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/IceFill.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/IceFill.java new file mode 100644 index 00000000..57386674 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/IceFill.java @@ -0,0 +1,170 @@ +package de.hysky.skyblocker.skyblock.dungeon.puzzle; + +import com.google.common.primitives.Booleans; +import com.mojang.brigadier.Command; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.debug.Debug; +import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; +import de.hysky.skyblocker.skyblock.dungeon.secrets.Room; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class IceFill extends DungeonPuzzle { + public static final IceFill INSTANCE = new IceFill(); + private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents(); + private static final BlockPos[] BOARD_ORIGINS = { + new BlockPos(16, 70, 9), + new BlockPos(17, 71, 16), + new BlockPos(18, 72, 25) + }; + private CompletableFuture<Void> solve; + private final boolean[][][] iceFillBoards = {new boolean[3][3], new boolean[5][5], new boolean[7][7]}; + private final List<List<Vector2ic>> iceFillPaths = List.of(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + + private IceFill() { + super("ice-fill", "ice-path"); + } + + public static void init() { + if (Debug.debugEnabled()) { + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("puzzle").then(literal(INSTANCE.puzzleName) + .then(literal("printBoard1").executes(context -> { + context.getSource().sendFeedback(Constants.PREFIX.get().append(boardToString(INSTANCE.iceFillBoards[0]))); + return Command.SINGLE_SUCCESS; + })).then(literal("printBoard2").executes(context -> { + context.getSource().sendFeedback(Constants.PREFIX.get().append(boardToString(INSTANCE.iceFillBoards[1]))); + return Command.SINGLE_SUCCESS; + })).then(literal("printBoard3").executes(context -> { + context.getSource().sendFeedback(Constants.PREFIX.get().append(boardToString(INSTANCE.iceFillBoards[2]))); + return Command.SINGLE_SUCCESS; + })).then(literal("printPath1").executes(context -> { + context.getSource().sendFeedback(Constants.PREFIX.get().append(INSTANCE.iceFillPaths.get(0).toString())); + return Command.SINGLE_SUCCESS; + })).then(literal("printPath2").executes(context -> { + context.getSource().sendFeedback(Constants.PREFIX.get().append(INSTANCE.iceFillPaths.get(1).toString())); + return Command.SINGLE_SUCCESS; + })).then(literal("printPath3").executes(context -> { + context.getSource().sendFeedback(Constants.PREFIX.get().append(INSTANCE.iceFillPaths.get(2).toString())); + return Command.SINGLE_SUCCESS; + })) + ))))); + } + } + + private static String boardToString(boolean[][] iceFillBoard) { + StringBuilder sb = new StringBuilder(); + for (boolean[] row : iceFillBoard) { + sb.append("\n"); + for (boolean cell : row) { + sb.append(cell ? '#' : '.'); + } + } + return sb.toString(); + } + + @Override + public void tick(MinecraftClient client) { + if (!SkyblockerConfigManager.get().locations.dungeons.solveIceFill || client.world == null || !DungeonManager.isCurrentRoomMatched() || solve != null && !solve.isDone()) { + return; + } + Room room = DungeonManager.getCurrentRoom(); + + solve = CompletableFuture.runAsync(() -> { + BlockPos.Mutable pos = new BlockPos.Mutable(); + for (int i = 0; i < 3; i++) { + if (updateBoard(client.world, room, iceFillBoards[i], pos.set(BOARD_ORIGINS[i]))) { + solve(iceFillBoards[i], iceFillPaths.get(i)); + } + } + }); + } + + private boolean updateBoard(World world, Room room, boolean[][] iceFillBoard, BlockPos.Mutable pos) { + boolean boardChanged = false; + for (int row = 0; row < iceFillBoard.length; pos.move(iceFillBoard[row].length, 0, -1), row++) { + for (int col = 0; col < iceFillBoard[row].length; pos.move(Direction.WEST), col++) { + BlockPos actualPos = room.relativeToActual(pos); + boolean isBlock = !world.getBlockState(actualPos).isAir(); + if (iceFillBoard[row][col] != isBlock) { + iceFillBoard[row][col] = isBlock; + boardChanged = true; + } + } + } + return boardChanged; + } + + void solve(boolean[][] iceFillBoard, List<Vector2ic> iceFillPath) { + Vector2ic start = new Vector2i(iceFillBoard.length - 1, iceFillBoard[0].length / 2); + int count = iceFillBoard.length * iceFillBoard[0].length - Arrays.stream(iceFillBoard).mapToInt(Booleans::countTrue).sum(); + + List<Vector2ic> newPath = solveDfs(iceFillBoard, count - 1, new ArrayList<>(List.of(start)), new HashSet<>(List.of(start))); + if (newPath != null) { + iceFillPath.clear(); + iceFillPath.addAll(newPath); + } + } + + private List<Vector2ic> solveDfs(boolean[][] iceFillBoard, int count, List<Vector2ic> path, Set<Vector2ic> visited) { + Vector2ic pos = path.get(path.size() - 1); + if (count == 0) { + if (pos.x() == 0 && pos.y() == iceFillBoard[0].length / 2) { + return path; + } else { + return null; + } + } + + Vector2ic[] newPosArray = {pos.add(1, 0, new Vector2i()), pos.add(-1, 0, new Vector2i()), pos.add(0, 1, new Vector2i()), pos.add(0, -1, new Vector2i())}; + for (Vector2ic newPos : newPosArray) { + if (newPos.x() >= 0 && newPos.x() < iceFillBoard.length && newPos.y() >= 0 && newPos.y() < iceFillBoard[0].length && !iceFillBoard[newPos.x()][newPos.y()] && !visited.contains(newPos)) { + path.add(newPos); + visited.add(newPos); + List<Vector2ic> newPath = solveDfs(iceFillBoard, count - 1, path, visited); + if (newPath != null) { + return newPath; + } + path.remove(path.size() - 1); + visited.remove(newPos); + } + } + + return null; + } + + @Override + public void render(WorldRenderContext context) { + if (!SkyblockerConfigManager.get().locations.dungeons.solveIceFill || !DungeonManager.isCurrentRoomMatched()) { + return; + } + Room room = DungeonManager.getCurrentRoom(); + for (int i = 0; i < 3; i++) { + renderPath(context, room, iceFillPaths.get(i), BOARD_ORIGINS[i]); + } + } + + private void renderPath(WorldRenderContext context, Room room, List<Vector2ic> iceFillPath, BlockPos originPos) { + BlockPos.Mutable pos = new BlockPos.Mutable(); + for (int i = 0; i < iceFillPath.size() - 1; i++) { + Vec3d start = Vec3d.ofCenter(room.relativeToActual(pos.set(originPos).move(-iceFillPath.get(i).y(), 0, -iceFillPath.get(i).x()))); + Vec3d end = Vec3d.ofCenter(room.relativeToActual(pos.set(originPos).move(-iceFillPath.get(i + 1).y(), 0, -iceFillPath.get(i + 1).x()))); + RenderHelper.renderLinesFromPoints(context, new Vec3d[]{start, end}, RED_COLOR_COMPONENTS, 1f, 5f, true); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Silverfish.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Silverfish.java new file mode 100644 index 00000000..b5cbc8ee --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Silverfish.java @@ -0,0 +1,181 @@ +package de.hysky.skyblocker.skyblock.dungeon.puzzle; + +import com.mojang.brigadier.Command; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.debug.Debug; +import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; +import de.hysky.skyblocker.skyblock.dungeon.secrets.Room; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.mob.SilverfishEntity; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class Silverfish extends DungeonPuzzle { + private static final Logger LOGGER = LoggerFactory.getLogger(Silverfish.class); + public static final Silverfish INSTANCE = new Silverfish(); + private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents(); + final boolean[][] silverfishBoard = new boolean[17][17]; + Vector2ic silverfishPos; + final List<Vector2ic> silverfishPath = new ArrayList<>(); + + private Silverfish() { + super("silverfish", "ice-silverfish-room"); + } + + public static void init() { + if (Debug.debugEnabled()) { + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("puzzle").then(literal(INSTANCE.puzzleName) + .then(literal("printBoard").executes(context -> { + context.getSource().sendFeedback(Constants.PREFIX.get().append(boardToString(INSTANCE.silverfishBoard))); + return Command.SINGLE_SUCCESS; + })).then(literal("printPath").executes(context -> { + context.getSource().sendFeedback(Constants.PREFIX.get().append(INSTANCE.silverfishPath.toString())); + return Command.SINGLE_SUCCESS; + })) + ))))); + } + } + + private static String boardToString(boolean[][] silverfishBoard) { + StringBuilder sb = new StringBuilder(); + for (boolean[] row : silverfishBoard) { + sb.append("\n"); + for (boolean cell : row) { + sb.append(cell ? '#' : '.'); + } + } + return sb.toString(); + } + + @Override + public void tick(MinecraftClient client) { + if (!SkyblockerConfigManager.get().locations.dungeons.solveSilverfish || client.world == null || !DungeonManager.isCurrentRoomMatched()) { + return; + } + Room room = DungeonManager.getCurrentRoom(); + + boolean boardChanged = false; + BlockPos.Mutable pos = new BlockPos.Mutable(23, 67, 24); + for (int row = 0; row < silverfishBoard.length; pos.move(silverfishBoard[row].length, 0, -1), row++) { + for (int col = 0; col < silverfishBoard[row].length; pos.move(Direction.WEST), col++) { + boolean isBlock = !client.world.getBlockState(room.relativeToActual(pos)).isAir(); + if (silverfishBoard[row][col] != isBlock) { + silverfishBoard[row][col] = isBlock; + boardChanged = true; + } + } + } + + List<SilverfishEntity> entities = client.world.getEntitiesByClass(SilverfishEntity.class, Box.of(Vec3d.ofCenter(room.relativeToActual(new BlockPos(15, 66, 16))), 16, 16, 16), silverfishEntity -> true); + if (entities.isEmpty()) { + return; + } + BlockPos newSilverfishBlockPos = room.actualToRelative(entities.get(0).getBlockPos()); + Vector2ic newSilverfishPos = new Vector2i(24 - newSilverfishBlockPos.getZ(), 23 - newSilverfishBlockPos.getX()); + if (newSilverfishPos.x() < 0 || newSilverfishPos.x() >= 17 || newSilverfishPos.y() < 0 || newSilverfishPos.y() >= 17) { + return; + } + boolean silverfishChanged = !newSilverfishPos.equals(silverfishPos); + if (silverfishChanged) { + silverfishPos = newSilverfishPos; + } + if (silverfishChanged || boardChanged) { + solve(); + } + } + + void solve() { + if (silverfishPos == null) { + return; + } + Set<Vector2ic> visited = new HashSet<>(); + Queue<List<Vector2ic>> queue = new ArrayDeque<>(); + queue.add(List.of(silverfishPos)); + visited.add(silverfishPos); + while (!queue.isEmpty()) { + List<Vector2ic> path = queue.poll(); + Vector2ic pos = path.get(path.size() - 1); + if (pos.x() == 0 && pos.y() >= 7 && pos.y() <= 9) { + silverfishPath.clear(); + silverfishPath.addAll(path); + return; + } + + Vector2i posMutable = new Vector2i(pos); + while (posMutable.x() < 17 && !silverfishBoard[posMutable.x()][posMutable.y()]) { + posMutable.add(1, 0); + } + posMutable.add(-1, 0); + addQueue(visited, queue, path, posMutable); + + posMutable = new Vector2i(pos); + while (posMutable.x() >= 0 && !silverfishBoard[posMutable.x()][posMutable.y()]) { + posMutable.add(-1, 0); + } + posMutable.add(1, 0); + addQueue(visited, queue, path, posMutable); + + posMutable = new Vector2i(pos); + while (posMutable.y() < 17 && !silverfishBoard[posMutable.x()][posMutable.y()]) { + posMutable.add(0, 1); + } + posMutable.add(0, -1); + addQueue(visited, queue, path, posMutable); + + posMutable = new Vector2i(pos); + while (posMutable.y() >= 0 && !silverfishBoard[posMutable.x()][posMutable.y()]) { + posMutable.add(0, -1); + } + posMutable.add(0, 1); + addQueue(visited, queue, path, posMutable); + } + } + + private void addQueue(Set<Vector2ic> visited, Queue<List<Vector2ic>> queue, List<Vector2ic> path, Vector2ic newPos) { + if (!visited.contains(newPos)) { + List<Vector2ic> newPath = new ArrayList<>(path); + newPath.add(newPos); + queue.add(newPath); + visited.add(newPos); + } + } + + @Override + public void render(WorldRenderContext context) { + if (!SkyblockerConfigManager.get().locations.dungeons.solveSilverfish || !DungeonManager.isCurrentRoomMatched() || silverfishPath.isEmpty()) { + return; + } + Room room = DungeonManager.getCurrentRoom(); + BlockPos.Mutable pos = new BlockPos.Mutable(); + for (int i = 0; i < silverfishPath.size() - 1; i++) { + Vec3d start = Vec3d.ofCenter(room.relativeToActual(pos.set(23 - silverfishPath.get(i).y(), 67, 24 - silverfishPath.get(i).x()))); + Vec3d end = Vec3d.ofCenter(room.relativeToActual(pos.set(23 - silverfishPath.get(i + 1).y(), 67, 24 - silverfishPath.get(i + 1).x()))); + RenderHelper.renderLinesFromPoints(context, new Vec3d[]{start, end}, RED_COLOR_COMPONENTS, 1f, 5f, true); + } + } + + @Override + public void reset() { + super.reset(); + for (boolean[] silverfishBoardRow : silverfishBoard) { + Arrays.fill(silverfishBoardRow, false); + } + silverfishPos = null; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java index 3244996a..ba4b9a5f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java @@ -88,17 +88,20 @@ public class Waterboard extends DungeonPuzzle { private Waterboard() { super("waterboard", "water-puzzle"); - UseBlockCallback.EVENT.register(this::onUseBlock); + } + + public static void init() { + UseBlockCallback.EVENT.register(INSTANCE::onUseBlock); if (Debug.debugEnabled()) { - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("puzzle").then(literal(puzzleName) + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("puzzle").then(literal(INSTANCE.puzzleName) .then(literal("printBoard").executes(context -> { - context.getSource().sendFeedback(Constants.PREFIX.get().append(boardToString(cells))); + context.getSource().sendFeedback(Constants.PREFIX.get().append(boardToString(INSTANCE.cells))); return Command.SINGLE_SUCCESS; })).then(literal("printDoors").executes(context -> { context.getSource().sendFeedback(Constants.PREFIX.get().append(Integer.toBinaryString(INSTANCE.doors))); return Command.SINGLE_SUCCESS; })).then(literal("printSimulationResults").then(argument("combination", IntegerArgumentType.integer(0, 63)).executes(context -> { - context.getSource().sendFeedback(Constants.PREFIX.get().append(results[IntegerArgumentType.getInteger(context, "combination")].toString())); + context.getSource().sendFeedback(Constants.PREFIX.get().append(INSTANCE.results[IntegerArgumentType.getInteger(context, "combination")].toString())); return Command.SINGLE_SUCCESS; }))).then(literal("printCurrentCombination").executes(context -> { context.getSource().sendFeedback(Constants.PREFIX.get().append(Integer.toBinaryString(INSTANCE.currentCombination))); @@ -111,9 +114,6 @@ public class Waterboard extends DungeonPuzzle { } } - public static void init() { - } - private static String boardToString(Cell[][] cells) { StringBuilder sb = new StringBuilder(); for (Cell[] row : cells) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java index 68f09344..2f748792 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java @@ -10,9 +10,6 @@ import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Http; import de.hysky.skyblocker.utils.Http.ApiResponse; import de.hysky.skyblocker.utils.Utils; -import it.unimi.dsi.fastutil.ints.IntIntPair; -import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.minecraft.client.MinecraftClient; @@ -24,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,8 +41,6 @@ public class SecretsTracker { ClientReceiveMessageEvents.GAME.register(SecretsTracker::onMessage); } - //If -1 is somehow encountered, it would be very rare, so I just disregard its possibility for now - //people would probably recognize if it was inaccurate so yeah private static void calculate(RunPhase phase) { switch (phase) { case START -> CompletableFuture.runAsync(() -> { @@ -57,11 +53,11 @@ public class SecretsTracker { //The player name will be blank if there isn't a player at that index if (!playerName.isEmpty()) { - //If the player was a part of the last run (and didn't have -1 secret count) and that run ended less than 5 mins ago then copy the secrets over - if (lastRun != null && System.currentTimeMillis() <= lastRunEnded + 300_000 && lastRun.secretCounts().getOrDefault(playerName, -1) != -1) { - newlyStartedRun.secretCounts().put(playerName, lastRun.secretCounts().getInt(playerName)); + //If the player was a part of the last run, had non-empty secret data and that run ended less than 5 mins ago then copy the secret data over + if (lastRun != null && System.currentTimeMillis() <= lastRunEnded + 300_000 && lastRun.playersSecretData().getOrDefault(playerName, SecretData.EMPTY) != SecretData.EMPTY) { + newlyStartedRun.playersSecretData().put(playerName, lastRun.playersSecretData().get(playerName)); } else { - newlyStartedRun.secretCounts().put(playerName, getPlayerSecrets(playerName).leftInt()); + newlyStartedRun.playersSecretData().put(playerName, getPlayerSecrets(playerName)); } } } @@ -72,22 +68,23 @@ public class SecretsTracker { case END -> CompletableFuture.runAsync(() -> { //In case the game crashes from something if (currentRun != null) { - Object2ObjectOpenHashMap<String, IntIntPair> secretsFound = new Object2ObjectOpenHashMap<>(); + Object2ObjectOpenHashMap<String, SecretData> secretsFound = new Object2ObjectOpenHashMap<>(); //Update secret counts - for (Entry<String> entry : currentRun.secretCounts().object2IntEntrySet()) { + for (Entry<String, SecretData> entry : currentRun.playersSecretData().entrySet()) { String playerName = entry.getKey(); - int startingSecrets = entry.getIntValue(); - IntIntPair secretsNow = getPlayerSecrets(playerName); - int secretsPlayerFound = secretsNow.leftInt() - startingSecrets; + SecretData startingSecrets = entry.getValue(); + SecretData secretsNow = getPlayerSecrets(playerName); + int secretsPlayerFound = secretsNow.secrets() - startingSecrets.secrets(); - secretsFound.put(playerName, IntIntPair.of(secretsPlayerFound, secretsNow.rightInt())); - entry.setValue(secretsNow.leftInt()); + //Add an entry to the secretsFound map with the data - if the secret data from now or the start was cached a warning will be shown + secretsFound.put(playerName, secretsNow.updated(secretsPlayerFound, startingSecrets.cached() || secretsNow.cached())); + entry.setValue(secretsNow); } //Print the results all in one go, so its clean and less of a chance of it being broken up - for (Map.Entry<String, IntIntPair> entry : secretsFound.entrySet()) { - sendResultMessage(entry.getKey(), entry.getValue().leftInt(), entry.getValue().rightInt(), true); + for (Map.Entry<String, SecretData> entry : secretsFound.entrySet()) { + sendResultMessage(entry.getKey(), entry.getValue(), true); } //Swap the current and last run as well as mark the run end time @@ -95,30 +92,31 @@ public class SecretsTracker { lastRun = currentRun; currentRun = null; } else { - sendResultMessage(null, -1, -1, false); + sendResultMessage(null, null, false); } }); } } - private static void sendResultMessage(String player, int secrets, int cacheAge, boolean success) { + private static void sendResultMessage(String player, SecretData secretData, boolean success) { + @SuppressWarnings("resource") PlayerEntity playerEntity = MinecraftClient.getInstance().player; if (playerEntity != null) { if (success) { - playerEntity.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secretsTracker.feedback", Text.literal(player).withColor(0xf57542), "§7" + secrets, getCacheText(cacheAge)))); + playerEntity.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secretsTracker.feedback", Text.literal(player).withColor(0xf57542), "§7" + secretData.secrets(), getCacheText(secretData.cached(), secretData.cacheAge())))); } else { playerEntity.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secretsTracker.failFeedback"))); } } } - private static Text getCacheText(int cacheAge) { - return Text.literal("\u2139").styled(style -> style.withColor(cacheAge == -1 ? 0x218bff : 0xeac864).withHoverEvent( - new HoverEvent(HoverEvent.Action.SHOW_TEXT, cacheAge == -1 ? Text.translatable("skyblocker.api.cache.MISS") : Text.translatable("skyblocker.api.cache.HIT", cacheAge)))); + private static Text getCacheText(boolean cached, int cacheAge) { + return Text.literal("\u2139").styled(style -> style.withColor(cached ? 0xeac864 : 0x218bff).withHoverEvent( + new HoverEvent(HoverEvent.Action.SHOW_TEXT, cached ? Text.translatable("skyblocker.api.cache.HIT", cacheAge) : Text.translatable("skyblocker.api.cache.MISS")))); } private static void onMessage(Text text, boolean overlay) { - if (Utils.isInDungeons() && SkyblockerConfigManager.get().locations.dungeons.playerSecretsTracker) { + if (Utils.isInDungeons() && SkyblockerConfigManager.get().locations.dungeons.playerSecretsTracker && !overlay) { String message = Formatting.strip(text.getString()); try { @@ -136,35 +134,44 @@ public class SecretsTracker { return matcher != null ? matcher.group("name") : ""; } - private static IntIntPair getPlayerSecrets(String name) { + private static SecretData getPlayerSecrets(String name) { String uuid = ApiUtils.name2Uuid(name); if (!uuid.isEmpty()) { try (ApiResponse response = Http.sendHypixelRequest("player", "?uuid=" + uuid)) { - return IntIntPair.of(getSecretCountFromAchievements(JsonParser.parseString(response.content()).getAsJsonObject()), response.age()); + return new SecretData(getSecretCountFromAchievements(JsonParser.parseString(response.content()).getAsJsonObject()), response.cached(), response.age()); } catch (Exception e) { LOGGER.error("[Skyblocker] Encountered an error while trying to fetch {} secret count!", name + "'s", e); } } - return IntIntPair.of(-1, -1); + return SecretData.EMPTY; } /** * Gets a player's secret count from their hypixel achievements */ private static int getSecretCountFromAchievements(JsonObject playerJson) { - JsonObject player = playerJson.get("player").getAsJsonObject(); - JsonObject achievements = (player.has("achievements")) ? player.get("achievements").getAsJsonObject() : null; + JsonObject player = playerJson.getAsJsonObject("player"); + JsonObject achievements = player.has("achievements") ? player.getAsJsonObject("achievements") : null; return (achievements != null && achievements.has("skyblock_treasure_hunter")) ? achievements.get("skyblock_treasure_hunter").getAsInt() : 0; } /** * This will either reflect the value at the start or the end depending on when this is called */ - private record TrackedRun(Object2IntOpenHashMap<String> secretCounts) { + private record TrackedRun(Object2ObjectOpenHashMap<String, SecretData> playersSecretData) { private TrackedRun() { - this(new Object2IntOpenHashMap<>()); + this(new Object2ObjectOpenHashMap<>()); + } + } + + private record SecretData(int secrets, boolean cached, int cacheAge) { + private static final SecretData EMPTY = new SecretData(0, false, 0); + + //If only we had Derived Record Creation :( - https://bugs.openjdk.org/browse/JDK-8321133 + private SecretData updated(int secrets, boolean cached) { + return new SecretData(secrets, cached, this.cacheAge); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java index 5913a3c6..608873c0 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java @@ -203,8 +203,8 @@ public class DwarvenHud { } public static void update() { - if (client.player == null || client.getNetworkHandler() == null || !SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledCommissions || (!Utils.isInCrystalHollows() - && !Utils.isInDwarvenMines())) + if (client.player == null || client.getNetworkHandler() == null || (!SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledCommissions && !SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledPowder) + || (!Utils.isInCrystalHollows() && !Utils.isInDwarvenMines())) return; commissionList = new ArrayList<>(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/BeaconHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/end/BeaconHighlighter.java index f69fca6d..6c89a07c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/BeaconHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/BeaconHighlighter.java @@ -3,8 +3,11 @@ package de.hysky.skyblocker.skyblock.end; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; +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.text.Text; import net.minecraft.util.math.BlockPos; import java.util.ArrayList; @@ -20,6 +23,20 @@ public class BeaconHighlighter { */ public static void init() { WorldRenderEvents.AFTER_TRANSLUCENT.register(BeaconHighlighter::render); + ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset()); + ClientReceiveMessageEvents.GAME.register(BeaconHighlighter::onMessage); + } + + private static void reset() { + beaconPositions.clear(); + } + + private static void onMessage(Text text, boolean overlay) { + if (Utils.isInTheEnd() && !overlay) { + String message = text.getString(); + + if (message.contains("SLAYER QUEST COMPLETE!") || message.contains("NICE! SLAYER BOSS SLAIN!")) reset(); + } } /** @@ -28,7 +45,7 @@ public class BeaconHighlighter { * * @param context An instance of WorldRenderContext for the RenderHelper to use */ - public static void render(WorldRenderContext context) { + private static void render(WorldRenderContext context) { if (Utils.isInTheEnd() && SkyblockerConfigManager.get().slayer.endermanSlayer.highlightBeacons) { for (BlockPos pos : beaconPositions) { RenderHelper.renderFilled(context, pos, RED_COLOR_COMPONENTS, 0.5f, false); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index 19f2e6fd..d5be7eee 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -140,15 +140,37 @@ public class ItemTooltip { } } + final Map<Integer, String> itemTierFloors = new HashMap<>() {{ + put(1, "F1"); + put(2, "F2"); + put(3, "F3"); + put(4, "F4/M1"); + put(5, "F5/M2"); + put(6, "F6/M3"); + put(7, "F7/M4"); + put(8, "M5"); + put(9, "M6"); + put(10, "M7"); + }}; + if (SkyblockerConfigManager.get().general.dungeonQuality) { NbtCompound ea = ItemUtils.getExtraAttributes(stack); if (ea != null && ea.contains("baseStatBoostPercentage")) { int baseStatBoostPercentage = ea.getInt("baseStatBoostPercentage"); - if (baseStatBoostPercentage == 50) { + boolean maxQuality = baseStatBoostPercentage == 50; + if (maxQuality) { lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD)); } else { lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE)); } + if (ea.contains("item_tier")) { // sometimes it just isn't here? + int itemTier = ea.getInt("item_tier"); + if (maxQuality) { + lines.add(Text.literal(String.format("%-17s", "Floor Tier:") + itemTier + " (" + itemTierFloors.get(itemTier) + ")").formatted(Formatting.RED).formatted(Formatting.BOLD)); + } else { + lines.add(Text.literal(String.format("%-21s", "Floor Tier:") + itemTier + " (" + itemTierFloors.get(itemTier) + ")").formatted(Formatting.BLUE)); + } + } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java index 02b694b6..7413e06f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java @@ -8,11 +8,6 @@ import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; public class TheRift { - /** - * @see de.hysky.skyblocker.utils.Utils#isInTheRift() Utils#isInTheRift(). - */ - public static final String LOCATION = "rift"; - public static void init() { WorldRenderEvents.AFTER_TRANSLUCENT.register(MirrorverseWaypoints::render); WorldRenderEvents.AFTER_TRANSLUCENT.register(EffigyWaypoints::render); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/OverlayScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/OverlayScreen.java index e1545c6c..b8907e27 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/OverlayScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/OverlayScreen.java @@ -1,4 +1,4 @@ -package de.hysky.skyblocker.skyblock.searchOverlay; +package de.hysky.skyblocker.skyblock.searchoverlay; import de.hysky.skyblocker.config.SkyblockerConfigManager; import net.minecraft.client.gui.DrawContext; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/SearchOverManager.java b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java index b2a453a9..1d740601 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/SearchOverManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java @@ -1,4 +1,4 @@ -package de.hysky.skyblocker.skyblock.searchOverlay; +package de.hysky.skyblocker.skyblock.searchoverlay; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -96,13 +96,15 @@ public class SearchOverManager { HashMap<String, String> namesToId = new HashMap<>(); //get bazaar items - try (Http.ApiResponse response = Http.sendHypixelRequest("skyblock/bazaar", "")) { - JsonObject products = JsonParser.parseString(response.content()).getAsJsonObject().get("products").getAsJsonObject(); + try { + if (TooltipInfoType.BAZAAR.getData() == null) TooltipInfoType.BAZAAR.run(); + + JsonObject products = TooltipInfoType.BAZAAR.getData(); for (Map.Entry<String, JsonElement> entry : products.entrySet()) { if (entry.getValue().isJsonObject()) { JsonObject product = entry.getValue().getAsJsonObject(); - String id = product.get("product_id").getAsString(); - int sellVolume = product.get("quick_status").getAsJsonObject().get("sellVolume").getAsInt(); + String id = product.get("id").getAsString(); + int sellVolume = product.get("sellVolume").getAsInt(); if (sellVolume == 0) continue; //do not add items that do not sell e.g. they are not actual in the bazaar Matcher matcher = BAZAAR_ENCHANTMENT_PATTERN.matcher(id); @@ -334,7 +336,7 @@ public class SearchOverManager { } } - static Pair<String, String> splitString(String s) { + public static Pair<String, String> splitString(String s) { if (s.length() <= 15) { return Pair.of(s, ""); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java index 82394a78..96ab35d5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java @@ -60,4 +60,5 @@ public class Ico { public static final ItemStack EXPERIENCE_BOTTLE = new ItemStack(Items.EXPERIENCE_BOTTLE); public static final ItemStack PINK_DYE = new ItemStack(Items.PINK_DYE); public static final ItemStack ENCHANTED_BOOK = new ItemStack(Items.ENCHANTED_BOOK); + public static final ItemStack SPIDER_EYE = new ItemStack(Items.SPIDER_EYE); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java index 6f40f5a8..309ba9ca 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java @@ -1,16 +1,19 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import java.util.regex.Pattern; + // this widget shows info about the secrets of the dungeon public class DungeonSecretWidget extends Widget { - private static final MutableText TITLE = Text.literal("Discoveries").formatted(Formatting.DARK_PURPLE, - Formatting.BOLD); + private static final MutableText TITLE = Text.literal("Discoveries").formatted(Formatting.DARK_PURPLE, Formatting.BOLD); + private static final Pattern DISCOVERIES = Pattern.compile("Discoveries: (\\d+)"); public DungeonSecretWidget() { super(TITLE, Formatting.DARK_PURPLE.getColorValue()); @@ -18,9 +21,12 @@ public class DungeonSecretWidget extends Widget { @Override public void updateContent() { - this.addSimpleIcoText(Ico.CHEST, "Secrets:", Formatting.YELLOW, 31); - this.addSimpleIcoText(Ico.SKULL, "Crypts:", Formatting.YELLOW, 32); - + if (PlayerListMgr.regexAt(31, DISCOVERIES) != null) { + this.addSimpleIcoText(Ico.CHEST, "Secrets:", Formatting.YELLOW, 32); + this.addSimpleIcoText(Ico.SKULL, "Crypts:", Formatting.YELLOW, 33); + } else { + this.addSimpleIcoText(Ico.CHEST, "Secrets:", Formatting.YELLOW, 31); + this.addSimpleIcoText(Ico.SKULL, "Crypts:", Formatting.YELLOW, 32); + } } - } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SpidersDenServerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SpidersDenServerWidget.java new file mode 100644 index 00000000..6751ea5d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SpidersDenServerWidget.java @@ -0,0 +1,87 @@ +package de.hysky.skyblocker.skyblock.tabhud.widget; + +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.Arrays; + +/** + * This widget shows info about the Spider's Den server + */ +public class SpidersDenServerWidget extends Widget { + + private static final MutableText TITLE = + Text.literal("Server Info").formatted(Formatting.DARK_AQUA, Formatting.BOLD); + + /** + * Broodmother Mini-Boss tab states + */ + private enum BroodmotherState { + SOON("Soon", Formatting.GOLD), + AWAKENING("Awakening", Formatting.GOLD), + IMMINENT("Imminent", Formatting.DARK_RED), + ALIVE("Alive!", Formatting.DARK_RED), + SLAIN("Slain", Formatting.YELLOW), + DORMANT("Dormant", Formatting.YELLOW), + UNKNOWN("Unknown", Formatting.GRAY); + + private final String text; + private final Formatting formatting; + + BroodmotherState(String text, Formatting formatting) { + this.text = text; + this.formatting = formatting; + } + + public String text() { + return this.text; + } + + public Formatting formatting() { + return this.formatting; + } + + /** + * Returns a state object by text + * + * @param text text state from tab + * @return Broodmother State object + */ + public static BroodmotherState from(String text) { + return Arrays.stream(BroodmotherState.values()) + .filter(broodmotherState -> text.equals(broodmotherState.text())).findFirst().orElse(UNKNOWN); + } + } + + public SpidersDenServerWidget() { + super(TITLE, Formatting.DARK_AQUA.getColorValue()); + } + + /** + * Parses the Broodmother string from tab and returns a state object. + * + * @return Broodmother State object + */ + private static BroodmotherState parseTab() { + String state = PlayerListMgr.strAt(45); + if (state == null || !state.contains(": ")) return BroodmotherState.UNKNOWN; + + return BroodmotherState.from(state.split(": ")[1]); + } + + /** + * Updates the information in the widget. + */ + @Override + public void updateContent() { + this.addSimpleIcoText(Ico.MAP, "Area:", Formatting.DARK_AQUA, 41); + this.addSimpleIcoText(Ico.NTAG, "Server ID:", Formatting.GRAY, 42); + this.addSimpleIcoText(Ico.EMERALD, "Gems:", Formatting.GREEN, 43); + + BroodmotherState broodmotherState = parseTab(); + this.addSimpleIcoText(Ico.SPIDER_EYE, "Broodmother: ", broodmotherState.formatting(), broodmotherState.text()); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java index 17079d15..58deced2 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Http.java +++ b/src/main/java/de/hysky/skyblocker/utils/Http.java @@ -53,7 +53,7 @@ public class Http { String body = new String(decodedInputStream.readAllBytes()); HttpHeaders headers = response.headers(); - return new ApiResponse(body, response.statusCode(), getCacheStatus(headers), getAge(headers)); + return new ApiResponse(body, response.statusCode(), getCacheStatuses(headers), getAge(headers)); } public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException { @@ -115,12 +115,12 @@ public class Http { } /** - * Returns the cache status of the resource + * Returns the cache statuses of the resource. All possible cache status values conform to Cloudflare's. * - * @see <a href="https://developers.cloudflare.com/cache/concepts/default-cache-behavior/#cloudflare-cache-responses">Cloudflare Cache Docs</a> + * @see <a href="https://developers.cloudflare.com/cache/concepts/cache-responses/">Cloudflare Cache Docs</a> */ - private static String getCacheStatus(HttpHeaders headers) { - return headers.firstValue("CF-Cache-Status").orElse("UNKNOWN"); + private static String[] getCacheStatuses(HttpHeaders headers) { + return new String[] { headers.firstValue("CF-Cache-Status").orElse("UNKNOWN"), headers.firstValue("Local-Cache-Status").orElse("UNKNOWN") }; } private static int getAge(HttpHeaders headers) { @@ -128,7 +128,7 @@ public class Http { } //TODO If ever needed, we could just replace cache status with the response headers and go from there - public record ApiResponse(String content, int statusCode, String cacheStatus, int age) implements AutoCloseable { + public record ApiResponse(String content, int statusCode, String[] cacheStatuses, int age) implements AutoCloseable { public boolean ok() { return statusCode == 200; @@ -139,7 +139,7 @@ public class Http { } public boolean cached() { - return cacheStatus.equals("HIT"); + return cacheStatuses[0].equals("HIT") || cacheStatuses[1].equals("HIT"); } @Override diff --git a/src/main/java/de/hysky/skyblocker/utils/Location.java b/src/main/java/de/hysky/skyblocker/utils/Location.java new file mode 100644 index 00000000..bd2773fd --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/Location.java @@ -0,0 +1,116 @@ +package de.hysky.skyblocker.utils; + +import java.util.Arrays; + +/** + * All Skyblock locations + */ +public enum Location { + /** + * mode: dynamic + */ + PRIVATE_ISLAND("dynamic"), + /** + * mode: garden + */ + GARDEN("garden"), + /** + * mode: hub + */ + HUB("hub"), + /** + * mode: farming_1 + */ + THE_FARMING_ISLAND("farming_1"), + /** + * mode: foraging_1 + */ + THE_PARK("foraging_1"), + /** + * mode: combat_1 + */ + SPIDERS_DEN("combat_1"), + /** + * mode: combat_2 + */ + BLAZING_FORTRESS("combat_2"), + /** + * mode: combat_3 + */ + THE_END("combat_3"), + /** + * mode: crimson_isle + */ + CRIMSON_ISLE("crimson_isle"), + /** + * mode: mining_1 + */ + GOLD_MINE("mining_1"), + /** + * mode: mining_2 + */ + DEEP_CAVERNS("mining_2"), + /** + * mode: mining_3 + */ + DWARVEN_MINES("mining_3"), + /** + * mode: dungeon_hub + */ + DUNGEON_HUB("dungeon_hub"), + /** + * mode: winter + */ + WINTER_ISLAND("winter"), + /** + * mode: rift + */ + THE_RIFT("rift"), + /** + * mode: dark_auction + */ + DARK_AUCTION("dark_auction"), + /** + * mode: crystal_hollows + */ + CRYSTAL_HOLLOWS("crystal_hollows"), + /** + * mode: dungeon + */ + DUNGEON("dungeon"), + /** + * mode: kuudra + */ + KUUDRAS_HOLLOW("kuudra"), + /** + * Unknown Skyblock location + */ + UNKNOWN("unknown"); + + /** + * location id from <a href="https://api.hypixel.net/v2/resources/games">Hypixel API</a> + */ + private final String id; + + /** + * @param id location id from <a href="https://api.hypixel.net/v2/resources/games">Hypixel API</a> + */ + Location(String id) { + this.id = id; + } + + /** + * @return location id + */ + public String id() { + return this.id; + } + + /** + * @param id location id from <a href="https://api.hypixel.net/v2/resources/games">Hypixel API</a> + * @return location object + */ + public static Location from(String id) { + return Arrays.stream(Location.values()).filter(loc -> id.equals(loc.id())).findFirst().orElse(UNKNOWN); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index cd739a0c..08d0b167 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -3,10 +3,8 @@ package de.hysky.skyblocker.utils; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import de.hysky.skyblocker.events.SkyblockEvents; -import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra; import de.hysky.skyblocker.skyblock.item.MuseumItemCache; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; -import de.hysky.skyblocker.skyblock.rift.TheRift; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.scheduler.Scheduler; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -37,15 +35,17 @@ import java.util.concurrent.CompletableFuture; public class Utils { private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class); private static final String ALTERNATE_HYPIXEL_ADDRESS = System.getProperty("skyblocker.alternateHypixelAddress", ""); - private static final String DUNGEONS_LOCATION = "dungeon"; - private static final String CRYSTAL_HOLLOWS_LOCATION = "crystal_hollows"; - private static final String DWARVEN_MINES_LOCATION = "mining_3"; private static final String PROFILE_PREFIX = "Profile: "; private static boolean isOnHypixel = false; private static boolean isOnSkyblock = false; private static boolean isInjected = false; /** + * Current Skyblock location (from /locraw) + */ + @NotNull + private static Location location = Location.UNKNOWN; + /** * The profile name parsed from the player list. */ @NotNull @@ -88,31 +88,30 @@ public class Utils { } public static boolean isInDungeons() { - return getLocationRaw().equals(DUNGEONS_LOCATION) || FabricLoader.getInstance().isDevelopmentEnvironment(); + return location == Location.DUNGEON || FabricLoader.getInstance().isDevelopmentEnvironment(); } public static boolean isInCrystalHollows() { - return getLocationRaw().equals(CRYSTAL_HOLLOWS_LOCATION) || FabricLoader.getInstance().isDevelopmentEnvironment(); + return location == Location.CRYSTAL_HOLLOWS || FabricLoader.getInstance().isDevelopmentEnvironment(); } public static boolean isInDwarvenMines() { - return getLocationRaw().equals(DWARVEN_MINES_LOCATION) || FabricLoader.getInstance().isDevelopmentEnvironment(); + return location == Location.DWARVEN_MINES || FabricLoader.getInstance().isDevelopmentEnvironment(); } public static boolean isInTheRift() { - return getLocationRaw().equals(TheRift.LOCATION); + return location == Location.THE_RIFT; } /** * @return if the player is in the end island */ public static boolean isInTheEnd() { - // /locraw returns "combat_3" when in The End - return getLocationRaw().equals("combat_3"); + return location == Location.THE_END; } public static boolean isInKuudra() { - return getLocationRaw().equals(Kuudra.LOCATION); + return location == Location.KUUDRAS_HOLLOW; } public static boolean isInjected() { @@ -133,6 +132,14 @@ public class Utils { } /** + * @return the location parsed from /locraw. + */ + @NotNull + public static Location getLocation() { + return location; + } + + /** * @return the server parsed from /locraw. */ @NotNull @@ -376,31 +383,45 @@ public class Utils { } /** + * Parses /locraw chat message and updates {@link #server}, {@link #gameType}, {@link #locationRaw}, {@link #map} + * and {@link #location} + * + * @param message json message from chat + */ + private static void parseLocRaw(String message) { + JsonObject locRaw = JsonParser.parseString(message).getAsJsonObject(); + + if (locRaw.has("server")) { + server = locRaw.get("server").getAsString(); + } + if (locRaw.has("gameType")) { + gameType = locRaw.get("gameType").getAsString(); + } + if (locRaw.has("mode")) { + locationRaw = locRaw.get("mode").getAsString(); + location = Location.from(locationRaw); + } else { + location = Location.UNKNOWN; + } + if (locRaw.has("map")) { + map = locRaw.get("map").getAsString(); + } + } + + /** * Parses the /locraw reply from the server and updates the player's profile id * * @return not display the message in chat if the command is sent by the mod */ public static boolean onChatMessage(Text text, boolean overlay) { String message = text.getString(); - if (message.startsWith("{\"server\":") && message.endsWith("}")) { - JsonObject locRaw = JsonParser.parseString(message).getAsJsonObject(); - if (locRaw.has("server")) { - server = locRaw.get("server").getAsString(); - if (locRaw.has("gameType")) { - gameType = locRaw.get("gameType").getAsString(); - } - if (locRaw.has("mode")) { - locationRaw = locRaw.get("mode").getAsString(); - } - if (locRaw.has("map")) { - map = locRaw.get("map").getAsString(); - } - boolean shouldFilter = !sentLocRaw; - sentLocRaw = false; + if (message.startsWith("{\"server\":") && message.endsWith("}")) { + parseLocRaw(message); + boolean shouldFilter = !sentLocRaw; + sentLocRaw = false; - return shouldFilter; - } + return shouldFilter; } if (isOnSkyblock && message.startsWith("Profile ID: ")) { @@ -419,6 +440,7 @@ public class Utils { gameType = ""; locationRaw = ""; map = ""; + location = Location.UNKNOWN; } private static void tickMayorCache(boolean refresh) { diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 52cc52c4..25818e6d 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -89,7 +89,7 @@ "text.autoconfig.skyblocker.option.general.itemTooltip.enableExoticTooltip": "Enable Exotic Tooltip", "text.autoconfig.skyblocker.option.general.itemTooltip.enableExoticTooltip.@Tooltip": "Displays the type of exotic below the item's name if an armor piece is exotic.", "text.autoconfig.skyblocker.option.general.dungeonQuality": "Dungeon Quality", - "text.autoconfig.skyblocker.option.general.dungeonQuality.@Tooltip": "Displays quality of dungeon drops from mobs", + "text.autoconfig.skyblocker.option.general.dungeonQuality.@Tooltip": "Displays quality and tier of dungeon drops from mobs.\n\n\nReminder:\nTier 1-3 dropped from F1-F3\nTier 4-7 dropped from F4-F7 or M1-M4\nTier 8-10 are dropped only from M5-M7", "text.autoconfig.skyblocker.option.general.itemInfoDisplay": "Item Info Display", "text.autoconfig.skyblocker.option.general.itemInfoDisplay.attributeShardInfo": "Attribute Shard Info", "text.autoconfig.skyblocker.option.general.itemInfoDisplay.attributeShardInfo.@Tooltip": "Displays the attribute's level as the stack count and the initials of the attribute's name.", @@ -271,6 +271,8 @@ "text.autoconfig.skyblocker.option.locations.dungeons.solveWaterboard.@Tooltip": "Click the levers with green boxes to solve the puzzle.", "text.autoconfig.skyblocker.option.locations.dungeons.solveBoulder": "Solve Boulder Puzzle", "text.autoconfig.skyblocker.option.locations.dungeons.solveBoulder.@Tooltip": "Draws a line to the chest and highlight button", + "text.autoconfig.skyblocker.option.locations.dungeons.solveIceFill": "Solve Ice Fill Puzzle", + "text.autoconfig.skyblocker.option.locations.dungeons.solveSilverfish": "Solve Silverfish Puzzle", "text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage": "Mimic Message", "text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.sendMimicMessage": "Enable Mimic Message", "text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.sendMimicMessage.@Tooltip": "Sends a message in chat upon killing a mimic for other players' score calculation mods.", @@ -456,7 +458,7 @@ "skyblocker.dungeons.secrets.customWaypointNotFound": "§cNo custom waypoint found at X: %d, Y: %d, Z: %d for room %s.", "skyblocker.dungeons.dungeonScore.scoreText": "Score: %s", "skyblocker.dungeons.puzzle.boulder.noSolution": "No solution found!", - + "skyblocker.dungeons.secretsTracker.feedback": "%s§f found %s§f secrets. %s", "skyblocker.dungeons.secretsTracker.failFeedback": "§cUnable to calculate the amount of secrets everybody did this run!", @@ -574,7 +576,7 @@ "skyblocker.partyFinder.yourParty": "Your party", "skyblocker.partyFinder.deList": "Click to de-list", "skyblocker.partyFinder.join": "Click to join", - + "skyblocker.crimson.kuudra.noArrowPoison": "No Arrow Poison!", "skyblocker.crimson.kuudra.lowArrowPoison": "Low on Arrow Poison!", diff --git a/src/main/resources/assets/skyblocker/tabhud/standard/spider_den.json b/src/main/resources/assets/skyblocker/tabhud/standard/spider_den.json new file mode 100644 index 00000000..faa437ac --- /dev/null +++ b/src/main/resources/assets/skyblocker/tabhud/standard/spider_den.json @@ -0,0 +1,17 @@ +{ + "widgets": [ + { + "name": "SpidersDenServerWidget", + "alias": "psw" + } + ], + "layout": [ + { + "op": "place", + "where": "center", + "apply_to": [ + "psw" + ] + } + ] +}
\ No newline at end of file diff --git a/src/main/resources/resourcepacks/top_aligned/assets/skyblocker/tabhud/standard/spider_den.json b/src/main/resources/resourcepacks/top_aligned/assets/skyblocker/tabhud/standard/spider_den.json new file mode 100644 index 00000000..0df51096 --- /dev/null +++ b/src/main/resources/resourcepacks/top_aligned/assets/skyblocker/tabhud/standard/spider_den.json @@ -0,0 +1,17 @@ +{ + "widgets": [ + { + "name": "SpidersDenServerWidget", + "alias": "psw" + } + ], + "layout": [ + { + "op": "place", + "where": "centerTop", + "apply_to": [ + "psw" + ] + } + ] +}
\ No newline at end of file |