Pathplanning #399
@ -45,6 +45,8 @@ import org.bukkit.block.data.type.Repeater;
|
|||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.LivingEntity;
|
import org.bukkit.entity.LivingEntity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
@ -76,7 +78,7 @@ public abstract class AI {
|
|||||||
|
|
||||||
entity = (LivingEntity) Config.world.spawnEntity(Config.SpecSpawn, EntityType.VILLAGER);
|
entity = (LivingEntity) Config.world.spawnEntity(Config.SpecSpawn, EntityType.VILLAGER);
|
||||||
entity.setCustomName(user.getUserName());
|
entity.setCustomName(user.getUserName());
|
||||||
entity.setAI(false);
|
((Villager)entity).setAware(false);
|
||||||
|
|
||||||
task = Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), this::run, 1, 1);
|
task = Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), this::run, 1, 1);
|
||||||
ais.put(entity.getUniqueId(), this);
|
ais.put(entity.getUniqueId(), this);
|
||||||
@ -114,7 +116,7 @@ public abstract class AI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void chat(String message) {
|
protected void chat(String message) {
|
||||||
FightSystem.getPlugin().getLogger().log(Level.INFO, entity.getName() + "» " + message);
|
FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + "» " + message);
|
||||||
Chat.broadcastChat("PARTICIPANT_CHAT", team.getColoredName(), entity.getName(), message);
|
Chat.broadcastChat("PARTICIPANT_CHAT", team.getColoredName(), entity.getName(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,13 +126,13 @@ public abstract class AI {
|
|||||||
if(Fight.getUnrotated() == team)
|
if(Fight.getUnrotated() == team)
|
||||||
return new Vector(
|
return new Vector(
|
||||||
location.getX() - extend.getMinX(),
|
location.getX() - extend.getMinX(),
|
||||||
location.getY() - extend.getMinY(),
|
location.getY() - team.getSchemRegion().getMinY(),
|
||||||
location.getZ() - extend.getMinZ()
|
location.getZ() - extend.getMinZ()
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
return new Vector(
|
return new Vector(
|
||||||
extend.getMaxX() - location.getX(),
|
extend.getMaxX() - location.getX(),
|
||||||
location.getY() - extend.getMinY(),
|
location.getY() - team.getSchemRegion().getMinY(),
|
||||||
extend.getMaxZ() - location.getZ()
|
extend.getMaxZ() - location.getZ()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -149,6 +151,9 @@ public abstract class AI {
|
|||||||
queue.add(new Action(1) {
|
queue.add(new Action(1) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if(FightState.getFightState() != FightState.RUNNING)
|
||||||
|
return;
|
||||||
|
|
||||||
Location location = translate(pos, true);
|
Location location = translate(pos, true);
|
||||||
if(interactionDistanceViolation(location))
|
if(interactionDistanceViolation(location))
|
||||||
return;
|
return;
|
||||||
@ -198,15 +203,17 @@ public abstract class AI {
|
|||||||
queue.add(new Action(2) {
|
queue.add(new Action(2) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if(!entity.isOnGround())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Location location = entity.getLocation();
|
Location location = entity.getLocation();
|
||||||
Location target = translate(pos, false);
|
Location target = translate(pos, false);
|
||||||
if(Math.abs(location.getX() - target.getX()) > 1 || Math.abs(location.getY() - target.getY()) > 1 || Math.abs(location.getZ() - target.getZ()) > 1)
|
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;
|
return;
|
||||||
|
|
||||||
entity.teleport(target);
|
entity.teleport(target, PlayerTeleportEvent.TeleportCause.COMMAND);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -264,14 +271,14 @@ public abstract class AI {
|
|||||||
return new Location(
|
return new Location(
|
||||||
Config.world,
|
Config.world,
|
||||||
pos.getX() + extend.getMinX(),
|
pos.getX() + extend.getMinX(),
|
||||||
pos.getY() + extend.getMinY(),
|
pos.getY() + team.getSchemRegion().getMinY(),
|
||||||
pos.getZ() + extend.getMinZ()
|
pos.getZ() + extend.getMinZ()
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
return new Location(
|
return new Location(
|
||||||
Config.world,
|
Config.world,
|
||||||
extend.getMaxX() - pos.getX() - (blockPos ? 1 : 0),
|
extend.getMaxX() - pos.getX() - (blockPos ? 1 : 0),
|
||||||
pos.getY() + extend.getMinY(),
|
pos.getY() + team.getSchemRegion().getMinY(),
|
||||||
extend.getMaxZ() - pos.getZ() - (blockPos ? 1 : 0)
|
extend.getMaxZ() - pos.getZ() - (blockPos ? 1 : 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,24 +23,38 @@ import de.steamwar.fightsystem.Config;
|
|||||||
import de.steamwar.fightsystem.fight.FightTeam;
|
import de.steamwar.fightsystem.fight.FightTeam;
|
||||||
import de.steamwar.sql.SchematicNode;
|
import de.steamwar.sql.SchematicNode;
|
||||||
import de.steamwar.sql.SteamwarUser;
|
import de.steamwar.sql.SteamwarUser;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class LixfelAI extends AI {
|
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
|
@Override
|
||||||
public SchematicNode chooseSchematic() {
|
public SchematicNode chooseSchematic() {
|
||||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB());
|
List<SchematicNode> 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
|
@Override
|
||||||
protected void plan() {
|
protected void plan() {
|
||||||
setReady();
|
setReady();
|
||||||
getEntity().setAI(true);
|
Vector destination = pathplanner.getWalkable().get(random.nextInt(pathplanner.getWalkable().size()));
|
||||||
|
List<Vector> path = pathplanner.plan(getPosition(), destination);
|
||||||
|
if(!path.isEmpty())
|
||||||
|
chat("Path size: " + path.size());
|
||||||
|
for(Vector p : path) {
|
||||||
|
move(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
141
FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelPathplanner.java
Normale Datei
141
FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelPathplanner.java
Normale Datei
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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() + 0.5, vector.getY(), vector.getZ() + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Vector> walkable = new ArrayList<>();
|
||||||
|
private final Map<Vector, Vector[]> neighbours = new HashMap<>();
|
||||||
|
|
||||||
|
public LixfelPathplanner(SchematicNode schem) {
|
||||||
|
try {
|
||||||
|
fillWalkable(new SchematicData(schem).load());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Vector> 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(nonsolid(clipboard, vector)) {
|
||||||
|
if(
|
||||||
|
(belowMaterial.getMaterial().isSolid() || belowMaterial.getId().equals("minecraft:ladder")) &&
|
||||||
|
(!region.contains(above) || nonsolid(clipboard, above))
|
||||||
|
)
|
||||||
|
walkable.add(toBukkit(vector.subtract(min)));
|
||||||
|
} else {
|
||||||
|
if(!region.contains(above))
|
||||||
|
walkable.add(toBukkit(above.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<Vector> planToAnywhere(Vector start, Vector destination) {
|
||||||
|
Vector intermediate = walkable.stream().filter(vector -> neighbouring(vector, destination)).findAny().orElse(null);
|
||||||
|
|
||||||
|
if(intermediate == null)
|
||||||
|
return Collections.emptyList();
|
||||||
|
|
||||||
|
List<Vector> plan = plan(start, intermediate);
|
||||||
|
plan.add(destination);
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Vector> plan(Vector start, Vector destination) {
|
||||||
|
if(neighbouring(start, destination))
|
||||||
|
return Collections.singletonList(destination);
|
||||||
|
|
||||||
|
Map<Vector, Vector> approach = new HashMap<>();
|
||||||
|
Set<Vector> checking = Collections.singleton(destination);
|
||||||
|
|
||||||
|
while(!checking.isEmpty()) {
|
||||||
|
Set<Vector> 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<Vector> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -89,8 +89,11 @@ public class FightPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ifAI(Consumer<AI> function) {
|
public void ifAI(Consumer<AI> function) {
|
||||||
if(!(entity instanceof Player))
|
if(entity instanceof Player)
|
||||||
function.accept(AI.getAI(entity.getUniqueId()));
|
return;
|
||||||
|
AI ai = AI.getAI(entity.getUniqueId());
|
||||||
|
if(ai != null)
|
||||||
|
function.accept(ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ifPlayer(Consumer<Player> function) {
|
public void ifPlayer(Consumer<Player> function) {
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren