diff --git a/Documentation/StaircasedGuide.md b/Documentation/StaircasedGuide.md index 41e1833..eec0087 100644 --- a/Documentation/StaircasedGuide.md +++ b/Documentation/StaircasedGuide.md @@ -1,6 +1,6 @@ -# Staircased Printer +# Fullblock Printer -The Staircased Printer builds arbitrary staircased fullblock maps line by line without any user interaction. +The Fullblock Printer builds flat & staircased fullblock maps line by line without any user interaction. The bot mines all placed blocks again to recycle all used materials. The printer uses a mapart platform to collect all mined blocks and feeds them into an item sorter on the north side of the map. This module will not work on servers where placing blocks in the air is disabled. diff --git a/README.md b/README.md index dcc8813..df735de 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,16 @@ [![Stars](https://img.shields.io/github/stars/Julflips/nerv-printer-addon)](https://github.com/Julflips/nerv-printer-addon/stargazers) -## Carpet Printer -The Carpet Printer prints the map line-by-line and does not reuse carpet items, making it only suited for servers where carpet duping is enabled. You can find the full documentation [here](Documentation/CarpetGuide.md). +## Carpet (Flat) Printer +The Carpet Printer prints the map line-by-line and does not reuse carpet items, making it only suited for servers where carpet duping is enabled. +You can find the full documentation [here](Documentation/CarpetGuide.md). -## Staircased Printer -The Staircased Printer builds arbitrary staircased fullblock maps line by line. +## Fullblock Printer +The Fullblock Printer builds flat & staircased fullblock maps line by line. The bot mines all placed blocks again to recycle all used materials and feeds them into an item sorter. This module **will not work on servers where placing blocks in the air is disabled**. You can find the full documentation [here](Documentation/StaircasedGuide.md). -## Fullblock Flat Printer (not supported) -The Fullblock Printer utilizes a TNT-bomber and a large item sorter to reuse most materials used to build the map. However, it is only compatible with servers where TNT duplication is enabled. You can find the full documentation [here](Documentation/FullblockGuide.md). - -This module is not updated anymore. We recommend using the staircased printer even for flat maps instead. - ## Map Namer Semi-automatically names unnamed map items in inventory. Pauses on anvil break and insufficient xp and can be resumed. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cd4b7aa..e1f0f9a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/julflips/nerv_printer/Addon.java b/src/main/java/com/julflips/nerv_printer/Addon.java index 85a5420..2fe2e97 100644 --- a/src/main/java/com/julflips/nerv_printer/Addon.java +++ b/src/main/java/com/julflips/nerv_printer/Addon.java @@ -1,7 +1,6 @@ package com.julflips.nerv_printer; import com.julflips.nerv_printer.modules.CarpetPrinter; -import com.julflips.nerv_printer.modules.FullBlockPrinter; import com.julflips.nerv_printer.modules.MapNamer; import com.julflips.nerv_printer.modules.StaircasedPrinter; import com.julflips.nerv_printer.utils.MapAreaCache; @@ -28,7 +27,6 @@ public void onInitialize() { // Modules Modules.get().add(new CarpetPrinter()); - Modules.get().add(new FullBlockPrinter()); Modules.get().add(new StaircasedPrinter()); Modules.get().add(new MapNamer()); } diff --git a/src/main/java/com/julflips/nerv_printer/modules/CarpetPrinter.java b/src/main/java/com/julflips/nerv_printer/modules/CarpetPrinter.java index 68e3f0e..3d3cff8 100644 --- a/src/main/java/com/julflips/nerv_printer/modules/CarpetPrinter.java +++ b/src/main/java/com/julflips/nerv_printer/modules/CarpetPrinter.java @@ -143,7 +143,7 @@ public class CarpetPrinter extends Module implements MapPrinter { .build() ); - private final Setting northToSouth = sgGeneral.add(new BoolSetting.Builder() + private final Setting startNorthToSouth = sgGeneral.add(new BoolSetting.Builder() .name("north-to-south") .description("Start printing on the north side and go south. Flipped if disabled.") .defaultValue(true) @@ -818,7 +818,7 @@ private void onTick(TickEvent.Pre event) { // Swap into Hotbar if (toBeSwappedSlot != -1) { - Utils.swapIntoHotbar(toBeSwappedSlot, availableHotBarSlots); + swapIntoHotbar(toBeSwappedSlot); toBeSwappedSlot = -1; if (postSwapDelay.get() != 0) { timeoutTicks = postSwapDelay.get(); @@ -856,7 +856,7 @@ private void onTick(TickEvent.Pre event) { if (state == State.Dumping) { int dumpSlot = getDumpSlot(); if (dumpSlot == -1) { - HashMap requiredItems = Utils.getRequiredItems(mapCorner, workingInterval, linesPerRun.get(), availableSlots.size(), map); + HashMap requiredItems = getRequiredItems(); Pair, HashMap> invInformation = Utils.getInvInformation(requiredItems, availableSlots); refillInventory(invInformation.getRight()); state = State.Walking; @@ -909,8 +909,8 @@ private void onTick(TickEvent.Pre event) { checkpoints.remove(0); switch (checkpointAction.getLeft()) { case "lineEnd": - boolean atCornerSide = goal.z == mapCorner.toCenterPos().z; - calculateBuildingPath(atCornerSide, false); + boolean reachedNorthSide = goal.z == mapCorner.toCenterPos().z; + calculateBuildingPath(reachedNorthSide, false); ArrayList newErrors = Utils.getInvalidPlacements(mapCorner, workingInterval, map, knownErrors); for (BlockPos errorPos : newErrors) { BlockPos relativePos = errorPos.subtract(mapCorner); @@ -1109,7 +1109,7 @@ private Pair getBestChest(Item item) { private void refillInventory(HashMap invMaterial) { //Fills restockList with required items restockList.clear(); - HashMap requiredItems = Utils.getRequiredItems(mapCorner, workingInterval, linesPerRun.get(), availableSlots.size(), map); + HashMap requiredItems = getRequiredItems(); for (Item item : invMaterial.keySet()) { int oldAmount = requiredItems.remove(item); requiredItems.put(item, oldAmount - invMaterial.get(item)); @@ -1228,17 +1228,17 @@ private boolean tryPlacingBlock(BlockPos pos) { // Path and Building Management - private void calculateBuildingPath(boolean cornerSide, boolean sprintFirst) { + private void calculateBuildingPath(boolean startNorthSide, boolean sprintFirst) { //Iterate over map and skip completed lines. Player has to be able to see the complete map area //Fills checkpoints list - boolean isStartSide = cornerSide; + boolean northToSouth = startNorthSide; checkpoints.clear(); for (int x = workingInterval.getLeft(); x <= workingInterval.getRight(); x += linesPerRun.get()) { if (!Utils.isInInterval(workingInterval, x)) continue; boolean lineFinished = true; for (int lineBonus = 0; lineBonus < linesPerRun.get(); lineBonus++) { int adjustedX = x + lineBonus; - if (adjustedX > workingInterval.getRight()) break; + if (!Utils.isInInterval(workingInterval, adjustedX)) break; for (int z = 0; z < 128; z++) { BlockState blockState = MapAreaCache.getCachedBlockState(mapCorner.add(adjustedX, 0, z)); if (blockState.isAir() && map[adjustedX][z] != null) { @@ -1251,14 +1251,14 @@ private void calculateBuildingPath(boolean cornerSide, boolean sprintFirst) { if (lineFinished) continue; Vec3d cp1 = mapCorner.toCenterPos().add(x, 0, 0); Vec3d cp2 = mapCorner.toCenterPos().add(x, 0, 127); - if (isStartSide) { + if (northToSouth) { checkpoints.add(new Pair(cp1, new Pair("", null))); checkpoints.add(new Pair(cp2, new Pair("lineEnd", null))); } else { checkpoints.add(new Pair(cp2, new Pair("", null))); checkpoints.add(new Pair(cp1, new Pair("lineEnd", null))); } - isStartSide = !isStartSide; + northToSouth = !northToSouth; } if (checkpoints.size() > 0 && sprintFirst) { //Make player sprint to the start of the map @@ -1271,7 +1271,7 @@ private void startBuilding() { if (!SlaveSystem.isSlave()) SlaveSystem.startAllSlaves(); if (availableSlots.isEmpty()) setupSlots(); MapAreaCache.reset(mapCorner); - calculateBuildingPath(northToSouth.get(), true); + calculateBuildingPath(startNorthToSouth.get(), true); checkpoints.add(0, new Pair(dumpStation.getLeft(), new Pair("dump", null))); state = State.Walking; } @@ -1321,7 +1321,7 @@ private boolean setupSlots() { } private int getDumpSlot() { - HashMap requiredItems = Utils.getRequiredItems(mapCorner, workingInterval, linesPerRun.get(), availableSlots.size(), map); + HashMap requiredItems = getRequiredItems(); Pair, HashMap> invInformation = Utils.getInvInformation(requiredItems, availableSlots); if (invInformation.getLeft().isEmpty()) { return -1; @@ -1329,6 +1329,164 @@ private int getDumpSlot() { return invInformation.getLeft().get(0); } + private HashMap getRequiredItems() { + // Calculate the next items to restock + HashMap requiredItems = new HashMap<>(); + boolean northToSouth = true; + boolean hasFoundAir = false; + for (int x = workingInterval.getLeft(); x <= workingInterval.getRight(); x += linesPerRun.get()) { + for (int z = 0; z < 128; z++) { + for (int lineBonus = 0; lineBonus < linesPerRun.get(); lineBonus++) { + int adjustedX = x + lineBonus; + if (adjustedX > workingInterval.getRight()) break; + int adjustedZ = z; + if (!northToSouth) adjustedZ = 127 - z; + BlockState blockState = MapAreaCache.getCachedBlockState(mapCorner.add(adjustedX, 0, adjustedZ)); + if (blockState.isAir() && map[adjustedX][adjustedZ] != null) { + if (!hasFoundAir) { + hasFoundAir = true; + BlockState oppositeBlockState = MapAreaCache.getCachedBlockState(mapCorner.add(adjustedX, 0, 127 - adjustedZ)); + // If the first air block does not have an opposite air block, the snake pattern got reversed at some point + // We reverse the search too + if (!oppositeBlockState.isAir() && z < 64) { + northToSouth = !northToSouth; + adjustedZ = 127 - z; + } + } + //ChatUtils.info("Add material for: " + mapCorner.add(x + lineBonus, 0, adjustedZ).toShortString()); + Item material = map[adjustedX][adjustedZ].asItem(); + if (!requiredItems.containsKey(material)) requiredItems.put(material, 0); + requiredItems.put(material, requiredItems.get(material) + 1); + //Check if the item fits into inventory. If not, undo the last increment and return + if (Utils.stacksRequired(requiredItems.values()) > availableSlots.size()) { + requiredItems.put(material, requiredItems.get(material) - 1); + return requiredItems; + } + } + } + } + northToSouth = !northToSouth; + } + return requiredItems; + } + + private void swapIntoHotbar(int slot) { + Map itemSlot = new HashMap<>(); + Map blocksUntilItemUse = new HashMap<>(); + Map itemFrequency = new HashMap<>(); + + int targetSlot = availableHotBarSlots.get(0); + + // Scan hotbar + for (int hotbarSlot : availableHotBarSlots) { + ItemStack stack = mc.player.getInventory().getStack(hotbarSlot); + if (!stack.isEmpty()) { + Item item = stack.getItem(); + itemSlot.put(item, hotbarSlot); + blocksUntilItemUse.put(item, -1); // -1 = never used + itemFrequency.put(item, 0); + } else { + targetSlot = hotbarSlot; + break; + } + } + + // PRIORITY 1: empty slot → instant choice + if (mc.player.getInventory().getStack(targetSlot).isEmpty()) { + Utils.performSwap(slot, targetSlot); + return; + } + + // Get blocks until next use of items in hotbar + int blockCounter = 0; + boolean northToSouth = startNorthToSouth.get(); + boolean hasFoundAir = false; + for (int x = workingInterval.getLeft(); x <= workingInterval.getRight(); x += linesPerRun.get()) { + if (!Utils.isInInterval(workingInterval, x)) continue; + + for (int z = 0; z < 128; z++) { + for (int lineBonus = 0; lineBonus < linesPerRun.get(); lineBonus++) { + int adjustedX = x + lineBonus; + int adjustedZ = z; + if (!northToSouth) adjustedZ = 127 - z; + if (!Utils.isInInterval(workingInterval, adjustedX)) break; + + blockCounter++; + BlockState state = MapAreaCache.getCachedBlockState(mapCorner.add(adjustedX, 0, adjustedZ)); + if (state.isAir()) { + if (!hasFoundAir) { + hasFoundAir = true; + BlockState oppositeBlockState = MapAreaCache.getCachedBlockState(mapCorner.add(adjustedX, 0, 127 - adjustedZ)); + // If the first air block does not have an opposite air block, the snake pattern got reversed at some point + // We reverse the search too + if (!oppositeBlockState.isAir() && z < 64) { + northToSouth = !northToSouth; + adjustedZ = 127 - z; + } + } + + Block block = map[adjustedX][adjustedZ]; + if (block == null) continue; + + Item item = block.asItem(); + + if (blocksUntilItemUse.containsKey(item) && + blocksUntilItemUse.get(item) == -1) { + blocksUntilItemUse.put(item, blockCounter); + } + } + } + } + northToSouth = !northToSouth; + } + + // Count frequency of items in hotbar + for (int hotbarSlot : availableHotBarSlots) { + ItemStack stack = mc.player.getInventory().getStack(hotbarSlot); + if (!stack.isEmpty()) { + Item item = stack.getItem(); + itemFrequency.put(item, itemFrequency.get(item) + 1); + } + } + + // Choose best candidate + Item bestItem = null; + int bestDistance = -2; // lower than -1 + int bestFrequency = -1; + + for (Item item : itemSlot.keySet()) { + int distance = blocksUntilItemUse.get(item); // -1 = never used + int frequency = itemFrequency.get(item); + + boolean better = false; + + // PRIORITY 2: never used (-1) + if (distance == -1 && bestDistance != -1) { + better = true; + } + // PRIORITY 3: hotbar frequency + else if (frequency > bestFrequency) { + better = true; + } + // PRIORITY 4: distance to next use + else if (frequency == bestFrequency && distance > bestDistance && bestDistance != -1) { + better = true; + } + + if (better) { + bestItem = item; + bestDistance = distance; + bestFrequency = frequency; + } + } + + if (bestItem != null) { + targetSlot = itemSlot.get(bestItem); + } + + Utils.performSwap(slot, targetSlot); + } + // MapPrinter Interface for Slave Logic public void setInterval(Pair interval) { diff --git a/src/main/java/com/julflips/nerv_printer/modules/FullBlockPrinter.java b/src/main/java/com/julflips/nerv_printer/modules/FullBlockPrinter.java deleted file mode 100644 index 66b979d..0000000 --- a/src/main/java/com/julflips/nerv_printer/modules/FullBlockPrinter.java +++ /dev/null @@ -1,1415 +0,0 @@ -package com.julflips.nerv_printer.modules; - -import com.julflips.nerv_printer.Addon; -import com.julflips.nerv_printer.utils.MapAreaCache; -import com.julflips.nerv_printer.utils.Utils; -import meteordevelopment.meteorclient.events.packets.PacketEvent; -import meteordevelopment.meteorclient.events.render.Render3DEvent; -import meteordevelopment.meteorclient.events.world.TickEvent; -import meteordevelopment.meteorclient.gui.utils.StarscriptTextBoxRenderer; -import meteordevelopment.meteorclient.renderer.ShapeMode; -import meteordevelopment.meteorclient.settings.*; -import meteordevelopment.meteorclient.systems.modules.Module; -import meteordevelopment.meteorclient.utils.player.InvUtils; -import meteordevelopment.meteorclient.utils.player.PlayerUtils; -import meteordevelopment.meteorclient.utils.player.Rotations; -import meteordevelopment.meteorclient.utils.render.color.SettingColor; -import meteordevelopment.meteorclient.utils.world.BlockUtils; -import meteordevelopment.orbit.EventHandler; -import net.minecraft.block.*; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtList; -import net.minecraft.nbt.NbtSizeTracker; -import net.minecraft.network.packet.c2s.play.*; -import net.minecraft.network.packet.s2c.play.InventoryS2CPacket; -import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket; -import net.minecraft.screen.slot.SlotActionType; -import net.minecraft.util.Hand; -import net.minecraft.util.Pair; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; -import net.minecraft.util.math.Vec3d; -import org.apache.commons.lang3.tuple.Triple; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public class FullBlockPrinter extends Module { - private final SettingGroup sgGeneral = settings.getDefaultGroup(); - private final SettingGroup sgAdvanced = settings.createGroup("Advanced", false); - private final SettingGroup sgError = settings.createGroup("Error Handling"); - private final SettingGroup sgRender = settings.createGroup("Render"); - - private final Setting linesPerRun = sgGeneral.add(new IntSetting.Builder() - .name("lines-per-run") - .description("How many lines to place in parallel per run.") - .defaultValue(3) - .min(1) - .sliderRange(1, 5) - .build() - ); - - private final Setting placeRange = sgGeneral.add(new DoubleSetting.Builder() - .name("place-range") - .description("The maximum range you can place blocks around yourself.") - .defaultValue(4) - .min(1) - .sliderRange(1, 5) - .build() - ); - - private final Setting placeDelay = sgGeneral.add(new IntSetting.Builder() - .name("place-delay") - .description("How many milliseconds to wait after placing.") - .defaultValue(50) - .min(1) - .sliderRange(10, 300) - .build() - ); - - private final Setting> startBlock = sgGeneral.add(new BlockListSetting.Builder() - .name("start-Block") - .description("Which block to interact with to start the printing process.") - .defaultValue(Blocks.STONE_BUTTON, Blocks.ACACIA_BUTTON, Blocks.BAMBOO_BUTTON, Blocks.BIRCH_BUTTON, - Blocks.CRIMSON_BUTTON, Blocks.DARK_OAK_BUTTON, Blocks.JUNGLE_BUTTON, Blocks.OAK_BUTTON, - Blocks.POLISHED_BLACKSTONE_BUTTON, Blocks.SPRUCE_BUTTON, Blocks.WARPED_BUTTON) - .build() - ); - - private final Setting mapFillSquareSize = sgGeneral.add(new IntSetting.Builder() - .name("map-fill-square-size") - .description("The radius of the square the bot fill walk to explore the map.") - .defaultValue(1) - .min(0) - .sliderRange(0, 50) - .build() - ); - - private final Setting resetDelay = sgGeneral.add(new IntSetting.Builder() - .name("reset-delay") - .description("How many ticks to wait after after reset button was pressed.") - .defaultValue(400) - .min(1) - .sliderRange(50, 600) - .build() - ); - - private final Setting tntDistance = sgGeneral.add(new IntSetting.Builder() - .name("tnt-distance") - .description("How many blocks the bot should stay away from the dropped tnt (z axis).") - .defaultValue(10) - .min(1) - .sliderRange(1, 30) - .build() - ); - - private final Setting activationReset = sgGeneral.add(new BoolSetting.Builder() - .name("activation-reset") - .description("Disable if the bot should continue after reconnecting to the server.") - .defaultValue(true) - .build() - ); - - private final Setting startResetNorth = sgGeneral.add(new BoolSetting.Builder() - .name("start-reset-north") - .description("If true, use the North Reset Trapped Chest first. Use south if not.") - .defaultValue(true) - .build() - ); - - private final Setting sprinting = sgGeneral.add(new EnumSetting.Builder() - .name("sprint-mode") - .description("How to sprint.") - .defaultValue(SprintMode.NotPlacing) - .build() - ); - - private final Setting rotate = sgGeneral.add(new BoolSetting.Builder() - .name("rotate") - .description("Rotate when placing a block.") - .defaultValue(true) - .build() - ); - - private final Setting customFolderPath = sgGeneral.add(new BoolSetting.Builder() - .name("custom-folder-path") - .description("Allows to set a custom path to the nbt folder.") - .defaultValue(false) - .build() - ); - - public final Setting mapPrinterFolderPath = sgGeneral.add(new StringSetting.Builder() - .name("nerv-printer-folder-path") - .description("The path to your nerv-printer directory.") - .defaultValue("C:\\Users\\(username)\\AppData\\Roaming\\.minecraft\\nerv-printer") - .wide() - .renderer(StarscriptTextBoxRenderer.class) - .visible(() -> customFolderPath.get()) - .build() - ); - - //Advanced - - private final Setting preRestockDelay = sgAdvanced.add(new IntSetting.Builder() - .name("pre-restock-delay") - .description("How many ticks to wait to take items after opening the chest.") - .defaultValue(10) - .min(1) - .sliderRange(1, 40) - .build() - ); - - private final Setting invActionDelay = sgAdvanced.add(new IntSetting.Builder() - .name("inventory-action-delay") - .description("How many ticks to wait between each inventory action (moving a stack).") - .defaultValue(2) - .min(1) - .sliderRange(1, 40) - .build() - ); - - private final Setting postRestockDelay = sgAdvanced.add(new IntSetting.Builder() - .name("post-restock-delay") - .description("How many ticks to wait after restocking.") - .defaultValue(10) - .min(1) - .sliderRange(1, 40) - .build() - ); - - private final Setting preSwapDelay = sgAdvanced.add(new IntSetting.Builder() - .name("pre-swap-delay") - .description("How many ticks to wait before swapping an item into the hotbar.") - .defaultValue(5) - .min(0) - .sliderRange(0, 20) - .build() - ); - - private final Setting postSwapDelay = sgAdvanced.add(new IntSetting.Builder() - .name("post-swap-delay") - .description("How many ticks to wait after swapping an item into the hotbar.") - .defaultValue(5) - .min(0) - .sliderRange(0, 20) - .build() - ); - - private final Setting resetChestCloseDelay = sgAdvanced.add(new IntSetting.Builder() - .name("reset-chest-close-delay") - .description("How many ticks to wait before closing the reset trap chest again.") - .defaultValue(10) - .min(1) - .sliderRange(1, 40) - .build() - ); - - private final Setting retryInteractTimer = sgAdvanced.add(new IntSetting.Builder() - .name("retry-interact-timer") - .description("How many ticks to wait for chest response before interacting with it again.") - .defaultValue(80) - .min(1) - .sliderRange(20, 200) - .build() - ); - - private final Setting posResetTimeout = sgAdvanced.add(new IntSetting.Builder() - .name("pos-reset-timeout") - .description("How many ticks to wait after the player position was reset by the server.") - .defaultValue(10) - .min(0) - .sliderRange(0, 40) - .build() - ); - - private final Setting checkpointBuffer = sgAdvanced.add(new DoubleSetting.Builder() - .name("checkpoint-buffer") - .description("The buffer area of the checkpoints. Larger means less precise walking, but might be desired at higher speeds.") - .defaultValue(0.2) - .min(0) - .sliderRange(0, 1) - .build() - ); - - private final Setting moveToFinishedFolder = sgAdvanced.add(new BoolSetting.Builder() - .name("move-to-finished-folder") - .description("Moves finished NBT files into the finished-maps folder in the nerv-printer folder.") - .defaultValue(true) - .build() - ); - - private final Setting disableOnFinished = sgAdvanced.add(new BoolSetting.Builder() - .name("disable-on-finished") - .description("Disables the printer when all nbt files are finished.") - .defaultValue(true) - .build() - ); - - private final Setting displayMaxRequirements = sgAdvanced.add(new BoolSetting.Builder() - .name("print-max-requirements") - .description("Print the maximum amount of material needed for all maps in the map-folder.") - .defaultValue(true) - .build() - ); - - private final Setting debugPrints = sgAdvanced.add(new BoolSetting.Builder() - .name("debug-prints") - .description("Prints additional information.") - .defaultValue(false) - .build() - ); - - //Error Handling - - private final Setting logErrors = sgError.add(new BoolSetting.Builder() - .name("log-errors") - .description("Prints warning when a misplacement is detected.") - .defaultValue(true) - .build() - ); - - private final Setting errorAction = sgError.add(new EnumSetting.Builder() - .name("error-action") - .description("What to do when a misplacement is detected.") - .defaultValue(ErrorAction.ToggleOff) - .build() - ); - - //Render - - private final Setting render = sgRender.add(new BoolSetting.Builder() - .name("render") - .description("Highlights the selected areas.") - .defaultValue(true) - .build() - ); - - private final Setting renderChestPositions = sgRender.add(new BoolSetting.Builder() - .name("render-chest-positions") - .description("Highlights the selected chests.") - .defaultValue(true) - .visible(() -> render.get()) - .build() - ); - - private final Setting renderOpenPositions = sgRender.add(new BoolSetting.Builder() - .name("render-open-positions") - .description("Indicate the position the bot will go to in order to interact with the chest.") - .defaultValue(true) - .visible(() -> render.get()) - .build() - ); - - private final Setting renderCheckpoints = sgRender.add(new BoolSetting.Builder() - .name("render-checkpoints") - .description("Indicate the checkpoints the bot will traverse.") - .defaultValue(true) - .visible(() -> render.get()) - .build() - ); - - private final Setting renderSpecialInteractions = sgRender.add(new BoolSetting.Builder() - .name("render-special-interactions") - .description("Indicate the position where the reset button and cartography table will be used.") - .defaultValue(true) - .visible(() -> render.get()) - .build() - ); - - private final Setting indicatorSize = sgRender.add(new DoubleSetting.Builder() - .name("indicator-size") - .description("How big the rendered indicator will be.") - .defaultValue(0.2) - .min(0) - .sliderRange(0, 1) - .visible(() -> render.get()) - .build() - ); - - private final Setting color = sgRender.add(new ColorSetting.Builder() - .name("color") - .description("The render color.") - .defaultValue(new SettingColor(22, 230, 206, 155)) - .visible(() -> render.get()) - .build() - ); - - int timeoutTicks; - int closeResetChestTicks; - int interactTimeout; - int toBeSwappedSlot; - long lastTickTime; - boolean closeNextInvPacket; - boolean atEdge; - boolean nextResetNorth; - State state; - State oldState; - Pair workingInterval = new Pair<>(0, 127); - Pair northReset; - Pair southReset; - Pair cartographyTable; - Pair finishedMapChest; - ArrayList> mapMaterialChests; - Pair> dumpStation; //Pos, Yaw, Pitch - BlockPos mapCorner; - BlockPos tempChestPos; - BlockPos lastInteractedChest; - Item lastSwappedMaterial; - InventoryS2CPacket toBeHandledInvPacket; - HashMap> blockPaletteDict; //Maps palette block id to the Minecraft block and amount - HashMap>> materialDict; //Maps block to the chest pos and the open position - ArrayList availableSlots; - ArrayList availableHotBarSlots; - ArrayList> restockList; //Material, Stacks, Raw Amount - ArrayList checkedChests; - ArrayList>> checkpoints; //(GoalPos, (checkpointAction, targetBlock)) - ArrayList startedFiles; - ArrayList restockBacklogSlots; - Block[][] map; - File mapFolder; - File mapFile; - - public FullBlockPrinter() { - super(Addon.CATEGORY, "full-block-printer", "Automatically builds 2D full-block maps from nbt files."); - } - - @Override - public void onActivate() { - lastTickTime = System.currentTimeMillis(); - if (!activationReset.get() && checkpoints != null) { - return; - } - materialDict = new HashMap<>(); - availableSlots = new ArrayList<>(); - availableHotBarSlots = new ArrayList<>(); - restockList = new ArrayList<>(); - checkedChests = new ArrayList<>(); - checkpoints = new ArrayList<>(); - startedFiles = new ArrayList<>(); - restockBacklogSlots = new ArrayList<>(); - northReset = null; - southReset = null; - mapCorner = null; - lastInteractedChest = null; - cartographyTable = null; - finishedMapChest = null; - mapMaterialChests = new ArrayList<>(); - dumpStation = null; - lastSwappedMaterial = null; - toBeHandledInvPacket = null; - closeNextInvPacket = false; - atEdge = false; - nextResetNorth = startResetNorth.get(); - timeoutTicks = 0; - interactTimeout = 0; - closeResetChestTicks = 0; - toBeSwappedSlot = -1; - - if (!customFolderPath.get()) { - mapFolder = new File(Utils.getMinecraftDirectory(), "nerv-printer"); - } else { - mapFolder = new File(mapPrinterFolderPath.get()); - } - if (!Utils.createFolders(mapFolder)) { - toggle(); - return; - } - - if (displayMaxRequirements.get()) { - HashMap materialCountDict = new HashMap<>(); - for (File file : mapFolder.listFiles()) { - if (!file.isFile()) continue; - if (!prepareNextMapFile()) return; - for (Pair material : blockPaletteDict.values()) { - if (!materialCountDict.containsKey(material.getLeft())) { - materialCountDict.put(material.getLeft(), material.getRight()); - } else { - materialCountDict.put(material.getLeft(), Math.max(materialCountDict.get(material.getLeft()), material.getRight())); - } - } - } - info("§aMaterial needed for all files:"); - for (Block block : materialCountDict.keySet()) { - float shulkerAmount = (float) Math.ceil((float) materialCountDict.get(block) / (float) (27 * 64) * 10) / (float) 10; - if (shulkerAmount == 0) continue; - info(block.getName().getString() + ": " + shulkerAmount + " shulker"); - } - startedFiles.clear(); - } - if (!prepareNextMapFile()) return; - info("Building: §a" + mapFile.getName()); - info("Requirements: "); - for (Pair p : blockPaletteDict.values()) { - if (p.getRight() == 0) continue; - info(p.getLeft().getName().getString() + ": " + p.getRight()); - } - state = State.SelectingMapArea; - info("Select the §aMap Building Area (128x128)"); - } - - @Override - public void onDeactivate() { - Utils.setForwardPressed(false); - } - - private void refillInventory(HashMap invMaterial) { - //Fills restockList with required items - restockList.clear(); - HashMap requiredItems = Utils.getRequiredItems(mapCorner, workingInterval, linesPerRun.get(), availableSlots.size(), map); - for (Item item : invMaterial.keySet()) { - int oldAmount = requiredItems.remove(item); - requiredItems.put(item, oldAmount - invMaterial.get(item)); - } - - for (Item item : requiredItems.keySet()) { - if (requiredItems.get(item) <= 0) continue; - int stacks = (int) Math.ceil((float) requiredItems.get(item) / 64f); - info("Restocking §a" + stacks + " stacks " + item.getName().getString() + " (" + requiredItems.get(item) + ")"); - restockList.add(0, Triple.of(item, stacks, requiredItems.get(item))); - } - addClosestRestockCheckpoint(); - } - - private void addClosestRestockCheckpoint() { - //Determine closest restock chest for material in restock list - if (restockList.size() == 0) return; - double smallestDistance = Double.MAX_VALUE; - Triple closestEntry = null; - Pair restockPos = null; - for (Triple entry : restockList) { - Pair bestRestockPos = getBestChest(entry.getLeft()); - if (bestRestockPos.getLeft() == null) { - warning("No chest found for " + entry.getLeft().getName().getString()); - toggle(); - return; - } - double chestDistance = PlayerUtils.distanceTo(bestRestockPos.getRight()); - if (chestDistance < smallestDistance) { - smallestDistance = chestDistance; - closestEntry = entry; - restockPos = bestRestockPos; - } - } - //Set closest material as first and as checkpoint - restockList.remove(closestEntry); - restockList.add(0, closestEntry); - checkpoints.add(0, new Pair(restockPos.getRight(), new Pair("refill", restockPos.getLeft()))); - } - - private void calculateBuildingPath(boolean cornerSide, boolean sprintFirst) { - //Iterate over map and skip completed lines. Player has to be able to see the complete map area - //Fills checkpoints list - boolean isStartSide = cornerSide; - checkpoints.clear(); - for (int x = 0; x < 128; x += linesPerRun.get()) { - boolean lineFinished = true; - for (int lineBonus = 0; lineBonus < linesPerRun.get(); lineBonus++) { - if (x + lineBonus > 127) break; - for (int z = 0; z < 128; z++) { - BlockState blockstate = MapAreaCache.getCachedBlockState(mapCorner.add(x + lineBonus, 0, z)); - if (blockstate.isAir()) { - lineFinished = false; - break; - } - } - } - if (lineFinished) continue; - Vec3d cp1 = mapCorner.toCenterPos().add(x + linesPerRun.get() - 1, 0, -1); - Vec3d cp2 = mapCorner.toCenterPos().add(x + linesPerRun.get() - 1, 0, 128); - if (isStartSide) { - checkpoints.add(new Pair(cp1, new Pair("nextLine", null))); - checkpoints.add(new Pair(cp2, new Pair("lineEnd", null))); - } else { - checkpoints.add(new Pair(cp2, new Pair("nextLine", null))); - checkpoints.add(new Pair(cp1, new Pair("lineEnd", null))); - } - isStartSide = !isStartSide; - } - if (checkpoints.size() > 0 && sprintFirst) { - //Make player sprint to the start of the map - Pair> firstPoint = checkpoints.remove(0); - checkpoints.add(0, new Pair(firstPoint.getLeft(), new Pair("sprint", firstPoint.getRight().getRight()))); - } - } - - private boolean arePlacementsCorrect() { - boolean valid = true; - for (int x = 0; x < 128; x += linesPerRun.get()) { - for (int lineBonus = 0; lineBonus < linesPerRun.get(); lineBonus++) { - if (x + lineBonus > 127) break; - for (int z = 0; z < 128; z++) { - BlockState blockState = MapAreaCache.getCachedBlockState(mapCorner.add(x + lineBonus, 0, z)); - if (!blockState.isAir()) { - if (map[x + lineBonus][z] != blockState.getBlock()) { - int xError = x + lineBonus + mapCorner.getX(); - int zError = z + mapCorner.getZ(); - if (logErrors.get()) warning("Error at " + xError + ", " + zError + ". " + - "Is " + blockState.getBlock().getName().getString() + " - Should be " + map[x + lineBonus][z].getName().getString()); - valid = false; - } - } - } - } - } - return valid; - } - - @EventHandler - private void onSendPacket(PacketEvent.Send event) { - if (event.packet instanceof PlayerMoveC2SPacket) { - if (MapAreaCache.getCachedBlockState(mc.player.getBlockPos().down()).isAir() && state == State.Walking && - (checkpoints.get(0).getRight().getLeft() == "" || checkpoints.get(0).getRight().getLeft() == "lineEnd")) { - atEdge = true; - Utils.setForwardPressed(false); - mc.player.setVelocity(0, 0, 0); - } else { - atEdge = false; - } - } - if (state == State.SelectingDumpStation && event.packet instanceof PlayerActionC2SPacket packet - && packet.getAction() == PlayerActionC2SPacket.Action.DROP_ITEM) { - dumpStation = new Pair<>(mc.player.getEntityPos(), new Pair<>(mc.player.getYaw(), mc.player.getPitch())); - state = State.SelectingFinishedMapChest; - info("Dump Station selected. Select the §aFinished Map Chest"); - return; - } - if (!(event.packet instanceof PlayerInteractBlockC2SPacket packet) || state == null) return; - switch (state) { - case SelectingMapArea: - BlockPos hitPos = packet.getBlockHitResult().getBlockPos().offset(packet.getBlockHitResult().getSide()); - int adjustedX = Utils.getIntervalStart(hitPos.getX()); - int adjustedZ = Utils.getIntervalStart(hitPos.getZ()); - mapCorner = new BlockPos(adjustedX, hitPos.getY(), adjustedZ); - MapAreaCache.reset(mapCorner); - state = State.SelectingNorthReset; - info("Map Area selected. Press the §aNorth Reset Trapped Chest §7used to remove the built map"); - break; - case SelectingNorthReset: - BlockPos blockPos = packet.getBlockHitResult().getBlockPos(); - if (MapAreaCache.getCachedBlockState(blockPos).getBlock() instanceof TrappedChestBlock) { - northReset = new Pair<>(packet.getBlockHitResult(), mc.player.getEntityPos()); - info("North Reset Trapped Chest selected. Select the §aSouth Reset Trapped Chest."); - state = State.SelectingSouthReset; - } - break; - case SelectingSouthReset: - blockPos = packet.getBlockHitResult().getBlockPos(); - if (MapAreaCache.getCachedBlockState(blockPos).getBlock() instanceof TrappedChestBlock) { - southReset = new Pair<>(packet.getBlockHitResult(), mc.player.getEntityPos()); - info("South Reset Trapped Chest selected. Select the §aCartography Table."); - state = State.SelectingTable; - } - break; - case SelectingTable: - blockPos = packet.getBlockHitResult().getBlockPos(); - if (MapAreaCache.getCachedBlockState(blockPos).getBlock().equals(Blocks.CARTOGRAPHY_TABLE)) { - cartographyTable = new Pair<>(packet.getBlockHitResult(), mc.player.getEntityPos()); - info("Cartography Table selected. Throw an item into the §aDump Station."); - state = State.SelectingDumpStation; - } - break; - case SelectingFinishedMapChest: - blockPos = packet.getBlockHitResult().getBlockPos(); - if (MapAreaCache.getCachedBlockState(blockPos).getBlock() instanceof AbstractChestBlock) { - finishedMapChest = new Pair<>(packet.getBlockHitResult(), mc.player.getEntityPos()); - info("Finished Map Chest selected. Select all §aMaterial- and Map-Chests."); - state = State.SelectingChests; - } - break; - case SelectingChests: - if (startBlock.get().isEmpty()) - warning("No block selected as Start Block! Please select one in the settings."); - blockPos = packet.getBlockHitResult().getBlockPos(); - BlockState blockState = MapAreaCache.getCachedBlockState(blockPos); - if (startBlock.get().contains(blockState.getBlock())) { - //Check if requirements to start building are met - if (materialDict.size() == 0) { - warning("No Material Chests selected!"); - return; - } - if (mapMaterialChests.size() == 0) { - warning("No Map Chests selected!"); - return; - } - Utils.setForwardPressed(true); - calculateBuildingPath(true, true); - availableSlots = Utils.getAvailableSlots(materialDict); - for (int slot : availableSlots) { - if (slot < 9) { - availableHotBarSlots.add(slot); - } - } - info("Inventory slots available for building: " + availableSlots); - - HashMap requiredItems = Utils.getRequiredItems(mapCorner, workingInterval, linesPerRun.get(), availableSlots.size(), map); - Pair, HashMap> invInformation = Utils.getInvInformation(requiredItems, availableSlots); - if (invInformation.getLeft().size() != 0) { - checkpoints.add(0, new Pair(dumpStation.getLeft(), new Pair("dump", null))); - } else { - refillInventory(invInformation.getRight()); - } - if (availableHotBarSlots.size() == 0) { - warning("No free slots found in hot-bar!"); - toggle(); - return; - } - if (availableSlots.size() < 2) { - warning("You need at least 2 free inventory slots!"); - toggle(); - return; - } - state = State.Walking; - } - if (MapAreaCache.getCachedBlockState(blockPos).getBlock().equals(Blocks.CHEST)) { - tempChestPos = blockPos; - state = State.AwaitRegisterResponse; - } - break; - } - } - - @EventHandler - private void onReceivePacket(PacketEvent.Receive event) { - if (state == null) return; - - if (event.packet instanceof PlayerPositionLookS2CPacket) { - timeoutTicks = posResetTimeout.get(); - if (timeoutTicks > 0) Utils.setForwardPressed(false); - } - - if (!(event.packet instanceof InventoryS2CPacket packet)) return; - - if (state.equals(State.AwaitRegisterResponse)) { - //info("Chest content received."); - Item foundItem = null; - boolean isMixedContent = false; - for (int i = 0; i < packet.contents().size() - 36; i++) { - ItemStack stack = packet.contents().get(i); - if (!stack.isEmpty()) { - if (foundItem != null && foundItem != stack.getItem().asItem()) { - isMixedContent = true; - } - foundItem = stack.getItem().asItem(); - if (foundItem == Items.MAP || foundItem == Items.GLASS_PANE) { - info("Registered §aMapChest"); - mapMaterialChests = Utils.saveAdd(mapMaterialChests, tempChestPos, mc.player.getEntityPos()); - state = State.SelectingChests; - return; - } - } - } - if (isMixedContent) { - warning("Different items found in chest. Please only have one item type in the chest."); - state = State.SelectingChests; - return; - } - if (foundItem == null) { - warning("No items found in chest."); - state = State.SelectingChests; - return; - } - info("Registered §a" + foundItem.getName().getString()); - if (!materialDict.containsKey(foundItem)) materialDict.put(foundItem, new ArrayList<>()); - ArrayList> oldList = materialDict.get(foundItem); - ArrayList newChestList = Utils.saveAdd(oldList, tempChestPos, mc.player.getEntityPos()); - materialDict.put(foundItem, newChestList); - state = State.SelectingChests; - } - - List allowedStates = Arrays.asList(State.AwaitRestockResponse, State.AwaitMapChestResponse, - State.AwaitCartographyResponse, State.AwaitFinishedMapChestResponse, State.AwaitResetResponse); - if (allowedStates.contains(state)) { - toBeHandledInvPacket = packet; - timeoutTicks = preRestockDelay.get(); - } - } - - private void handleInventoryPacket(InventoryS2CPacket packet) { - if (debugPrints.get()) info("Handling InvPacket for: " + state); - closeNextInvPacket = true; - switch (state) { - case AwaitRestockResponse: - interactTimeout = 0; - boolean foundMaterials = false; - for (int i = 0; i < packet.contents().size() - 36; i++) { - ItemStack stack = packet.contents().get(i); - - if (restockList.get(0).getMiddle() == 0) { - foundMaterials = true; - break; - } - if (!stack.isEmpty() && stack.getCount() == 64) { - //info("Taking Stack of " + restockList.get(0).getLeft().getName().getString()); - foundMaterials = true; - int highestFreeSlot = Utils.findHighestFreeSlot(packet); - if (highestFreeSlot == -1) { - warning("No free slots found in inventory."); - checkpoints.add(0, new Pair(dumpStation.getLeft(), new Pair("dump", null))); - state = State.Walking; - return; - } - restockBacklogSlots.add(i); - Triple oldTriple = restockList.remove(0); - restockList.add(0, Triple.of(oldTriple.getLeft(), oldTriple.getMiddle() - 1, oldTriple.getRight() - 64)); - } - } - if (!foundMaterials) endRestocking(); - break; - case AwaitMapChestResponse: - int mapSlot = -1; - int paneSlot = -1; - //Search for map and glass pane - for (int slot = 0; slot < packet.contents().size() - 36; slot++) { - ItemStack stack = packet.contents().get(slot); - if (stack.getItem() == Items.MAP) mapSlot = slot; - if (stack.getItem() == Items.GLASS_PANE) paneSlot = slot; - } - if (mapSlot == -1 || paneSlot == -1) { - warning("Not enough Empty Maps/Glass Panes in Map Material Chest"); - return; - } - interactTimeout = 0; - timeoutTicks = postRestockDelay.get(); - Utils.getOneItem(mapSlot, false, availableSlots, availableHotBarSlots, packet); - Utils.getOneItem(paneSlot, true, availableSlots, availableHotBarSlots, packet); - mc.player.getInventory().setSelectedSlot(availableHotBarSlots.get(0)); - - Vec3d center = mapCorner.add(map.length / 2 - 1, 0, map[0].length / 2 - 1).toCenterPos(); - checkpoints.add(new Pair(center, new Pair("fillMap", null))); - state = State.Walking; - break; - case AwaitCartographyResponse: - interactTimeout = 0; - timeoutTicks = postRestockDelay.get(); - boolean searchingMap = true; - for (int slot : availableSlots) { - if (slot < 9) { //Stupid slot correction - slot += 30; - } else { - slot -= 6; - } - ItemStack stack = packet.contents().get(slot); - if (searchingMap && stack.getItem() == Items.FILLED_MAP) { - mc.interactionManager.clickSlot(packet.syncId(), slot, 0, SlotActionType.QUICK_MOVE, mc.player); - searchingMap = false; - } - } - for (int slot : availableSlots) { - if (slot < 9) { //Stupid slot correction - slot += 30; - } else { - slot -= 6; - } - ItemStack stack = packet.contents().get(slot); - if (!searchingMap && stack.getItem() == Items.GLASS_PANE) { - mc.interactionManager.clickSlot(packet.syncId(), slot, 0, SlotActionType.QUICK_MOVE, mc.player); - break; - } - } - mc.interactionManager.clickSlot(packet.syncId(), 2, 0, SlotActionType.QUICK_MOVE, mc.player); - checkpoints.add(new Pair(finishedMapChest.getRight(), new Pair("finishedMapChest", null))); - state = State.Walking; - break; - case AwaitFinishedMapChestResponse: - interactTimeout = 0; - timeoutTicks = postRestockDelay.get(); - for (int slot = packet.contents().size() - 36; slot < packet.contents().size(); slot++) { - ItemStack stack = packet.contents().get(slot); - if (stack.getItem() == Items.FILLED_MAP) { - mc.interactionManager.clickSlot(packet.syncId(), slot, 0, SlotActionType.QUICK_MOVE, mc.player); - break; - } - } - if (nextResetNorth) { - checkpoints.add(new Pair(northReset.getRight(), new Pair("reset", null))); - } else { - checkpoints.add(new Pair(southReset.getRight(), new Pair("reset", null))); - } - state = State.Walking; - break; - case AwaitResetResponse: - interactTimeout = 0; - closeNextInvPacket = false; - closeResetChestTicks = resetChestCloseDelay.get(); - break; - } - } - - private int getFirstIntactRow() { - for (int z = 0; z < map[0].length; z++) { - int adjustedZ = z; - if (nextResetNorth) adjustedZ = map[0].length - z - 1; - for (int x = 0; x < map.length; x++) { - BlockPos pos = new BlockPos(mapCorner.add(x, 0, adjustedZ)); - if (MapAreaCache.getCachedBlockState(pos).isAir()) { - return adjustedZ; - } - } - } - if (nextResetNorth) { - return -1; - } else { - return map[0].length; - } - } - - private boolean isCleared() { - for (int z = 0; z < map[0].length; z++) { - for (int x = 0; x < map.length; x++) { - BlockPos pos = new BlockPos(mapCorner.add(x, 0, z)); - if (!MapAreaCache.getCachedBlockState(pos).isAir()) return false; - } - } - return true; - } - - private void endTNTAvoid() { - if (nextResetNorth) { - Vec3d southCP = mapCorner.add(-1, 1, map[0].length).toCenterPos(); - checkpoints.add(new Pair<>(southCP, new Pair<>("sprint", null))); - Vec3d northCP = mapCorner.add(-1, 1, -1).toCenterPos(); - checkpoints.add(new Pair<>(northCP, new Pair<>("finishedAvoid", null))); - } else { - Vec3d centerCP = mapCorner.add(map.length / 2, 1, -1).toCenterPos(); - checkpoints.add(new Pair<>(centerCP, new Pair<>("finishedAvoid", null))); - } - nextResetNorth = !nextResetNorth; - timeoutTicks = resetDelay.get(); - state = State.AwaitNBTFile; - } - - @EventHandler - private void onTick(TickEvent.Pre event) { - if (state == null) return; - - if (oldState != state) { - oldState = state; - if (debugPrints.get()) info("Changed state to " + state.name()); - } - - long timeDifference = System.currentTimeMillis() - lastTickTime; - int allowedPlacements = (int) Math.floor(timeDifference / (long) placeDelay.get()); - lastTickTime += (long) allowedPlacements * placeDelay.get(); - - if (interactTimeout > 0) { - interactTimeout--; - if (interactTimeout == 0) { - info("Interaction timed out. Interacting again..."); - if (state == State.AwaitCartographyResponse) { - interactWithBlock(cartographyTable.getLeft()); - } else { - interactWithBlock(lastInteractedChest); - } - } - } - - if (closeResetChestTicks > 0) { - closeResetChestTicks--; - if (closeResetChestTicks == 0) { - mc.player.closeHandledScreen(); - state = State.AvoidTNT; - } - } - - if (timeoutTicks > 0) { - timeoutTicks--; - return; - } - - // Swap into Hotbar - if (toBeSwappedSlot != -1) { - Utils.swapIntoHotbar(toBeSwappedSlot, availableHotBarSlots); - toBeSwappedSlot = -1; - if (postSwapDelay.get() != 0) { - timeoutTicks = postSwapDelay.get(); - return; - } - } - - // Restocking - if (restockBacklogSlots.size() > 0) { - int slot = restockBacklogSlots.remove(0); - mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, slot, 1, SlotActionType.QUICK_MOVE, mc.player); - if (restockBacklogSlots.size() == 0) { - if (state.equals(State.AwaitRestockResponse)) { - endRestocking(); - } - } else { - timeoutTicks = invActionDelay.get(); - } - return; - } - - // Dump unnecessary items - if (state == State.Dumping) { - int dumpSlot = getDumpSlot(); - if (dumpSlot == -1) { - HashMap requiredItems = Utils.getRequiredItems(mapCorner, workingInterval, linesPerRun.get(), availableSlots.size(), map); - Pair, HashMap> invInformation = Utils.getInvInformation(requiredItems, availableSlots); - refillInventory(invInformation.getRight()); - state = State.Walking; - } else { - if (debugPrints.get()) - info("Dumping §a" + mc.player.getInventory().getStack(dumpSlot).getName().getString() + " (slot " + dumpSlot + ")"); - InvUtils.drop().slot(dumpSlot); - timeoutTicks = invActionDelay.get(); - } - } - - if (state == State.AvoidTNT) { - if (isCleared()) { - endTNTAvoid(); - return; - } - int offset = tntDistance.get(); - if (!nextResetNorth) offset *= -1; - Vec3d targetPos = mapCorner.add(map.length / 2, 1, getFirstIntactRow() + offset).toCenterPos(); - targetPos.add(0, mc.player.getY() - targetPos.y, 0); - if (PlayerUtils.distanceTo(targetPos) > 0.9) { - checkpoints.add(0, new Pair<>(targetPos, new Pair<>("switchAvoidTNT", null))); - state = State.Walking; - Utils.setForwardPressed(true); - } - return; - } - - // Load next nbt file - if (state == State.AwaitNBTFile) { - if (!prepareNextMapFile()) return; - info("Building: §a" + mapFile.getName()); - info("Requirements: "); - for (Pair p : blockPaletteDict.values()) { - if (p.getRight() == 0) continue; - info(p.getLeft().getName().getString() + ": " + p.getRight()); - } - state = State.Walking; - } - - // Handle Block Entity interaction response - if (toBeHandledInvPacket != null) { - handleInventoryPacket(toBeHandledInvPacket); - toBeHandledInvPacket = null; - return; - } - - if (closeNextInvPacket) { - if (mc.currentScreen != null) { - mc.player.closeHandledScreen(); - } - closeNextInvPacket = false; - } - - // Main Loop for building - if (!state.equals(State.Walking)) return; - if (!atEdge) Utils.setForwardPressed(true); - if (checkpoints.isEmpty()) { - error("Checkpoints are empty. Stopping..."); - Utils.setForwardPressed(false); - toggle(); - return; - } - Vec3d goal = checkpoints.get(0).getLeft(); - if (PlayerUtils.distanceTo(goal.add(0, mc.player.getY() - goal.y, 0)) < checkpointBuffer.get()) { - Pair checkpointAction = checkpoints.get(0).getRight(); - if (debugPrints.get() && checkpointAction.getLeft() != null) info("Reached " + checkpointAction.getLeft()); - checkpoints.remove(0); - switch (checkpointAction.getLeft()) { - case "lineEnd": - arePlacementsCorrect(); - boolean atCornerSide = goal.z == mapCorner.north().toCenterPos().z; - calculateBuildingPath(atCornerSide, false); - break; - case "mapMaterialChest": - BlockPos mapMaterialChest = getBestChest(Items.CARTOGRAPHY_TABLE).getLeft(); - interactWithBlock(mapMaterialChest); - state = State.AwaitMapChestResponse; - return; - case "fillMap": - mc.getNetworkHandler().sendPacket(new PlayerInteractItemC2SPacket(Hand.MAIN_HAND, Utils.getNextInteractID(), mc.player.getYaw(), mc.player.getPitch())); - if (mapFillSquareSize.get() == 0) { - checkpoints.add(0, new Pair(cartographyTable.getRight(), new Pair<>("cartographyTable", null))); - } else { - checkpoints.add(new Pair(goal.add(-mapFillSquareSize.get(), 0, mapFillSquareSize.get()), new Pair("sprint", null))); - checkpoints.add(new Pair(goal.add(mapFillSquareSize.get(), 0, mapFillSquareSize.get()), new Pair("sprint", null))); - checkpoints.add(new Pair(goal.add(mapFillSquareSize.get(), 0, -mapFillSquareSize.get()), new Pair("sprint", null))); - checkpoints.add(new Pair(goal.add(-mapFillSquareSize.get(), 0, -mapFillSquareSize.get()), new Pair("sprint", null))); - checkpoints.add(new Pair(cartographyTable.getRight(), new Pair("cartographyTable", null))); - } - return; - case "cartographyTable": - state = State.AwaitCartographyResponse; - interactWithBlock(cartographyTable.getLeft()); - return; - case "finishedMapChest": - state = State.AwaitFinishedMapChestResponse; - interactWithBlock(finishedMapChest.getLeft().getBlockPos()); - return; - case "reset": - state = State.AwaitResetResponse; - info("Resetting..."); - if (nextResetNorth) { - interactWithBlock(northReset.getLeft()); - lastInteractedChest = northReset.getLeft().getBlockPos(); - } else { - interactWithBlock(southReset.getLeft()); - lastInteractedChest = southReset.getLeft().getBlockPos(); - } - return; - case "switchAvoidTNT": - state = State.AvoidTNT; - Utils.setForwardPressed(false); - return; - case "finishedAvoid": - calculateBuildingPath(true, true); - checkpoints.add(0, new Pair(dumpStation.getLeft(), new Pair("dump", null))); - return; - case "dump": - state = State.Dumping; - Utils.setForwardPressed(false); - mc.player.setYaw(dumpStation.getRight().getLeft()); - mc.player.setPitch(dumpStation.getRight().getRight()); - return; - case "refill": - state = State.AwaitRestockResponse; - interactWithBlock(checkpointAction.getRight()); - return; - } - if (checkpoints.size() == 0) { - if (!arePlacementsCorrect() && errorAction.get() == ErrorAction.ToggleOff) { - checkpoints.add(new Pair(mc.player.getEntityPos(), new Pair("lineEnd", null))); - warning("ErrorAction is ToggleOff: Stopping because of error..."); - toggle(); - return; - } - info("Finished building map"); - Pair bestChest = getBestChest(Items.CARTOGRAPHY_TABLE); - checkpoints.add(0, new Pair(bestChest.getRight(), new Pair("mapMaterialChest", bestChest.getLeft()))); - try { - if (moveToFinishedFolder.get()) { - mapFile.renameTo(new File(mapFile.getParentFile().getAbsolutePath() + File.separator + "_finished_maps" + File.separator + mapFile.getName())); - } - } catch (Exception e) { - warning("Failed to move map file " + mapFile.getName() + " to finished map folder"); - e.printStackTrace(); - } - checkpoints.add(0, new Pair(dumpStation.getLeft(), new Pair("dump", null))); - } - goal = checkpoints.get(0).getLeft(); - } - mc.player.setYaw((float) Rotations.getYaw(goal)); - String nextAction = checkpoints.get(0).getRight().getLeft(); - - if ((nextAction == "" || nextAction == "lineEnd") && sprinting.get() != SprintMode.Always) { - mc.player.setSprinting(false); - } else if (sprinting.get() != SprintMode.Off) { - mc.player.setSprinting(true); - } - if (nextAction == "refill" || nextAction == "dump" || nextAction == "walkRestock" - || nextAction == "switchAvoidTNT" || nextAction == "nextLine") return; - - ArrayList placements = new ArrayList<>(); - for (int i = 0; i < allowedPlacements; i++) { - AtomicReference closestPos = new AtomicReference<>(); - final Vec3d currentGoal = goal; - BlockPos playerGroundPos = mc.player.getBlockPos().add(0, mapCorner.getY() - mc.player.getBlockY(), 0); - Utils.iterateBlocks(playerGroundPos, (int) Math.ceil(placeRange.get()) + 1, 0, ((blockPos, blockState) -> { - Double posDistance = PlayerUtils.distanceTo(blockPos.toCenterPos()); - if ((blockState.isAir()) && posDistance <= placeRange.get() && MapAreaCache.isWithingMap(blockPos) - && blockPos.getX() <= currentGoal.getX() && !placements.contains(blockPos)) { - if (closestPos.get() == null) { - if (!MapAreaCache.getCachedBlockState(blockPos.west()).isAir()) - closestPos.set(new BlockPos(blockPos.getX(), blockPos.getY(), blockPos.getZ())); - return; - } - int blockPosZDiff = Math.abs(mc.player.getBlockPos().getZ() - blockPos.getZ()); - int closestPosZDiff = Math.abs(mc.player.getBlockPos().getZ() - closestPos.get().getZ()); - if (!MapAreaCache.getCachedBlockState(blockPos.west()).isAir() && (blockPosZDiff < closestPosZDiff || - (blockPosZDiff == closestPosZDiff && blockPos.getX() < closestPos.get().getX()))) { - closestPos.set(new BlockPos(blockPos.getX(), blockPos.getY(), blockPos.getZ())); - } - } - })); - - if (closestPos.get() != null) { - //Stop placing if restocking - placements.add(closestPos.get()); - if (!tryPlacingBlock(closestPos.get())) { - return; - } - } - } - } - - private int getDumpSlot() { - HashMap requiredItems = Utils.getRequiredItems(mapCorner, workingInterval, linesPerRun.get(), availableSlots.size(), map); - Pair, HashMap> invInformation = Utils.getInvInformation(requiredItems, availableSlots); - if (invInformation.getLeft().isEmpty()) { - return -1; - } - return invInformation.getLeft().get(0); - } - - private boolean tryPlacingBlock(BlockPos pos) { - BlockPos relativePos = pos.subtract(mapCorner); - Item material = map[relativePos.getX()][relativePos.getZ()].asItem(); - //info("Placing " + material.getName().getString() + " at: " + relativePos.toShortString()); - //Check hot-bar slots - for (int slot : availableHotBarSlots) { - if (mc.player.getInventory().getStack(slot).isEmpty()) continue; - Item foundMaterial = mc.player.getInventory().getStack(slot).getItem(); - if (foundMaterial.equals(material)) { - BlockUtils.place(pos, Hand.MAIN_HAND, slot, rotate.get(), 50, true, true, false); - if (material == lastSwappedMaterial) lastSwappedMaterial = null; - return true; - } - } - for (int slot : availableSlots) { - if (mc.player.getInventory().getStack(slot).isEmpty() || availableHotBarSlots.contains(slot)) continue; - Item foundMaterial = mc.player.getInventory().getStack(slot).getItem(); - if (foundMaterial.equals(material)) { - lastSwappedMaterial = material; - toBeSwappedSlot = slot; - Utils.setForwardPressed(false); - mc.player.setVelocity(0, 0, 0); - timeoutTicks = preSwapDelay.get(); - return false; - } - } - if (lastSwappedMaterial == material) return false; //Wait for swapped material - info("No " + material.getName().getString() + " found in inventory. Resetting..."); - Vec3d pathCheckpoint1 = mc.player.getEntityPos().offset(Direction.WEST, linesPerRun.get()); - Vec3d pathCheckpoint2 = new Vec3d(pathCheckpoint1.getX(), pathCheckpoint1.y, mapCorner.north().toCenterPos().getZ()); - checkpoints.add(0, new Pair(mc.player.getEntityPos(), new Pair("walkRestock", null))); - checkpoints.add(0, new Pair(pathCheckpoint1, new Pair("walkRestock", null))); - checkpoints.add(0, new Pair(pathCheckpoint2, new Pair("walkRestock", null))); - checkpoints.add(0, new Pair(dumpStation.getLeft(), new Pair("dump", null))); - checkpoints.add(0, new Pair(pathCheckpoint2, new Pair("walkRestock", null))); - checkpoints.add(0, new Pair(pathCheckpoint1, new Pair("walkRestock", null))); - return false; - } - - private void endRestocking() { - if (restockList.get(0).getMiddle() > 0) { - warning("Not all necessary stacks restocked. Searching for another chest..."); - //Search for the next best chest - checkedChests.add(lastInteractedChest); - Pair bestRestockPos = getBestChest(getMaterialFromPos(lastInteractedChest)); - checkpoints.add(0, new Pair<>(bestRestockPos.getRight(), new Pair<>("refill", bestRestockPos.getLeft()))); - } else { - checkedChests.clear(); - restockList.remove(0); - addClosestRestockCheckpoint(); - } - timeoutTicks = postRestockDelay.get(); - state = State.Walking; - } - - private Pair getBestChest(Item item) { - Vec3d bestPos = null; - BlockPos bestChestPos = null; - ArrayList> list = new ArrayList<>(); - if (item.equals(Items.CARTOGRAPHY_TABLE)) { - list = mapMaterialChests; - } else if (materialDict.containsKey(item)) { - list = materialDict.get(item); - } else { - warning("No chest found for " + item.getName().getString()); - toggle(); - return new Pair<>(new BlockPos(0, 0, 0), new Vec3d(0, 0, 0)); - } - //Get nearest chest - for (Pair p : list) { - //Skip chests that have already been checked - if (checkedChests.contains(p.getLeft())) continue; - if (bestPos == null || PlayerUtils.distanceTo(p.getRight()) < PlayerUtils.distanceTo(bestPos)) { - bestPos = p.getRight(); - bestChestPos = p.getLeft(); - } - } - if (bestPos == null || bestChestPos == null) { - checkedChests.clear(); - return getBestChest(item); - } - return new Pair(bestChestPos, bestPos); - } - - private void interactWithBlock(BlockPos chestPos) { - Utils.setForwardPressed(false); - mc.player.setVelocity(0, 0, 0); - mc.player.setYaw((float) Rotations.getYaw(chestPos.toCenterPos())); - mc.player.setPitch((float) Rotations.getPitch(chestPos.toCenterPos())); - - BlockHitResult hitResult = new BlockHitResult(chestPos.toCenterPos(), Utils.getInteractionSide(chestPos), chestPos, false); - BlockUtils.interact(hitResult, Hand.MAIN_HAND, true); - //Set timeout for chest interaction - interactTimeout = retryInteractTimer.get(); - lastInteractedChest = chestPos; - } - - private void interactWithBlock(BlockHitResult hitResult) { - Utils.setForwardPressed(false); - mc.player.setVelocity(0, 0, 0); - mc.player.setYaw((float) Rotations.getYaw(hitResult.getBlockPos().toCenterPos())); - mc.player.setPitch((float) Rotations.getPitch(hitResult.getBlockPos().toCenterPos())); - BlockUtils.interact(hitResult, Hand.MAIN_HAND, true); - interactTimeout = retryInteractTimer.get(); - } - - private Item getMaterialFromPos(BlockPos pos) { - for (Item material : materialDict.keySet()) { - for (Pair p : materialDict.get(material)) { - if (p.getLeft().equals(pos)) return material; - } - } - warning("Could not find material for chest position : " + pos.toShortString()); - toggle(); - return null; - } - - private boolean prepareNextMapFile() { - mapFile = Utils.getNextMapFile(mapFolder, startedFiles, moveToFinishedFolder.get()); - - if (mapFile == null) { - if (disableOnFinished.get()) { - info("All nbt files finished"); - toggle(); - return false; - } else { - return false; - } - } - if (!loadNBTFile()) { - warning("Failed to read nbt file."); - toggle(); - return false; - } - - return true; - } - - private boolean loadNBTFile() { - try { - NbtSizeTracker sizeTracker = new NbtSizeTracker(0x20000000L, 100); - NbtCompound nbt = NbtIo.readCompressed(mapFile.toPath(), sizeTracker); - //Extracting the palette - NbtList paletteList = (NbtList) nbt.get("palette"); - blockPaletteDict = Utils.getBlockPalette(paletteList); - - NbtList blockList = (NbtList) nbt.get("blocks"); - map = Utils.generateMapArray(blockList, blockPaletteDict); - - //Check if a full 128x128 map is present - for (int x = 0; x < map.length; x++) { - for (int z = 0; z < map[x].length; z++) { - if (map[x][z] == null) { - warning("No 2D 128x128 map present in file: " + mapFile.getName()); - return false; - } - } - } - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - @Override - public String getInfoString() { - if (mapFile != null) { - return mapFile.getName(); - } else { - return "None"; - } - } - - @EventHandler - private void onRender(Render3DEvent event) { - if (mapCorner == null || !render.get()) return; - event.renderer.box(mapCorner, color.get(), color.get(), ShapeMode.Lines, 0); - event.renderer.box(mapCorner.getX(), mapCorner.getY(), mapCorner.getZ(), mapCorner.getX() + 128, mapCorner.getY(), mapCorner.getZ() + 128, color.get(), color.get(), ShapeMode.Lines, 0); - - ArrayList> renderedPairs = new ArrayList<>(); - for (ArrayList> list : materialDict.values()) { - renderedPairs.addAll(list); - } - renderedPairs.addAll(mapMaterialChests); - for (Pair pair : renderedPairs) { - if (renderChestPositions.get()) - event.renderer.box(pair.getLeft(), color.get(), color.get(), ShapeMode.Lines, 0); - if (renderOpenPositions.get()) { - Vec3d openPos = pair.getRight(); - event.renderer.box(openPos.x - indicatorSize.get(), openPos.y - indicatorSize.get(), openPos.z - indicatorSize.get(), openPos.x + indicatorSize.get(), openPos.y + indicatorSize.get(), openPos.z + indicatorSize.get(), color.get(), color.get(), ShapeMode.Both, 0); - } - } - - if (renderCheckpoints.get()) { - for (Pair> pair : checkpoints) { - Vec3d cp = pair.getLeft(); - event.renderer.box(cp.x - indicatorSize.get(), cp.y - indicatorSize.get(), cp.z - indicatorSize.get(), cp.getX() + indicatorSize.get(), cp.getY() + indicatorSize.get(), cp.getZ() + indicatorSize.get(), color.get(), color.get(), ShapeMode.Both, 0); - } - } - - if (renderSpecialInteractions.get()) { - if (northReset != null) { - event.renderer.box(northReset.getLeft().getBlockPos(), color.get(), color.get(), ShapeMode.Lines, 0); - event.renderer.box(northReset.getRight().x - indicatorSize.get(), northReset.getRight().y - indicatorSize.get(), northReset.getRight().z - indicatorSize.get(), northReset.getRight().getX() + indicatorSize.get(), northReset.getRight().getY() + indicatorSize.get(), northReset.getRight().getZ() + indicatorSize.get(), color.get(), color.get(), ShapeMode.Both, 0); - } - if (southReset != null) { - event.renderer.box(southReset.getLeft().getBlockPos(), color.get(), color.get(), ShapeMode.Lines, 0); - event.renderer.box(southReset.getRight().x - indicatorSize.get(), northReset.getRight().y - indicatorSize.get(), southReset.getRight().z - indicatorSize.get(), southReset.getRight().getX() + indicatorSize.get(), southReset.getRight().getY() + indicatorSize.get(), southReset.getRight().getZ() + indicatorSize.get(), color.get(), color.get(), ShapeMode.Both, 0); - } - if (cartographyTable != null) { - event.renderer.box(cartographyTable.getLeft().getBlockPos(), color.get(), color.get(), ShapeMode.Lines, 0); - event.renderer.box(cartographyTable.getRight().x - indicatorSize.get(), cartographyTable.getRight().y - indicatorSize.get(), cartographyTable.getRight().z - indicatorSize.get(), cartographyTable.getRight().getX() + indicatorSize.get(), cartographyTable.getRight().getY() + indicatorSize.get(), cartographyTable.getRight().getZ() + indicatorSize.get(), color.get(), color.get(), ShapeMode.Both, 0); - } - if (dumpStation != null) { - event.renderer.box(dumpStation.getLeft().x - indicatorSize.get(), dumpStation.getLeft().y - indicatorSize.get(), dumpStation.getLeft().z - indicatorSize.get(), dumpStation.getLeft().getX() + indicatorSize.get(), dumpStation.getLeft().getY() + indicatorSize.get(), dumpStation.getLeft().getZ() + indicatorSize.get(), color.get(), color.get(), ShapeMode.Both, 0); - } - if (finishedMapChest != null) { - event.renderer.box(finishedMapChest.getLeft().getBlockPos(), color.get(), color.get(), ShapeMode.Lines, 0); - event.renderer.box(finishedMapChest.getRight().x - indicatorSize.get(), finishedMapChest.getRight().y - indicatorSize.get(), finishedMapChest.getRight().z - indicatorSize.get(), finishedMapChest.getRight().getX() + indicatorSize.get(), finishedMapChest.getRight().getY() + indicatorSize.get(), finishedMapChest.getRight().getZ() + indicatorSize.get(), color.get(), color.get(), ShapeMode.Both, 0); - } - } - } - - private enum State { - SelectingNorthReset, - SelectingSouthReset, - SelectingChests, - SelectingFinishedMapChest, - SelectingDumpStation, - SelectingTable, - SelectingMapArea, - AwaitRegisterResponse, - AwaitRestockResponse, - AwaitResetResponse, - AwaitMapChestResponse, - AwaitFinishedMapChestResponse, - AwaitCartographyResponse, - AwaitNBTFile, - AvoidTNT, - Walking, - Dumping - } - - private enum SprintMode { - Off, - NotPlacing, - Always - } - - private enum ErrorAction { - Ignore, - ToggleOff - } -} diff --git a/src/main/java/com/julflips/nerv_printer/modules/StaircasedPrinter.java b/src/main/java/com/julflips/nerv_printer/modules/StaircasedPrinter.java index 0c3a381..c73d843 100644 --- a/src/main/java/com/julflips/nerv_printer/modules/StaircasedPrinter.java +++ b/src/main/java/com/julflips/nerv_printer/modules/StaircasedPrinter.java @@ -476,7 +476,7 @@ public class StaircasedPrinter extends Module implements MapPrinter { File mapFile; public StaircasedPrinter() { - super(Addon.CATEGORY, "staircased-printer", "Automatically builds full-block maps with staircasing from nbt files."); + super(Addon.CATEGORY, "fullblock-printer", "Automatically builds fullblock maps with optional staircasing from nbt files."); } @Override @@ -933,7 +933,7 @@ private void onTick(TickEvent.Pre event) { // Swap into Hotbar if (toBeSwappedSlot != -1) { - Utils.swapIntoHotbar(toBeSwappedSlot, availableHotBarSlots); + swapIntoHotbar(toBeSwappedSlot); toBeSwappedSlot = -1; if (postSwapDelay.get() != 0) { timeoutTicks = postSwapDelay.get(); @@ -1012,7 +1012,7 @@ private void onTick(TickEvent.Pre event) { if (SlaveSystem.isSlave() && checkpoints.isEmpty()) { refillMiningInventory(); } else { - HashMap requiredItems = getRequiredItems(mapCorner, workingInterval, availableSlots.size(), map); + HashMap requiredItems = getRequiredItems(); Pair, HashMap> invInformation = Utils.getInvInformation(requiredItems, availableSlots); refillBuildingInventory(invInformation.getRight()); } @@ -1251,7 +1251,7 @@ private Pair getBestChest(Item item) { private void refillBuildingInventory(HashMap invMaterial) { //Fills restockList with required build materials restockList.clear(); - HashMap requiredItems = getRequiredItems(mapCorner, workingInterval, availableSlots.size(), map); + HashMap requiredItems = getRequiredItems(); for (Item item : invMaterial.keySet()) { int oldAmount = requiredItems.remove(item); requiredItems.put(item, oldAmount - invMaterial.get(item)); @@ -1633,7 +1633,7 @@ private boolean setupSlots() { } private int getDumpSlot() { - HashMap requiredItems = getRequiredItems(mapCorner, workingInterval, availableSlots.size(), map); + HashMap requiredItems = getRequiredItems(); Pair, HashMap> invInformation = Utils.getInvInformation(requiredItems, availableSlots); if (invInformation.getLeft().isEmpty()) { return -1; @@ -1641,11 +1641,11 @@ private int getDumpSlot() { return invInformation.getLeft().get(0); } - public HashMap getRequiredItems(BlockPos mapCorner, Pair interval, int availableSlotsSize, Pair[][] map) { + private HashMap getRequiredItems() { //Calculate the next items to restock //Iterate over map. Player has to be able to see the complete map area HashMap requiredItems = new HashMap<>(); - for (int x = interval.getLeft(); x <= interval.getRight(); x++) { + for (int x = workingInterval.getLeft(); x <= workingInterval.getRight(); x++) { for (int z = 0; z < 128; z++) { BlockState blockState = MapAreaCache.getCachedBlockState(mapCorner.add(x, map[x][z].getRight(), z)); if (blockState.isAir() && map[x][z] != null) { @@ -1654,7 +1654,7 @@ public HashMap getRequiredItems(BlockPos mapCorner, Pair availableSlotsSize) { + if (Utils.stacksRequired(requiredItems.values()) > availableSlots.size()) { requiredItems.put(material, requiredItems.get(material) - 1); return requiredItems; } @@ -1664,6 +1664,102 @@ public HashMap getRequiredItems(BlockPos mapCorner, Pair itemSlot = new HashMap<>(); + Map itemDistance = new HashMap<>(); + Map itemFrequency = new HashMap<>(); + + int targetSlot = availableHotBarSlots.get(0); + + // Scan hotbar + for (int hotbarSlot : availableHotBarSlots) { + ItemStack stack = mc.player.getInventory().getStack(hotbarSlot); + if (!stack.isEmpty()) { + Item item = stack.getItem(); + itemSlot.put(item, hotbarSlot); + itemDistance.put(item, -1); // -1 = never used + itemFrequency.put(item, 0); + } else { + targetSlot = hotbarSlot; + break; + } + } + + // PRIORITY 1: empty slot → instant choice + if (mc.player.getInventory().getStack(targetSlot).isEmpty()) { + Utils.performSwap(slot, targetSlot); + return; + } + + // Get blocks until next use of items in hotbar + int blockCounter = 0; + for (int x = workingInterval.getLeft(); x <= workingInterval.getRight(); x++) { + for (int z = 0; z < 128; z++) { + if (!Utils.isInInterval(workingInterval, x)) break; + blockCounter++; + + BlockState state = MapAreaCache.getCachedBlockState(mapCorner.add(x, map[x][z].getRight(), z)); + if (state.isAir()) { + Block block = map[x][z].getLeft(); + if (block == null) continue; + + Item item = block.asItem(); + + if (itemDistance.containsKey(item) && + itemDistance.get(item) == -1) { + itemDistance.put(item, blockCounter); + } + } + } + } + + // Count frequency of items in hotbar + for (int hotbarSlot : availableHotBarSlots) { + ItemStack stack = mc.player.getInventory().getStack(hotbarSlot); + if (!stack.isEmpty()) { + Item item = stack.getItem(); + itemFrequency.put(item, itemFrequency.get(item) + 1); + } + } + + // Choose best candidate + Item bestItem = null; + int bestDistance = -2; // lower than -1 + int bestFrequency = -1; + + for (Item item : itemSlot.keySet()) { + int distance = itemDistance.get(item); // -1 = never used + int frequency = itemFrequency.get(item); + + boolean better = false; + + // PRIORITY 2: never used (-1) + if (distance == -1 && bestDistance != -1) { + better = true; + } + // PRIORITY 3: hotbar frequency + else if (frequency > bestFrequency) { + better = true; + } + // PRIORITY 4: distance to next use + else if (frequency == bestFrequency && distance > bestDistance && bestDistance != -1) { + better = true; + } + + if (better) { + bestItem = item; + bestDistance = distance; + bestFrequency = frequency; + } + } + + if (bestItem != null) { + targetSlot = itemSlot.get(bestItem); + } + + Utils.performSwap(slot, targetSlot); + } + // MapPrinter Interface for Slave Logic public void setInterval(Pair interval) { diff --git a/src/main/java/com/julflips/nerv_printer/utils/SlaveSystem.java b/src/main/java/com/julflips/nerv_printer/utils/SlaveSystem.java index 7344000..77dda9d 100644 --- a/src/main/java/com/julflips/nerv_printer/utils/SlaveSystem.java +++ b/src/main/java/com/julflips/nerv_printer/utils/SlaveSystem.java @@ -129,8 +129,12 @@ public static void generateIntervals() { } toBeRemoved.forEach((message) -> toBeSentMessages.remove(message)); + // Sort slaves deterministically + ArrayList sortedSlaves = new ArrayList<>(slaves); + Collections.sort(sortedSlaves, String.CASE_INSENSITIVE_ORDER); + for (int i = 0; i < intervals.size(); i++) { - String slave = slaves.get(i); + String slave = sortedSlaves.get(i); SlaveSystem.queueDM(slave, "interval:" + intervals.get(i).getLeft() + ":" + intervals.get(i).getRight()); } } diff --git a/src/main/java/com/julflips/nerv_printer/utils/Utils.java b/src/main/java/com/julflips/nerv_printer/utils/Utils.java index 6360c1a..3db1b3e 100644 --- a/src/main/java/com/julflips/nerv_printer/utils/Utils.java +++ b/src/main/java/com/julflips/nerv_printer/utils/Utils.java @@ -75,37 +75,6 @@ public static ArrayList getAvailableSlots(HashMap getRequiredItems(BlockPos mapCorner, Pair interval, int linesPerRun, int availableSlotsSize, Block[][] map) { - //Calculate the next items to restock - //Iterate over map. Player has to be able to see the complete map area - HashMap requiredItems = new HashMap<>(); - boolean isStartSide = true; - for (int x = interval.getLeft(); x <= interval.getRight(); x += linesPerRun) { - for (int z = 0; z < 128; z++) { - for (int lineBonus = 0; lineBonus < linesPerRun; lineBonus++) { - int adjustedX = x + lineBonus; - if (adjustedX > interval.getRight()) break; - int adjustedZ = z; - if (!isStartSide) adjustedZ = 127 - z; - BlockState blockState = MapAreaCache.getCachedBlockState(mapCorner.add(adjustedX, 0, adjustedZ)); - if (blockState.isAir() && map[adjustedX][adjustedZ] != null) { - //ChatUtils.info("Add material for: " + mapCorner.add(x + lineBonus, 0, adjustedZ).toShortString()); - Item material = map[adjustedX][adjustedZ].asItem(); - if (!requiredItems.containsKey(material)) requiredItems.put(material, 0); - requiredItems.put(material, requiredItems.get(material) + 1); - //Check if the item fits into inventory. If not, undo the last increment and return - if (stacksRequired(requiredItems.values()) > availableSlotsSize) { - requiredItems.put(material, requiredItems.get(material) - 1); - return requiredItems; - } - } - } - } - isStartSide = !isStartSide; - } - return requiredItems; - } - public static Pair, HashMap> getInvInformation(HashMap requiredItems, ArrayList availableSlots) { //Return a list of slots to be dumped and a Hashmap of material-amount we can keep in the inventory ArrayList dumpSlots = new ArrayList<>(); @@ -196,52 +165,19 @@ public static int findHighestFreeSlot(InventoryS2CPacket packet) { return -1; } - public static void swapIntoHotbar(int slot, ArrayList hotBarSlots) { - HashMap itemFrequency = new HashMap<>(); - HashMap itemSlot = new HashMap<>(); - int targetSlot = hotBarSlots.get(0); - - //Search the most frequent item in the hotbar - for (int i : hotBarSlots) { - if (!mc.player.getInventory().getStack(i).isEmpty()) { - Item item = mc.player.getInventory().getStack(i).getItem(); - if (!itemFrequency.containsKey(item)) { - itemFrequency.put(item, 1); - itemSlot.put(item, i); - } else { - itemFrequency.put(item, itemFrequency.get(item) + 1); - } - } - } - int topFrequency = 0; - ArrayList topFrequencyItems = new ArrayList<>(); - for (Item item : itemFrequency.keySet()) { - if (itemFrequency.get(item) > topFrequency) { - topFrequency = itemFrequency.get(item); - topFrequencyItems = new ArrayList<>(Collections.singletonList(item)); - } else if (itemFrequency.get(item) == topFrequency) { - topFrequencyItems.add(item); - } - } - if (!topFrequencyItems.isEmpty()) { - Random random = new Random(); - Item item = topFrequencyItems.get(random.nextInt(topFrequencyItems.size())); - targetSlot = itemSlot.get(item); - } - - //Prefer emtpy slots - for (int i : hotBarSlots) { - if (mc.player.getInventory().getStack(i).isEmpty()) { - targetSlot = i; - } - } + public static void performSwap(int fromSlot, int toSlot) { + mc.player.getInventory().setSelectedSlot(toSlot); - //info("Swapping " + slot + " into " + targetSlot); - mc.player.getInventory().setSelectedSlot(targetSlot); + IClientPlayerInteractionManager cim = + (IClientPlayerInteractionManager) mc.interactionManager; - IClientPlayerInteractionManager cim = (IClientPlayerInteractionManager) mc.interactionManager; - cim.clickSlot(mc.player.currentScreenHandler.syncId, slot, targetSlot, SlotActionType.SWAP, mc.player); - //mc.getNetworkHandler().sendPacket(new ClickSlotC2SPacket(0, slot, targetSlot, 0, SlotActionType.SWAP, new ItemStack(Items.AIR), Int2ObjectMaps.emptyMap())); + cim.clickSlot( + mc.player.currentScreenHandler.syncId, + fromSlot, + toSlot, + SlotActionType.SWAP, + mc.player + ); } public static void iterateBlocks(BlockPos startingPos, int horizontalRadius, int verticalRadius, BiConsumer function) {