From e7bb1e6669499139abfb505663df7ac41bd860dd Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 2 Sep 2023 18:23:30 +0200 Subject: [PATCH] Pathplanning Signed-off-by: Lixfel --- .../src/de/steamwar/fightsystem/ai/AI.java | 10 +- .../de/steamwar/fightsystem/ai/LixfelAI.java | 22 ++- .../fightsystem/ai/LixfelPathplanner.java | 129 ++++++++++++++++++ 3 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelPathplanner.java diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java index 96d9df9..1706a93 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java @@ -42,9 +42,7 @@ import org.bukkit.block.data.Powerable; import org.bukkit.block.data.type.Comparator; import org.bukkit.block.data.type.NoteBlock; import org.bukkit.block.data.type.Repeater; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; +import org.bukkit.entity.*; import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.Vector; @@ -149,6 +147,9 @@ public abstract class AI { queue.add(new Action(1) { @Override public void run() { + if(FightState.getFightState() != FightState.RUNNING) + return; + Location location = translate(pos, true); if(interactionDistanceViolation(location)) return; @@ -206,6 +207,9 @@ public abstract class AI { if(Math.abs(location.getX() - target.getX()) > 1 || Math.abs(location.getY() - target.getY()) > 1 || Math.abs(location.getZ() - target.getZ()) > 1) return; + if(!team.getFightPlayer(entity).canEntern() && !team.getExtendRegion().inRegion(target)) + return; + entity.teleport(target); } }); diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java index fcc834b..2a89353 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java @@ -23,24 +23,38 @@ import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; +import org.bukkit.util.Vector; import java.util.List; import java.util.Random; public class LixfelAI extends AI { - public LixfelAI(FightTeam team) { - super(team, SteamwarUser.get("public")); + + private final Random random = new Random(); + private LixfelPathplanner pathplanner; + + public LixfelAI(FightTeam team, String user) { + super(team, SteamwarUser.get(user)); } + @Override public SchematicNode chooseSchematic() { List publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB()); - return publics.get(new Random().nextInt(publics.size())); + SchematicNode schem = publics.get(new Random().nextInt(publics.size())); + pathplanner = new LixfelPathplanner(schem); + return schem; } @Override protected void plan() { setReady(); - getEntity().setAI(true); + 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/LixfelPathplanner.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelPathplanner.java new file mode 100644 index 0000000..463bed5 --- /dev/null +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelPathplanner.java @@ -0,0 +1,129 @@ +/* + * 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; + +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.world.block.BlockType; +import de.steamwar.fightsystem.Config; +import de.steamwar.sql.SchematicData; +import de.steamwar.sql.SchematicNode; +import org.bukkit.util.Vector; + +import java.io.IOException; +import java.util.*; + +public class LixfelPathplanner { + + private static BlockType getBlockType(Clipboard clipboard, BlockVector3 vector) { + return clipboard.getBlock(vector).getBlockType(); + } + + private static boolean nonsolid(Clipboard clipboard, BlockVector3 vector) { + return !getBlockType(clipboard, vector).getMaterial().isSolid(); + } + + private static Vector toBukkit(BlockVector3 vector) { + return new Vector(vector.getX(), vector.getY(), vector.getZ()); + } + + private final List walkable = new ArrayList<>(); + private final Map neighbours = new HashMap<>(); + + public LixfelPathplanner(SchematicNode schem) { + try { + fillWalkable(new SchematicData(schem).load()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public List getWalkable() { + return walkable; + } + + private void fillWalkable(Clipboard clipboard) { + BlockVector3 min = clipboard.getRegion().getMinimumPoint().subtract(Config.PreperationArea, 0, Config.PreperationArea); //TODO assumes nonextended Schematic with maximal size + Region region = clipboard.getRegion(); + clipboard.getRegion().forEach(vector -> { + BlockVector3 below = vector.subtract(0, 1, 0); + if(!region.contains(below)) + return; + + BlockType belowMaterial = getBlockType(clipboard, below); + BlockVector3 above = vector.add(0, 1, 0); + if( + (belowMaterial.getMaterial().isSolid() || belowMaterial.getId().equals("minecraft:ladder")) && + nonsolid(clipboard, vector) && + (!region.contains(above) || nonsolid(clipboard, above)) + ) + walkable.add(toBukkit(vector.subtract(min))); + }); + + for(Vector vector : walkable) { + neighbours.put(vector, walkable.stream().filter(neighbour -> neighbouring(neighbour, vector)).filter(neighbour -> neighbour != vector).toArray(Vector[]::new)); + } + } + + public List planToAnywhere(Vector start, Vector destination) { + return plan(start, destination); //TODO (destination neighbor search) + } + + public List plan(Vector start, Vector destination) { + if(neighbouring(start, destination)) + return Collections.singletonList(destination); + + Map approach = new HashMap<>(); + Set checking = Collections.singleton(destination); + + while(!checking.isEmpty()) { + Set toCheck = new HashSet<>(); + for(Vector current : checking) { + Vector firstStep = Arrays.stream(neighbours.get(current)) + .filter(vector -> !approach.containsKey(vector)) + .filter(next -> { + approach.put(next, current); + toCheck.add(next); + return neighbouring(next, start); + }) + .findAny().orElse(null); + + if(firstStep != null) { + List path = new ArrayList<>(); + path.add(firstStep); + + while(path.get(path.size()-1) != destination) { + path.add(approach.get(path.get(path.size()-1))); + } + + return path; + } + } + checking = toCheck; + } + + return Collections.emptyList(); + } + + private boolean neighbouring(Vector a, Vector b) { + return Math.abs(a.getX() - b.getX()) <= 1 && Math.abs(a.getY() - b.getY()) <= 1 && Math.abs(a.getZ() - b.getZ()) <= 1; + } +}