From 2452d72f2c0f27ed07c098b8e7a44bf94b2ad888 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 3 Sep 2023 18:16:39 +0200 Subject: [PATCH 1/7] Add NavMesh --- .../de/steamwar/fightsystem/FightSystem.java | 9 ++ .../de/steamwar/fightsystem/ai/LixfelAI.java | 27 ++-- .../fightsystem/ai/navmesh/NavMesh.java | 137 ++++++++++++++++++ 3 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index 5de02c4..56dee07 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -22,6 +22,7 @@ package de.steamwar.fightsystem; import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.core.Core; import de.steamwar.fightsystem.ai.LixfelAI; +import de.steamwar.fightsystem.ai.navmesh.NavMesh; import de.steamwar.fightsystem.commands.*; import de.steamwar.fightsystem.countdown.*; import de.steamwar.fightsystem.event.HellsBells; @@ -42,6 +43,7 @@ import de.steamwar.fightsystem.utils.*; import de.steamwar.fightsystem.winconditions.*; import de.steamwar.message.Message; import de.steamwar.sql.SchematicNode; +import de.steamwar.sql.SteamwarUser; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; @@ -167,6 +169,13 @@ public class FightSystem extends JavaPlugin { }else if(Config.mode == ArenaMode.PREPARE) { Fight.getUnrotated().setSchem(SchematicNode.getSchematicNode(Config.PrepareSchemID)); } + + FightStatistics.unrank(); + + Bukkit.getScheduler().runTask(getPlugin(), () -> { + new LixfelAI(Fight.getBlueTeam(), "Lixfel.AI"); + new LixfelAI(Fight.getRedTeam(), "YoyoNow.AI"); + }); } @Override diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java index 2a89353..42bab27 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java @@ -19,10 +19,13 @@ package de.steamwar.fightsystem.ai; +import de.steamwar.entity.REntityServer; import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.ai.navmesh.NavMesh; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; +import org.bukkit.entity.Player; import org.bukkit.util.Vector; import java.util.List; @@ -30,31 +33,33 @@ import java.util.Random; public class LixfelAI extends AI { - private final Random random = new Random(); - private LixfelPathplanner pathplanner; + private final REntityServer entityServer = new REntityServer(); + private final FightTeam team; + private final NavMesh navMesh; public LixfelAI(FightTeam team, String user) { super(team, SteamwarUser.get(user)); + this.team = team; + navMesh = new NavMesh(team, entityServer); } - @Override public SchematicNode chooseSchematic() { List publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB()); SchematicNode schem = publics.get(new Random().nextInt(publics.size())); - pathplanner = new LixfelPathplanner(schem); return schem; } + @Override + public boolean acceptJoinRequest(Player player, FightTeam team) { + if (team == this.team) { + entityServer.addPlayer(player); + } + return super.acceptJoinRequest(player, team); + } + @Override protected void plan() { setReady(); - Vector destination = pathplanner.getWalkable().get(random.nextInt(pathplanner.getWalkable().size())); - List path = pathplanner.plan(getPosition(), destination); - if(!path.isEmpty()) - chat("Path size: " + path.size()); - for(Vector p : path) { - move(p); - } } } diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java new file mode 100644 index 0000000..6fed8e5 --- /dev/null +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java @@ -0,0 +1,137 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.fightsystem.ai.navmesh; + +import de.steamwar.entity.RArmorStand; +import de.steamwar.entity.REntityServer; +import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.fightsystem.FightSystem; +import de.steamwar.fightsystem.fight.FightTeam; +import de.steamwar.fightsystem.states.FightState; +import de.steamwar.fightsystem.states.OneShotStateDependent; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.event.Listener; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; +import org.bukkit.util.VoxelShape; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; + +public class NavMesh implements Listener { + + private static final World WORLD = Bukkit.getWorlds().get(0); + private static final double PLAYER_HEIGHT = 1.8125; + private static final BoundingBox PLAYER_SHAPE = new BoundingBox(0.2, 0, 0.2, 0.8, PLAYER_HEIGHT, 0.8); + + + private FightTeam fightTeam; + + public NavMesh(FightTeam fightTeam, REntityServer entityServer) { + this.fightTeam = fightTeam; + + new OneShotStateDependent(ArenaMode.All, FightState.PostSchemSetup, () -> { + Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> { + fightTeam.getExtendRegion().forEach(this::checkWalkable); + System.out.println(fightTeam + " " + floorBlock.size()); + iterateWalkableBlocks((vector, ceilingBlock) -> { + RArmorStand armorStand = new RArmorStand(entityServer, vector.toLocation(WORLD), RArmorStand.Size.MARKER); + armorStand.setNoGravity(true); + armorStand.setInvisible(true); + armorStand.setDisplayName("+"); + }); + }, 20); + }); + new OneShotStateDependent(ArenaMode.All, FightState.Spectate, () -> { + floorBlock.clear(); + }); + } + + private static class Pos { + private int x; + private int y; + private int z; + + public Pos(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Pos)) return false; + Pos pos = (Pos) o; + return x == pos.x && y == pos.y && z == pos.z; + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z); + } + } + + private Map floorBlock = new HashMap<>(); + + private void checkWalkable(int x, int y, int z) { + Block block = WORLD.getBlockAt(x, y, z); + VoxelShape floor = block.getCollisionShape(); + if (block.getType() != Material.LADDER && !overlaps(floor, 0, 0, 0)) return; + + Double floorHeight = null; + for (BoundingBox box : floor.getBoundingBoxes()) { + double by = box.getMaxY(); + if (!overlaps(floor, 0, by, 0)) { + if (floorHeight == null) { + floorHeight = by; + } else { + floorHeight = Math.min(floorHeight, by); + } + } + } + if (floorHeight == null) return; + + if (overlaps(WORLD.getBlockAt(x, y + 1, z).getCollisionShape(), 0, floorHeight - 1, 0)) return; + if (overlaps(WORLD.getBlockAt(x, y + 2, z).getCollisionShape(), 0, floorHeight - 2, 0)) return; + if (overlaps(WORLD.getBlockAt(x, y + 3, z).getCollisionShape(), 0, floorHeight - 3, 0)) return; + + floorBlock.put(new Pos(x, y, z), y + floorHeight); + } + + private boolean overlaps(VoxelShape voxelShape, double x, double y, double z) { + PLAYER_SHAPE.shift(x, y, z); + boolean overlaps = voxelShape.overlaps(PLAYER_SHAPE); + PLAYER_SHAPE.shift(-x, -y, -z); + return overlaps; + } + + public void iterateWalkableBlocks(BiConsumer consumer) { + floorBlock.forEach((pos, aDouble) -> { + Vector vector = new Vector(pos.x + 0.5, aDouble, pos.z + 0.5); + consumer.accept(vector, null); + }); + } +} From c3e39296b55761db89fd1c420736d0714a8aecfb Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 3 Sep 2023 21:50:06 +0200 Subject: [PATCH 2/7] Add Neighbour Navigation calculation --- .../fightsystem/ai/navmesh/NavMesh.java | 92 ++++++++++++++++--- 1 file changed, 77 insertions(+), 15 deletions(-) diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java index 6fed8e5..4aaf9f4 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java @@ -30,22 +30,23 @@ import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.event.Listener; import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; import org.bukkit.util.VoxelShape; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.BiConsumer; +import java.util.stream.Collectors; public class NavMesh implements Listener { private static final World WORLD = Bukkit.getWorlds().get(0); + private static final double PLAYER_JUMP_HEIGHT = 1.25; private static final double PLAYER_HEIGHT = 1.8125; - private static final BoundingBox PLAYER_SHAPE = new BoundingBox(0.2, 0, 0.2, 0.8, PLAYER_HEIGHT, 0.8); - + private static final BoundingBox PLAYER_SHADOW = new BoundingBox(0.2, 0, 0.2, 0.8, 1, 0.8); + private static final BlockFace[] FACES = new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST}; private FightTeam fightTeam; @@ -54,13 +55,18 @@ public class NavMesh implements Listener { new OneShotStateDependent(ArenaMode.All, FightState.PostSchemSetup, () -> { Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> { + long time = System.currentTimeMillis(); fightTeam.getExtendRegion().forEach(this::checkWalkable); + floorBlock.forEach(this::checkNeighbouring); + System.out.println(System.currentTimeMillis() - time + " ms"); + // For neighbour map -2-+2 blocks System.out.println(fightTeam + " " + floorBlock.size()); - iterateWalkableBlocks((vector, ceilingBlock) -> { + iterateWalkableBlocks((vector, ceilingOffset, neightbourConnections) -> { + String connecting = neightbourConnections.stream().map(face -> face.name().substring(0, 1)).collect(Collectors.joining()); RArmorStand armorStand = new RArmorStand(entityServer, vector.toLocation(WORLD), RArmorStand.Size.MARKER); armorStand.setNoGravity(true); armorStand.setInvisible(true); - armorStand.setDisplayName("+"); + armorStand.setDisplayName("+" + (ceilingOffset == null ? "∞" : ceilingOffset) + " " + connecting); }); }, 20); }); @@ -95,6 +101,8 @@ public class NavMesh implements Listener { } private Map floorBlock = new HashMap<>(); + private Map ceilingOffset = new HashMap<>(); + private Map> neighbourConnections = new HashMap<>(); private void checkWalkable(int x, int y, int z) { Block block = WORLD.getBlockAt(x, y, z); @@ -114,24 +122,78 @@ public class NavMesh implements Listener { } if (floorHeight == null) return; - if (overlaps(WORLD.getBlockAt(x, y + 1, z).getCollisionShape(), 0, floorHeight - 1, 0)) return; - if (overlaps(WORLD.getBlockAt(x, y + 2, z).getCollisionShape(), 0, floorHeight - 2, 0)) return; - if (overlaps(WORLD.getBlockAt(x, y + 3, z).getCollisionShape(), 0, floorHeight - 3, 0)) return; + Double ceilingOffset = null; + for (int cy = y + 1; cy < fightTeam.getExtendRegion().getMaxY(); cy++) { + VoxelShape current = WORLD.getBlockAt(x, cy, z).getCollisionShape(); + if (!overlaps(current, 0, 0, 0)) continue; + + Double ceilingHeight = null; + for (BoundingBox box : current.getBoundingBoxes()) { + double by = box.getMinY(); + if (!overlaps(current, 0, -(1 - by), 0)) { + if (ceilingHeight == null) { + ceilingHeight = by; + } else { + ceilingHeight = Math.max(ceilingHeight, by); + } + } + } + if (ceilingHeight != null) { + ceilingOffset = cy - y + ceilingHeight - floorHeight; + } + break; + } + + if (ceilingOffset != null && ceilingOffset < PLAYER_HEIGHT) return; floorBlock.put(new Pos(x, y, z), y + floorHeight); + this.ceilingOffset.put(new Pos(x, y, z), ceilingOffset); + } + + private void checkNeighbouring(Pos pos, double posFloorHeight) { + for (BlockFace blockFace : FACES) { + for (int cy = pos.y - 2; cy <= pos.y + 2; cy++) { + Pos other = new Pos(pos.x + blockFace.getModX(), cy, pos.z + blockFace.getModZ()); + Double otherFloorHeight = floorBlock.get(other); + if (otherFloorHeight == null) continue; + double floorDiff = Math.abs(posFloorHeight - otherFloorHeight); + if (floorDiff > PLAYER_JUMP_HEIGHT) continue; + + Double posCeilingOffset = ceilingOffset.get(pos); + Double otherCeilingOffset = ceilingOffset.get(other); + if (posCeilingOffset == null && otherCeilingOffset == null) { + neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(blockFace); + continue; + } + + if (posCeilingOffset != null && posFloorHeight + posCeilingOffset - otherFloorHeight >= PLAYER_HEIGHT) { + neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(blockFace); + } + if (otherCeilingOffset != null && otherFloorHeight + otherCeilingOffset - posFloorHeight >= PLAYER_HEIGHT) { + neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(blockFace); + } + } + } + + // TODO: Ladder movement } private boolean overlaps(VoxelShape voxelShape, double x, double y, double z) { - PLAYER_SHAPE.shift(x, y, z); - boolean overlaps = voxelShape.overlaps(PLAYER_SHAPE); - PLAYER_SHAPE.shift(-x, -y, -z); + PLAYER_SHADOW.shift(x, y, z); + boolean overlaps = voxelShape.overlaps(PLAYER_SHADOW); + PLAYER_SHADOW.shift(-x, -y, -z); return overlaps; } - public void iterateWalkableBlocks(BiConsumer consumer) { + public void iterateWalkableBlocks(TriConsumer> consumer) { floorBlock.forEach((pos, aDouble) -> { Vector vector = new Vector(pos.x + 0.5, aDouble, pos.z + 0.5); - consumer.accept(vector, null); + consumer.accept(vector, ceilingOffset.get(pos), neighbourConnections.getOrDefault(pos, new HashSet<>())); }); } + + public interface TriConsumer { + + void accept(A a, B b, C c); + } } From 45fd36907cca01ab30b036a9a27fe69da7d2e3e6 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 3 Sep 2023 22:06:11 +0200 Subject: [PATCH 3/7] Add some more TODOS --- .../src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java index 4aaf9f4..7599eac 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java @@ -152,7 +152,9 @@ public class NavMesh implements Listener { private void checkNeighbouring(Pos pos, double posFloorHeight) { for (BlockFace blockFace : FACES) { + // TODO: Check FenceGates, Doors, usw. for (int cy = pos.y - 2; cy <= pos.y + 2; cy++) { + // TODO: Fix Ladder in Underground Pos other = new Pos(pos.x + blockFace.getModX(), cy, pos.z + blockFace.getModZ()); Double otherFloorHeight = floorBlock.get(other); if (otherFloorHeight == null) continue; From 69a0e02ab96754fe8e3f5a279310cd62e9bc5ebf Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 4 Sep 2023 18:38:05 +0200 Subject: [PATCH 4/7] Update and Fix some NavMesh stuff --- .../src/de/steamwar/fightsystem/ai/AI.java | 20 ++- .../de/steamwar/fightsystem/ai/LixfelAI.java | 24 ++- .../fightsystem/ai/navmesh/NavMesh.java | 168 +++++++++++++++--- 3 files changed, 179 insertions(+), 33 deletions(-) diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java index 6f5cb00..cf3be49 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java @@ -120,6 +120,22 @@ public abstract class AI { Chat.broadcastChat("PARTICIPANT_CHAT", team.getColoredName(), entity.getName(), message); } + protected Vector toAIPosition(Vector location) { + Region extend = team.getExtendRegion(); + if(Fight.getUnrotated() == team) + return new Vector( + location.getX() - extend.getMinX(), + location.getY() - team.getSchemRegion().getMinY(), + location.getZ() - extend.getMinZ() + ); + else + return new Vector( + extend.getMaxX() - location.getX(), + location.getY() - team.getSchemRegion().getMinY(), + extend.getMaxZ() - location.getZ() + ); + } + protected Vector getPosition() { Location location = entity.getLocation(); Region extend = team.getExtendRegion(); @@ -205,15 +221,17 @@ public abstract class AI { public void run() { Location location = entity.getLocation(); Location target = translate(pos, false); + /* if(Math.abs(location.getX() - target.getX()) > 1 || Math.abs(location.getY() - target.getY()) > 1.2 || Math.abs(location.getZ() - target.getZ()) > 1) { FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + ": Overdistance movement " + location.toVector() + " " + target.toVector()); return; } + */ if(!team.getFightPlayer(entity).canEntern() && !team.getExtendRegion().inRegion(target)) return; - entity.teleport(target, PlayerTeleportEvent.TeleportCause.COMMAND); + entity.teleport(target, PlayerTeleportEvent.TeleportCause.PLUGIN); } }); } diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java index 42bab27..0d43eab 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java @@ -22,6 +22,7 @@ package de.steamwar.fightsystem.ai; import de.steamwar.entity.REntityServer; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.ai.navmesh.NavMesh; +import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; @@ -33,6 +34,7 @@ import java.util.Random; public class LixfelAI extends AI { + private final Random random = new Random(); private final REntityServer entityServer = new REntityServer(); private final FightTeam team; private final NavMesh navMesh; @@ -45,9 +47,10 @@ public class LixfelAI extends AI { @Override public SchematicNode chooseSchematic() { - List publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB()); - SchematicNode schem = publics.get(new Random().nextInt(publics.size())); - return schem; + return SchematicNode.byIdAndUser(SteamwarUser.get(0), 111476); + // List publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB()); + // SchematicNode schem = publics.get(new Random().nextInt(publics.size())); + // return schem; } @Override @@ -61,5 +64,20 @@ public class LixfelAI extends AI { @Override protected void plan() { setReady(); + + if (navMesh == null) return; + + List walkableBlocks = navMesh.getWalkableBlocks(getEntity().getLocation().toVector()); + if (walkableBlocks.isEmpty()) return; + Vector destination = walkableBlocks.get(random.nextInt(walkableBlocks.size())); + List path = navMesh.path(getEntity().getLocation().toVector(), destination); + if (path.isEmpty()) return; + chat(getEntity().getLocation().toVector() + " -> " + destination + " = " + path.size()); + for(Vector p : path) { + move(toAIPosition(p)); + } + for (int i = 0; i < 40; i++) { + move(path.get(path.size() - 1)); + } } } diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java index 7599eac..b8f45af 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java @@ -46,7 +46,18 @@ public class NavMesh implements Listener { private static final double PLAYER_JUMP_HEIGHT = 1.25; private static final double PLAYER_HEIGHT = 1.8125; private static final BoundingBox PLAYER_SHADOW = new BoundingBox(0.2, 0, 0.2, 0.8, 1, 0.8); - private static final BlockFace[] FACES = new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST}; + private static final Set RELATIVE_BLOCKS_TO_CHECK = new HashSet<>(); + + static { + for (int y = -2; y <= 2; y++) { + RELATIVE_BLOCKS_TO_CHECK.add(new Pos(BlockFace.NORTH.getDirection().setY(y))); + RELATIVE_BLOCKS_TO_CHECK.add(new Pos(BlockFace.SOUTH.getDirection().setY(y))); + RELATIVE_BLOCKS_TO_CHECK.add(new Pos(BlockFace.EAST.getDirection().setY(y))); + RELATIVE_BLOCKS_TO_CHECK.add(new Pos(BlockFace.WEST.getDirection().setY(y))); + } + RELATIVE_BLOCKS_TO_CHECK.add(new Pos(BlockFace.UP.getDirection())); + RELATIVE_BLOCKS_TO_CHECK.add(new Pos(BlockFace.DOWN.getDirection())); + } private FightTeam fightTeam; @@ -56,17 +67,19 @@ public class NavMesh implements Listener { new OneShotStateDependent(ArenaMode.All, FightState.PostSchemSetup, () -> { Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> { long time = System.currentTimeMillis(); - fightTeam.getExtendRegion().forEach(this::checkWalkable); + fightTeam.getExtendRegion().forEach((x, y, z) -> { + if (y < fightTeam.getSchemRegion().getMinY()) return; + checkWalkable(x, y, z); + }); floorBlock.forEach(this::checkNeighbouring); System.out.println(System.currentTimeMillis() - time + " ms"); // For neighbour map -2-+2 blocks System.out.println(fightTeam + " " + floorBlock.size()); - iterateWalkableBlocks((vector, ceilingOffset, neightbourConnections) -> { - String connecting = neightbourConnections.stream().map(face -> face.name().substring(0, 1)).collect(Collectors.joining()); + iterateWalkableBlocks((vector, ceilingOffset) -> { RArmorStand armorStand = new RArmorStand(entityServer, vector.toLocation(WORLD), RArmorStand.Size.MARKER); armorStand.setNoGravity(true); armorStand.setInvisible(true); - armorStand.setDisplayName("+" + (ceilingOffset == null ? "∞" : ceilingOffset) + " " + connecting); + armorStand.setDisplayName("+" + (ceilingOffset == null ? "∞" : ceilingOffset)); }); }, 20); }); @@ -86,6 +99,12 @@ public class NavMesh implements Listener { this.z = z; } + public Pos(Vector vector) { + this.x = vector.getBlockX(); + this.y = vector.getBlockY(); + this.z = vector.getBlockZ(); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -98,11 +117,19 @@ public class NavMesh implements Listener { public int hashCode() { return Objects.hash(x, y, z); } + + public Vector toVector() { + return new Vector(x, y, z); + } + + public Pos offset(Pos pos) { + return new Pos(x + pos.x, y + pos.y, z + pos.z); + } } private Map floorBlock = new HashMap<>(); private Map ceilingOffset = new HashMap<>(); - private Map> neighbourConnections = new HashMap<>(); + private Map> neighbourConnections = new HashMap<>(); private void checkWalkable(int x, int y, int z) { Block block = WORLD.getBlockAt(x, y, z); @@ -151,33 +178,37 @@ public class NavMesh implements Listener { } private void checkNeighbouring(Pos pos, double posFloorHeight) { - for (BlockFace blockFace : FACES) { - // TODO: Check FenceGates, Doors, usw. - for (int cy = pos.y - 2; cy <= pos.y + 2; cy++) { - // TODO: Fix Ladder in Underground - Pos other = new Pos(pos.x + blockFace.getModX(), cy, pos.z + blockFace.getModZ()); - Double otherFloorHeight = floorBlock.get(other); - if (otherFloorHeight == null) continue; - double floorDiff = Math.abs(posFloorHeight - otherFloorHeight); - if (floorDiff > PLAYER_JUMP_HEIGHT) continue; + for (Pos relativeCheck : RELATIVE_BLOCKS_TO_CHECK) { + Pos other = new Pos(pos.x + relativeCheck.x, pos.y + relativeCheck.y, pos.z + relativeCheck.z); + Double otherFloorHeight = floorBlock.get(other); + if (otherFloorHeight == null) continue; + double floorDiff = Math.abs(posFloorHeight - otherFloorHeight); + if (floorDiff > PLAYER_JUMP_HEIGHT) continue; - Double posCeilingOffset = ceilingOffset.get(pos); - Double otherCeilingOffset = ceilingOffset.get(other); - if (posCeilingOffset == null && otherCeilingOffset == null) { - neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(blockFace); - continue; - } + Double posCeilingOffset = ceilingOffset.get(pos); + Double otherCeilingOffset = ceilingOffset.get(other); + if (posCeilingOffset == null && otherCeilingOffset == null) { + neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(relativeCheck); + continue; + } - if (posCeilingOffset != null && posFloorHeight + posCeilingOffset - otherFloorHeight >= PLAYER_HEIGHT) { - neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(blockFace); + if (posCeilingOffset != null && otherCeilingOffset == null) { + if (posFloorHeight + posCeilingOffset - otherFloorHeight >= PLAYER_HEIGHT) { + neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(relativeCheck); } - if (otherCeilingOffset != null && otherFloorHeight + otherCeilingOffset - posFloorHeight >= PLAYER_HEIGHT) { - neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(blockFace); + continue; + } + if (otherCeilingOffset != null && posCeilingOffset == null) { + if (otherFloorHeight + otherCeilingOffset - posFloorHeight >= PLAYER_HEIGHT) { + neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(relativeCheck); } + continue; + } + + if (posFloorHeight + posCeilingOffset - otherFloorHeight >= PLAYER_HEIGHT && otherFloorHeight + otherCeilingOffset - posFloorHeight >= PLAYER_HEIGHT) { + neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(relativeCheck); } } - - // TODO: Ladder movement } private boolean overlaps(VoxelShape voxelShape, double x, double y, double z) { @@ -187,10 +218,10 @@ public class NavMesh implements Listener { return overlaps; } - public void iterateWalkableBlocks(TriConsumer> consumer) { + private void iterateWalkableBlocks(BiConsumer consumer) { floorBlock.forEach((pos, aDouble) -> { Vector vector = new Vector(pos.x + 0.5, aDouble, pos.z + 0.5); - consumer.accept(vector, ceilingOffset.get(pos), neighbourConnections.getOrDefault(pos, new HashSet<>())); + consumer.accept(vector, ceilingOffset.get(pos)); }); } @@ -198,4 +229,83 @@ public class NavMesh implements Listener { void accept(A a, B b, C c); } + + private Pos toPos(Vector vector) { + Pos pos = new Pos(vector); + if (floorBlock.containsKey(pos)) return pos; + pos = new Pos(pos.x, pos.y - 1, pos.z); + if (floorBlock.containsKey(pos)) return pos; + return null; + } + + public List getAllWalkableBlocks() { + return floorBlock.keySet().stream().map(Pos::toVector).collect(Collectors.toList()); + } + + public List getWalkableBlocks(Vector fromVector) { + Pos from = toPos(fromVector); + if (from == null) { + return Collections.emptyList(); + } + + Set checked = new HashSet<>(); + List checking = new ArrayList<>(); + checking.add(from); + while (!checking.isEmpty()) { + Pos pos = checking.remove(0); + checked.add(pos); + + neighbourConnections.getOrDefault(pos, new HashSet<>()).forEach(p -> { + Pos n = pos.offset(p); + if (checked.contains(n)) return; + if (checking.contains(n)) return; + checking.add(n); + }); + } + + return checked.stream().map(Pos::toVector).collect(Collectors.toList()); + } + + public List path(Vector fromVector, Vector toVector) { + Pos from = toPos(fromVector); + Pos to = toPos(toVector); + if (from == null || to == null) { + return Collections.emptyList(); + } + + List checking = new ArrayList<>(Arrays.asList(to)); + Map route = new HashMap<>(); + while (!checking.isEmpty()) { + Set toCheck = new HashSet<>(); + for (Pos pos : checking) { + boolean foundFrom = neighbourConnections.getOrDefault(pos, new HashSet<>()).stream() + .map(pos::offset) + .filter(next -> !route.containsKey(next)) + .anyMatch(next -> { + route.put(next, pos); + toCheck.add(next); + return next.equals(from); + }); + + if (foundFrom) { + List path = new ArrayList<>(); + path.add(from); + + while (path.get(path.size() - 1) != to) { + path.add(route.get(path.get(path.size() - 1))); + } + + return path.stream().map(p -> { + double floorHeight = floorBlock.get(p); + return new Vector(p.x + 0.5, floorHeight, p.z + 0.5); + }).collect(Collectors.toList()); + } + } + + checking.clear(); + checking.addAll(toCheck); + } + + return Collections.emptyList(); + } } From f10238cae29cd1308bbe8b5343a58958ddc07ec2 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 4 Sep 2023 21:30:51 +0200 Subject: [PATCH 5/7] Update NavMesh and implement some kind of update --- .../de/steamwar/fightsystem/FightSystem.java | 1 + .../de/steamwar/fightsystem/ai/LixfelAI.java | 59 ++++++++++++----- .../fightsystem/ai/navmesh/NavMesh.java | 66 ++++++++++++++++--- 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index 56dee07..2aa5739 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -171,6 +171,7 @@ public class FightSystem extends JavaPlugin { } FightStatistics.unrank(); + FightWorld.forceLoad(); Bukkit.getScheduler().runTask(getPlugin(), () -> { new LixfelAI(Fight.getBlueTeam(), "Lixfel.AI"); diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java index 0d43eab..799edd1 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java @@ -22,7 +22,6 @@ package de.steamwar.fightsystem.ai; import de.steamwar.entity.REntityServer; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.ai.navmesh.NavMesh; -import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; @@ -47,10 +46,10 @@ public class LixfelAI extends AI { @Override public SchematicNode chooseSchematic() { - return SchematicNode.byIdAndUser(SteamwarUser.get(0), 111476); - // List publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB()); - // SchematicNode schem = publics.get(new Random().nextInt(publics.size())); - // return schem; + // return SchematicNode.byIdAndUser(SteamwarUser.get(0), 111476); + List publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB()); + SchematicNode schem = publics.get(new Random().nextInt(publics.size())); + return schem; } @Override @@ -61,23 +60,53 @@ public class LixfelAI extends AI { return super.acceptJoinRequest(player, team); } + private Vector source = null; + private Vector destination = null; + private int index = 0; + @Override protected void plan() { setReady(); if (navMesh == null) return; - List walkableBlocks = navMesh.getWalkableBlocks(getEntity().getLocation().toVector()); - if (walkableBlocks.isEmpty()) return; - Vector destination = walkableBlocks.get(random.nextInt(walkableBlocks.size())); - List path = navMesh.path(getEntity().getLocation().toVector(), destination); - if (path.isEmpty()) return; - chat(getEntity().getLocation().toVector() + " -> " + destination + " = " + path.size()); - for(Vector p : path) { - move(toAIPosition(p)); + if (source == null || destination == null) { + source = getEntity().getLocation().toVector(); + List walkableBlocks = navMesh.getWalkableBlocks(source); + if (walkableBlocks.isEmpty()) return; + destination = walkableBlocks.get(random.nextInt(walkableBlocks.size())); + index = 0; } - for (int i = 0; i < 40; i++) { - move(path.get(path.size() - 1)); + + List oldRoute = navMesh.path(source, destination); + navMesh.update(getEntity().getLocation().toVector()); + List path = navMesh.path(source, destination); + if (path.isEmpty()) { + source = null; + destination = null; + chat("no route"); + return; + } + if (!oldRoute.equals(path)) { + chat("new route " + path.size()); + } + if (index == 0) { + chat(source + " -> " + destination + " = " + path.size()); + } + + if (index > path.size()) { + source = null; + destination = null; + chat("route cancelled"); + return; + } + + Vector location = path.get(index++); + move(toAIPosition(location)); + + if (index == path.size()) { + source = null; + destination = null; } } } diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java index b8f45af..52db4d4 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java @@ -20,6 +20,7 @@ package de.steamwar.fightsystem.ai.navmesh; import de.steamwar.entity.RArmorStand; +import de.steamwar.entity.REntity; import de.steamwar.entity.REntityServer; import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.FightSystem; @@ -37,6 +38,7 @@ import org.bukkit.util.Vector; import org.bukkit.util.VoxelShape; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -60,9 +62,12 @@ public class NavMesh implements Listener { } private FightTeam fightTeam; + private List rEntities = new ArrayList<>(); + private REntityServer entityServer; public NavMesh(FightTeam fightTeam, REntityServer entityServer) { this.fightTeam = fightTeam; + this.entityServer = entityServer; new OneShotStateDependent(ArenaMode.All, FightState.PostSchemSetup, () -> { Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> { @@ -73,14 +78,14 @@ public class NavMesh implements Listener { }); floorBlock.forEach(this::checkNeighbouring); System.out.println(System.currentTimeMillis() - time + " ms"); - // For neighbour map -2-+2 blocks - System.out.println(fightTeam + " " + floorBlock.size()); + /* iterateWalkableBlocks((vector, ceilingOffset) -> { RArmorStand armorStand = new RArmorStand(entityServer, vector.toLocation(WORLD), RArmorStand.Size.MARKER); armorStand.setNoGravity(true); armorStand.setInvisible(true); armorStand.setDisplayName("+" + (ceilingOffset == null ? "∞" : ceilingOffset)); }); + */ }, 20); }); new OneShotStateDependent(ArenaMode.All, FightState.Spectate, () -> { @@ -132,6 +137,10 @@ public class NavMesh implements Listener { private Map> neighbourConnections = new HashMap<>(); private void checkWalkable(int x, int y, int z) { + Pos pos = new Pos(x, y, z); + floorBlock.remove(pos); + ceilingOffset.remove(pos); + Block block = WORLD.getBlockAt(x, y, z); VoxelShape floor = block.getCollisionShape(); if (block.getType() != Material.LADDER && !overlaps(floor, 0, 0, 0)) return; @@ -173,11 +182,13 @@ public class NavMesh implements Listener { if (ceilingOffset != null && ceilingOffset < PLAYER_HEIGHT) return; - floorBlock.put(new Pos(x, y, z), y + floorHeight); - this.ceilingOffset.put(new Pos(x, y, z), ceilingOffset); + floorBlock.put(pos, y + floorHeight); + this.ceilingOffset.put(pos, ceilingOffset); } private void checkNeighbouring(Pos pos, double posFloorHeight) { + neighbourConnections.remove(pos); + for (Pos relativeCheck : RELATIVE_BLOCKS_TO_CHECK) { Pos other = new Pos(pos.x + relativeCheck.x, pos.y + relativeCheck.y, pos.z + relativeCheck.z); Double otherFloorHeight = floorBlock.get(other); @@ -225,11 +236,6 @@ public class NavMesh implements Listener { }); } - public interface TriConsumer { - - void accept(A a, B b, C c); - } - private Pos toPos(Vector vector) { Pos pos = new Pos(vector); if (floorBlock.containsKey(pos)) return pos; @@ -238,6 +244,20 @@ public class NavMesh implements Listener { return null; } + public void update(Vector posVector) { + Pos pos = toPos(posVector); + if (pos == null) return; + + for (int x = -2; x <= 2; x++) { + for (int z = -2; z <= 2; z++) { + for (int y = fightTeam.getSchemRegion().getMinY(); y <= pos.y + 2; y++) { + checkWalkable(pos.x + x, y, pos.z + z); + } + } + } + floorBlock.forEach(this::checkNeighbouring); + } + public List getAllWalkableBlocks() { return floorBlock.keySet().stream().map(Pos::toVector).collect(Collectors.toList()); } @@ -267,11 +287,17 @@ public class NavMesh implements Listener { } public List path(Vector fromVector, Vector toVector) { + rEntities.forEach(REntity::die); + rEntities.clear(); + Pos from = toPos(fromVector); Pos to = toPos(toVector); if (from == null || to == null) { return Collections.emptyList(); } + if (from.equals(to)) { + return Collections.emptyList(); + } List checking = new ArrayList<>(Arrays.asList(to)); Map route = new HashMap<>(); @@ -295,10 +321,30 @@ public class NavMesh implements Listener { path.add(route.get(path.get(path.size() - 1))); } - return path.stream().map(p -> { + List vectors = path.stream().map(p -> { double floorHeight = floorBlock.get(p); return new Vector(p.x + 0.5, floorHeight, p.z + 0.5); }).collect(Collectors.toList()); + + AtomicReference last = new AtomicReference<>(); + vectors.forEach(vector -> { + RArmorStand armorStand = new RArmorStand(entityServer, vector.toLocation(WORLD), RArmorStand.Size.MARKER); + armorStand.setInvisible(true); + armorStand.setNoGravity(true); + armorStand.setDisplayName("+"); + rEntities.add(armorStand); + + Vector lastVector = last.getAndSet(vector); + if (lastVector == null) return; + lastVector = lastVector.clone().add(vector).divide(new Vector(2, 2, 2)); + armorStand = new RArmorStand(entityServer, lastVector.toLocation(WORLD), RArmorStand.Size.MARKER); + armorStand.setInvisible(true); + armorStand.setNoGravity(true); + armorStand.setDisplayName("+"); + rEntities.add(armorStand); + }); + + return vectors; } } From 578c0056cdf0271ebc5e41a9f5a103155c7138cc Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 4 Sep 2023 21:42:51 +0200 Subject: [PATCH 6/7] Add TODO line --- .../src/de/steamwar/fightsystem/ai/LixfelAI.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java index 799edd1..2b710a1 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java @@ -81,6 +81,7 @@ public class LixfelAI extends AI { List oldRoute = navMesh.path(source, destination); navMesh.update(getEntity().getLocation().toVector()); List path = navMesh.path(source, destination); + // TODO: New Route detection if (path.isEmpty()) { source = null; destination = null; @@ -88,7 +89,10 @@ public class LixfelAI extends AI { return; } if (!oldRoute.equals(path)) { - chat("new route " + path.size()); + source = getEntity().getLocation().toVector(); + index = 0; + chat("new route"); + return; } if (index == 0) { chat(source + " -> " + destination + " = " + path.size()); From cffa60e43d8710e8d3942561b765915f905d8cdf Mon Sep 17 00:00:00 2001 From: yoyosource Date: Tue, 5 Sep 2023 20:55:05 +0200 Subject: [PATCH 7/7] Fix NavMesh --- .../de/steamwar/fightsystem/ai/LixfelAI.java | 11 +++- .../fightsystem/ai/navmesh/NavMesh.java | 61 ++++++++++++++----- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java index 2b710a1..1d5ddd5 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java @@ -25,6 +25,7 @@ import de.steamwar.fightsystem.ai.navmesh.NavMesh; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.util.Vector; @@ -46,10 +47,13 @@ public class LixfelAI extends AI { @Override public SchematicNode chooseSchematic() { + if (false) { + List publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB()); + SchematicNode schem = publics.get(new Random().nextInt(publics.size())); + return schem; + } // return SchematicNode.byIdAndUser(SteamwarUser.get(0), 111476); - List publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB()); - SchematicNode schem = publics.get(new Random().nextInt(publics.size())); - return schem; + return SchematicNode.byIdAndUser(SteamwarUser.get(0), 98711); } @Override @@ -69,6 +73,7 @@ public class LixfelAI extends AI { setReady(); if (navMesh == null) return; + if (!getEntity().isOnGround() && getEntity().getLocation().getBlock().getType() != Material.LADDER) return; if (source == null || destination == null) { source = getEntity().getLocation().toVector(); diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java index 52db4d4..d746c71 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java @@ -110,6 +110,11 @@ public class NavMesh implements Listener { this.z = vector.getBlockZ(); } + @Override + public String toString() { + return x + "," + y + "," + z; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -127,14 +132,19 @@ public class NavMesh implements Listener { return new Vector(x, y, z); } - public Pos offset(Pos pos) { + public Pos add(Pos pos) { return new Pos(x + pos.x, y + pos.y, z + pos.z); } + + public Pos subtract(Pos pos) { + return new Pos(x - pos.x, y - pos.y, z - pos.z); + } } private Map floorBlock = new HashMap<>(); private Map ceilingOffset = new HashMap<>(); private Map> neighbourConnections = new HashMap<>(); + private Map> reverseNeighbourConnections = new HashMap<>(); private void checkWalkable(int x, int y, int z) { Pos pos = new Pos(x, y, z); @@ -187,37 +197,47 @@ public class NavMesh implements Listener { } private void checkNeighbouring(Pos pos, double posFloorHeight) { - neighbourConnections.remove(pos); + Set connections = neighbourConnections.remove(pos); + if (connections != null) { + connections.forEach(p -> { + reverseNeighbourConnections.getOrDefault(pos.add(p), Collections.emptySet()).remove(pos); + }); + } for (Pos relativeCheck : RELATIVE_BLOCKS_TO_CHECK) { Pos other = new Pos(pos.x + relativeCheck.x, pos.y + relativeCheck.y, pos.z + relativeCheck.z); Double otherFloorHeight = floorBlock.get(other); if (otherFloorHeight == null) continue; - double floorDiff = Math.abs(posFloorHeight - otherFloorHeight); - if (floorDiff > PLAYER_JUMP_HEIGHT) continue; + if (otherFloorHeight > posFloorHeight && otherFloorHeight - posFloorHeight > PLAYER_JUMP_HEIGHT) continue; + // double floorDiff = Math.abs(posFloorHeight - otherFloorHeight); + // if (floorDiff > PLAYER_JUMP_HEIGHT) continue; Double posCeilingOffset = ceilingOffset.get(pos); Double otherCeilingOffset = ceilingOffset.get(other); if (posCeilingOffset == null && otherCeilingOffset == null) { neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(relativeCheck); + reverseNeighbourConnections.computeIfAbsent(pos.add(relativeCheck), __ -> new LinkedHashSet<>()).add(pos); continue; } if (posCeilingOffset != null && otherCeilingOffset == null) { if (posFloorHeight + posCeilingOffset - otherFloorHeight >= PLAYER_HEIGHT) { neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(relativeCheck); + reverseNeighbourConnections.computeIfAbsent(pos.add(relativeCheck), __ -> new LinkedHashSet<>()).add(pos); } continue; } if (otherCeilingOffset != null && posCeilingOffset == null) { if (otherFloorHeight + otherCeilingOffset - posFloorHeight >= PLAYER_HEIGHT) { neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(relativeCheck); + reverseNeighbourConnections.computeIfAbsent(pos.add(relativeCheck), __ -> new LinkedHashSet<>()).add(pos); } continue; } if (posFloorHeight + posCeilingOffset - otherFloorHeight >= PLAYER_HEIGHT && otherFloorHeight + otherCeilingOffset - posFloorHeight >= PLAYER_HEIGHT) { neighbourConnections.computeIfAbsent(pos, __ -> new LinkedHashSet<>()).add(relativeCheck); + reverseNeighbourConnections.computeIfAbsent(pos.add(relativeCheck), __ -> new LinkedHashSet<>()).add(pos); } } } @@ -276,7 +296,7 @@ public class NavMesh implements Listener { checked.add(pos); neighbourConnections.getOrDefault(pos, new HashSet<>()).forEach(p -> { - Pos n = pos.offset(p); + Pos n = pos.add(p); if (checked.contains(n)) return; if (checking.contains(n)) return; checking.add(n); @@ -304,14 +324,15 @@ public class NavMesh implements Listener { while (!checking.isEmpty()) { Set toCheck = new HashSet<>(); for (Pos pos : checking) { - boolean foundFrom = neighbourConnections.getOrDefault(pos, new HashSet<>()).stream() - .map(pos::offset) - .filter(next -> !route.containsKey(next)) - .anyMatch(next -> { - route.put(next, pos); - toCheck.add(next); - return next.equals(from); - }); + boolean foundFrom = false; + Set successors = reverseNeighbourConnections.get(pos); + for (Pos p : successors) { + if (route.containsKey(p)) continue; + route.put(p, pos); + toCheck.add(p); + foundFrom = p.equals(from); + if (foundFrom) break; + } if (foundFrom) { List path = new ArrayList<>(); @@ -321,9 +342,18 @@ public class NavMesh implements Listener { path.add(route.get(path.get(path.size() - 1))); } + for (int i = path.size() - 1; i > 0; i--) { + Pos current = path.get(i); + Pos last = path.get(i - 1); + if (last.y > current.y) { + path.set(i, new Pos(current.x, last.y, current.z)); + } + } + List vectors = path.stream().map(p -> { - double floorHeight = floorBlock.get(p); - return new Vector(p.x + 0.5, floorHeight, p.z + 0.5); + Double floorHeight = floorBlock.get(p); + if (p.y > (floorHeight == null ? p.y : floorHeight)) floorHeight = null; + return new Vector(p.x + 0.5, floorHeight == null ? p.y : floorHeight, p.z + 0.5); }).collect(Collectors.toList()); AtomicReference last = new AtomicReference<>(); @@ -334,6 +364,7 @@ public class NavMesh implements Listener { armorStand.setDisplayName("+"); rEntities.add(armorStand); + if (true) return; Vector lastVector = last.getAndSet(vector); if (lastVector == null) return; lastVector = lastVector.clone().add(vector).divide(new Vector(2, 2, 2));