diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index 6024cc11a2..5544fc99b7 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -160,14 +160,14 @@ private void onPlayerTeleported(PlayerPositionLookS2CPacket packet, CallbackInfo SmoothAOTE.playerTeleported(); } - @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/DebugHud;shouldShowPacketSizeAndPingCharts()Z")) - private boolean shouldShowPacketSizeAndPingCharts(DebugHud instance, Operation original) { + @ModifyExpressionValue(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/DebugHud;shouldShowPacketSizeAndPingCharts()Z")) + private boolean shouldShowPacketSizeAndPingCharts(boolean original) { //make the f3+3 screen always send ping packets even when closed //this is needed to make smooth AOTE work if (Utils.isOnSkyblock()) { return true; } - return original.call(instance); + return original; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/SmoothAOTE.java b/src/main/java/de/hysky/skyblocker/skyblock/SmoothAOTE.java index 0fcbaaa846..2e34bef6ad 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/SmoothAOTE.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/SmoothAOTE.java @@ -32,434 +32,434 @@ public class SmoothAOTE { - private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); - - private static final Pattern MANA_LORE = Pattern.compile("Mana Cost: (\\d+)"); - private static final long MAX_TELEPORT_TIME = 2500; //2.5 seconds - - private static long startTime; - private static Vec3d startPos; - private static Vec3d cameraStartPos; - private static Vec3d teleportVector; - private static long lastPing; - private static int teleportsAhead; - private static long lastTeleportTime; - private static boolean teleportDisabled; - - @Init - public static void init() { - UseItemCallback.EVENT.register(SmoothAOTE::onItemInteract); - UseBlockCallback.EVENT.register(SmoothAOTE::onBlockInteract); - } - - /** - * When a player receives a teleport packet finish a teleport - */ - public static void playerTeleported() { - //the player has been teleported so 1 less teleport ahead - teleportsAhead = Math.max(0, teleportsAhead - 1); - //re-enable the animation if the player is teleported as this means they can teleport again. and reset timer for last teleport update - lastTeleportTime = System.currentTimeMillis(); - teleportDisabled = false; - - //if the server is in sync in number of teleports - if (teleportsAhead == 0) { - //reset when player has reached the end of the teleports - startPos = null; - teleportVector = null; - } - } - - /** - * checks to see if a teleport device is using transmission tuner to increase the range - * - * @param customData the custom data of the teleport device - * @param baseRange the base range for the device without tuner - * @return the range with tuner - */ - private static int extractTunedCustomData(NbtCompound customData, int baseRange) { - return customData != null && customData.contains("tuned_transmission") ? baseRange + customData.getInt("tuned_transmission") : baseRange; - } - - /** - * When an item is right-clicked send off to calculate teleport with the clicked item - * - * @param playerEntity player - * @param world world - * @param hand held item - * @return pass - */ - private static TypedActionResult onItemInteract(PlayerEntity playerEntity, World world, Hand hand) { - if (CLIENT.player == null) { - return null; - } - calculateTeleportUse(hand); - return TypedActionResult.pass(CLIENT.player.getStackInHand(hand)); - } - - /** - * Allows shovel teleport items to be used when aiming at interactable blocks - * - * @param playerEntity player - * @param world world - * @param hand hand item - * @param blockHitResult target block - * @return always pass - */ - private static ActionResult onBlockInteract(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) { - ItemStack itemStack = playerEntity.getStackInHand(hand); - if (isShovel(itemStack) && canShovelActOnBlock(world.getBlockState(blockHitResult.getBlockPos()).getBlock())) { - calculateTeleportUse(hand); - } - return ActionResult.PASS; - } - - private static boolean isShovel(ItemStack itemStack) { - return itemStack.isOf(Items.WOODEN_SHOVEL) || - itemStack.isOf(Items.STONE_SHOVEL) || - itemStack.isOf(Items.IRON_SHOVEL) || - itemStack.isOf(Items.GOLDEN_SHOVEL) || - itemStack.isOf(Items.DIAMOND_SHOVEL); - } - - /** - * Checks if the block is one that the shovel can turn into a path (e.g., grass or dirt) - * - * @param block block to check - * @return if block can be turned into path - */ - private static boolean canShovelActOnBlock(Block block) { - return block == Blocks.GRASS_BLOCK || - block == Blocks.DIRT || - block == Blocks.COARSE_DIRT || - block == Blocks.PODZOL; - } - - /** - * Finds if a player uses a teleport and then saves the start position and time. then works out final position and saves that too - * - * @param hand what the player is holding - */ - - private static void calculateTeleportUse(Hand hand) { - //stop checking if player does not exist - if (CLIENT.player == null || CLIENT.world == null) { - return; - } - //get return item - ItemStack stack = CLIENT.player.getStackInHand(hand); - - //make sure it's not disabled - if (teleportDisabled) { - return; - } - - // make sure the camera is not in 3rd person - if (CLIENT.options.getPerspective() != Perspective.FIRST_PERSON) { - return; - } - - //make sure the player is in an area teleporting is allowed not allowed in glacite mineshafts and floor 7 boss - if (!isAllowedLocation()) { - return; - } - - //work out if the player is holding a teleporting item that is enabled and if so how far the item will take them - ItemStack heldItem = CLIENT.player.getMainHandStack(); - String itemId = heldItem.getSkyblockId(); - NbtCompound customData = ItemUtils.getCustomData(heldItem); - - int distance; - switch (itemId) { - case "ASPECT_OF_THE_LEECH_1" -> { - if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableWeirdTransmission) { - distance = 3; - break; - } - return; - - } - case "ASPECT_OF_THE_LEECH_2" -> { - if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableWeirdTransmission) { - distance = 4; - break; - } - return; - } - case "ASPECT_OF_THE_END", "ASPECT_OF_THE_VOID" -> { - if (CLIENT.options.sneakKey.isPressed() && customData.getInt("ethermerge") == 1) { - if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableEtherTransmission) { - distance = extractTunedCustomData(customData, 57); - break; - } - } else if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableInstantTransmission) { - distance = extractTunedCustomData(customData, 8); - break; - } - return; - } - case "ETHERWARP_CONDUIT" -> { - if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableEtherTransmission) { - distance = extractTunedCustomData(customData, 57); - break; - } - return; - } - case "SINSEEKER_SCYTHE" -> { - if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableSinrecallTransmission) { - distance = extractTunedCustomData(customData, 4); - break; - } - return; - } - case "NECRON_BLADE", "ASTRAEA", "HYPERION", "SCYLLA", "VALKYRIE" -> { - if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableWitherImpact) { - distance = 10; - break; - } - return; - } - default -> { - return; - } - } - //make sure the player has enough mana to do the teleport - Matcher manaNeeded = ItemUtils.getLoreLineIfMatch(heldItem, MANA_LORE); - if (manaNeeded != null && manaNeeded.matches()) { - int manaCost = Integer.parseInt(manaNeeded.group(1)); - int predictedMana = SkyblockerMod.getInstance().statusBarTracker.getMana().value() - teleportsAhead * manaCost; - if (predictedMana < manaCost) { // todo the players mana can lag behind as it is updated server side. client side mana calculations would help with this - return; - } - } - - //work out start pos of warp and set start time. if there is an active warp going on make the end of that the start of the next one - if (teleportsAhead == 0 || startPos == null || teleportVector == null) { - //start of teleport sequence - startPos = CLIENT.player.getPos().add(0, 1.62, 0); // the eye poss should not be affected by crouching - cameraStartPos = CLIENT.player.getEyePos(); - lastTeleportTime = System.currentTimeMillis(); - } else { - //add to the end of the teleport sequence - startPos = startPos.add(teleportVector); - //set the camera start pos to how far though the teleport the player is to make is smoother - cameraStartPos = getInterpolatedPos(); - } - - startTime = System.currentTimeMillis(); - - - // calculate the vector the player will follow for the teleport - //get direction - float pitch = CLIENT.player.getPitch(); - float yaw = CLIENT.player.getYaw(); - Vec3d look = CLIENT.player.getRotationVector(pitch, yaw); - - //find target location depending on how far the item they are using takes them - teleportVector = raycast(distance, look, startPos); - if (teleportVector == null) { - startPos = null; - return; - } - - //compensate for hypixel round to center of block (to x.5 y.62 z.5) - Vec3d predictedEnd = startPos.add(teleportVector); - Vec3d offsetVec = new Vec3d(predictedEnd.x - roundToCenter(predictedEnd.x), predictedEnd.y - (Math.ceil(predictedEnd.y) + 0.62), predictedEnd.z - roundToCenter(predictedEnd.z)); - teleportVector = teleportVector.subtract(offsetVec); - //add 1 to teleports ahead - teleportsAhead += 1; - } - - /** - * Rounds a value to the nearest 0.5 - * - * @param input number to round - * @return rounded number - */ - private static double roundToCenter(double input) { - return Math.round(input - 0.5) + 0.5; - } - - /** - * Works out if the players location lets them use teleportation or not - * - * @return if the player should be allowed to teleport - */ - private static boolean isAllowedLocation() { - //check mines shafts - if (Utils.getMap().equals("Mineshaft")) { - return false; - } else if (Utils.getIslandArea().equals("⏣ Jungle Temple")) { //do not allow in jungle temple - return false; - } else if (Utils.getLocation() == Location.PRIVATE_ISLAND && !Utils.getIslandArea().equals("⏣ Your Island")) { //do not allow it when visiting - return false; - } else if (Utils.isInDungeons()) { //check places in dungeons where you can't teleport - if (DungeonManager.isInBoss() && DungeonManager.getBoss() == DungeonBoss.MAXOR) { - return false; - } - //make sure the player is in a room then check for disallowed rooms - if (!DungeonManager.isCurrentRoomMatched()) { - return true; - } - //does not work in boulder room - if (DungeonManager.getCurrentRoom().getName().equals("boxes-room")) { - return false; - } - //does not work in teleport maze room - if (DungeonManager.getCurrentRoom().getName().equals("teleport-pad-room")) { - return false; - } - //does not work in trap room - if (DungeonManager.getCurrentRoom().getName().startsWith("trap")) { - return false; - } - } - - return true; - } - - /** - * Custom raycast for teleporting checks for blocks for each 1 block forward in teleport. (very similar to hypixels method) - * - * @param distance maximum distance - * @return teleport vector - */ - private static Vec3d raycast(int distance, Vec3d direction, Vec3d startPos) { - if (CLIENT.world == null) { - return null; - } - - //based on which way the ray is going get the needed vector for checking diagonals - BlockPos xDiagonalOffset; - BlockPos zDiagonalOffset; - if (direction.getX() > 0) { - xDiagonalOffset = new BlockPos(-1, 0, 0); - } else { - xDiagonalOffset = new BlockPos(1, 0, 0); - } - if (direction.getZ() > 0) { - zDiagonalOffset = new BlockPos(0, 0, -1); - } else { - zDiagonalOffset = new BlockPos(0, 0, 1); - } - - //initialise the closest floor value outside of possible values - int closeFloorY = 1000; - - //loop though each block of a teleport checking each block if there are blocks in the way - for (double offset = 0; offset <= distance; offset++) { - Vec3d pos = startPos.add(direction.multiply(offset)); - BlockPos checkPos = BlockPos.ofFloored(pos); - - //check if there is a block at the check location - if (!canTeleportThrough(checkPos)) { - if (offset == 0) { - // no teleport can happen - return null; - } - return direction.multiply(offset - 1); - } - - //check if the block at head height is free - if (!canTeleportThrough(checkPos.up())) { - if (offset == 0) { - //cancel the check if starting height is too low - Vec3d justAhead = startPos.add(direction.multiply(0.2)); - if ((justAhead.getY() - Math.floor(justAhead.getY())) <= 0.495) { - continue; - } - // no teleport can happen - return null; - } - return direction.multiply(offset - 1); - } - - //check the diagonals to make sure player is not going through diagonal wall (full height block in the way on both sides at either height) - if (offset != 0 && (isBlockFloor(checkPos.add(xDiagonalOffset)) || isBlockFloor(checkPos.up().add(xDiagonalOffset))) && (isBlockFloor(checkPos.add(zDiagonalOffset)) || isBlockFloor(checkPos.up().add(zDiagonalOffset)))) { - return direction.multiply(offset - 1); - } - - //if the player is close to the floor (including diagonally) save Y and when player goes bellow this y finish teleport - if (offset != 0 && (isBlockFloor(checkPos.down()) || (isBlockFloor(checkPos.down().subtract(xDiagonalOffset)) && isBlockFloor(checkPos.down().subtract(zDiagonalOffset)))) && (pos.getY() - Math.floor(pos.getY())) < 0.31) { - closeFloorY = checkPos.getY() - 1; - } - - //if the checking Y is same as closeY finish - if (closeFloorY == checkPos.getY()) { - return direction.multiply(offset - 1); - } - } - - //return full distance if no collision found - return direction.multiply(distance); - } - - /** - * Checks to see if a block is in the allowed list to teleport though - * Air, Buttons, carpets, crops, pots, mushrooms, nether wart, redstone, ladder, water, fire, lava, 3 or less snow layers - * - * @param blockPos block location - * @return if a block location can be teleported though - */ - private static Boolean canTeleportThrough(BlockPos blockPos) { - if (CLIENT.world == null) { - return false; - } - - BlockState blockState = CLIENT.world.getBlockState(blockPos); - if (blockState.isAir()) { - return true; - } - Block block = blockState.getBlock(); - return block instanceof ButtonBlock || block instanceof CarpetBlock || block instanceof CropBlock || block instanceof FlowerPotBlock || block.equals(Blocks.BROWN_MUSHROOM) || block.equals(Blocks.RED_MUSHROOM) || block.equals(Blocks.NETHER_WART) || block.equals(Blocks.REDSTONE_WIRE) || block.equals(Blocks.LADDER) || block.equals(Blocks.FIRE) || (block.equals(Blocks.SNOW) && blockState.get(Properties.LAYERS) <= 3) || block.equals(Blocks.WATER) || block.equals(Blocks.LAVA); - } - - /** - * Checks to see if a block goes to the top if so class it as a floor - * - * @param blockPos block location - * @return if it's a floor block - */ - private static Boolean isBlockFloor(BlockPos blockPos) { - if (CLIENT.world == null) { - return false; - } - - BlockState blockState = CLIENT.world.getBlockState(blockPos); - VoxelShape shape = blockState.getCollisionShape(CLIENT.world, blockPos); - if (shape.isEmpty()) { - return false; - } - return shape.getBoundingBox().maxY == 1; - } - - /** - * works out where they player should be based on how far though the predicted teleport time. - * - * @return the camera position for the interpolated pos - */ - - public static Vec3d getInterpolatedPos() { - if (CLIENT.player == null || teleportVector == null || startPos == null || teleportDisabled) { - return null; - } - long gap = System.currentTimeMillis() - startTime; - //make sure the player is actually getting teleported if not disable teleporting until they are teleported again - if (System.currentTimeMillis() - lastTeleportTime > 1000) { - teleportDisabled = true; - startPos = null; - teleportVector = null; - teleportsAhead = 0; - return null; - } - double percentage = Math.min((double) (gap) / Math.min(lastPing, MAX_TELEPORT_TIME), 1); - - return cameraStartPos.add(teleportVector.multiply(percentage)); - } - - public static void updatePing(long ping) { - lastPing = ping; - } + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + + private static final Pattern MANA_LORE = Pattern.compile("Mana Cost: (\\d+)"); + private static final long MAX_TELEPORT_TIME = 2500; //2.5 seconds + + private static long startTime; + private static Vec3d startPos; + private static Vec3d cameraStartPos; + private static Vec3d teleportVector; + private static long lastPing; + private static int teleportsAhead; + private static long lastTeleportTime; + private static boolean teleportDisabled; + + @Init + public static void init() { + UseItemCallback.EVENT.register(SmoothAOTE::onItemInteract); + UseBlockCallback.EVENT.register(SmoothAOTE::onBlockInteract); + } + + /** + * When a player receives a teleport packet finish a teleport + */ + public static void playerTeleported() { + //the player has been teleported so 1 less teleport ahead + teleportsAhead = Math.max(0, teleportsAhead - 1); + //re-enable the animation if the player is teleported as this means they can teleport again. and reset timer for last teleport update + lastTeleportTime = System.currentTimeMillis(); + teleportDisabled = false; + + //if the server is in sync in number of teleports + if (teleportsAhead == 0) { + //reset when player has reached the end of the teleports + startPos = null; + teleportVector = null; + } + } + + /** + * checks to see if a teleport device is using transmission tuner to increase the range + * + * @param customData the custom data of the teleport device + * @param baseRange the base range for the device without tuner + * @return the range with tuner + */ + private static int extractTunedCustomData(NbtCompound customData, int baseRange) { + return customData != null && customData.contains("tuned_transmission") ? baseRange + customData.getInt("tuned_transmission") : baseRange; + } + + /** + * When an item is right-clicked send off to calculate teleport with the clicked item + * + * @param playerEntity player + * @param world world + * @param hand held item + * @return pass + */ + private static TypedActionResult onItemInteract(PlayerEntity playerEntity, World world, Hand hand) { + if (CLIENT.player == null) { + return null; + } + calculateTeleportUse(hand); + return TypedActionResult.pass(CLIENT.player.getStackInHand(hand)); + } + + /** + * Allows shovel teleport items to be used when aiming at interactable blocks + * + * @param playerEntity player + * @param world world + * @param hand hand item + * @param blockHitResult target block + * @return always pass + */ + private static ActionResult onBlockInteract(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) { + ItemStack itemStack = playerEntity.getStackInHand(hand); + if (isShovel(itemStack) && canShovelActOnBlock(world.getBlockState(blockHitResult.getBlockPos()).getBlock())) { + calculateTeleportUse(hand); + } + return ActionResult.PASS; + } + + private static boolean isShovel(ItemStack itemStack) { + return itemStack.isOf(Items.WOODEN_SHOVEL) || + itemStack.isOf(Items.STONE_SHOVEL) || + itemStack.isOf(Items.IRON_SHOVEL) || + itemStack.isOf(Items.GOLDEN_SHOVEL) || + itemStack.isOf(Items.DIAMOND_SHOVEL); + } + + /** + * Checks if the block is one that the shovel can turn into a path (e.g., grass or dirt) + * + * @param block block to check + * @return if block can be turned into path + */ + private static boolean canShovelActOnBlock(Block block) { + return block == Blocks.GRASS_BLOCK || + block == Blocks.DIRT || + block == Blocks.COARSE_DIRT || + block == Blocks.PODZOL; + } + + /** + * Finds if a player uses a teleport and then saves the start position and time. then works out final position and saves that too + * + * @param hand what the player is holding + */ + + private static void calculateTeleportUse(Hand hand) { + //stop checking if player does not exist + if (CLIENT.player == null || CLIENT.world == null) { + return; + } + //get return item + ItemStack stack = CLIENT.player.getStackInHand(hand); + + //make sure it's not disabled + if (teleportDisabled) { + return; + } + + // make sure the camera is not in 3rd person + if (CLIENT.options.getPerspective() != Perspective.FIRST_PERSON) { + return; + } + + //make sure the player is in an area teleporting is allowed not allowed in glacite mineshafts and floor 7 boss + if (!isAllowedLocation()) { + return; + } + + //work out if the player is holding a teleporting item that is enabled and if so how far the item will take them + ItemStack heldItem = CLIENT.player.getMainHandStack(); + String itemId = heldItem.getSkyblockId(); + NbtCompound customData = ItemUtils.getCustomData(heldItem); + + int distance; + switch (itemId) { + case "ASPECT_OF_THE_LEECH_1" -> { + if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableWeirdTransmission) { + distance = 3; + break; + } + return; + + } + case "ASPECT_OF_THE_LEECH_2" -> { + if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableWeirdTransmission) { + distance = 4; + break; + } + return; + } + case "ASPECT_OF_THE_END", "ASPECT_OF_THE_VOID" -> { + if (CLIENT.options.sneakKey.isPressed() && customData.getInt("ethermerge") == 1) { + if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableEtherTransmission) { + distance = extractTunedCustomData(customData, 57); + break; + } + } else if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableInstantTransmission) { + distance = extractTunedCustomData(customData, 8); + break; + } + return; + } + case "ETHERWARP_CONDUIT" -> { + if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableEtherTransmission) { + distance = extractTunedCustomData(customData, 57); + break; + } + return; + } + case "SINSEEKER_SCYTHE" -> { + if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableSinrecallTransmission) { + distance = extractTunedCustomData(customData, 4); + break; + } + return; + } + case "NECRON_BLADE", "ASTRAEA", "HYPERION", "SCYLLA", "VALKYRIE" -> { + if (SkyblockerConfigManager.get().uiAndVisuals.smoothAOTE.enableWitherImpact) { + distance = 10; + break; + } + return; + } + default -> { + return; + } + } + //make sure the player has enough mana to do the teleport + Matcher manaNeeded = ItemUtils.getLoreLineIfMatch(heldItem, MANA_LORE); + if (manaNeeded != null && manaNeeded.matches()) { + int manaCost = Integer.parseInt(manaNeeded.group(1)); + int predictedMana = SkyblockerMod.getInstance().statusBarTracker.getMana().value() - teleportsAhead * manaCost; + if (predictedMana < manaCost) { // todo the players mana can lag behind as it is updated server side. client side mana calculations would help with this + return; + } + } + + //work out start pos of warp and set start time. if there is an active warp going on make the end of that the start of the next one + if (teleportsAhead == 0 || startPos == null || teleportVector == null) { + //start of teleport sequence + startPos = CLIENT.player.getPos().add(0, 1.62, 0); // the eye poss should not be affected by crouching + cameraStartPos = CLIENT.player.getEyePos(); + lastTeleportTime = System.currentTimeMillis(); + } else { + //add to the end of the teleport sequence + startPos = startPos.add(teleportVector); + //set the camera start pos to how far though the teleport the player is to make is smoother + cameraStartPos = getInterpolatedPos(); + } + + startTime = System.currentTimeMillis(); + + + // calculate the vector the player will follow for the teleport + //get direction + float pitch = CLIENT.player.getPitch(); + float yaw = CLIENT.player.getYaw(); + Vec3d look = CLIENT.player.getRotationVector(pitch, yaw); + + //find target location depending on how far the item they are using takes them + teleportVector = raycast(distance, look, startPos); + if (teleportVector == null) { + startPos = null; + return; + } + + //compensate for hypixel round to center of block (to x.5 y.62 z.5) + Vec3d predictedEnd = startPos.add(teleportVector); + Vec3d offsetVec = new Vec3d(predictedEnd.x - roundToCenter(predictedEnd.x), predictedEnd.y - (Math.ceil(predictedEnd.y) + 0.62), predictedEnd.z - roundToCenter(predictedEnd.z)); + teleportVector = teleportVector.subtract(offsetVec); + //add 1 to teleports ahead + teleportsAhead += 1; + } + + /** + * Rounds a value to the nearest 0.5 + * + * @param input number to round + * @return rounded number + */ + private static double roundToCenter(double input) { + return Math.round(input - 0.5) + 0.5; + } + + /** + * Works out if the players location lets them use teleportation or not + * + * @return if the player should be allowed to teleport + */ + private static boolean isAllowedLocation() { + //check mines shafts + if (Utils.getMap().equals("Mineshaft")) { + return false; + } else if (Utils.getIslandArea().equals("⏣ Jungle Temple")) { //do not allow in jungle temple + return false; + } else if (Utils.getLocation() == Location.PRIVATE_ISLAND && !Utils.getIslandArea().equals("⏣ Your Island")) { //do not allow it when visiting + return false; + } else if (Utils.isInDungeons()) { //check places in dungeons where you can't teleport + if (DungeonManager.isInBoss() && DungeonManager.getBoss() == DungeonBoss.MAXOR) { + return false; + } + //make sure the player is in a room then check for disallowed rooms + if (!DungeonManager.isCurrentRoomMatched()) { + return true; + } + //does not work in boulder room + if (DungeonManager.getCurrentRoom().getName().equals("boxes-room")) { + return false; + } + //does not work in teleport maze room + if (DungeonManager.getCurrentRoom().getName().equals("teleport-pad-room")) { + return false; + } + //does not work in trap room + if (DungeonManager.getCurrentRoom().getName().startsWith("trap")) { + return false; + } + } + + return true; + } + + /** + * Custom raycast for teleporting checks for blocks for each 1 block forward in teleport. (very similar to hypixels method) + * + * @param distance maximum distance + * @return teleport vector + */ + private static Vec3d raycast(int distance, Vec3d direction, Vec3d startPos) { + if (CLIENT.world == null || direction == null || startPos == null) { + return null; + } + + //based on which way the ray is going get the needed vector for checking diagonals + BlockPos xDiagonalOffset; + BlockPos zDiagonalOffset; + if (direction.getX() > 0) { + xDiagonalOffset = new BlockPos(-1, 0, 0); + } else { + xDiagonalOffset = new BlockPos(1, 0, 0); + } + if (direction.getZ() > 0) { + zDiagonalOffset = new BlockPos(0, 0, -1); + } else { + zDiagonalOffset = new BlockPos(0, 0, 1); + } + + //initialise the closest floor value outside of possible values + int closeFloorY = 1000; + + //loop though each block of a teleport checking each block if there are blocks in the way + for (double offset = 0; offset <= distance; offset++) { + Vec3d pos = startPos.add(direction.multiply(offset)); + BlockPos checkPos = BlockPos.ofFloored(pos); + + //check if there is a block at the check location + if (!canTeleportThrough(checkPos)) { + if (offset == 0) { + // no teleport can happen + return null; + } + return direction.multiply(offset - 1); + } + + //check if the block at head height is free + if (!canTeleportThrough(checkPos.up())) { + if (offset == 0) { + //cancel the check if starting height is too low + Vec3d justAhead = startPos.add(direction.multiply(0.2)); + if ((justAhead.getY() - Math.floor(justAhead.getY())) <= 0.495) { + continue; + } + // no teleport can happen + return null; + } + return direction.multiply(offset - 1); + } + + //check the diagonals to make sure player is not going through diagonal wall (full height block in the way on both sides at either height) + if (offset != 0 && (isBlockFloor(checkPos.add(xDiagonalOffset)) || isBlockFloor(checkPos.up().add(xDiagonalOffset))) && (isBlockFloor(checkPos.add(zDiagonalOffset)) || isBlockFloor(checkPos.up().add(zDiagonalOffset)))) { + return direction.multiply(offset - 1); + } + + //if the player is close to the floor (including diagonally) save Y and when player goes bellow this y finish teleport + if (offset != 0 && (isBlockFloor(checkPos.down()) || (isBlockFloor(checkPos.down().subtract(xDiagonalOffset)) && isBlockFloor(checkPos.down().subtract(zDiagonalOffset)))) && (pos.getY() - Math.floor(pos.getY())) < 0.31) { + closeFloorY = checkPos.getY() - 1; + } + + //if the checking Y is same as closeY finish + if (closeFloorY == checkPos.getY()) { + return direction.multiply(offset - 1); + } + } + + //return full distance if no collision found + return direction.multiply(distance); + } + + /** + * Checks to see if a block is in the allowed list to teleport though + * Air, Buttons, carpets, crops, pots, mushrooms, nether wart, redstone, ladder, water, fire, lava, 3 or less snow layers + * + * @param blockPos block location + * @return if a block location can be teleported though + */ + private static Boolean canTeleportThrough(BlockPos blockPos) { + if (CLIENT.world == null) { + return false; + } + + BlockState blockState = CLIENT.world.getBlockState(blockPos); + if (blockState.isAir()) { + return true; + } + Block block = blockState.getBlock(); + return block instanceof ButtonBlock || block instanceof CarpetBlock || block instanceof CropBlock || block instanceof FlowerPotBlock || block.equals(Blocks.BROWN_MUSHROOM) || block.equals(Blocks.RED_MUSHROOM) || block.equals(Blocks.NETHER_WART) || block.equals(Blocks.REDSTONE_WIRE) || block.equals(Blocks.LADDER) || block.equals(Blocks.FIRE) || (block.equals(Blocks.SNOW) && blockState.get(Properties.LAYERS) <= 3) || block.equals(Blocks.WATER) || block.equals(Blocks.LAVA); + } + + /** + * Checks to see if a block goes to the top if so class it as a floor + * + * @param blockPos block location + * @return if it's a floor block + */ + private static Boolean isBlockFloor(BlockPos blockPos) { + if (CLIENT.world == null) { + return false; + } + + BlockState blockState = CLIENT.world.getBlockState(blockPos); + VoxelShape shape = blockState.getCollisionShape(CLIENT.world, blockPos); + if (shape.isEmpty()) { + return false; + } + return shape.getBoundingBox().maxY == 1; + } + + /** + * works out where they player should be based on how far though the predicted teleport time. + * + * @return the camera position for the interpolated pos + */ + + public static Vec3d getInterpolatedPos() { + if (CLIENT.player == null || teleportVector == null || startPos == null || teleportDisabled) { + return null; + } + long gap = System.currentTimeMillis() - startTime; + //make sure the player is actually getting teleported if not disable teleporting until they are teleported again + if (System.currentTimeMillis() - lastTeleportTime > Math.min(2 * lastPing, MAX_TELEPORT_TIME)) { + teleportDisabled = true; + startPos = null; + teleportVector = null; + teleportsAhead = 0; + return null; + } + double percentage = Math.min((double) (gap) / Math.min(lastPing, MAX_TELEPORT_TIME), 1); + + return cameraStartPos.add(teleportVector.multiply(percentage)); + } + + public static void updatePing(long ping) { + lastPing = ping; + } } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 2e73b638d3..ada087db3f 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -766,17 +766,17 @@ "skyblocker.config.uiAndVisuals.showEquipmentInInventory": "Show Equipment in Inventory", "skyblocker.config.uiAndVisuals.smoothAOTE": "Smooth AOTE", - "skyblocker.config.uiAndVisuals.smoothAOTE.@Tooltip": "Smooths out teleporting with right click teleport ability's", + "skyblocker.config.uiAndVisuals.smoothAOTE.@Tooltip": "Smooths out teleporting with right click teleport abilities.", "skyblocker.config.uiAndVisuals.smoothAOTE.enableEtherTransmission": "Enable Ether Transmission", - "skyblocker.config.uiAndVisuals.smoothAOTE.enableEtherTransmission.@Tooltip": "for: Etherwarp Conduit and Ether Merged", - "skyblocker.config.uiAndVisuals.smoothAOTE.enableInstantTransmission": "Enable Instant Transmission ", - "skyblocker.config.uiAndVisuals.smoothAOTE.enableInstantTransmission.@Tooltip": "for: AOTE and AOTV ", + "skyblocker.config.uiAndVisuals.smoothAOTE.enableEtherTransmission.@Tooltip": "For: Etherwarp Conduit and Ether Merged.", + "skyblocker.config.uiAndVisuals.smoothAOTE.enableInstantTransmission": "Enable Instant Transmission", + "skyblocker.config.uiAndVisuals.smoothAOTE.enableInstantTransmission.@Tooltip": "For: AOTE and AOTV.", "skyblocker.config.uiAndVisuals.smoothAOTE.enableSinrecallTransmission": "Enable Sinrecall Transmission", - "skyblocker.config.uiAndVisuals.smoothAOTE.enableSinrecallTransmission.@Tooltip": "for: Sinseeker Scythe", + "skyblocker.config.uiAndVisuals.smoothAOTE.enableSinrecallTransmission.@Tooltip": "For: Sinseeker Scythe.", "skyblocker.config.uiAndVisuals.smoothAOTE.enableWeirdTransmission": "Enable Weird Transmission", - "skyblocker.config.uiAndVisuals.smoothAOTE.enableWeirdTransmission.@Tooltip": "for: Aspect of the Leech 1 and 2", + "skyblocker.config.uiAndVisuals.smoothAOTE.enableWeirdTransmission.@Tooltip": "For: Aspect of the Leech.", "skyblocker.config.uiAndVisuals.smoothAOTE.enableWitherImpact": "Enable Wither Impact", - "skyblocker.config.uiAndVisuals.smoothAOTE.enableWitherImpact.@Tooltip": "for: Necron Blade, Hyperion, Scylla, Valkyrie", + "skyblocker.config.uiAndVisuals.smoothAOTE.enableWitherImpact.@Tooltip": "For: Necron's Blade, Hyperion, Astraea, Scylla, and Valkyrie.", "skyblocker.config.uiAndVisuals.tabHud": "Fancy tab HUD (Temporarily disabled outside dungeons)", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground": "Enable HUD Background",