aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorolim88 <bobq4582@gmail.com>2025-03-03 04:10:02 +0000
committerGitHub <noreply@github.com>2025-03-03 12:10:02 +0800
commitfa9e6b7663c6f81e08e6e3cc1cf25907522ae82a (patch)
treef4c8fe69bc8b7cf40a86ee906ff5b478f6286c64 /src/main/java
parentcd728aecd0da561aa6800f5b5b975b8ad9435e0a (diff)
downloadSkyblocker-fa9e6b7663c6f81e08e6e3cc1cf25907522ae82a.tar.gz
Skyblocker-fa9e6b7663c6f81e08e6e3cc1cf25907522ae82a.tar.bz2
Skyblocker-fa9e6b7663c6f81e08e6e3cc1cf25907522ae82a.zip
Fossil Solver (#1156)
* nearly working nearly working state however lots of the code will need refactoring once i understand what it needs to do * fix broken code still needs refactor but works now * code should work now needs a refactor next and a few small feature * add usefull infomation to tooltips * big refactor * add config option * fix bugs * improve test and tweak fossil types * add more tooltip infomation * add translations for tooltips * fix rebase * clean up code hopefully good code now. ready to finally make a pr * Fix config location * Fix static usage * Clean up names --------- Co-authored-by: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com>
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilCalculations.java242
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilSolver.java171
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilTypes.java74
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/Structures.java145
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java4
8 files changed, 648 insertions, 1 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
index 4b7d198d..fa3294c6 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
@@ -275,6 +275,14 @@ public class MiningCategory {
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.mining.glacite.fossilSolver"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.glacite.fossilSolver.@Tooltip")))
+ .binding(defaults.mining.glacite.fossilSolver,
+ () -> config.mining.glacite.fossilSolver,
+ newValue -> config.mining.glacite.fossilSolver = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.mining.glacite.enableCorpseFinder"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.glacite.enableCorpseFinder.@Tooltip")))
.binding(defaults.mining.glacite.enableCorpseFinder,
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
index 33ed1b05..671b777a 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
@@ -165,6 +165,9 @@ public class MiningConfig {
@SerialEntry
public boolean coldOverlay = true;
+ @SerialEntry
+ public boolean fossilSolver = true;
+
@SerialEntry
public boolean enableCorpseFinder = true;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilCalculations.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilCalculations.java
new file mode 100644
index 00000000..14ca38c3
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilCalculations.java
@@ -0,0 +1,242 @@
+package de.hysky.skyblocker.skyblock.dwarven.fossil;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class FossilCalculations {
+ private static final List<Structures.permutation> POSSIBLE_STATES = getAllPossibleStates();
+ private static final int EXCAVATOR_WIDTH = 9;
+ private static final int EXCAVATOR_HEIGHT = 6;
+
+
+ /**
+ * The number of still possible fossil permutations from last time {@link FossilCalculations#getFossilChance} was invoked
+ */
+ protected static int permutations = -1;
+ /**
+ * The least amount of clicks needed to uncover a fossil from last time {@link FossilCalculations#getFossilChance} was invoked
+ */
+ protected static int minimumTiles;
+
+ protected static String fossilName;
+
+ /**
+ * Returns an array of how likely a slot is to contain a fossil assuming one has to exist
+ *
+ * @param tiles the state of the excavator window
+ * @return the probability of a fossil being in a tile
+ */
+
+ protected static double[] getFossilChance(Structures.TileGrid tiles, String percentage) {
+ int[] total = new int[EXCAVATOR_WIDTH * EXCAVATOR_HEIGHT];
+ minimumTiles = EXCAVATOR_WIDTH * EXCAVATOR_HEIGHT;
+ AtomicInteger fossilCount = new AtomicInteger();
+ Arrays.stream(tiles.state()).forEach(row -> Arrays.stream(row).forEach(tile -> {if (tile.equals(Structures.TileState.FOSSIL)) fossilCount.getAndIncrement();}));
+
+ //loop though tile options and if they are valid
+ List<Structures.permutation> validStates = new ArrayList<>();
+ for (Structures.permutation state : POSSIBLE_STATES) {
+ if (state.isValid(tiles, percentage)) {
+ validStates.add(state);
+ //update minimum left if it's smaller than current value
+ int min = state.type().tileCount - fossilCount.get();
+ if (min < minimumTiles) {
+ minimumTiles = min;
+ }
+ }
+ }
+ //update permutations value
+ permutations = validStates.size();
+
+ //update fossil name value
+ if (validStates.isEmpty()) {
+ fossilName = null;
+ } else {
+ //assume there is only one type of fossil and set name to value of first fossil
+ fossilName = validStates.getFirst().type().name;
+ for (Structures.permutation fossil : validStates) {
+ //if there is more than one type of fossil reset name to null
+ if (!fossil.type().name.equals(fossilName)) {
+ fossilName = null;
+ break;
+ }
+ }
+ }
+
+ //from all the valid states work out the chance of each tile being a fossil
+ int index = 0;
+ for (int y = 0; y < EXCAVATOR_HEIGHT; y++) {
+ for (int x = 0; x < EXCAVATOR_WIDTH; x++) {
+ if (tiles.getSlot(x, y) == Structures.TileState.UNKNOWN) {
+ for (Structures.permutation state : validStates) {
+ if (state.isFossilCollision(x, y)) {
+ total[index] += 1;
+ }
+ }
+ }
+ index++;
+ }
+ }
+
+
+ return Arrays.stream(total).mapToDouble(x -> (double) x / validStates.size()).toArray();
+ }
+
+ /**
+ * Converts a dictionary of item stacks to{@link Structures.TileGrid}. assuming each row will be 9 tiles and there will be 6 rows
+ *
+ * @param currentState dictionary of item in container
+ * @return input container converted into 2d {@link Structures.TileState} array
+ */
+ protected static Structures.TileGrid convertItemsToTiles(Int2ObjectMap<ItemStack> currentState) {
+ Structures.TileGrid output = new Structures.TileGrid(new Structures.TileState[EXCAVATOR_HEIGHT][EXCAVATOR_WIDTH]);
+ //go through each slot and work out its state
+ int index = 0;
+ for (int y = 0; y < EXCAVATOR_HEIGHT; y++) {
+ for (int x = 0; x < EXCAVATOR_WIDTH; x++) {
+ Item item = currentState.get(index).getItem();
+ if (item == Items.WHITE_STAINED_GLASS_PANE) {
+ output.updateSlot(x, y, Structures.TileState.FOSSIL);
+ } else if (item == Items.BROWN_STAINED_GLASS_PANE) {
+ output.updateSlot(x, y, Structures.TileState.UNKNOWN);
+ } else {
+ output.updateSlot(x, y, Structures.TileState.EMPTY);
+ }
+ index++;
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Finds all possible fossil combinations and creates a list to return
+ *
+ * @return list of all possible fossil arrangements
+ */
+ protected static List<Structures.permutation> getAllPossibleStates() {
+ List<Structures.permutation> output = new ArrayList<>();
+ //loop though each fossil type and for each possible rotation add valid offset of add to output list
+
+ //loop through fossils
+ for (FossilTypes fossil : FossilTypes.values()) {
+ //loop though rotations
+ for (Structures.TransformationOptions rotation : fossil.rotations) {
+ //get the rotated grid of the fossil
+ Structures.TileGrid grid = transformGrid(new Structures.TileGrid(fossil.grid), rotation);
+ //get possible offsets for the grid based on width and height
+ int maxXOffset = EXCAVATOR_WIDTH - grid.width();
+ int maxYOffset = EXCAVATOR_HEIGHT - grid.height();
+ //loop though possible offsets and for each of them create a screen state and return the value
+ for (int x = 0; x <= maxXOffset; x++) {
+ for (int y = 0; y <= maxYOffset; y++) {
+ output.add(new Structures.permutation(fossil, grid, x, y));
+ }
+ }
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Transforms a grid for each of the options in {@link Structures.TransformationOptions}
+ *
+ * @param grid input grid
+ * @param transformation transformation to perform on gird
+ * @return transformed grid
+ */
+ private static Structures.TileGrid transformGrid(Structures.TileGrid grid, Structures.TransformationOptions transformation) {
+ switch (transformation) {
+ case ROTATED_90 -> {
+ return rotateGrid(grid, 90);
+ }
+ case ROTATED_180 -> {
+ return rotateGrid(grid, 180);
+ }
+ case ROTATED_270 -> {
+ return rotateGrid(grid, 270);
+ }
+ case FLIP_ROTATED_0 -> {
+ return flipGrid(grid);
+ }
+ case FLIP_ROTATED_90 -> {
+ return rotateGrid(flipGrid(grid), 90);
+ }
+ case FLIP_ROTATED_180 -> {
+ return rotateGrid(flipGrid(grid), 180);
+ }
+ case FLIP_ROTATED_270 -> {
+ return rotateGrid(flipGrid(grid), 270);
+ }
+ default -> {
+ return grid;
+ }
+ }
+ }
+
+ /**
+ * Flips the grid along the vertical axis
+ *
+ * @param grid input grid
+ * @return flipped grid
+ */
+ private static Structures.TileGrid flipGrid(Structures.TileGrid grid) {
+ Structures.TileGrid output = new Structures.TileGrid(new Structures.TileState[grid.height()][grid.width()]);
+ for (int x = 0; x < grid.width(); x++) {
+ for (int y = 0; y < grid.height(); y++) {
+ output.updateSlot(x, y, grid.getSlot(x, grid.height() - 1 - y));
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Applies a rotation to a grid
+ *
+ * @param grid input grid
+ * @param rotation rotation amount in degrees
+ * @return rotated grid
+ */
+ private static Structures.TileGrid rotateGrid(Structures.TileGrid grid, int rotation) {
+ int startingWidth = grid.width() - 1;
+ int startingHeight = grid.height() - 1;
+ switch (rotation) {
+ case 90 -> {
+ Structures.TileGrid output = new Structures.TileGrid(new Structures.TileState[grid.height()][grid.width()]);
+ for (int x = 0; x < grid.width(); x++) {
+ for (int y = 0; y < grid.height(); y++) {
+ output.updateSlot(startingWidth - x, y, grid.getSlot(x, y));
+ }
+ }
+ return output;
+ }
+ case 180 -> {
+ Structures.TileGrid output = new Structures.TileGrid(new Structures.TileState[grid.height()][grid.width()]);
+ for (int x = 0; x < grid.width(); x++) {
+ for (int y = 0; y < grid.height(); y++) {
+ output.updateSlot(startingWidth - x, startingHeight - y, grid.getSlot(x, y));
+ }
+ }
+ return output;
+ }
+ case 270 -> {
+ Structures.TileGrid output = new Structures.TileGrid(new Structures.TileState[grid.height()][grid.width()]);
+ for (int x = 0; x < grid.width(); x++) {
+ for (int y = 0; y < grid.height(); y++) {
+ output.updateSlot(x, startingHeight - y, grid.getSlot(x, y));
+ }
+ }
+ return output;
+ }
+ default -> {
+ return grid;
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilSolver.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilSolver.java
new file mode 100644
index 00000000..6951bf0f
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilSolver.java
@@ -0,0 +1,171 @@
+package de.hysky.skyblocker.skyblock.dwarven.fossil;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.dwarven.fossil.Structures.TileGrid;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.LineSmoothener;
+import de.hysky.skyblocker.utils.container.SimpleContainerSolver;
+import de.hysky.skyblocker.utils.container.TooltipAdder;
+import de.hysky.skyblocker.utils.render.gui.ColorHighlight;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.item.tooltip.TooltipType;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.OptionalDouble;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static de.hysky.skyblocker.skyblock.dwarven.fossil.FossilCalculations.*;
+
+public class FossilSolver extends SimpleContainerSolver implements TooltipAdder {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static final Pattern PERCENTAGE_PATTERN = Pattern.compile("Fossil Excavation Progress: (\\d{1,2}.\\d)%");
+ private static final Pattern CHARGES_PATTERN = Pattern.compile("Chisel Charges Remaining: (\\d{1,2})");
+
+ private static String percentage;
+ private static double[] probability;
+ private static int chiselLeft = -1;
+
+ public FossilSolver() {
+ super("Fossil Excavator");
+ }
+
+ @Override
+ public List<ColorHighlight> getColors(Int2ObjectMap<ItemStack> slots) {
+ //convert to container
+ TileGrid mainTileGrid = convertItemsToTiles(slots);
+ //get how many chisels the player has left
+ if (!slots.isEmpty()) {
+ chiselLeft = getChiselLeft(slots.values().stream().findAny().get());
+ } else {
+ chiselLeft = -1;
+ }
+ //get the fossil chance percentage
+ if (percentage == null) {
+ percentage = getFossilPercentage(slots);
+ }
+ //get chance for each
+ probability = getFossilChance(mainTileGrid, percentage);
+ //get the highlight amount and return
+ return convertChanceToColor(probability);
+ }
+
+ /**
+ * Checks the tooltip of an item to see how many chisel uses a player has left
+ *
+ * @param itemStack item to use to check the tooltip
+ * @return how many chisels are left and "-1" if not found
+ */
+ private int getChiselLeft(ItemStack itemStack) {
+ for (Text line : itemStack.getTooltip(Item.TooltipContext.DEFAULT, CLIENT.player, TooltipType.BASIC)) {
+ Matcher matcher = CHARGES_PATTERN.matcher(line.getString());
+ if (matcher.find()) {
+ return Integer.parseInt(matcher.group(1));
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * See if there is any found fossils then see if there is a fossil chance percentage in the tooltip
+ *
+ * @param slots items to check tooltip of
+ * @return null if there is none or the value of the percentage
+ */
+ private String getFossilPercentage(Int2ObjectMap<ItemStack> slots) {
+ for (ItemStack item : slots.values()) {
+ for (Text line : item.getTooltip(Item.TooltipContext.DEFAULT, CLIENT.player, TooltipType.BASIC)) {
+ Matcher matcher = PERCENTAGE_PATTERN.matcher(line.getString());
+ if (matcher.matches()) {
+ return matcher.group(1);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return SkyblockerConfigManager.get().mining.glacite.fossilSolver;
+ }
+
+ /**
+ * Converts tile probability into a color for each of the remaining tiles
+ * @param chances the probability in order from 0-1
+ * @return the colors formated for the {@link SimpleContainerSolver#getColors(Int2ObjectMap)}
+ */
+ private static List<ColorHighlight> convertChanceToColor(double[] chances) {
+ List<ColorHighlight> outputColors = new ArrayList<>();
+ Color gradientColor = Color.BLUE;
+ //loop though all the chance values and set the color to match probability. full color means that its 100%
+ OptionalDouble highProbability = Arrays.stream(chances).max();
+ for (int i = 0; i < chances.length; i++) {
+ double chance = chances[i];
+ if (Double.isNaN(chances[i]) || chances[i] == 0) {
+ continue;
+ }
+ if (chances[i] == highProbability.getAsDouble()) {
+ outputColors.add(ColorHighlight.green(i));
+ continue;
+ }
+ outputColors.add(new ColorHighlight(i, (int) (chance * 255) << 24 | gradientColor.getRed() << 16 | gradientColor.getGreen() << 8 | gradientColor.getBlue()));
+ }
+ return outputColors;
+ }
+
+ /**
+ * Add solver info to tooltips
+ *
+ * @param focusedSlot the slot focused by the player
+ * @param stack unused
+ * @param lines the lines for the tooltip
+ */
+ @Override
+ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { //todo translatable
+ //only add if fossil or dirt
+ if (stack.getItem() != Items.GRAY_STAINED_GLASS_PANE && stack.getItem() != Items.BROWN_STAINED_GLASS_PANE) {
+ return;
+ }
+ //add spacer
+ lines.add(LineSmoothener.createSmoothLine());
+
+ //if no permutation say this instead of other stats
+ if (permutations == 0) {
+ lines.add(Text.translatable("skyblocker.config.mining.glacite.fossilSolver.toolTip.noFossilFound").formatted(Formatting.GOLD));
+ return;
+ }
+
+ //add permutation count
+ lines.add(Text.translatable("skyblocker.config.mining.glacite.fossilSolver.toolTip.possiblePatterns").append(Text.literal(String.valueOf(permutations)).formatted(Formatting.YELLOW)));
+ //add minimum tiles left count
+ lines.add(Text.translatable("skyblocker.config.mining.glacite.fossilSolver.toolTip.minimumTilesLeft").append(Text.literal(String.valueOf(minimumTiles)).formatted(chiselLeft >= minimumTiles ? Formatting.YELLOW : Formatting.RED)));
+ //add probability if available and not uncovered
+ if (focusedSlot != null && probability != null && probability.length > focusedSlot.getIndex() && stack.getItem() == Items.BROWN_STAINED_GLASS_PANE) {
+ lines.add(Text.translatable("skyblocker.config.mining.glacite.fossilSolver.toolTip.probability").append(Text.literal(Math.round(probability[focusedSlot.getIndex()] * 100) + "%").formatted(Formatting.YELLOW)));
+ }
+ //if only 1 type of fossil left and a fossil is partially uncovered add the fossil name
+ if (fossilName != null && percentage != null) {
+ lines.add(Text.translatable("skyblocker.config.mining.glacite.fossilSolver.toolTip.foundFossil").append(Text.literal(fossilName).formatted(Formatting.YELLOW)));
+ }
+ }
+
+ @Override
+ public int getPriority() {
+ return 0;
+ }
+
+
+}
+
+
+
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilTypes.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilTypes.java
new file mode 100644
index 00000000..4e3d1b1d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/FossilTypes.java
@@ -0,0 +1,74 @@
+package de.hysky.skyblocker.skyblock.dwarven.fossil;
+
+import java.util.List;
+
+/**
+ * All the possible fossils and there transformations
+ */
+public enum FossilTypes {
+ CLAW(new Structures.TileState[][]{
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY},
+ {Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY}
+ }, List.of(Structures.TransformationOptions.ROTATED_0, Structures.TransformationOptions.ROTATED_180, Structures.TransformationOptions.FLIP_ROTATED_90, Structures.TransformationOptions.FLIP_ROTATED_180), "7.7", 13, "Claw"),
+ TUSK(new Structures.TileState[][]{
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY}
+ }, List.of(Structures.TransformationOptions.ROTATED_0, Structures.TransformationOptions.ROTATED_90, Structures.TransformationOptions.ROTATED_180, Structures.TransformationOptions.FLIP_ROTATED_270), "12.5", 8, "Tusk"),
+ UGLY(new Structures.TileState[][]{
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY},
+ {Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY}
+ }, List.of(Structures.TransformationOptions.ROTATED_0, Structures.TransformationOptions.ROTATED_90, Structures.TransformationOptions.ROTATED_180, Structures.TransformationOptions.ROTATED_270), "6.2", 16, "Ugly"),
+ HELIX(new Structures.TileState[][]{
+ {Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL},
+ {Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL}
+ }, List.of(Structures.TransformationOptions.ROTATED_0, Structures.TransformationOptions.ROTATED_90, Structures.TransformationOptions.ROTATED_180, Structures.TransformationOptions.ROTATED_270), "7.1", 14, "Helix"),
+ WEBBED(new Structures.TileState[][]{
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY},
+ {Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY}
+ }, List.of(Structures.TransformationOptions.ROTATED_0, Structures.TransformationOptions.FLIP_ROTATED_0), "10", 10, "Webbed"),
+ FOOTPRINT(new Structures.TileState[][]{
+ {Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.FOSSIL},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY}
+ }, List.of(Structures.TransformationOptions.ROTATED_0, Structures.TransformationOptions.ROTATED_90, Structures.TransformationOptions.ROTATED_180, Structures.TransformationOptions.ROTATED_270), "7.7", 13, "Footprint"),
+ CLUBBED(new Structures.TileState[][]{
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL},
+ {Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.EMPTY}
+ }, List.of(Structures.TransformationOptions.ROTATED_0, Structures.TransformationOptions.ROTATED_180, Structures.TransformationOptions.FLIP_ROTATED_0, Structures.TransformationOptions.FLIP_ROTATED_180), "9.1", 11, "Clubbed"),
+ SPINE(new Structures.TileState[][]{
+ {Structures.TileState.EMPTY, Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY, Structures.TileState.EMPTY},
+ {Structures.TileState.EMPTY, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.EMPTY},
+ {Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL, Structures.TileState.FOSSIL}
+ }, List.of(Structures.TransformationOptions.ROTATED_0, Structures.TransformationOptions.ROTATED_90, Structures.TransformationOptions.ROTATED_180, Structures.TransformationOptions.ROTATED_270), "8.3", 12, "Spine");
+
+ final List<Structures.TransformationOptions> rotations;
+ final Structures.TileState[][] grid;
+ final String percentage;
+ final int tileCount;
+ final String name;
+
+
+ FossilTypes(Structures.TileState[][] grid, List<Structures.TransformationOptions> rotations, String percentage, int tileCount, String name) {
+ this.grid = grid;
+ this.rotations = rotations;
+ this.percentage = percentage;
+ this.tileCount = tileCount;
+ this.name = name;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/Structures.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/Structures.java
new file mode 100644
index 00000000..3e7af14f
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/fossil/Structures.java
@@ -0,0 +1,145 @@
+package de.hysky.skyblocker.skyblock.dwarven.fossil;
+
+public class Structures {
+ /**
+ * The three possible states a tile could be in
+ */
+ protected enum TileState {
+ UNKNOWN,
+ EMPTY,
+ FOSSIL
+ }
+
+ protected enum TransformationOptions {
+ ROTATED_0,
+ ROTATED_90,
+ ROTATED_180,
+ ROTATED_270,
+ FLIP_ROTATED_0,
+ FLIP_ROTATED_90,
+ FLIP_ROTATED_180,
+ FLIP_ROTATED_270;
+ }
+
+ /**
+ * Stores a grid of {@link TileState} as a 2d array referencable with an x and y
+ *
+ * @param state the starting state of the grid
+ */
+ protected record TileGrid(TileState[][] state) {
+ void updateSlot(int x, int y, TileState newState) {
+ state[y][x] = newState;
+ }
+
+ TileState getSlot(int x, int y) {
+ return state[y][x];
+ }
+
+ int width() {
+ return state[0].length;
+ }
+
+ int height() {
+ return state.length;
+ }
+ }
+
+ /**
+ * Stores all the different value a permutation of a fossil can have
+ *
+ * @param type what the type of fossil it is
+ * @param grid what the grid will look like for the fossil
+ * @param xOffset where it's positioned in the excavator window in the x direction
+ * @param yOffset where it's positioned in the excavator window in the y direction
+ */
+ protected record permutation(FossilTypes type, TileGrid grid, int xOffset, int yOffset) {
+ /**
+ * Works out if this is a valid state based on the current state of the excavator window
+ *
+ * @param currentState the state of the excavator window
+ * @return if this screen state can exist depending on found tiles
+ */
+ boolean isValid(TileGrid currentState, String percentage) {
+ //check the percentage
+ if (percentage != null && !percentage.equals(type.percentage)) {
+ return false;
+ }
+ //check conflicting tiles
+ for (int x = 0; x < currentState.width(); x++) {
+ for (int y = 0; y < currentState.height(); y++) {
+ TileState knownState = currentState.getSlot(x, y);
+ //if there is a miss match return false
+ switch (knownState) {
+ case UNKNOWN -> {
+ //still do not know if the tiles will match or not so carry on
+ continue;
+ }
+ case FOSSIL -> {
+ if (!isFossilCollision(x, y)) {
+ return false;
+ }
+ }
+ case EMPTY -> {
+ if (!isEmptyCollision(x, y)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ //if no conflicts return ture
+ return true;
+ }
+
+ /**
+ * If an empty tile is able to exist in this position in the excavator window and the permutation to still be valid
+ * (will still be valid if out of bound of the permutation)
+ *
+ * @param positionX pos x
+ * @param positionY pos y
+ * @return if the fossil is valid
+ */
+ private boolean isEmptyCollision(int positionX, int positionY) {
+ try {
+ return isState(positionX, positionY, TileState.EMPTY);
+ } catch (IndexOutOfBoundsException f) {
+ return true;
+ }
+ }
+
+ /**
+ * If a fossil is able to exist in this position in the excavator window and the permutation to still be valid
+ * (will not be valid of out of bounds of the permutation)
+ *
+ * @param positionX pos x
+ * @param positionY pos y
+ * @return if the fossil is valid
+ */
+ boolean isFossilCollision(int positionX, int positionY) {
+ try {
+ return isState(positionX, positionY, TileState.FOSSIL);
+ } catch (IndexOutOfBoundsException f) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the given state and this permutation line up at a given location
+ *
+ * @param positionX position in the excavator window x
+ * @param positionY position in the excavator window y
+ * @param state the state the excavator window is at this location
+ * @return if states match
+ */
+ private boolean isState(int positionX, int positionY, TileState state) {
+ int x = positionX - xOffset;
+ int y = positionY - yOffset;
+ //if they are not in range of the grid they are not a fossil
+ if (x < 0 || x >= grid.width() || y < 0 || y >= grid.height()) {
+ throw new IndexOutOfBoundsException("not in grid");
+ }
+ //return if position in grid is fossil
+ return grid.getSlot(x, y) == state;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
index b015e755..32ccf8e3 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
@@ -4,6 +4,7 @@ import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor;
import de.hysky.skyblocker.skyblock.bazaar.ReorderHelper;
import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver;
+import de.hysky.skyblocker.skyblock.dwarven.fossil.FossilSolver;
import de.hysky.skyblocker.skyblock.item.tooltip.adders.*;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.container.TooltipAdder;
@@ -29,6 +30,7 @@ public class TooltipManager {
new SupercraftReminder(),
ChocolateFactorySolver.INSTANCE,
BitsHelper.INSTANCE,
+ new FossilSolver(),
new ReorderHelper(),
new StackingEnchantProgressTooltip(0), //Would be best to have after the lore but the tech doesn't exist for that
new NpcPriceTooltip(1),
diff --git a/src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java
index 2f4e9bc0..5b7f999b 100644
--- a/src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java
@@ -15,6 +15,7 @@ import de.hysky.skyblocker.skyblock.dungeon.terminal.LightsOnTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.OrderTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.StartsWithTerminal;
import de.hysky.skyblocker.skyblock.dwarven.CommissionHighlight;
+import de.hysky.skyblocker.skyblock.dwarven.fossil.FossilSolver;
import de.hysky.skyblocker.skyblock.experiment.ChronomatronSolver;
import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver;
import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver;
@@ -54,7 +55,8 @@ public class ContainerSolverManager {
ChocolateFactorySolver.INSTANCE,
new ReorderHelper(),
BitsHelper.INSTANCE,
- new RaffleTaskHighlight()
+ new RaffleTaskHighlight(),
+ new FossilSolver()
};
private static ContainerSolver currentSolver = null;
private static List<ColorHighlight> highlights;