diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml new file mode 100644 index 0000000..0e7059f --- /dev/null +++ b/.github/workflows/compile.yml @@ -0,0 +1,32 @@ +name: "Compile" + +on: [push, pull_request] + +jobs: + gradle: + strategy: + matrix: + os: [ ubuntu-latest ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Execute Gradle build + run: ./gradlew build + + - name: Upload a Build Artifact + uses: actions/upload-artifact@v2.2.4 + if: success() + with: + name: TerminatorPlus + path: build/libs/ diff --git a/.github/workflows/dev-analysis.yml b/.github/workflows/dev-analysis.yml deleted file mode 100644 index 8d11f31..0000000 --- a/.github/workflows/dev-analysis.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: "CodeQL" - -on: [push, pull_request] - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'java' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Cache local repo - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 - - gradle: - strategy: - matrix: - os: [ ubuntu-latest ] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 17 - - - name: Make gradlew executable - run: chmod +x ./gradlew - - - name: Setup Gradle - uses: gradle/gradle-build-action@v2 - - - name: Execute Gradle build - run: ./gradlew build - - - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.4 - if: success() - with: - name: TerminatorPlus - path: build/libs/ diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/BotManager.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/BotManager.java index ad1eeb4..5e3e1bb 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/BotManager.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/BotManager.java @@ -10,13 +10,17 @@ import java.util.Set; import java.util.UUID; public interface BotManager { + Location getSpawnLoc(); + + void setSpawnLoc(Location loc); + Set fetch(); Agent getAgent(); void add(Terminator bot); - Terminator getFirst(String name); + Terminator getFirst(String name, Location target); List fetchNames(); @@ -50,4 +54,7 @@ public interface BotManager { void setMobTarget(boolean mobTarget); + boolean addToPlayerList(); + + void setAddToPlayerList(boolean addPlayerList); } diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/Terminator.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/Terminator.java index 9d368d2..dca945b 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/Terminator.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/Terminator.java @@ -4,13 +4,17 @@ import com.mojang.authlib.GameProfile; import net.nuggetmc.tplus.api.agent.legacyagent.ai.NeuralNetwork; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; +import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; +import java.util.List; import java.util.UUID; public interface Terminator { @@ -30,6 +34,8 @@ public interface Terminator { boolean hasNeuralNetwork(); Location getLocation(); + + BoundingBox getBotBoundingBox(); boolean isBotAlive(); //Has to be named like this because paper re-obfuscates it @@ -37,8 +43,6 @@ public interface Terminator { float getBotMaxHealth(); - void ignite(); - boolean isBotOnFire(); boolean isFalling(); @@ -50,6 +54,8 @@ public interface Terminator { boolean isBotInWater(); boolean isBotOnGround(); + + List getStandingOn(); void setBotPitch(float pitch); @@ -106,14 +112,18 @@ public interface Terminator { void addVelocity(Vector velocity); int getAliveTicks(); + + int getNoFallTicks(); boolean tickDelay(int ticks); void renderBot(Object packetListener, boolean login); - void setOnFirePackets(boolean onFire); - UUID getTargetPlayer(); void setTargetPlayer(UUID target); + + boolean isInPlayerList(); + + World.Environment getDimension(); } diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/EnumTargetGoal.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/EnumTargetGoal.java index c2e8a1c..02eb72f 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/EnumTargetGoal.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/EnumTargetGoal.java @@ -7,6 +7,7 @@ public enum EnumTargetGoal { NEAREST_VULNERABLE_PLAYER("Locate the nearest real player that is in either Survival or Adventure mode."), NEAREST_PLAYER("Locate the nearest real online player, despite the gamemode."), NEAREST_HOSTILE("Locate the nearest hostile entity."), + NEAREST_RAIDER("Locate the nearest raider."), NEAREST_MOB("Locate the nearest mob."), NEAREST_BOT("Locate the nearest bot."), NEAREST_BOT_DIFFER("Locate the nearest bot with a different username."), @@ -20,10 +21,12 @@ public enum EnumTargetGoal { this.put("nearestvulnerableplayer", NEAREST_VULNERABLE_PLAYER); this.put("nearestplayer", NEAREST_PLAYER); this.put("nearesthostile", NEAREST_HOSTILE); + this.put("nearestraider", NEAREST_RAIDER); this.put("nearestmob", NEAREST_MOB); this.put("nearestbot", NEAREST_BOT); this.put("nearestbotdiffer", NEAREST_BOT_DIFFER); this.put("nearestbotdifferalpha", NEAREST_BOT_DIFFER_ALPHA); + this.put("player", PLAYER); } }; diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyAgent.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyAgent.java index 90874e9..0833d46 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyAgent.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyAgent.java @@ -11,18 +11,25 @@ import net.nuggetmc.tplus.api.event.BotDamageByPlayerEvent; import net.nuggetmc.tplus.api.event.BotDeathEvent; import net.nuggetmc.tplus.api.event.BotFallDamageEvent; import net.nuggetmc.tplus.api.event.TerminatorLocateTargetEvent; +import net.nuggetmc.tplus.api.utils.BotUtils; import net.nuggetmc.tplus.api.utils.MathUtils; import net.nuggetmc.tplus.api.utils.PlayerUtils; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.block.data.type.*; import org.bukkit.entity.*; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; +import com.google.common.base.Optional; + import java.util.*; +import java.util.Map.Entry; import java.util.regex.Pattern; // Yes, this code is very unoptimized, I know. @@ -43,24 +50,12 @@ public class LegacyAgent extends Agent { private final Map mining = new HashMap<>(); private final Set fallDamageCooldown = new HashSet<>(); public boolean offsets = true; - private List instantBreakBlocks = Arrays.asList( - Material.TALL_GRASS, - Material.GRASS, - Material.KELP_PLANT, - Material.WHEAT, - Material.POTATOES, - Material.CARROT, - Material.BEETROOT, - Material.SUGAR_CANE, - Material.SWEET_BERRY_BUSH, - Material.LILY_PAD, - Material.DANDELION, - Material.POPPY, - Material.ROSE_BUSH, - Material.PUMPKIN_STEM, - Material.MELON_STEM - ); + private List botsInPlayerList; private EnumTargetGoal goal; + private BoundingBox region; + private double regionWeightX; + private double regionWeightY; + private double regionWeightZ; public LegacyAgent(BotManager manager, Plugin plugin) { super(manager, plugin); @@ -75,7 +70,8 @@ public class LegacyAgent extends Agent { @Override protected void tick() { - manager.fetch().forEach(this::tickBot); + botsInPlayerList = manager.fetch().stream().filter(t -> t.isInPlayerList()).map(b -> b.getBukkitEntity()).toList(); + manager.fetch().forEach(this::tickBot); } private void center(Terminator bot) { @@ -115,6 +111,8 @@ public class LegacyAgent extends Agent { Location loc = bot.getLocation(); LivingEntity livingTarget = locateTarget(bot, loc); + blockCheck.tryPreMLG(bot, loc); + if (livingTarget == null) { stopMining(bot); return; @@ -180,11 +178,13 @@ public class LegacyAgent extends Agent { if (checkAt(bot, block, botPlayer)) return; - if (checkFence(bot, loc.getBlock(), botPlayer)) return; + if (checkFenceAndGates(bot, loc.getBlock(), botPlayer)) return; + + if (checkObstacles(bot, loc.getBlock(), botPlayer)) return; if (checkDown(bot, botPlayer, livingTarget.getLocation(), bothXZ)) return; - if ((withinTargetXZ || sameXZ) && checkUp(bot, livingTarget, botPlayer, target, withinTargetXZ)) return; + if ((withinTargetXZ || sameXZ) && checkUp(bot, livingTarget, botPlayer, target, withinTargetXZ, sameXZ)) return; if (bothXZ) sideResult = checkSide(bot, livingTarget, botPlayer); @@ -303,7 +303,7 @@ public class LegacyAgent extends Agent { Material itemType; - if (bot.getBukkitEntity().getWorld().getEnvironment() == World.Environment.NETHER) { + if (bot.getDimension() == World.Environment.NETHER) { itemType = Material.TWISTING_VINES; } else { itemType = Material.WATER_BUCKET; @@ -344,26 +344,50 @@ public class LegacyAgent extends Agent { Material itemType; Material placeType; Sound sound; + Location groundLoc = null; + boolean nether = bot.getDimension() == World.Environment.NETHER; + double yPos = bot.getBukkitEntity().getLocation().getY(); - if (bot.getBukkitEntity().getWorld().getEnvironment() == World.Environment.NETHER) { + if (nether) { itemType = Material.TWISTING_VINES; sound = Sound.BLOCK_WEEPING_VINES_PLACE; placeType = itemType; + + for (Block block : event.getStandingOn()) { + if (LegacyMats.canPlaceTwistingVines(block)) { + groundLoc = block.getLocation(); + break; + } + } } else { itemType = Material.WATER_BUCKET; sound = Sound.ITEM_BUCKET_EMPTY; placeType = Material.WATER; + + for (Block block : event.getStandingOn()) { + if (LegacyMats.canPlaceWater(block, Optional.of(yPos))) { + groundLoc = block.getLocation(); + break; + } + } } - - Location loc = bot.getLocation(); - - if (!loc.clone().add(0, -1, 0).getBlock().getType().isSolid()) return; + + if (groundLoc == null) return; + + Location loc = !LegacyMats.shouldReplace(groundLoc.getBlock(), yPos, nether) ? groundLoc.add(0, 1, 0) : groundLoc; + boolean waterloggable = !nether && loc.getBlock().getBlockData() instanceof Waterlogged; + boolean waterlogged = waterloggable && ((Waterlogged)loc.getBlock().getBlockData()).isWaterlogged(); event.setCancelled(true); - if (loc.getBlock().getType() != placeType) { + if (loc.getBlock().getType() != placeType && !waterlogged) { bot.punch(); - loc.getBlock().setType(placeType); + if (waterloggable) { + Waterlogged data = (Waterlogged)loc.getBlock().getBlockData(); + data.setWaterlogged(true); + loc.getBlock().setBlockData(data); + } else + loc.getBlock().setType(placeType); world.playSound(loc, sound, 1, 1); if (itemType == Material.WATER_BUCKET) { @@ -372,11 +396,18 @@ public class LegacyAgent extends Agent { scheduler.runTaskLater(plugin, () -> { Block block = loc.getBlock(); - if (block.getType() == Material.WATER) { + boolean waterloggedNow = !nether && block.getBlockData() instanceof Waterlogged + && ((Waterlogged)block.getBlockData()).isWaterlogged(); + if (block.getType() == Material.WATER || waterloggedNow) { bot.look(BlockFace.DOWN); bot.setItem(new ItemStack(Material.WATER_BUCKET)); world.playSound(loc, Sound.ITEM_BUCKET_FILL, 1, 1); - block.setType(Material.AIR); + if (waterloggedNow) { + Waterlogged data = (Waterlogged)loc.getBlock().getBlockData(); + data.setWaterlogged(false); + loc.getBlock().setBlockData(data); + } else + block.setType(Material.AIR); } }, 5); } @@ -442,7 +473,7 @@ public class LegacyAgent extends Agent { if (level == null) { resetHand(npc, target, playerNPC); return 1; - } else if (level.isSide()) { + } else if (level.isSide() || level == LegacyLevel.BELOW || level == LegacyLevel.ABOVE) { return 0; } else { return 2; @@ -457,6 +488,71 @@ public class LegacyAgent extends Agent { BlockFace dir = player.getFacing(); LegacyLevel level = null; Block get = null; + + BoundingBox box = player.getBoundingBox(); + double[] xVals = new double[]{ + box.getMinX(), + box.getMaxX() - 0.01 + }; + + double[] zVals = new double[]{ + box.getMinZ(), + box.getMaxZ() - 0.01 + }; + List locStanding = new ArrayList<>(); + for (double x : xVals) { + for (double z : zVals) { + Location loc = new Location(player.getWorld(), Math.floor(x), npc.getLocation().getBlockY(), Math.floor(z)); + if (!locStanding.contains(loc)) + locStanding.add(loc); + } + } + Collections.sort(locStanding, (a, b) -> + Double.compare(BotUtils.getHorizSqDist(a, player.getLocation()), BotUtils.getHorizSqDist(b, player.getLocation()))); + + //Break potential obstructing walls + for (Location loc : locStanding) { + boolean up = false; + get = loc.getBlock(); + if (!LegacyMats.FENCE.contains(get.getType())) { + up = true; + get = loc.add(0, 1, 0).getBlock(); + if (!LegacyMats.FENCE.contains(get.getType())) { + get = null; + } + } + + if (get != null) { + int distanceX = get.getLocation().getBlockX() - player.getLocation().getBlockX(); + int distanceZ = get.getLocation().getBlockZ() - player.getLocation().getBlockZ(); + if (distanceX == 1 && distanceZ == 0) { + if (dir == BlockFace.NORTH || dir == BlockFace.SOUTH) { + npc.faceLocation(get.getLocation()); + level = up ? LegacyLevel.EAST : LegacyLevel.EAST_D; + } + } else if (distanceX == -1 && distanceZ == 0) { + if (dir == BlockFace.NORTH || dir == BlockFace.SOUTH) { + npc.faceLocation(get.getLocation()); + level = up ? LegacyLevel.WEST : LegacyLevel.WEST_D; + } + } else if (distanceX == 0 && distanceZ == 1) { + if (dir == BlockFace.EAST || dir == BlockFace.WEST) { + npc.faceLocation(get.getLocation()); + level = up ? LegacyLevel.SOUTH : LegacyLevel.SOUTH_D; + } + } else if (distanceX == 0 && distanceZ == -1) { + if (dir == BlockFace.EAST || dir == BlockFace.WEST) { + npc.faceLocation(get.getLocation()); + level = up ? LegacyLevel.NORTH : LegacyLevel.NORTH_D; + } + } + + if (level != null) { + preBreak(npc, player, get, level); + return level; + } + } + } switch (dir) { case NORTH: @@ -466,6 +562,35 @@ public class LegacyAgent extends Agent { } else if (checkSideBreak(get.getLocation().add(0, -1, 0).getBlock().getType())) { get = get.getLocation().add(0, -1, 0).getBlock(); level = LegacyLevel.NORTH_D; + } else if (LegacyMats.FENCE.contains(get.getLocation().add(0, -2, 0).getBlock().getType())) { + get = get.getLocation().add(0, -2, 0).getBlock(); + level = LegacyLevel.NORTH_D_2; + } else { + Block standing = npc.getStandingOn().isEmpty() ? null : npc.getStandingOn().get(0); + if(standing == null) + break; + boolean obstructed = standing.getLocation().getBlockY() == player.getLocation().getBlockY() + || (standing.getLocation().getBlockY() + 1 == player.getLocation().getBlockY() + && (LegacyMats.FENCE.contains(standing.getType()) || LegacyMats.GATES.contains(standing.getType()))); + if(obstructed) { + Block belowStanding = standing.getLocation().add(0, -1, 0).getBlock(); + if(!LegacyMats.BREAK.contains(belowStanding.getType()) && !LegacyMats.NONSOLID.contains(belowStanding.getType())) { + //Break standing block + get = standing; + level = LegacyLevel.BELOW; + } else { + //Break above + Block above = npc.getLocation().add(0, 2, 0).getBlock(); + Block aboveSide = get.getLocation().add(0, 1, 0).getBlock(); + if(!LegacyMats.BREAK.contains(above.getType())) { + get = above; + level = LegacyLevel.ABOVE; + } else if(!LegacyMats.BREAK.contains(aboveSide.getType())) { + get = aboveSide; + level = LegacyLevel.NORTH_U; + } + } + } } break; case SOUTH: @@ -475,6 +600,35 @@ public class LegacyAgent extends Agent { } else if (checkSideBreak(get.getLocation().add(0, -1, 0).getBlock().getType())) { get = get.getLocation().add(0, -1, 0).getBlock(); level = LegacyLevel.SOUTH_D; + } else if (LegacyMats.FENCE.contains(get.getLocation().add(0, -2, 0).getBlock().getType())) { + get = get.getLocation().add(0, -2, 0).getBlock(); + level = LegacyLevel.SOUTH_D_2; + } else { + Block standing = npc.getStandingOn().isEmpty() ? null : npc.getStandingOn().get(0); + if(standing == null) + break; + boolean obstructed = standing.getLocation().getBlockY() == player.getLocation().getBlockY() + || (standing.getLocation().getBlockY() + 1 == player.getLocation().getBlockY() + && (LegacyMats.FENCE.contains(standing.getType()) || LegacyMats.GATES.contains(standing.getType()))); + if(obstructed) { + Block belowStanding = standing.getLocation().add(0, -1, 0).getBlock(); + if(!LegacyMats.BREAK.contains(belowStanding.getType()) && !LegacyMats.NONSOLID.contains(belowStanding.getType())) { + //Break standing block + get = standing; + level = LegacyLevel.BELOW; + } else { + //Break above + Block above = npc.getLocation().add(0, 2, 0).getBlock(); + Block aboveSide = get.getLocation().add(0, 1, 0).getBlock(); + if(!LegacyMats.BREAK.contains(above.getType())) { + get = above; + level = LegacyLevel.ABOVE; + } else if(!LegacyMats.BREAK.contains(aboveSide.getType())) { + get = aboveSide; + level = LegacyLevel.SOUTH_U; + } + } + } } break; case EAST: @@ -484,6 +638,35 @@ public class LegacyAgent extends Agent { } else if (checkSideBreak(get.getLocation().add(0, -1, 0).getBlock().getType())) { get = get.getLocation().add(0, -1, 0).getBlock(); level = LegacyLevel.EAST_D; + } else if (LegacyMats.FENCE.contains(get.getLocation().add(0, -2, 0).getBlock().getType())) { + get = get.getLocation().add(0, -2, 0).getBlock(); + level = LegacyLevel.EAST_D_2; + } else { + Block standing = npc.getStandingOn().isEmpty() ? null : npc.getStandingOn().get(0); + if(standing == null) + break; + boolean obstructed = standing.getLocation().getBlockY() == player.getLocation().getBlockY() + || (standing.getLocation().getBlockY() + 1 == player.getLocation().getBlockY() + && (LegacyMats.FENCE.contains(standing.getType()) || LegacyMats.GATES.contains(standing.getType()))); + if(obstructed) { + Block belowStanding = standing.getLocation().add(0, -1, 0).getBlock(); + if(!LegacyMats.BREAK.contains(belowStanding.getType()) && !LegacyMats.NONSOLID.contains(belowStanding.getType())) { + //Break standing block + get = standing; + level = LegacyLevel.BELOW; + } else { + //Break above + Block above = npc.getLocation().add(0, 2, 0).getBlock(); + Block aboveSide = get.getLocation().add(0, 1, 0).getBlock(); + if(!LegacyMats.BREAK.contains(above.getType())) { + get = above; + level = LegacyLevel.ABOVE; + } else if(!LegacyMats.BREAK.contains(aboveSide.getType())) { + get = aboveSide; + level = LegacyLevel.EAST_U; + } + } + } } break; case WEST: @@ -493,27 +676,91 @@ public class LegacyAgent extends Agent { } else if (checkSideBreak(get.getLocation().add(0, -1, 0).getBlock().getType())) { get = get.getLocation().add(0, -1, 0).getBlock(); level = LegacyLevel.WEST_D; + } else if (LegacyMats.FENCE.contains(get.getLocation().add(0, -2, 0).getBlock().getType())) { + get = get.getLocation().add(0, -2, 0).getBlock(); + level = LegacyLevel.WEST_D_2; + } else { + Block standing = npc.getStandingOn().isEmpty() ? null : npc.getStandingOn().get(0); + if(standing == null) + break; + boolean obstructed = standing.getLocation().getBlockY() == player.getLocation().getBlockY() + || (standing.getLocation().getBlockY() + 1 == player.getLocation().getBlockY() + && (LegacyMats.FENCE.contains(standing.getType()) || LegacyMats.GATES.contains(standing.getType()))); + if(obstructed) { + Block belowStanding = standing.getLocation().add(0, -1, 0).getBlock(); + if(!LegacyMats.BREAK.contains(belowStanding.getType()) && !LegacyMats.NONSOLID.contains(belowStanding.getType())) { + //Break standing block + get = standing; + level = LegacyLevel.BELOW; + } else { + //Break above + Block above = npc.getLocation().add(0, 2, 0).getBlock(); + Block aboveSide = get.getLocation().add(0, 1, 0).getBlock(); + if(!LegacyMats.BREAK.contains(above.getType())) { + get = above; + level = LegacyLevel.ABOVE; + } else if(!LegacyMats.BREAK.contains(aboveSide.getType())) { + get = aboveSide; + level = LegacyLevel.WEST_U; + } + } + } } break; default: break; } - if (level == LegacyLevel.EAST_D || level == LegacyLevel.WEST_D || level == LegacyLevel.NORTH_D || level == LegacyLevel.SOUTH_D) { + if (level == LegacyLevel.EAST_D || level == LegacyLevel.WEST_D || level == LegacyLevel.NORTH_D || level == LegacyLevel.SOUTH_D + || level == LegacyLevel.EAST_D_2 || level == LegacyLevel.WEST_D_2 || level == LegacyLevel.NORTH_D_2 || level == LegacyLevel.SOUTH_D_2) { if (LegacyMats.AIR.contains(player.getLocation().add(0, 2, 0).getBlock().getType()) - && LegacyMats.AIR.contains(get.getLocation().add(0, 2, 0).getBlock().getType())) { + && LegacyMats.AIR.contains(get.getLocation().add(0, 2, 0).getBlock().getType()) + && !LegacyMats.FENCE.contains(get.getType()) && !LegacyMats.GATES.contains(get.getType())) { return null; } } + if (level == LegacyLevel.ABOVE || level == LegacyLevel.BELOW) { + Block check; + + switch (dir) { + case NORTH: + check = player.getLocation().add(0, 2, -1).getBlock(); + break; + case SOUTH: + check = player.getLocation().add(0, 2, 1).getBlock(); + break; + case EAST: + check = player.getLocation().add(1, 2, 0).getBlock(); + break; + case WEST: + check = player.getLocation().add(-1, 2, 0).getBlock(); + break; + default: + check = null; + } + if (LegacyMats.AIR.contains(player.getLocation().add(0, 2, 0).getBlock().getType()) + && LegacyMats.AIR.contains(check.getType())) + return null; + } if (level != null) { + if (level == LegacyLevel.BELOW) { + noJump.add(player); + scheduler.runTaskLater(plugin, () -> { + noJump.remove(player); + }, 15); + + npc.look(BlockFace.DOWN); + downMine(npc, player, get); + } else if (level == LegacyLevel.ABOVE) + npc.look(BlockFace.UP); preBreak(npc, player, get, level); } return level; } - private boolean checkUp(Terminator npc, LivingEntity target, LivingEntity playerNPC, Location loc, boolean c) { + private boolean checkUp(Terminator npc, LivingEntity target, LivingEntity playerNPC, Location loc, boolean c, boolean sameXZ) { Location a = playerNPC.getLocation(); Location b = target.getLocation(); @@ -568,24 +815,25 @@ public class LegacyAgent extends Agent { npc.look(BlockFace.DOWN); // maybe put this in lower if statement onGround() - scheduler.runTaskLater(plugin, () -> { - npc.sneak(); - npc.setItem(new ItemStack(Material.COBBLESTONE)); - npc.punch(); - npc.look(BlockFace.DOWN); - - scheduler.runTaskLater(plugin, () -> { - npc.look(BlockFace.DOWN); - }, 1); - - blockCheck.placeBlock(npc, playerNPC, place); - - if (!towerList.containsKey(playerNPC)) { - if (c) { - towerList.put(playerNPC, playerNPC.getLocation()); - } - } - }, 5); + if (m0 != Material.WATER) + scheduler.runTaskLater(plugin, () -> { + npc.sneak(); + npc.setItem(new ItemStack(Material.COBBLESTONE)); + npc.punch(); + npc.look(BlockFace.DOWN); + + scheduler.runTaskLater(plugin, () -> { + npc.look(BlockFace.DOWN); + }, 1); + + blockCheck.placeBlock(npc, playerNPC, place); + + if (!towerList.containsKey(playerNPC)) { + if (c) { + towerList.put(playerNPC, playerNPC.getLocation()); + } + } + }, 3); if (npc.isBotOnGround()) { if (target.getLocation().distance(playerNPC.getLocation()) < 16) { @@ -645,6 +893,16 @@ public class LegacyAgent extends Agent { } return true; + } else if (sameXZ && LegacyMats.BREAK.contains(m1)) { + Block block = npc.getStandingOn().isEmpty() ? null : npc.getStandingOn().get(0); + if (block != null && block.getLocation().getBlockY() == playerNPC.getLocation().getBlockY() + && !LegacyMats.BREAK.contains(block.getType())) { + npc.look(BlockFace.DOWN); + + downMine(npc, playerNPC, block); + preBreak(npc, playerNPC, block, LegacyLevel.BELOW); + return true; + } } } @@ -657,7 +915,9 @@ public class LegacyAgent extends Agent { return false; if (c && npc.getLocation().getBlockY() > loc.getBlockY() + 1) { - Block block = npc.getLocation().add(0, -1, 0).getBlock(); + Block block = npc.getStandingOn().isEmpty() ? null : npc.getStandingOn().get(0); + if (block == null) + return false; npc.look(BlockFace.DOWN); downMine(npc, player, block); @@ -671,7 +931,9 @@ public class LegacyAgent extends Agent { b.setY(0); if (npc.getLocation().getBlockY() > loc.getBlockY() + 10 && a.distance(b) < 10) { - Block block = npc.getLocation().add(0, -1, 0).getBlock(); + Block block = npc.getStandingOn().isEmpty() ? null : npc.getStandingOn().get(0); + if (block == null) + return false; npc.look(BlockFace.DOWN); downMine(npc, player, block); @@ -719,14 +981,31 @@ public class LegacyAgent extends Agent { } } - private boolean checkFence(Terminator bot, Block block, LivingEntity player) { - if (LegacyMats.FENCE.contains(block.getType())) { + private boolean checkFenceAndGates(Terminator bot, Block block, LivingEntity player) { + if (LegacyMats.FENCE.contains(block.getType()) || LegacyMats.GATES.contains(block.getType())) { preBreak(bot, player, block, LegacyLevel.AT_D); return true; } return false; } + + private boolean checkObstacles(Terminator bot, Block block, LivingEntity player) { + if (LegacyMats.OBSTACLES.contains(block.getType()) || isDoorObstacle(block)) { + preBreak(bot, player, block, LegacyLevel.AT_D); + return true; + } + + return false; + } + + private boolean isDoorObstacle(Block block) { + if (block.getType().data == Door.class) + return true; + if (block.getType().data == TrapDoor.class && ((TrapDoor)block.getBlockData()).isOpen()) + return true; + return false; + } private boolean checkAt(Terminator bot, Block block, LivingEntity player) { if (LegacyMats.BREAK.contains(block.getType())) { @@ -751,13 +1030,15 @@ public class LegacyAgent extends Agent { bot.setItem(new ItemStack(item)); - if (level == LegacyLevel.EAST_D || level == LegacyLevel.NORTH_D || level == LegacyLevel.SOUTH_D || level == LegacyLevel.WEST_D) { + if (level.isSideDown() || level.isSideDown2()) { bot.setBotPitch(69); scheduler.runTaskLater(plugin, () -> { btCheck.put(player, true); }, 5); - } else if (level == LegacyLevel.AT_D || level == LegacyLevel.AT) { + } else if (level.isSideUp()) { + bot.setBotPitch(-53); + }else if (level == LegacyLevel.AT_D || level == LegacyLevel.AT) { Location blockLoc = block.getLocation().add(0.5, -1, 0.5); bot.faceLocation(blockLoc); } @@ -777,10 +1058,10 @@ public class LegacyAgent extends Agent { miningAnim.put(player, task); } - blockBreakEffect(player, block, level); + blockBreakEffect(bot, player, block, new LegacyLevel.LevelWrapper(level)); } - private void blockBreakEffect(LivingEntity player, Block block, LegacyLevel level) { + private void blockBreakEffect(Terminator bot, LivingEntity player, Block block, LegacyLevel.LevelWrapper wrapper) { if (LegacyMats.NO_CRACK.contains(block.getType())) return; @@ -792,47 +1073,45 @@ public class LegacyAgent extends Agent { byte i = mining.get(this); Block cur; - switch (level) { - case ABOVE: - cur = player.getLocation().add(0, 2, 0).getBlock(); - break; - case BELOW: - cur = player.getLocation().add(0, -1, 0).getBlock(); - break; - case NORTH: - cur = player.getLocation().add(0, 1, -1).getBlock(); - break; - case SOUTH: - cur = player.getLocation().add(0, 1, 1).getBlock(); - break; - case EAST: - cur = player.getLocation().add(1, 1, 0).getBlock(); - break; - case WEST: - cur = player.getLocation().add(-1, 1, 0).getBlock(); - break; - case NORTH_D: - cur = player.getLocation().add(0, 0, -1).getBlock(); - break; - case SOUTH_D: - cur = player.getLocation().add(0, 0, 1).getBlock(); - break; - case EAST_D: - cur = player.getLocation().add(1, 0, 0).getBlock(); - break; - case WEST_D: - cur = player.getLocation().add(-1, 0, 0).getBlock(); - break; - case AT_D: - cur = player.getLocation().getBlock(); - break; - default: - cur = player.getLocation().add(0, 1, 0).getBlock(); - } + if (wrapper.getLevel() == null) + cur = player.getLocation().add(0, 1, 0).getBlock(); + else if (wrapper.getLevel() == LegacyLevel.BELOW) + cur = bot.getStandingOn().isEmpty() ? null : bot.getStandingOn().get(0); + else + cur = wrapper.getLevel().offset(player.getLocation()).getBlock(); + // Fix boat clutching while breaking block + // As a side effect, the bot is able to break multiple blocks at once while over lava + if ((wrapper.getLevel().isSideAt() || wrapper.getLevel().isSideUp()) + && bot.getLocation().add(0, -2, 0).getBlock().getType() == Material.LAVA + && block.getLocation().clone().add(0, 1, 0).equals(cur.getLocation())) { + cur = block; + wrapper.setLevel(wrapper.getLevel().sideDown()); + + if (wrapper.getLevel().isSideDown() || wrapper.getLevel().isSideDown2()) + bot.setBotPitch(69); + else if (wrapper.getLevel().isSideUp()) + bot.setBotPitch(-53); + else if (wrapper.getLevel().isSide()) + bot.setBotPitch(0); + } + if ((wrapper.getLevel().isSideAt() || wrapper.getLevel().isSideDown()) + && bot.getLocation().add(0, -1, 0).getBlock().getType() == Material.LAVA + && block.getLocation().clone().add(0, -1, 0).equals(cur.getLocation())) { + cur = block; + wrapper.setLevel(wrapper.getLevel().sideUp()); + + if (wrapper.getLevel().isSideDown() || wrapper.getLevel().isSideDown2()) + bot.setBotPitch(69); + else if (wrapper.getLevel().isSideUp()) + bot.setBotPitch(-53); + else if (wrapper.getLevel().isSide()) + bot.setBotPitch(0); + } + // wow this repeated code is so bad lmao - if (player.isDead() || (!block.equals(cur) || block.getType() != cur.getType())) { + if (player.isDead() || cur == null || (!block.equals(cur) || block.getType() != cur.getType())) { this.cancel(); TerminatorPlusAPI.getInternalBridge().sendBlockDestructionPacket(crackList.get(block), block.getX(), block.getY(), block.getZ(), -1); @@ -857,7 +1136,7 @@ public class LegacyAgent extends Agent { block.breakNaturally(); - if (level == LegacyLevel.ABOVE) { + if (wrapper.getLevel() == LegacyLevel.ABOVE) { noJump.add(player); scheduler.runTaskLater(plugin, () -> { @@ -875,10 +1154,13 @@ public class LegacyAgent extends Agent { all.playSound(block.getLocation(), sound, SoundCategory.BLOCKS, (float) 0.3, 1); } - if (block.getType() == Material.BARRIER || block.getType() == Material.BEDROCK || block.getType() == Material.END_PORTAL_FRAME) + if (block.getType() == Material.BARRIER || block.getType() == Material.BEDROCK || block.getType() == Material.END_PORTAL_FRAME + || block.getType() == Material.STRUCTURE_BLOCK || block.getType() == Material.STRUCTURE_BLOCK + || block.getType() == Material.COMMAND_BLOCK || block.getType() == Material.REPEATING_COMMAND_BLOCK + || block.getType() == Material.CHAIN_COMMAND_BLOCK) return; - if (instantBreakBlocks.contains(block.getType())) { // instant break blocks + if (LegacyMats.INSTANT_BREAK.contains(block.getType())) { // instant break blocks block.breakNaturally(); return; } @@ -924,7 +1206,7 @@ public class LegacyAgent extends Agent { Location loc = bot.getLocation(); if (bot.isBotOnFire()) { - if (bot.getBukkitEntity().getWorld().getEnvironment() != World.Environment.NETHER) { + if (bot.getDimension() != World.Environment.NETHER) { placeWaterDown(bot, world, loc); } } @@ -932,7 +1214,7 @@ public class LegacyAgent extends Agent { Material atType = loc.getBlock().getType(); if (atType == Material.FIRE || atType == Material.SOUL_FIRE) { - if (bot.getBukkitEntity().getWorld().getEnvironment() != World.Environment.NETHER) { + if (bot.getDimension() != World.Environment.NETHER) { placeWaterDown(bot, world, loc); world.playSound(loc, Sound.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 1, 1); } else { @@ -944,7 +1226,7 @@ public class LegacyAgent extends Agent { } if (atType == Material.LAVA) { - if (bot.getBukkitEntity().getWorld().getEnvironment() == World.Environment.NETHER) { + if (bot.getDimension() == World.Environment.NETHER) { bot.attemptBlockPlace(loc, Material.COBBLESTONE, false); } else { placeWaterDown(bot, world, loc); @@ -955,7 +1237,7 @@ public class LegacyAgent extends Agent { Material headType = head.getBlock().getType(); if (headType == Material.LAVA) { - if (bot.getBukkitEntity().getWorld().getEnvironment() == World.Environment.NETHER) { + if (bot.getDimension() == World.Environment.NETHER) { bot.attemptBlockPlace(head, Material.COBBLESTONE, false); } else { placeWaterDown(bot, world, head); @@ -963,7 +1245,7 @@ public class LegacyAgent extends Agent { } if (headType == Material.FIRE || headType == Material.SOUL_FIRE) { - if (bot.getBukkitEntity().getWorld().getEnvironment() == World.Environment.NETHER) { + if (bot.getDimension() == World.Environment.NETHER) { bot.look(BlockFace.DOWN); bot.punch(); world.playSound(head, Sound.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 1, 1); @@ -1111,12 +1393,39 @@ public class LegacyAgent extends Agent { bot.attack(target); } + + public void setRegion(BoundingBox region, double regionWeightX, double regionWeightY, double regionWeightZ) { + this.region = region; + this.regionWeightX = regionWeightX; + this.regionWeightY = regionWeightY; + this.regionWeightZ = regionWeightZ; + } + + public BoundingBox getRegion() { + return region; + } + + public double getRegionWeightX() { + return regionWeightX; + } + + public double getRegionWeightY() { + return regionWeightY; + } + + public double getRegionWeightZ() { + return regionWeightZ; + } + + public EnumTargetGoal getTargetType() { + return goal; + } public void setTargetType(EnumTargetGoal goal) { this.goal = goal; } - public LivingEntity locateTarget(Terminator bot, Location loc, EnumTargetGoal... targetGoal) { + private LivingEntity locateTarget(Terminator bot, Location loc, EnumTargetGoal... targetGoal) { LivingEntity result = null; EnumTargetGoal g = goal; @@ -1127,7 +1436,7 @@ public class LegacyAgent extends Agent { case NEAREST_PLAYER: { for (Player player : Bukkit.getOnlinePlayers()) { - if (validateCloserEntity(player, loc, result)) { + if (!botsInPlayerList.contains(player) && validateCloserEntity(player, loc, result)) { result = player; } } @@ -1137,7 +1446,7 @@ public class LegacyAgent extends Agent { case NEAREST_VULNERABLE_PLAYER: { for (Player player : Bukkit.getOnlinePlayers()) { - if (!PlayerUtils.isInvincible(player.getGameMode()) && validateCloserEntity(player, loc, result)) { + if (!botsInPlayerList.contains(player) && !PlayerUtils.isInvincible(player.getGameMode()) && validateCloserEntity(player, loc, result)) { result = player; } } @@ -1154,6 +1463,16 @@ public class LegacyAgent extends Agent { break; } + + case NEAREST_RAIDER: { + for (LivingEntity entity : bot.getBukkitEntity().getWorld().getLivingEntities()) { + if ((entity instanceof Raider || (entity instanceof Vex vex && vex.getSummoner() instanceof Raider)) && validateCloserEntity(entity, loc, result)) { + result = entity; + } + } + + break; + } case NEAREST_MOB: { for (LivingEntity entity : bot.getBukkitEntity().getWorld().getLivingEntities()) { @@ -1211,7 +1530,7 @@ public class LegacyAgent extends Agent { case PLAYER: { //Target a single player. Defaults to NEAREST_VULNERABLE_PLAYER if no player found. if (bot.getTargetPlayer() != null) { Player player = Bukkit.getPlayer(bot.getTargetPlayer()); - if (player != null) { + if (player != null && !botsInPlayerList.contains(player) && validateCloserEntity(player, loc, null)) { return player; } } @@ -1225,6 +1544,36 @@ public class LegacyAgent extends Agent { } private boolean validateCloserEntity(LivingEntity entity, Location loc, LivingEntity result) { - return loc.getWorld() == entity.getWorld() && !entity.isDead() && (result == null || loc.distance(entity.getLocation()) < loc.distance(result.getLocation())); + double regionDistEntity = getWeightedRegionDist(entity.getLocation()); + if (regionDistEntity == Double.MAX_VALUE) + return false; + double regionDistResult = result == null ? 0 : getWeightedRegionDist(result.getLocation()); + return loc.getWorld() == entity.getWorld() && !entity.isDead() + && (result == null || (loc.distanceSquared(entity.getLocation()) + regionDistEntity) < (loc.distanceSquared(result.getLocation())) + regionDistResult); + } + + private double getWeightedRegionDist(Location loc) { + if (region == null) + return 0; + double diffX = Math.max(0, Math.abs(region.getCenterX() - loc.getX()) - region.getWidthX() * 0.5); + double diffY = Math.max(0, Math.abs(region.getCenterY() - loc.getY()) - region.getHeight() * 0.5); + double diffZ = Math.max(0, Math.abs(region.getCenterZ() - loc.getZ()) - region.getWidthZ() * 0.5); + if (regionWeightX == 0 && regionWeightY == 0 && regionWeightZ == 0) + if (diffX > 0 || diffY > 0 || diffZ > 0) + return Double.MAX_VALUE; + return diffX * diffX * regionWeightX + diffY * diffY * regionWeightY + diffZ * diffZ * regionWeightZ; + } + + @Override + public void stopAllTasks() { + super.stopAllTasks(); + + Iterator> itr = crackList.entrySet().iterator(); + while(itr.hasNext()) { + Block block = itr.next().getKey(); + TerminatorPlusAPI.getInternalBridge().sendBlockDestructionPacket(crackList.get(block), block.getX(), block.getY(), block.getZ(), -1); + itr.remove(); + } + mining.clear(); } } diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyBlockCheck.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyBlockCheck.java index a9c2d12..3fffacc 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyBlockCheck.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyBlockCheck.java @@ -1,6 +1,8 @@ package net.nuggetmc.tplus.api.agent.legacyagent; import net.nuggetmc.tplus.api.Terminator; +import net.nuggetmc.tplus.api.utils.BotUtils; + import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -8,9 +10,15 @@ import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; +import org.bukkit.util.BoundingBox; +import com.google.common.base.Optional; + +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Set; public class LegacyBlockCheck { @@ -135,6 +143,90 @@ public class LegacyBlockCheck { all.playSound(loc, Sound.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1, 1); placeFinal(bot, player, block.getLocation()); } + + public boolean tryPreMLG(Terminator bot, Location botLoc) { + if(bot.isBotOnGround() || bot.getVelocity().getY() >= -0.8D || bot.getNoFallTicks() > 7) + return false; + if (tryPreMLG(bot, botLoc, 3)) + return true; + return tryPreMLG(bot, botLoc, 2); + } + + private boolean tryPreMLG(Terminator bot, Location botLoc, int blocksBelow) { + BoundingBox box = bot.getBotBoundingBox(); + double[] xVals = new double[]{ + box.getMinX(), + box.getMaxX() - 0.01 + }; + + double[] zVals = new double[]{ + box.getMinZ(), + box.getMaxZ() - 0.01 + }; + Set below2Set = new HashSet<>(); + + for (double x : xVals) { + for (double z : zVals) { + Location below = botLoc.clone(); + below.setX(x); + below.setZ(z); + below.setY(bot.getLocation().getBlockY()); + for (int i = 0; i < blocksBelow - 1; i++) { + below.setY(below.getY() - 1); + + // Blocks before must all be pass-through + Material type = below.getBlock().getType(); + if (type.isSolid() || LegacyMats.canStandOn(type)) + return false; + below = below.clone(); + } + below.setY(bot.getLocation().getBlockY() - blocksBelow); + below2Set.add(below.getBlock().getLocation()); + } + } + + // Second block below must have at least one unplaceable block (that is landable) + boolean nether = bot.getDimension() == World.Environment.NETHER; + Iterator itr = below2Set.iterator(); + while (itr.hasNext()) { + Block next = itr.next().getBlock(); + boolean placeable = nether ? LegacyMats.canPlaceTwistingVines(next) + : LegacyMats.canPlaceWater(next, Optional.absent()); + if (placeable || (!next.getType().isSolid() && !LegacyMats.canStandOn(next.getType()))) + itr.remove(); + } + + // Clutch + if (!below2Set.isEmpty()) { + List below2List = new ArrayList<>(below2Set); + below2List.sort((a, b) -> { + Block aBlock = a.clone().add(0, 1, 0).getBlock(); + Block bBlock = b.clone().add(0, 1, 0).getBlock(); + if (aBlock.getType().isAir() && !bBlock.getType().isAir()) + return -1; + if (!bBlock.getType().isAir() && aBlock.getType().isAir()) + return 1; + return Double.compare(BotUtils.getHorizSqDist(a, botLoc), BotUtils.getHorizSqDist(b, botLoc)); + }); + + Location faceLoc = below2List.get(0); + Location loc = faceLoc.clone().add(0, 1, 0); + bot.faceLocation(faceLoc); + bot.look(BlockFace.DOWN); + + Bukkit.getScheduler().runTaskLater(plugin, () -> { + bot.faceLocation(faceLoc); + }, 1); + + bot.punch(); + for (Player all : Bukkit.getOnlinePlayers()) + all.playSound(loc, Sound.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1, 1); + bot.setItem(new ItemStack(Material.COBBLESTONE)); + loc.getBlock().setType(Material.COBBLESTONE); + } + + return false; + } public void clutch(Terminator bot, LivingEntity target) { Location botLoc = bot.getLocation(); diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyLevel.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyLevel.java index b4f622e..9f6b44c 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyLevel.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyLevel.java @@ -4,32 +4,162 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; -public enum LegacyLevel { - ABOVE, - BELOW, - AT, - AT_D, - NORTH, - SOUTH, - EAST, - WEST, - NORTH_D, - SOUTH_D, - EAST_D, - WEST_D; +import org.bukkit.Location; - private static final Set SIDE = new HashSet<>(Arrays.asList( +public enum LegacyLevel { + ABOVE(0, 2, 0), + BELOW(0, -1, 0), + AT(0, 1, 0), + AT_D(0, 0, 0), + NORTH_U(0, 2, -1), + SOUTH_U(0, 2, 1), + EAST_U(1, 2, 0), + WEST_U(-1, 2, 0), + NORTH(0, 1, -1), + SOUTH(0, 1, 1), + EAST(1, 1, 0), + WEST(-1, 1, 0), + NORTH_D(0, 0, -1), + SOUTH_D(0, 0, 1), + EAST_D(1, 0, 0), + WEST_D(-1, 0, 0), + NORTHWEST_D(-1, 0, -1), + SOUTHWEST_D(-1, 0, 1), + NORTHEAST_D(1, 0, -1), + SOUTHEAST_D(1, 0, 1), + NORTH_D_2(0, -1, -1), + SOUTH_D_2(0, -1, 1), + EAST_D_2(1, -1, 0), + WEST_D_2(-1, -1, 0); + + private final int offsetX; + private final int offsetY; + private final int offsetZ; + + private LegacyLevel(int offsetX, int offsetY, int offsetZ) { + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + } + + private static final Set NON_SIDE = new HashSet<>(Arrays.asList( + ABOVE, + BELOW, + AT, + AT_D + )); + private static final Set SIDE_AT = new HashSet<>(Arrays.asList( NORTH, SOUTH, EAST, - WEST, + WEST + )); + private static final Set SIDE_UP = new HashSet<>(Arrays.asList( + NORTH_U, + SOUTH_U, + EAST_U, + WEST_U + )); + private static final Set SIDE_DOWN = new HashSet<>(Arrays.asList( NORTH_D, SOUTH_D, EAST_D, WEST_D )); + private static final Set SIDE_DOWN_2 = new HashSet<>(Arrays.asList( + NORTH_D_2, + SOUTH_D_2, + EAST_D_2, + WEST_D_2 + )); public boolean isSide() { - return SIDE.contains(this); + return !NON_SIDE.contains(this); + } + + public boolean isSideAt() { + return SIDE_AT.contains(this); + } + + public boolean isSideUp() { + return SIDE_UP.contains(this); + } + + public boolean isSideDown() { + return SIDE_DOWN.contains(this); + } + + public boolean isSideDown2() { + return SIDE_DOWN_2.contains(this); + } + + public LegacyLevel sideUp() { + switch(this) { + case NORTH: return NORTH_U; + case SOUTH: return SOUTH_U; + case EAST: return EAST_U; + case WEST: return WEST_U; + case NORTH_D: return NORTH; + case SOUTH_D: return SOUTH; + case EAST_D: return EAST; + case WEST_D: return WEST; + case NORTH_D_2: return NORTH_D; + case SOUTH_D_2: return SOUTH_D; + case EAST_D_2: return EAST_D; + case WEST_D_2: return WEST_D; + default: + return null; + } + } + + public LegacyLevel sideDown() { + switch(this) { + case NORTH_U: return NORTH; + case SOUTH_U: return SOUTH; + case EAST_U: return EAST; + case WEST_U: return WEST; + case NORTH: return NORTH_D; + case SOUTH: return SOUTH_D; + case EAST: return EAST_D; + case WEST: return WEST_D; + case NORTH_D: return NORTH_D_2; + case SOUTH_D: return SOUTH_D_2; + case EAST_D: return EAST_D_2; + case WEST_D: return WEST_D_2; + default: + return null; + } + } + + public static LegacyLevel getOffset(Location start, Location end) { + int diffX = end.getBlockX() - start.getBlockX(); + int diffY = end.getBlockY() - start.getBlockY(); + int diffZ = end.getBlockZ() - start.getBlockZ(); + for (LegacyLevel level : LegacyLevel.values()) { + if (level.offsetX == diffX && level.offsetY == diffY && level.offsetZ == diffZ) { + return level; + } + } + return null; + } + + public Location offset(Location loc) { + return loc.add(offsetX, offsetY, offsetZ); + } + + public static class LevelWrapper { + private LegacyLevel level; + + public LevelWrapper(LegacyLevel level) { + this.level = level; + } + + public LegacyLevel getLevel() { + return level; + } + + public void setLevel(LegacyLevel level) { + this.level = level; + } } } diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyMats.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyMats.java index 53b2a5e..fe37af8 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyMats.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/agent/legacyagent/LegacyMats.java @@ -1,16 +1,29 @@ package net.nuggetmc.tplus.api.agent.legacyagent; +import org.bukkit.Axis; import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.block.data.Bisected.Half; +import org.bukkit.block.data.type.*; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; + +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; public class LegacyMats { public static final Set AIR = new HashSet<>(Arrays.asList( Material.WATER, - Material.OAK_TRAPDOOR, Material.FIRE, Material.LAVA, Material.SNOW, @@ -36,33 +49,49 @@ public class LegacyMats { Material.FIRE, Material.LAVA, Material.CAVE_AIR, + Material.VOID_AIR, + Material.AIR, Material.SOUL_FIRE )); - public static final Set SHOVEL = new HashSet<>(Arrays.asList( + public static final Set SHOVEL = new HashSet<>(concatTypes(Lists.newArrayList( + Material.CLAY, Material.DIRT, + Material.GRASS_BLOCK, + Material.COARSE_DIRT, + Material.PODZOL, + Material.MYCELIUM, Material.GRAVEL, + Material.MUD, + Material.MUDDY_MANGROVE_ROOTS, Material.SAND, - Material.SNOW - )); + Material.RED_SAND, + Material.SOUL_SAND, + Material.SOUL_SOIL, + Material.SNOW, + Material.SNOW_BLOCK + ), Arrays.asList(), Arrays.asList(), m -> m.name().endsWith("_CONCRETE_POWDER"))); public static final Set AXE = new HashSet<>(Arrays.asList( - Material.OAK_PLANKS, Material.OAK_DOOR, Material.OAK_FENCE, Material.OAK_FENCE_GATE, Material.OAK_LOG, Material.OAK_PLANKS, + Material.OAK_PLANKS, Material.OAK_DOOR, Material.OAK_FENCE, Material.OAK_FENCE_GATE, Material.OAK_LOG, Material.OAK_SIGN, Material.OAK_SLAB, Material.OAK_STAIRS, Material.OAK_TRAPDOOR, Material.OAK_WALL_SIGN, Material.OAK_WOOD, - Material.DARK_OAK_PLANKS, Material.DARK_OAK_DOOR, Material.DARK_OAK_FENCE, Material.DARK_OAK_FENCE_GATE, Material.DARK_OAK_LOG, Material.DARK_OAK_PLANKS, + Material.DARK_OAK_PLANKS, Material.DARK_OAK_DOOR, Material.DARK_OAK_FENCE, Material.DARK_OAK_FENCE_GATE, Material.DARK_OAK_LOG, Material.DARK_OAK_SIGN, Material.DARK_OAK_SLAB, Material.DARK_OAK_STAIRS, Material.DARK_OAK_TRAPDOOR, Material.DARK_OAK_WALL_SIGN, Material.DARK_OAK_WOOD, - Material.ACACIA_PLANKS, Material.ACACIA_DOOR, Material.ACACIA_FENCE, Material.ACACIA_FENCE_GATE, Material.ACACIA_LOG, Material.ACACIA_PLANKS, + Material.ACACIA_PLANKS, Material.ACACIA_DOOR, Material.ACACIA_FENCE, Material.ACACIA_FENCE_GATE, Material.ACACIA_LOG, Material.ACACIA_SIGN, Material.ACACIA_SLAB, Material.ACACIA_STAIRS, Material.ACACIA_TRAPDOOR, Material.ACACIA_WALL_SIGN, Material.ACACIA_WOOD, - Material.BIRCH_PLANKS, Material.BIRCH_DOOR, Material.BIRCH_FENCE, Material.BIRCH_FENCE_GATE, Material.BIRCH_LOG, Material.BIRCH_PLANKS, + Material.BIRCH_PLANKS, Material.BIRCH_DOOR, Material.BIRCH_FENCE, Material.BIRCH_FENCE_GATE, Material.BIRCH_LOG, Material.BIRCH_SIGN, Material.BIRCH_SLAB, Material.BIRCH_STAIRS, Material.BIRCH_TRAPDOOR, Material.BIRCH_WALL_SIGN, Material.BIRCH_WOOD, - Material.JUNGLE_PLANKS, Material.JUNGLE_DOOR, Material.JUNGLE_FENCE, Material.JUNGLE_FENCE_GATE, Material.JUNGLE_LOG, Material.JUNGLE_PLANKS, + Material.JUNGLE_PLANKS, Material.JUNGLE_DOOR, Material.JUNGLE_FENCE, Material.JUNGLE_FENCE_GATE, Material.JUNGLE_LOG, Material.JUNGLE_SIGN, Material.JUNGLE_SLAB, Material.JUNGLE_STAIRS, Material.JUNGLE_TRAPDOOR, Material.JUNGLE_WALL_SIGN, Material.JUNGLE_WOOD, - Material.SPRUCE_PLANKS, Material.SPRUCE_DOOR, Material.SPRUCE_FENCE, Material.SPRUCE_FENCE_GATE, Material.SPRUCE_LOG, Material.SPRUCE_PLANKS, + Material.SPRUCE_PLANKS, Material.SPRUCE_DOOR, Material.SPRUCE_FENCE, Material.SPRUCE_FENCE_GATE, Material.SPRUCE_LOG, Material.SPRUCE_SIGN, Material.SPRUCE_SLAB, Material.SPRUCE_STAIRS, Material.SPRUCE_TRAPDOOR, Material.SPRUCE_WALL_SIGN, Material.SPRUCE_WOOD, - Material.CRIMSON_PLANKS, Material.CRIMSON_DOOR, Material.CRIMSON_FENCE, Material.CRIMSON_FENCE_GATE, Material.CRIMSON_PLANKS, + Material.MANGROVE_PLANKS, Material.MANGROVE_DOOR, Material.MANGROVE_FENCE, Material.MANGROVE_FENCE_GATE, Material.MANGROVE_LOG, + Material.MANGROVE_SIGN, Material.MANGROVE_SLAB, Material.MANGROVE_STAIRS, Material.MANGROVE_TRAPDOOR, Material.MANGROVE_WALL_SIGN, Material.MANGROVE_WOOD, + Material.CRIMSON_PLANKS, Material.CRIMSON_DOOR, Material.CRIMSON_FENCE, Material.CRIMSON_FENCE_GATE, Material.CRIMSON_STEM, Material.CRIMSON_SIGN, Material.CRIMSON_SLAB, Material.CRIMSON_STAIRS, Material.CRIMSON_TRAPDOOR, Material.CRIMSON_WALL_SIGN, - Material.WARPED_PLANKS, Material.WARPED_DOOR, Material.WARPED_FENCE, Material.WARPED_FENCE_GATE, Material.WARPED_PLANKS, - Material.WARPED_SIGN, Material.WARPED_SLAB, Material.WARPED_STAIRS, Material.WARPED_TRAPDOOR, Material.WARPED_WALL_SIGN + Material.WARPED_PLANKS, Material.WARPED_DOOR, Material.WARPED_FENCE, Material.WARPED_FENCE_GATE, Material.WARPED_STEM, + Material.WARPED_SIGN, Material.WARPED_SLAB, Material.WARPED_STAIRS, Material.WARPED_TRAPDOOR, Material.WARPED_WALL_SIGN, + Material.CHEST, Material.TRAPPED_CHEST )); public static final Set BREAK = new HashSet<>(Arrays.asList( @@ -70,14 +99,13 @@ public class LegacyMats { Material.WATER, Material.LAVA, Material.TALL_GRASS, - Material.SNOW, - Material.DIRT_PATH, Material.CAVE_AIR, Material.VINE, Material.FERN, Material.LARGE_FERN, Material.SUGAR_CANE, Material.TWISTING_VINES, + Material.TWISTING_VINES_PLANT, Material.WEEPING_VINES, Material.SEAGRASS, Material.TALL_SEAGRASS, @@ -135,34 +163,356 @@ public class LegacyMats { Material.WATER )); - public static final Set FENCE = new HashSet<>(Arrays.asList( - Material.OAK_FENCE, - Material.ACACIA_FENCE, - Material.BIRCH_FENCE, - Material.CRIMSON_FENCE, - Material.DARK_OAK_FENCE, - Material.JUNGLE_FENCE, - Material.NETHER_BRICK_FENCE, - Material.SPRUCE_FENCE, - Material.WARPED_FENCE, - Material.COBBLESTONE_WALL, - Material.ANDESITE_WALL, - Material.BLACKSTONE_WALL, - Material.BRICK_WALL, - Material.GRANITE_WALL, - Material.DIORITE_WALL, - Material.SANDSTONE_WALL, - Material.RED_SANDSTONE_WALL, - Material.RED_NETHER_BRICK_WALL, - Material.IRON_BARS, - Material.COBWEB - )); + public static final Set FENCE = new HashSet<>(concatTypes(new ArrayList<>(), + Arrays.asList(Material.GLASS_PANE, Material.IRON_BARS), Arrays.asList(Fence.class, Wall.class))); + + public static final Set GATES = new HashSet<>(concatTypes(Gate.class)); + + public static final Set OBSTACLES = new HashSet<>(concatTypes(Lists.newArrayList( + Material.IRON_BARS, + Material.CHAIN, + Material.END_ROD, + Material.LIGHTNING_ROD, + Material.COBWEB, + Material.SWEET_BERRY_BUSH, + Material.FLOWER_POT, + Material.GLASS_PANE + ), Arrays.asList(), Arrays.asList(GlassPane.class), m -> m.name().startsWith("POTTED_"))); - public static final Set LEAVES = new HashSet<>(Arrays.asList( - Material.BIRCH_LEAVES, - Material.DARK_OAK_LEAVES, - Material.JUNGLE_LEAVES, - Material.OAK_LEAVES, - Material.SPRUCE_LEAVES - )); + //Notice: We exclude blocks that cannot exist without a solid block below (such as rails or crops) + public static final Set NONSOLID = new HashSet<>(concatTypes(Lists.newArrayList( + Material.COBWEB, + Material.END_GATEWAY, + Material.END_PORTAL, + Material.NETHER_PORTAL, + Material.CAVE_VINES_PLANT, + Material.GLOW_LICHEN, + Material.HANGING_ROOTS, + Material.POWDER_SNOW, + Material.SCULK_VEIN, + Material.STRING, + Material.TRIPWIRE_HOOK, + Material.LADDER, + Material.VINE, + Material.SOUL_WALL_TORCH, + Material.REDSTONE_WALL_TORCH, + Material.WALL_TORCH, + Material.WEEPING_VINES_PLANT, + Material.WEEPING_VINES, + Material.CAVE_VINES_PLANT, + Material.CAVE_VINES + ), Arrays.asList(), Arrays.asList(Switch.class, CoralWallFan.class, WallSign.class), m -> m.name().endsWith("_WALL_BANNER"))); + + public static final Set LEAVES = new HashSet<>(concatTypes(Leaves.class)); + + public static final Set INSTANT_BREAK = new HashSet<>(concatTypes(Lists.newArrayList( + Material.TALL_GRASS, + Material.GRASS, + Material.FERN, + Material.LARGE_FERN, + Material.KELP_PLANT, + Material.DEAD_BUSH, + Material.WHEAT_SEEDS, + Material.POTATOES, + Material.CARROTS, + Material.BEETROOT_SEEDS, + Material.PUMPKIN_STEM, + Material.MELON_STEM, + Material.SUGAR_CANE, + Material.SWEET_BERRY_BUSH, + Material.LILY_PAD, + Material.DANDELION, + Material.POPPY, + Material.BLUE_ORCHID, + Material.ALLIUM, + Material.AZURE_BLUET, + Material.RED_TULIP, + Material.ORANGE_TULIP, + Material.WHITE_TULIP, + Material.PINK_TULIP, + Material.OXEYE_DAISY, + Material.CORNFLOWER, + Material.LILY_OF_THE_VALLEY, + Material.WITHER_ROSE, + Material.SUNFLOWER, + Material.LILAC, + Material.ROSE_BUSH, + Material.PEONY, + Material.NETHER_WART, + Material.FLOWER_POT, + Material.AZALEA, + Material.FLOWERING_AZALEA, + Material.REPEATER, + Material.COMPARATOR, + Material.REDSTONE_WIRE, + Material.REDSTONE_TORCH, + Material.REDSTONE_WALL_TORCH, + Material.TORCH, + Material.WALL_TORCH, + Material.SOUL_TORCH, + Material.SOUL_WALL_TORCH, + Material.SCAFFOLDING, + Material.SLIME_BLOCK, + Material.HONEY_BLOCK, + Material.TNT, + Material.TRIPWIRE, + Material.TRIPWIRE_HOOK, + Material.SPORE_BLOSSOM, + Material.RED_MUSHROOM, + Material.BROWN_MUSHROOM, + Material.CRIMSON_FUNGUS, + Material.WARPED_FUNGUS, + Material.CRIMSON_ROOTS, + Material.WARPED_ROOTS, + Material.HANGING_ROOTS, + Material.WEEPING_VINES, + Material.WEEPING_VINES_PLANT, + Material.TWISTING_VINES, + Material.TWISTING_VINES_PLANT, + Material.CAVE_VINES, + Material.CAVE_VINES_PLANT, + Material.SEA_PICKLE + ), Arrays.asList(), Arrays.asList(Sapling.class, CoralWallFan.class), m -> m.name().endsWith("_CORAL_FAN") || m.name().endsWith("_CORAL") + || m.name().startsWith("POTTED_"))); + + private static List concatTypes(Class... types) { + return concatTypes(new ArrayList<>(), Arrays.asList(types)); + } + + private static List concatTypes(List materials, List> types) { + return concatTypes(materials, Arrays.asList(), types); + } + + private static List concatTypes(List materials, List exclusions, List> types) { + return concatTypes(materials, exclusions, types, m -> false); + } + + private static List concatTypes(List materials, List exclusions, List> types, Predicate otherFilter) { + materials.addAll(Stream.of(Material.values()).filter(m -> (types.contains(m.data) || otherFilter.test(m)) + && !exclusions.contains(m) && !m.isLegacy()).toList()); + return materials; + } + + /** + * Checks for non-solid blocks that can hold an entity up. + */ + public static boolean canStandOn(Material mat) { + if(mat == Material.END_ROD || mat == Material.FLOWER_POT || mat == Material.REPEATER || mat == Material.COMPARATOR + || mat == Material.SNOW || mat == Material.LADDER || mat == Material.VINE || mat == Material.SCAFFOLDING + || mat == Material.AZALEA || mat == Material.FLOWERING_AZALEA || mat == Material.BIG_DRIPLEAF + || mat == Material.CHORUS_FLOWER || mat == Material.CHORUS_PLANT || mat == Material.COCOA + || mat == Material.LILY_PAD || mat == Material.SEA_PICKLE) + return true; + + if(mat.name().endsWith("_CARPET")) + return true; + + if(mat.name().startsWith("POTTED_")) + return true; + + if((mat.name().endsWith("_HEAD") || mat.name().endsWith("_SKULL")) && !mat.name().equals("PISTON_HEAD")) + return true; + + if(mat.data == Candle.class) + return true; + return false; + } + + public static boolean canPlaceWater(Block block, Optional entityYPos) { + if (block.getType().isSolid()) { + if (block.getType() == Material.CHAIN && ((Chain)block.getBlockData()).getAxis() == Axis.Y + && !((Chain)block.getBlockData()).isWaterlogged()) + return false; + if ((block.getType().data == Leaves.class || block.getType() == Material.MANGROVE_ROOTS + || block.getType() == Material.IRON_BARS || block.getType().name().endsWith("GLASS_PANE")) + && !((Waterlogged)block.getBlockData()).isWaterlogged()) + return false; + if (block.getType().data == Slab.class && ((Slab)block.getBlockData()).getType() == Slab.Type.TOP + && !((Slab)block.getBlockData()).isWaterlogged()) + return false; + if (block.getType().data == Stairs.class && ((Stairs)block.getBlockData()).getHalf() == Bisected.Half.TOP + && !((Stairs)block.getBlockData()).isWaterlogged()) + return false; + if (block.getType().data == Stairs.class && ((Stairs)block.getBlockData()).getHalf() == Bisected.Half.BOTTOM + && !((Stairs)block.getBlockData()).isWaterlogged() + && (!entityYPos.isPresent() || (int)entityYPos.get().doubleValue() != block.getLocation().getBlockY())) + return false; + if ((block.getType().data == Fence.class || block.getType().data == Wall.class) + && !((Waterlogged)block.getBlockData()).isWaterlogged()) + return false; + if (block.getType() == Material.LIGHTNING_ROD && !((LightningRod)block.getBlockData()).isWaterlogged() + && (((LightningRod)block.getBlockData()).getFacing() == BlockFace.UP || ((LightningRod)block.getBlockData()).getFacing() == BlockFace.DOWN)) + return false; + if (block.getType().data == TrapDoor.class && (((TrapDoor)block.getBlockData()).getHalf() == Half.TOP + || (((TrapDoor)block.getBlockData()).getHalf() == Half.BOTTOM && ((TrapDoor)block.getBlockData()).isOpen())) + && !((TrapDoor)block.getBlockData()).isWaterlogged()) + return false; + return true; + } else { + if (block.getType().name().endsWith("_CARPET")) + return true; + if (block.getType().data == Candle.class) + return true; + if (block.getType().name().startsWith("POTTED_")) + return true; + if ((block.getType().name().endsWith("_HEAD") || block.getType().name().endsWith("_SKULL")) + && !block.getType().name().equals("PISTON_HEAD")) + return true; + switch (block.getType()) { + case SNOW: + case AZALEA: + case FLOWERING_AZALEA: + case CHORUS_FLOWER: + case CHORUS_PLANT: + case COCOA: + case LILY_PAD: + case SEA_PICKLE: + case END_ROD: + case FLOWER_POT: + case SCAFFOLDING: + case COMPARATOR: + case REPEATER: + return true; + default: + break; + } + } + return false; + } + + public static boolean canPlaceTwistingVines(Block block) { + if (block.getType().isSolid()) { + if (block.getType().data == Leaves.class) + return false; + if (block.getType().name().endsWith("_CORAL_FAN") || block.getType().name().endsWith("_CORAL") + || block.getType().name().endsWith("_CORAL_WALL_FAN")) + return false; + if (block.getType().name().endsWith("GLASS_PANE")) + return false; + if (block.getType().data == Slab.class && ((Slab)block.getBlockData()).getType() == Slab.Type.BOTTOM) + return false; + if (block.getType().data == Stairs.class && ((Stairs)block.getBlockData()).getHalf() == Bisected.Half.BOTTOM) + return false; + if (block.getType().data == Fence.class || block.getType().data == Wall.class) + return false; + if (block.getType().name().endsWith("_BANNER")) + return false; + if (block.getType().name().endsWith("_WALL_BANNER")) + return false; + if (block.getType().data == Bed.class) + return false; + if (block.getType().name().endsWith("CANDLE_CAKE")) + return false; + if (block.getType().data == Door.class) + return false; + if (block.getType().data == Gate.class) + return false; + if (block.getType() == Material.PISTON_HEAD && ((PistonHead)block.getBlockData()).getFacing() != BlockFace.UP) + return false; + if (block.getType().data == Piston.class && ((Piston)block.getBlockData()).getFacing() != BlockFace.DOWN + && ((Piston)block.getBlockData()).isExtended()) + return false; + if (block.getType().data == TrapDoor.class && (((TrapDoor)block.getBlockData()).getHalf() == Half.BOTTOM + || ((TrapDoor)block.getBlockData()).isOpen())) + return false; + switch (block.getType()) { + case POINTED_DRIPSTONE: + case SMALL_AMETHYST_BUD: + case MEDIUM_AMETHYST_BUD: + case LARGE_AMETHYST_BUD: + case AMETHYST_CLUSTER: + case BAMBOO: + case CACTUS: + case DRAGON_EGG: + case TURTLE_EGG: + case CHAIN: + case IRON_BARS: + case LANTERN: + case SOUL_LANTERN: + case ANVIL: + case BREWING_STAND: + case CHEST: + case ENDER_CHEST: + case TRAPPED_CHEST: + case ENCHANTING_TABLE: + case GRINDSTONE: + case LECTERN: + case STONECUTTER: + case BELL: + case CAKE: + case CAMPFIRE: + case SOUL_CAMPFIRE: + case CAULDRON: + case COMPOSTER: + case CONDUIT: + case END_PORTAL_FRAME: + case FARMLAND: + case DAYLIGHT_DETECTOR: + case HONEY_BLOCK: + case HOPPER: + case LIGHTNING_ROD: + case SCULK_SENSOR: + case SCULK_SHRIEKER: + return false; + default: + } + return true; + } else { + switch (block.getType()) { + case CHORUS_FLOWER: + case SCAFFOLDING: + case AZALEA: + case FLOWERING_AZALEA: + return true; + case SNOW: + return ((Snow)block.getBlockData()).getLayers() == 1 || ((Snow)block.getBlockData()).getLayers() == 8; + default: + } + } + return false; + } + + public static boolean shouldReplace(Block block, double entityYPos, boolean nether) { + if ((int)entityYPos != block.getLocation().getBlockY()) + return false; + if (nether) { + return false; + } else { + if (block.getType().name().endsWith("_CORAL_FAN") || block.getType().name().endsWith("_CORAL") + || block.getType().name().endsWith("_CORAL_WALL_FAN")) + return true; + if (block.getType().data == Slab.class && ((Slab)block.getBlockData()).getType() == Slab.Type.BOTTOM) + return true; + if (block.getType().data == Stairs.class && !((Stairs)block.getBlockData()).isWaterlogged()) + return true; + if (block.getType().data == Chain.class && !((Chain)block.getBlockData()).isWaterlogged()) + return true; + if (block.getType().data == Candle.class) + return true; + if (block.getType().data == TrapDoor.class && !((TrapDoor)block.getBlockData()).isWaterlogged()) + return true; + switch (block.getType()) { + case POINTED_DRIPSTONE: + case SMALL_AMETHYST_BUD: + case MEDIUM_AMETHYST_BUD: + case LARGE_AMETHYST_BUD: + case AMETHYST_CLUSTER: + case SEA_PICKLE: + case LANTERN: + case SOUL_LANTERN: + case CHEST: + case ENDER_CHEST: + case TRAPPED_CHEST: + case CAMPFIRE: + case SOUL_CAMPFIRE: + case CONDUIT: + case LIGHTNING_ROD: + case SCULK_SENSOR: + case SCULK_SHRIEKER: + return true; + default: + } + return false; + } + } } diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/event/BotFallDamageEvent.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/event/BotFallDamageEvent.java index d46f7d3..a294fdf 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/event/BotFallDamageEvent.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/event/BotFallDamageEvent.java @@ -1,20 +1,30 @@ package net.nuggetmc.tplus.api.event; +import java.util.List; + +import org.bukkit.block.Block; + import net.nuggetmc.tplus.api.Terminator; public class BotFallDamageEvent { private final Terminator bot; + private final List standingOn; private boolean cancelled; - public BotFallDamageEvent(Terminator bot) { + public BotFallDamageEvent(Terminator bot, List standingOn) { this.bot = bot; + this.standingOn = standingOn; } public Terminator getBot() { return bot; } + + public List getStandingOn() { + return standingOn; + } public boolean isCancelled() { return cancelled; diff --git a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/utils/BotUtils.java b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/utils/BotUtils.java index 6349309..85cfce6 100644 --- a/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/utils/BotUtils.java +++ b/TerminatorPlus-API/src/main/java/net/nuggetmc/tplus/api/utils/BotUtils.java @@ -2,9 +2,8 @@ package net.nuggetmc.tplus.api.utils; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.block.Block; import org.bukkit.util.BoundingBox; -import org.bukkit.util.Vector; +import org.bukkit.util.NumberConversions; import java.util.Arrays; import java.util.HashSet; @@ -17,6 +16,12 @@ public class BotUtils { Material.WATER, Material.LAVA, Material.TWISTING_VINES, + Material.TWISTING_VINES_PLANT, + Material.WEEPING_VINES, + Material.WEEPING_VINES_PLANT, + Material.SWEET_BERRY_BUSH, + Material.POWDER_SNOW, + Material.COBWEB, Material.VINE )); @@ -29,24 +34,12 @@ public class BotUtils { return randomSteveUUID(); } - - public static boolean solidAt(Location loc) { // not perfect, still cuts corners of fences - Block block = loc.getBlock(); - BoundingBox box = block.getBoundingBox(); - Vector position = loc.toVector(); - - double x = position.getX(); - double y = position.getY(); - double z = position.getZ(); - - double minX = box.getMinX(); - double minY = box.getMinY(); - double minZ = box.getMinZ(); - - double maxX = box.getMaxX(); - double maxY = box.getMaxY(); - double maxZ = box.getMaxZ(); - - return x > minX && x < maxX && y > minY && y < maxY && z > minZ && z < maxZ; + + public static boolean overlaps(BoundingBox playerBox, BoundingBox blockBox) { + return playerBox.overlaps(blockBox); + } + + public static double getHorizSqDist(Location blockLoc, Location pLoc) { + return NumberConversions.square(blockLoc.getX() + 0.5 - pLoc.getX()) + NumberConversions.square(blockLoc.getZ() + 0.5 - pLoc.getZ()); } } diff --git a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/bot/Bot.java b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/bot/Bot.java index 2dcc356..b2f2751 100644 --- a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/bot/Bot.java +++ b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/bot/Bot.java @@ -2,8 +2,6 @@ package net.nuggetmc.tplus.bot; import com.mojang.authlib.GameProfile; import com.mojang.datafixers.util.Pair; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; import net.minecraft.network.Connection; import net.minecraft.network.PacketSendListener; import net.minecraft.network.protocol.Packet; @@ -27,6 +25,7 @@ import net.minecraft.world.phys.Vec3; import net.nuggetmc.tplus.TerminatorPlus; import net.nuggetmc.tplus.api.Terminator; import net.nuggetmc.tplus.api.agent.Agent; +import net.nuggetmc.tplus.api.agent.legacyagent.LegacyMats; import net.nuggetmc.tplus.api.agent.legacyagent.ai.NeuralNetwork; import net.nuggetmc.tplus.api.event.BotDamageByPlayerEvent; import net.nuggetmc.tplus.api.event.BotFallDamageEvent; @@ -35,6 +34,7 @@ import net.nuggetmc.tplus.api.utils.*; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Waterlogged; import org.bukkit.craftbukkit.v1_19_R1.CraftEquipmentSlot; import org.bukkit.craftbukkit.v1_19_R1.CraftServer; import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; @@ -45,11 +45,13 @@ import org.bukkit.entity.Damageable; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.UUID; @@ -69,13 +71,14 @@ public class Bot extends ServerPlayer implements Terminator { private boolean removeOnDeath; private int aliveTicks; private int kills; - private byte fireTicks; // Fire animation isn't played? Bot still takes damage. private byte groundTicks; private byte jumpTicks; private byte noFallTicks; - private boolean ignoredByMobs = true; + private List standingOn = new ArrayList<>(); private UUID targetPlayer = null; - private Bot(MinecraftServer minecraftServer, ServerLevel worldServer, GameProfile profile) { + private boolean inPlayerList; + + private Bot(MinecraftServer minecraftServer, ServerLevel worldServer, GameProfile profile, boolean addToPlayerList) { super(minecraftServer, worldServer, profile, null); this.plugin = TerminatorPlus.getInstance(); @@ -85,9 +88,12 @@ public class Bot extends ServerPlayer implements Terminator { this.velocity = new Vector(0, 0, 0); this.oldVelocity = velocity.clone(); this.noFallTicks = 60; - this.fireTicks = 0; this.removeOnDeath = true; this.offset = MathUtils.circleOffset(3); + if (addToPlayerList) { + minecraftServer.getPlayerList().getPlayers().add(this); + inPlayerList = true; + } //this.entityData.set(new EntityDataAccessor<>(16, EntityDataSerializers.BYTE), (byte) 0xFF); } @@ -103,8 +109,10 @@ public class Bot extends ServerPlayer implements Terminator { UUID uuid = BotUtils.randomSteveUUID(); CustomGameProfile profile = new CustomGameProfile(uuid, ChatUtils.trim16(name), skin); + + boolean addPlayerList = TerminatorPlus.getInstance().getManager().addToPlayerList(); - Bot bot = new Bot(nmsServer, nmsWorld, profile); + Bot bot = new Bot(nmsServer, nmsWorld, profile, addPlayerList); bot.connection = new ServerGamePacketListenerImpl(nmsServer, new Connection(PacketFlow.CLIENTBOUND) { @@ -119,7 +127,10 @@ public class Bot extends ServerPlayer implements Terminator { bot.getBukkitEntity().setNoDamageTicks(0); Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().connection.send( new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, bot))); - nmsWorld.addFreshEntity(bot); + if (addPlayerList) + nmsWorld.addNewPlayer(bot); + else + nmsWorld.addFreshEntity(bot); bot.renderAll(); TerminatorPlus.getInstance().getManager().add(bot); @@ -243,6 +254,11 @@ public class Bot extends ServerPlayer implements Terminator { public int getAliveTicks() { return aliveTicks; } + + @Override + public int getNoFallTicks() { + return noFallTicks; + } @Override public boolean tickDelay(int i) { @@ -278,8 +294,6 @@ public class Bot extends ServerPlayer implements Terminator { aliveTicks++; - if (fireTicks > 0) --fireTicks; - if (invulnerableTime > 0) --invulnerableTime; if (jumpTicks > 0) --jumpTicks; if (noFallTicks > 0) --noFallTicks; @@ -290,6 +304,8 @@ public class Bot extends ServerPlayer implements Terminator { } updateLocation(); + + if (!isAlive()) return; float health = getHealth(); float maxHealth = getMaxHealth(); @@ -304,14 +320,11 @@ public class Bot extends ServerPlayer implements Terminator { setHealth(amount); - fireDamageCheck(); fallDamageCheck(); - if (position().y < -64) { - die(DamageSource.OUT_OF_WORLD); - } - oldVelocity = velocity.clone(); + + doTick(); } private void loadChunks() { @@ -328,52 +341,6 @@ public class Bot extends ServerPlayer implements Terminator { } } - private void fireDamageCheck() { - if (!isAlive()) { - return; // maybe also have packet reset thing - } - - Material type = getLocation().getBlock().getType(); - - if (type == Material.WATER) { - setOnFirePackets(false); // maybe also play extinguish noise? - fireTicks = 0; - return; - } - - boolean lava = type == org.bukkit.Material.LAVA; - - if (lava || type == org.bukkit.Material.FIRE || type == Material.SOUL_FIRE) { - ignite(); - } - - if (invulnerableTime == 0) { - if (lava) { - hurt(DamageSource.LAVA, 4); - invulnerableTime = 20;//this used to be 12 ticks but that would cause the bot to take damage too quickly - } else if (fireTicks > 1) { - hurt(DamageSource.IN_FIRE, 1); - invulnerableTime = 20; - } - } - - if (fireTicks == 1) { - setOnFirePackets(false); - } - } - - @Override - public void ignite() { - if (fireTicks <= 1) setOnFirePackets(true); - fireTicks = 100; - } - - @Override - public void setOnFirePackets(boolean onFire) { - //entityData.set(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), onFire ? (byte) 1 : (byte) 0); - //sendPacket(new ClientboundSetEntityDataPacket(getId(), entityData, false)); - } - @Override public UUID getTargetPlayer() { return targetPlayer; @@ -386,12 +353,12 @@ public class Bot extends ServerPlayer implements Terminator { @Override public boolean isBotOnFire() { - return fireTicks != 0; + return this.isOnFire(); } private void fallDamageCheck() { // TODO create a better bot event system in the future, also have bot.getAgent() - if (groundTicks != 0 && noFallTicks == 0 && !(oldVelocity.getY() >= -0.8) && !BotUtils.NO_FALL.contains(getLocation().getBlock().getType())) { - BotFallDamageEvent event = new BotFallDamageEvent(this); + if (groundTicks != 0 && noFallTicks == 0 && !(oldVelocity.getY() >= -0.8) && !isFallBlocked()) { + BotFallDamageEvent event = new BotFallDamageEvent(this, new ArrayList<>(getStandingOn())); plugin.getManager().getAgent().onFallDamage(event); @@ -400,6 +367,33 @@ public class Bot extends ServerPlayer implements Terminator { } } } + + private boolean isFallBlocked() { + AABB box = getBoundingBox(); + double[] xVals = new double[]{ + box.minX, + box.maxX - 0.01 + }; + + double[] zVals = new double[]{ + box.minZ, + box.maxZ - 0.01 + }; + BoundingBox playerBox = new BoundingBox(box.minX, position().y - 0.01, box.minZ, + box.maxX, position().y + getBbHeight(), box.maxZ); + for (double x : xVals) { + for (double z : zVals) { + Location loc = new Location(getBukkitEntity().getWorld(), Math.floor(x), getLocation().getY(), Math.floor(z)); + Block block = loc.getBlock(); + if (block.getBlockData() instanceof Waterlogged wl && wl.isWaterlogged()) + return true; + if (BotUtils.NO_FALL.contains(loc.getBlock().getType()) && (BotUtils.overlaps(playerBox, loc.getBlock().getBoundingBox()) + || loc.getBlock().getType() == Material.WATER || loc.getBlock().getType() == Material.LAVA)) + return true; + } + } + return false; + } @Override public boolean isFalling() { @@ -525,7 +519,11 @@ public class Bot extends ServerPlayer implements Terminator { return false; } - World world = getBukkitEntity().getWorld(); + return checkStandingOn(); + } + + public boolean checkStandingOn() { + World world = getBukkitEntity().getWorld(); AABB box = getBoundingBox(); double[] xVals = new double[]{ @@ -537,19 +535,55 @@ public class Bot extends ServerPlayer implements Terminator { box.minZ, box.maxZ }; + BoundingBox playerBox = new BoundingBox(box.minX, position().y - 0.01, box.minZ, + box.maxX, position().y + getBbHeight(), box.maxZ); + List standingOn = new ArrayList<>(); + List locations = new ArrayList<>(); for (double x : xVals) { for (double z : zVals) { Location loc = new Location(world, x, position().y - 0.01, z); Block block = world.getBlockAt(loc); - if (block.getType().isSolid() && BotUtils.solidAt(loc)) { - return true; + if ((block.getType().isSolid() || LegacyMats.canStandOn(block.getType())) && BotUtils.overlaps(playerBox, block.getBoundingBox())) { + if (!locations.contains(block.getLocation())) { + standingOn.add(block); + locations.add(block.getLocation()); + } + } + } + } + + //Fence/wall check + for (double x : xVals) { + for (double z : zVals) { + Location loc = new Location(world, x, position().y - 0.51, z); + Block block = world.getBlockAt(loc); + BoundingBox blockBox = loc.getBlock().getBoundingBox(); + BoundingBox modifiedBox = new BoundingBox(blockBox.getMinX(), blockBox.getMinY(), blockBox.getMinZ(), blockBox.getMaxX(), + blockBox.getMinY() + 1.5, blockBox.getMaxZ()); + + if ((LegacyMats.FENCE.contains(block.getType()) || LegacyMats.GATES.contains(block.getType())) + && block.getType().isSolid() && BotUtils.overlaps(playerBox, modifiedBox)) { + if (!locations.contains(block.getLocation())) { + standingOn.add(block); + locations.add(block.getLocation()); + } } } } - return false; + //Closest block comes first + Collections.sort(standingOn, (a, b) -> + Double.compare(BotUtils.getHorizSqDist(a.getLocation(), getLocation()), BotUtils.getHorizSqDist(b.getLocation(), getLocation()))); + + this.standingOn = standingOn; + return !standingOn.isEmpty(); + } + + @Override + public List getStandingOn() { + return standingOn; } @Override @@ -582,6 +616,8 @@ public class Bot extends ServerPlayer implements Terminator { scheduler.runTask(plugin, () -> this.remove(RemovalReason.DISCARDED)); } this.removeVisually(); + if (inPlayerList) + this.server.getPlayerList().getPlayers().remove(this); } private void removeTab() { @@ -609,7 +645,7 @@ public class Bot extends ServerPlayer implements Terminator { // this should fix the concurrentmodificationexception mentioned above, I used the ConcurrentHashMap.newKeySet to make a "ConcurrentHashSet" plugin.getManager().remove(this); - scheduler.runTaskLater(plugin, this::setDead, 30); + scheduler.runTaskLater(plugin, this::removeBot, 20); this.removeTab(); } @@ -728,6 +764,11 @@ public class Bot extends ServerPlayer implements Terminator { public Location getLocation() { return getBukkitEntity().getLocation(); } + + @Override + public BoundingBox getBotBoundingBox() { + return getBukkitEntity().getBoundingBox(); + } @Override public void setBotPitch(float pitch) { @@ -842,25 +883,16 @@ public class Bot extends ServerPlayer implements Terminator { @Override public void doTick() { - if (this.hurtTime > 0) { - this.hurtTime -= 1; - } - baseTick(); - tickEffects(); - - this.animStepO = (int) this.animStep; - this.yBodyRotO = this.yBodyRot; - this.yHeadRotO = this.yHeadRot; - this.yRotO = this.getYRot(); - this.xRotO = this.getXRot(); } - - public boolean isIgnoredByMobs() { - return ignoredByMobs; + + @Override + public boolean isInPlayerList() { + return inPlayerList; } - - public void setIgnoredByMobs(boolean ignoredByMobs) { - this.ignoredByMobs = ignoredByMobs; + + @Override + public World.Environment getDimension() { + return getBukkitEntity().getWorld().getEnvironment(); } } diff --git a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/bot/BotManagerImpl.java b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/bot/BotManagerImpl.java index 51925f9..b4a9486 100644 --- a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/bot/BotManagerImpl.java +++ b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/bot/BotManagerImpl.java @@ -35,11 +35,24 @@ public class BotManagerImpl implements BotManager, Listener { public boolean joinMessages = false; private boolean mobTarget = false; + private boolean addPlayerList = false; + private Location spawnLoc; + public BotManagerImpl() { this.agent = new LegacyAgent(this, TerminatorPlus.getInstance()); this.bots = ConcurrentHashMap.newKeySet(); //should fix concurrentmodificationexception this.numberFormat = NumberFormat.getInstance(Locale.US); } + + @Override + public Location getSpawnLoc() { + return spawnLoc; + } + + @Override + public void setSpawnLoc(Location loc) { + spawnLoc = loc; + } @Override public Set fetch() { @@ -56,7 +69,17 @@ public class BotManagerImpl implements BotManager, Listener { } @Override - public Terminator getFirst(String name) { + public Terminator getFirst(String name, Location target) { + if (target != null) { + Terminator closest = null; + for (Terminator bot : bots) { + if (name.equals(bot.getBotName()) && (closest == null + || target.distanceSquared(bot.getLocation()) < target.distanceSquared(closest.getLocation()))) { + closest = bot; + } + } + return closest; + } for (Terminator bot : bots) { if (name.equals(bot.getBotName())) { return bot; @@ -98,7 +121,18 @@ public class BotManagerImpl implements BotManager, Listener { skinName = skinName == null ? name : skinName; - createBots(sender.getLocation(), name, MojangAPI.getSkin(skinName), n, network); + if (spawnLoc != null) { + sender.sendMessage("The spawn location is " + + ChatColor.BLUE + String.format("(%s, %s, %s)", spawnLoc.getX(), spawnLoc.getY(), spawnLoc.getZ()) + ChatColor.RESET + + ". This will be reset to the player location next time."); + Location loc = sender.getLocation().clone(); + loc.setX(spawnLoc.getX()); + loc.setY(spawnLoc.getY()); + loc.setZ(spawnLoc.getZ()); + createBots(loc, name, MojangAPI.getSkin(skinName), n, network); + spawnLoc = null; + } else + createBots(sender.getLocation(), name, MojangAPI.getSkin(skinName), n, network); sender.sendMessage("Process completed (" + ChatColor.RED + ((System.currentTimeMillis() - timestamp) / 1000D) + "s" + ChatColor.RESET + ")."); } @@ -139,7 +173,6 @@ public class BotManagerImpl implements BotManager, Listener { } else if (i > 1) { bot.setVelocity(randomVelocity().multiply(f)); } - bot.setIgnoredByMobs(!mobTarget); bots.add(bot); i++; @@ -164,7 +197,7 @@ public class BotManagerImpl implements BotManager, Listener { @Override public void reset() { if (!bots.isEmpty()) { - bots.forEach(Terminator::removeVisually); + bots.forEach(Terminator::removeBot); bots.clear(); // Not always necessary, but a good security measure } @@ -204,6 +237,16 @@ public class BotManagerImpl implements BotManager, Listener { public void setMobTarget(boolean mobTarget) { this.mobTarget = mobTarget; } + + @Override + public boolean addToPlayerList() { + return addPlayerList; + } + + @Override + public void setAddToPlayerList(boolean addPlayerList) { + this.addPlayerList = addPlayerList; + } @EventHandler public void onJoin(PlayerJoinEvent event) { @@ -222,8 +265,10 @@ public class BotManagerImpl implements BotManager, Listener { @EventHandler public void onMobTarget(EntityTargetLivingEntityEvent event) { - Bot bot = (Bot) getBot(event.getEntity().getUniqueId()); - if (bot != null && bot.isIgnoredByMobs()) { + if (mobTarget || event.getTarget() == null) + return; + Bot bot = (Bot) getBot(event.getTarget().getUniqueId()); + if (bot != null) { event.setCancelled(true); } } diff --git a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/command/commands/AICommand.java b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/command/commands/AICommand.java index 4b95949..02a57fd 100644 --- a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/command/commands/AICommand.java +++ b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/command/commands/AICommand.java @@ -121,7 +121,7 @@ public class AICommand extends CommandInstance implements AIManager { scheduler.runTaskAsynchronously(plugin, () -> { try { - Terminator bot = manager.getFirst(name); + Terminator bot = manager.getFirst(name, (sender instanceof Player pl) ? pl.getLocation() : null); if (bot == null) { sender.sendMessage("Could not find bot " + ChatColor.GREEN + name + ChatColor.RESET + "!"); diff --git a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/command/commands/BotCommand.java b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/command/commands/BotCommand.java index 36b0157..b553648 100644 --- a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/command/commands/BotCommand.java +++ b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/command/commands/BotCommand.java @@ -22,10 +22,13 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; import java.text.DecimalFormat; import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; public class BotCommand extends CommandInstance { @@ -141,7 +144,6 @@ public class BotCommand extends CommandInstance { desc = "Gives all bots an armor set.", autofill = "armorAutofill" ) - @SuppressWarnings("deprecation") public void armor(CommandSender sender, @Arg("armor-tier") String armorTier) { String tier = armorTier.toLowerCase(); @@ -190,7 +192,7 @@ public class BotCommand extends CommandInstance { scheduler.runTaskAsynchronously(plugin, () -> { try { - Terminator bot = manager.getFirst(name); + Terminator bot = manager.getFirst(name, (sender instanceof Player pl) ? pl.getLocation() : null); if (bot == null) { sender.sendMessage("Could not find bot " + ChatColor.GREEN + name + ChatColor.RESET + "!"); @@ -211,7 +213,7 @@ public class BotCommand extends CommandInstance { String botName = bot.getBotName(); String world = ChatColor.YELLOW + bot.getBukkitEntity().getWorld().getName(); Location loc = bot.getLocation(); - String strLoc = ChatColor.YELLOW + formatter.format(loc.getBlockX()) + ", " + formatter.format(loc.getBlockY()) + ", " + formatter.format(loc.getBlockZ()); + String strLoc = ChatColor.YELLOW + formatter.format(loc.getX()) + ", " + formatter.format(loc.getY()) + ", " + formatter.format(loc.getZ()); Vector vel = bot.getVelocity(); String strVel = ChatColor.AQUA + formatter.format(vel.getX()) + ", " + formatter.format(vel.getY()) + ", " + formatter.format(vel.getZ()); @@ -231,6 +233,23 @@ public class BotCommand extends CommandInstance { public List infoAutofill(CommandSender sender, String[] args) { return args.length == 2 ? manager.fetchNames() : null; } + + @Command( + name = "count", + desc = "Counts the amount of bots on screen by name." + ) + public void count(CommandSender sender) { + List names = manager.fetchNames(); + Map freqMap = names.stream().collect(Collectors.toMap(s -> s, s -> 1, Integer::sum)); + List> entries = freqMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList()); + + sender.sendMessage(ChatUtils.LINE); + entries.forEach(en -> sender.sendMessage(ChatColor.GREEN + en.getKey() + + ChatColor.RESET + " - " + ChatColor.BLUE + en.getValue().toString() + ChatColor.RESET)); + sender.sendMessage("Total bots: " + ChatColor.BLUE + freqMap.values().stream().reduce(0, Integer::sum) + ChatColor.RESET); + sender.sendMessage(ChatUtils.LINE); + } @Command( name = "reset", @@ -267,17 +286,68 @@ public class BotCommand extends CommandInstance { String extra = ChatColor.GRAY + " [" + ChatColor.YELLOW + "/bot settings" + ChatColor.GRAY + "]"; - if (arg1 == null || ((!arg1.equalsIgnoreCase("setgoal")) && !arg1.equalsIgnoreCase("mobtarget") && !arg1.equalsIgnoreCase("playertarget"))) { + if (arg1 == null || (!arg1.equalsIgnoreCase("spawnloc") && !arg1.equalsIgnoreCase("setgoal") && !arg1.equalsIgnoreCase("mobtarget") && !arg1.equalsIgnoreCase("playertarget") + && !arg1.equalsIgnoreCase("addplayerlist") && !arg1.equalsIgnoreCase("region"))) { sender.sendMessage(ChatUtils.LINE); sender.sendMessage(ChatColor.GOLD + "Bot Settings" + extra); + sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + "spawnloc" + ChatUtils.BULLET_FORMATTED + "Set the location where the bots should spawn. This will be reset after a spawn command is executed."); sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + "setgoal" + ChatUtils.BULLET_FORMATTED + "Set the global bot target selection method."); - sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + "mobtarget" + ChatUtils.BULLET_FORMATTED + "Allow all future bots spawned to be targeted by hostile mobs."); + sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + "mobtarget" + ChatUtils.BULLET_FORMATTED + "Allow all bots to be targeted by hostile mobs."); + sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + "playertarget" + ChatUtils.BULLET_FORMATTED + "Sets a player name for spawned bots to focus on if the goal is PLAYER."); + sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + "addplayerlist" + ChatUtils.BULLET_FORMATTED + "Adds newly spawned bots to the player list. This allows the bots to be affected by player selectors like @a and @p."); + sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + "region" + ChatUtils.BULLET_FORMATTED + "Sets a region for the bots to prioritize entities inside."); sender.sendMessage(ChatUtils.LINE); return; } - if (arg1.equalsIgnoreCase("setgoal")) { - EnumTargetGoal goal = EnumTargetGoal.from(arg2 == null ? "" : arg2); + if (arg1.equalsIgnoreCase("spawnloc")) { + if (arg2 == null) { + if (manager.getSpawnLoc() == null) + sender.sendMessage("No custom spawn location has been set. The bots will spawn at the player location."); + else { + Location loc = manager.getSpawnLoc(); + sender.sendMessage("The next spawn location will be at " + ChatColor.BLUE + String.format("(%s, %s, %s)", loc.getX(), loc.getY(), loc.getZ()) + ChatColor.RESET + "."); + } + return; + } + if (arg2.equalsIgnoreCase("clear")) { + manager.setSpawnLoc(null); + sender.sendMessage("The spawn location has been reset to the player location."); + return; + } + if (arg2.equalsIgnoreCase("playerloc")) { + if (!(sender instanceof Player)) { + sender.sendMessage("You must be a player to do this!"); + return; + } + Location loc = ((Player)sender).getLocation(); + manager.setSpawnLoc(loc.clone()); + sender.sendMessage("The spawn location has been set to " + ChatColor.BLUE + formatter.format(loc.getX()) + ", " + formatter.format(loc.getY()) + ", " + formatter.format(loc.getZ()) + ChatColor.RESET + "."); + return; + } + if (args.size() != 4) { + sender.sendMessage("Incorrect argument size. Correct syntax: " + ChatColor.YELLOW + "/bot settings spawnloc " + ChatColor.RESET); + sender.sendMessage("Additionally, to specify a spawnloc at the current player position: " + ChatColor.YELLOW + "/bot settings spawnloc playerloc" + ChatColor.RESET); + return; + } + double x, y, z; + try { + x = Double.parseDouble(args.get(1)); + y = Double.parseDouble(args.get(2)); + z = Double.parseDouble(args.get(3)); + } catch (NumberFormatException e) { + sender.sendMessage("The block coordinates must be doubles!"); + sender.sendMessage("Correct syntax: " + ChatColor.YELLOW + "/bot settings spawnloc " + ChatColor.RESET); + return; + } + manager.setSpawnLoc(new Location(null, x, y, z)); + sender.sendMessage("The next spawn location has been set to " + ChatColor.BLUE + String.format("(%s, %s, %s)", x, y, z) + ChatColor.RESET + "."); + } else if (arg1.equalsIgnoreCase("setgoal")) { + if (arg2 == null) { + sender.sendMessage("The global bot goal is currently " + ChatColor.BLUE + agent.getTargetType() + ChatColor.RESET + "."); + return; + } + EnumTargetGoal goal = EnumTargetGoal.from(arg2); if (goal == null) { sender.sendMessage(ChatUtils.LINE); @@ -290,14 +360,22 @@ public class BotCommand extends CommandInstance { agent.setTargetType(goal); sender.sendMessage("The global bot goal has been set to " + ChatColor.BLUE + goal.name() + ChatColor.RESET + "."); } else if (arg1.equalsIgnoreCase("mobtarget")) { - manager.setMobTarget(!manager.isMobTarget()); - sender.sendMessage("Mob targeting is now " + (manager.isMobTarget() ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.RESET + ". (for all future bots)"); + if (arg2 == null) { + sender.sendMessage("Mob targeting is currently " + (manager.isMobTarget() ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.RESET + "."); + return; + } + if (!arg2.equals("true") && !arg2.equals("false")) { + sender.sendMessage(ChatColor.RED + "You must specify true or false!"); + return; + } + manager.setMobTarget(Boolean.parseBoolean(arg2)); + sender.sendMessage("Mob targeting is now " + (manager.isMobTarget() ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.RESET + "."); } else if (arg1.equalsIgnoreCase("playertarget")) { - if (args.size() < 2) { + if (args.size() < 2) { sender.sendMessage(ChatColor.RED + "You must specify a player name!"); return; } - String playerName = args.get(1); + String playerName = arg2; Player player = Bukkit.getPlayer(playerName); if (player == null) { sender.sendMessage(ChatColor.RED + "Could not find player " + ChatColor.YELLOW + playerName + ChatColor.RED + "!"); @@ -307,6 +385,88 @@ public class BotCommand extends CommandInstance { fetch.setTargetPlayer(player.getUniqueId()); } sender.sendMessage("All spawned bots are now set to target " + ChatColor.BLUE + player.getName() + ChatColor.RESET + ". They will target the closest player if they can't be found.\nYou may need to set the goal to PLAYER."); + } else if (arg1.equalsIgnoreCase("addplayerlist")) { + if (arg2 == null) { + sender.sendMessage("Adding bots to the player list is currently " + (manager.addToPlayerList() ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.RESET + "."); + return; + } + if (!arg2.equals("true") && !arg2.equals("false")) { + sender.sendMessage(ChatColor.RED + "You must specify true or false!"); + return; + } + manager.setAddToPlayerList(Boolean.parseBoolean(arg2)); + sender.sendMessage("Adding bots to the player list is now " + (manager.addToPlayerList() ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.RESET + "."); + } else if (arg1.equalsIgnoreCase("region")) { + if (arg2 == null) { + if (agent.getRegion() == null) { + sender.sendMessage("No region has been set."); + return; + } + sender.sendMessage("The current region is " + ChatColor.BLUE + agent.getRegion() + ChatColor.RESET + "."); + if (agent.getRegionWeightX() == 0 && agent.getRegionWeightY() == 0 && agent.getRegionWeightZ() == 0) + sender.sendMessage("Entities out of range will not be targeted."); + else { + sender.sendMessage("The region X weight is " + ChatColor.BLUE + agent.getRegionWeightX() + ChatColor.RESET + "."); + sender.sendMessage("The region Y weight is " + ChatColor.BLUE + agent.getRegionWeightY() + ChatColor.RESET + "."); + sender.sendMessage("The region Z weight is " + ChatColor.BLUE + agent.getRegionWeightZ() + ChatColor.RESET + "."); + } + return; + } + if (arg2.equalsIgnoreCase("clear")) { + agent.setRegion(null, 0, 0, 0); + sender.sendMessage("The region has been cleared."); + return; + } + boolean strict = args.size() == 8 && args.get(7).equalsIgnoreCase("strict"); + if (args.size() != 10 && !strict) { + sender.sendMessage(ChatUtils.LINE); + sender.sendMessage(ChatColor.GOLD + "Bot Region Settings" + extra); + sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + " " + ChatUtils.BULLET_FORMATTED + + "Sets a region for bots to prioritize entities within."); + sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + " strict" + ChatUtils.BULLET_FORMATTED + + "Sets a region so that the bots only target entities within the region."); + sender.sendMessage(ChatUtils.BULLET_FORMATTED + ChatColor.YELLOW + "clear" + ChatUtils.BULLET_FORMATTED + + "Clears the region."); + sender.sendMessage("Without strict mode, the entity distance from the region is multiplied by the weight values if outside the region."); + sender.sendMessage("The resulting value is added to the entity distance when selecting an entity."); + sender.sendMessage(ChatUtils.LINE); + return; + } + double x1, y1, z1, x2, y2, z2, wX, wY, wZ; + try { + Location loc = sender instanceof Player pl ? pl.getLocation() : null; + x1 = parseDoubleOrRelative(args.get(1), loc, 0); + y1 = parseDoubleOrRelative(args.get(2), loc, 1); + z1 = parseDoubleOrRelative(args.get(3), loc, 2); + x2 = parseDoubleOrRelative(args.get(4), loc, 0); + y2 = parseDoubleOrRelative(args.get(5), loc, 1); + z2 = parseDoubleOrRelative(args.get(6), loc, 2); + if (strict) + wX = wY = wZ = 0; + else { + wX = Double.parseDouble(args.get(7)); + wY = Double.parseDouble(args.get(8)); + wZ = Double.parseDouble(args.get(9)); + if (wX <= 0 || wY <= 0 || wZ <= 0) { + sender.sendMessage("The region weights must be positive values!"); + return; + } + } + } catch (NumberFormatException e) { + sender.sendMessage("The region bounds and weights must be valid numbers!"); + sender.sendMessage("Correct syntax: " + ChatColor.YELLOW + "/bot settings region " + + ChatColor.RESET); + return; + } + agent.setRegion(new BoundingBox(x1, y1, z1, x2, y2, z2), wX, wY, wZ); + sender.sendMessage("The region has been set to " + ChatColor.BLUE + agent.getRegion() + ChatColor.RESET + "."); + if (wX == 0 && wY == 0 && wZ == 0) + sender.sendMessage("Entities out of range will not be targeted."); + else { + sender.sendMessage("The region X weight is " + ChatColor.BLUE + agent.getRegionWeightX() + ChatColor.RESET + "."); + sender.sendMessage("The region Y weight is " + ChatColor.BLUE + agent.getRegionWeightY() + ChatColor.RESET + "."); + sender.sendMessage("The region Z weight is " + ChatColor.BLUE + agent.getRegionWeightZ() + ChatColor.RESET + "."); + } } } @@ -323,8 +483,12 @@ public class BotCommand extends CommandInstance { // lookall if (args.length == 2) { + output.add("spawnloc"); output.add("setgoal"); output.add("mobtarget"); + output.add("playertarget"); + output.add("addplayerlist"); + output.add("region"); } else if (args.length == 3) { if (args[1].equalsIgnoreCase("setgoal")) { Arrays.stream(EnumTargetGoal.values()).forEach(goal -> output.add(goal.name().replace("_", "").toLowerCase())); @@ -333,6 +497,15 @@ public class BotCommand extends CommandInstance { output.add("true"); output.add("false"); } + if (args[1].equalsIgnoreCase("playertarget")) { + for (Player player : Bukkit.getOnlinePlayers()) { + output.add(player.getName()); + } + } + if (args[1].equalsIgnoreCase("addplayerlist")) { + output.add("true"); + output.add("false"); + } } return output; @@ -346,4 +519,20 @@ public class BotCommand extends CommandInstance { public void debug(CommandSender sender, @Arg("expression") String expression) { new Debugger(sender).execute(expression); } + + private double parseDoubleOrRelative(String pos, Location loc, int type) { + if (loc == null || pos.length() == 0 || pos.charAt(0) != '~') + return Double.parseDouble(pos); + double relative = Double.parseDouble(pos.substring(1)); + switch (type) { + case 0: + return relative + Math.round(loc.getX() * 1000) / 1000D; + case 1: + return relative + Math.round(loc.getY() * 1000) / 1000D; + case 2: + return relative + Math.round(loc.getZ() * 1000) / 1000D; + default: + return 0; + } + } } diff --git a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/utils/Debugger.java b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/utils/Debugger.java index 42a5caf..e3fcfb8 100644 --- a/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/utils/Debugger.java +++ b/TerminatorPlus-Plugin/src/main/java/net/nuggetmc/tplus/utils/Debugger.java @@ -365,10 +365,6 @@ public class Debugger { print("This has been established as a feature as \"" + ChatColor.AQUA + "/bot settings setgoal" + ChatColor.RESET + "\"!"); } - public void fire(boolean b) { - TerminatorPlus.getInstance().getManager().fetch().forEach(bot -> bot.setOnFirePackets(b)); - } - public void trackYVel() { if (!(sender instanceof Player)) return;