forked from SteamWar/SteamWar
Add WorldCoordinate and LocalCoordinate to remove mixup
Improve NavMesh Add Action
This commit is contained in:
@@ -23,14 +23,12 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
|||||||
import de.steamwar.fightsystem.ArenaMode;
|
import de.steamwar.fightsystem.ArenaMode;
|
||||||
import de.steamwar.fightsystem.Config;
|
import de.steamwar.fightsystem.Config;
|
||||||
import de.steamwar.fightsystem.FightSystem;
|
import de.steamwar.fightsystem.FightSystem;
|
||||||
import de.steamwar.fightsystem.fight.Fight;
|
|
||||||
import de.steamwar.fightsystem.fight.FightTeam;
|
import de.steamwar.fightsystem.fight.FightTeam;
|
||||||
import de.steamwar.fightsystem.fight.JoinRequest;
|
import de.steamwar.fightsystem.fight.JoinRequest;
|
||||||
import de.steamwar.fightsystem.listener.Chat;
|
import de.steamwar.fightsystem.listener.Chat;
|
||||||
import de.steamwar.fightsystem.record.GlobalRecorder;
|
import de.steamwar.fightsystem.record.GlobalRecorder;
|
||||||
import de.steamwar.fightsystem.states.FightState;
|
import de.steamwar.fightsystem.states.FightState;
|
||||||
import de.steamwar.fightsystem.states.OneShotStateDependent;
|
import de.steamwar.fightsystem.states.OneShotStateDependent;
|
||||||
import de.steamwar.fightsystem.utils.Region;
|
|
||||||
import de.steamwar.sql.SchematicNode;
|
import de.steamwar.sql.SchematicNode;
|
||||||
import de.steamwar.sql.SteamwarUser;
|
import de.steamwar.sql.SteamwarUser;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -54,7 +52,6 @@ import org.bukkit.entity.LivingEntity;
|
|||||||
import org.bukkit.entity.Villager;
|
import org.bukkit.entity.Villager;
|
||||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
import org.bukkit.util.Vector;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -87,6 +84,8 @@ public abstract class AI {
|
|||||||
private final BukkitTask task;
|
private final BukkitTask task;
|
||||||
private final Queue<Action> queue = new ArrayDeque<>();
|
private final Queue<Action> queue = new ArrayDeque<>();
|
||||||
|
|
||||||
|
protected final NavMesh navMesh;
|
||||||
|
|
||||||
protected AI(FightTeam team, SteamwarUser user) {
|
protected AI(FightTeam team, SteamwarUser user) {
|
||||||
this.team = team;
|
this.team = team;
|
||||||
|
|
||||||
@@ -98,6 +97,8 @@ public abstract class AI {
|
|||||||
ais.put(entity.getUniqueId(), this);
|
ais.put(entity.getUniqueId(), this);
|
||||||
team.addMember(entity, user);
|
team.addMember(entity, user);
|
||||||
|
|
||||||
|
navMesh = new NavMesh(team);
|
||||||
|
|
||||||
if(FightState.Schem.contains(FightState.getFightState()))
|
if(FightState.Schem.contains(FightState.getFightState()))
|
||||||
Bukkit.getScheduler().runTask(FightSystem.getPlugin(), () -> schematic(team.getClipboard()));
|
Bukkit.getScheduler().runTask(FightSystem.getPlugin(), () -> schematic(team.getClipboard()));
|
||||||
}
|
}
|
||||||
@@ -134,45 +135,40 @@ public abstract class AI {
|
|||||||
Chat.broadcastChat("PARTICIPANT_CHAT", team.getColoredName(), entity.getName(), message);
|
Chat.broadcastChat("PARTICIPANT_CHAT", team.getColoredName(), entity.getName(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector getPosition() {
|
public LocalCoordinate getPosition() {
|
||||||
Location location = entity.getLocation();
|
return WorldCoordinate.from(entity.getLocation()).toLocal(team);
|
||||||
return untranslate(location.toVector());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector untranslate(Vector vector) {
|
public Material getBlock(WorldCoordinate pos) {
|
||||||
Region extend = team.getExtendRegion();
|
return getBlock(pos.toLocal(team));
|
||||||
if(Fight.getUnrotated() == team)
|
|
||||||
return new Vector(
|
|
||||||
vector.getX() - extend.getMinX(),
|
|
||||||
vector.getY() - team.getSchemRegion().getMinY(),
|
|
||||||
vector.getZ() - extend.getMinZ()
|
|
||||||
);
|
|
||||||
else
|
|
||||||
return new Vector(
|
|
||||||
extend.getMaxX() - vector.getX(),
|
|
||||||
vector.getY() - team.getSchemRegion().getMinY(),
|
|
||||||
extend.getMaxZ() - vector.getZ()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Material getBlock(Vector pos) {
|
public Material getBlock(LocalCoordinate pos) {
|
||||||
queue.add(new Action(1));
|
queue.add(new Action(1));
|
||||||
return translate(pos).getBlock().getType();
|
return pos.toWorld(team).toLocation(Config.world).getBlock().getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockData getBlockData(Vector pos) {
|
public BlockData getBlockData(WorldCoordinate pos) {
|
||||||
|
return getBlockData(pos.toLocal(team));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockData getBlockData(LocalCoordinate pos) {
|
||||||
queue.add(new Action(1));
|
queue.add(new Action(1));
|
||||||
return translate(pos).getBlock().getBlockData();
|
return pos.toWorld(team).toLocation(Config.world).getBlock().getBlockData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTNT(Vector pos) {
|
public void setTNT(WorldCoordinate pos) {
|
||||||
|
setTNT(pos.toLocal(team));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTNT(LocalCoordinate pos) {
|
||||||
queue.add(new Action(1) {
|
queue.add(new Action(1) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if(FightState.getFightState() != FightState.RUNNING)
|
if(FightState.getFightState() != FightState.RUNNING)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Location location = translate(pos);
|
Location location = pos.toWorld(team).toLocation(Config.world);
|
||||||
if(interactionDistanceViolation(location)) {
|
if(interactionDistanceViolation(location)) {
|
||||||
chat("InteractionDistanceViolation: setTNT");
|
chat("InteractionDistanceViolation: setTNT");
|
||||||
return;
|
return;
|
||||||
@@ -185,11 +181,15 @@ public abstract class AI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void interact(Vector pos) {
|
public void interact(WorldCoordinate pos) {
|
||||||
|
interact(pos.toLocal(team));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void interact(LocalCoordinate pos) {
|
||||||
queue.add(new Action(1) {
|
queue.add(new Action(1) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Location location = translate(pos);
|
Location location = pos.toWorld(team).toLocation(Config.world);
|
||||||
if(interactionDistanceViolation(location)) {
|
if(interactionDistanceViolation(location)) {
|
||||||
chat("InteractionDistanceViolation: interact");
|
chat("InteractionDistanceViolation: interact");
|
||||||
return;
|
return;
|
||||||
@@ -200,11 +200,15 @@ public abstract class AI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void interact(Vector pos, int n) {
|
public void interact(WorldCoordinate pos, int n) {
|
||||||
|
interact(pos.toLocal(team), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void interact(LocalCoordinate pos, int n) {
|
||||||
queue.add(new Action(1) {
|
queue.add(new Action(1) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Location location = translate(pos);
|
Location location = pos.toWorld(team).toLocation(Config.world);
|
||||||
if (interactionDistanceViolation(location))
|
if (interactionDistanceViolation(location))
|
||||||
return;
|
return;
|
||||||
Block block = location.getBlock();
|
Block block = location.getBlock();
|
||||||
@@ -221,24 +225,54 @@ public abstract class AI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void move(Vector pos) {
|
public MoveResult checkMove(WorldCoordinate target) {
|
||||||
|
Location location = entity.getLocation();
|
||||||
|
if(!entity.isOnGround() && location.getBlock().getType() != Material.LADDER) {
|
||||||
|
return MoveResult.FALLING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Math.abs(location.getX() - target.getX()) > 1.5 || Math.abs(location.getY() - target.getY()) > 1.5 || Math.abs(location.getZ() - target.getZ()) > 1.5) {
|
||||||
|
return MoveResult.OVERDISTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!team.getFightPlayer(entity).canEntern() && !team.getExtendRegion().inRegion(target.toLocation(null)))
|
||||||
|
return MoveResult.OUT_OF_BORDER;
|
||||||
|
return MoveResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MoveResult {
|
||||||
|
FALLING,
|
||||||
|
OVERDISTANCE,
|
||||||
|
OUT_OF_BORDER,
|
||||||
|
OK,
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(WorldCoordinate pos) {
|
||||||
queue.add(new Action(MOVEMENT_DELAY) {
|
queue.add(new Action(MOVEMENT_DELAY) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Location location = entity.getLocation();
|
MoveResult moveResult = checkMove(pos);
|
||||||
if(!entity.isOnGround() && location.getBlock().getType() != Material.LADDER) {
|
switch (moveResult) {
|
||||||
|
case FALLING:
|
||||||
FightSystem.getPlugin().getLogger().log(Level.INFO, "Entity falling");
|
FightSystem.getPlugin().getLogger().log(Level.INFO, "Entity falling");
|
||||||
return;
|
return;
|
||||||
|
case OVERDISTANCE:
|
||||||
|
FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + ": Overdistance movement " + entity.getLocation().toVector() + " " + pos);
|
||||||
|
return;
|
||||||
|
case OUT_OF_BORDER:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Location target = translate(pos);
|
Location currentLocation = entity.getLocation();
|
||||||
if(Math.abs(location.getX() - target.getX()) > 1.5 || Math.abs(location.getY() - target.getY()) > 1.5 || Math.abs(location.getZ() - target.getZ()) > 1.5) {
|
Location target = pos.toLocation(Config.world);
|
||||||
FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + ": Overdistance movement " + location.toVector() + " " + target.toVector());
|
if (currentLocation.getBlockX() != target.getBlockX() || currentLocation.getBlockY() != target.getBlockY() || currentLocation.getBlockZ() != target.getBlockZ()) {
|
||||||
return;
|
target.setDirection(target.toVector().subtract(currentLocation.toVector()));
|
||||||
|
}
|
||||||
|
if (currentLocation.getBlockX() == target.getBlockX() && currentLocation.getBlockZ() == target.getBlockZ()) {
|
||||||
|
target.setYaw(currentLocation.getYaw());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!team.getFightPlayer(entity).canEntern() && !team.getExtendRegion().inRegion(target))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(!entity.teleport(target, PlayerTeleportEvent.TeleportCause.PLUGIN))
|
if(!entity.teleport(target, PlayerTeleportEvent.TeleportCause.PLUGIN))
|
||||||
FightSystem.getPlugin().getLogger().log(Level.INFO, "Entity not teleported: " + entity.isValid());
|
FightSystem.getPlugin().getLogger().log(Level.INFO, "Entity not teleported: " + entity.isValid());
|
||||||
@@ -248,6 +282,10 @@ public abstract class AI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void move(LocalCoordinate pos) {
|
||||||
|
move(pos.toWorld(team));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean interactionDistanceViolation(Location location) {
|
private boolean interactionDistanceViolation(Location location) {
|
||||||
return location.distance(entity.getEyeLocation()) > INTERACTION_RANGE;
|
return location.distance(entity.getEyeLocation()) > INTERACTION_RANGE;
|
||||||
}
|
}
|
||||||
@@ -312,6 +350,7 @@ public abstract class AI {
|
|||||||
private void run() {
|
private void run() {
|
||||||
if(queue.isEmpty()) {
|
if(queue.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
|
navMesh.update(getPosition().toWorld(team));
|
||||||
plan();
|
plan();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
stop();
|
stop();
|
||||||
@@ -323,24 +362,6 @@ public abstract class AI {
|
|||||||
queue.poll().run();
|
queue.poll().run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Location translate(Vector pos) {
|
|
||||||
Region extend = team.getExtendRegion();
|
|
||||||
if(Fight.getUnrotated() == team)
|
|
||||||
return new Location(
|
|
||||||
Config.world,
|
|
||||||
pos.getX() + extend.getMinX(),
|
|
||||||
pos.getY() + team.getSchemRegion().getMinY(),
|
|
||||||
pos.getZ() + extend.getMinZ()
|
|
||||||
);
|
|
||||||
else
|
|
||||||
return new Location(
|
|
||||||
Config.world,
|
|
||||||
extend.getMaxX() - pos.getX(),
|
|
||||||
pos.getY() + team.getSchemRegion().getMinY(),
|
|
||||||
extend.getMaxZ() - pos.getZ()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Action {
|
private static class Action {
|
||||||
private int delay;
|
private int delay;
|
||||||
public Action(int delay) {
|
public Action(int delay) {
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 java.util.List;
|
||||||
|
|
||||||
|
public interface Action {
|
||||||
|
boolean isCompletable();
|
||||||
|
Result step(AI ai);
|
||||||
|
|
||||||
|
enum Result {
|
||||||
|
ONGOING,
|
||||||
|
FINISHED,
|
||||||
|
FAILED,
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveAction implements Action {
|
||||||
|
|
||||||
|
private final WorldCoordinate destination;
|
||||||
|
private List<WorldCoordinate> path;
|
||||||
|
private int overDistanceCounter = 0;
|
||||||
|
|
||||||
|
public MoveAction(AI ai, LocalCoordinate destination) {
|
||||||
|
this(ai, destination.toWorld(ai.team));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoveAction(AI ai, WorldCoordinate destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
this.path = ai.navMesh.pathToNearest(ai.getPosition().toWorld(ai.team), destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCompletable() {
|
||||||
|
return !path.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result step(AI ai) {
|
||||||
|
if (this.path.isEmpty()) return Result.FAILED;
|
||||||
|
WorldCoordinate coordinate = path.get(0);
|
||||||
|
|
||||||
|
if (!ai.navMesh.isWalkable(coordinate)) {
|
||||||
|
path = ai.navMesh.pathToNearest(ai.getPosition().toWorld(ai.team), destination);
|
||||||
|
if (path.isEmpty()) return Result.FAILED;
|
||||||
|
coordinate = path.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
AI.MoveResult moveResult = ai.checkMove(coordinate);
|
||||||
|
if (moveResult == AI.MoveResult.OVERDISTANCE && overDistanceCounter++ > 5) {
|
||||||
|
return Result.FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moveResult == AI.MoveResult.FALLING) {
|
||||||
|
return Result.ONGOING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moveResult == AI.MoveResult.OK) {
|
||||||
|
path.remove(0);
|
||||||
|
ai.move(coordinate);
|
||||||
|
}
|
||||||
|
return path.isEmpty() ? Result.FINISHED : Result.ONGOING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InteractAction implements Action {
|
||||||
|
private final LocalCoordinate destination;
|
||||||
|
|
||||||
|
public InteractAction(LocalCoordinate destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCompletable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result step(AI ai) {
|
||||||
|
ai.interact(destination);
|
||||||
|
return Result.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 de.steamwar.fightsystem.fight.Fight;
|
||||||
|
import de.steamwar.fightsystem.fight.FightTeam;
|
||||||
|
import de.steamwar.fightsystem.utils.Region;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.util.NumberConversions;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LocalCoordinate {
|
||||||
|
private double x;
|
||||||
|
private double y;
|
||||||
|
private double z;
|
||||||
|
|
||||||
|
public int getBlockX() {
|
||||||
|
return NumberConversions.floor(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockY() {
|
||||||
|
return NumberConversions.floor(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockZ() {
|
||||||
|
return NumberConversions.floor(z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double distanceSquared(LocalCoordinate coordinate) {
|
||||||
|
return NumberConversions.square(x - coordinate.x) + NumberConversions.square(y - coordinate.y) + NumberConversions.square(z - coordinate.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldCoordinate toWorld(FightTeam team) {
|
||||||
|
Region extend = team.getExtendRegion();
|
||||||
|
if(Fight.getUnrotated() == team) {
|
||||||
|
return new WorldCoordinate(
|
||||||
|
x + extend.getMinX(),
|
||||||
|
y + team.getSchemRegion().getMinY(),
|
||||||
|
z + extend.getMinZ()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new WorldCoordinate(
|
||||||
|
extend.getMaxX() - x,
|
||||||
|
y + team.getSchemRegion().getMinY(),
|
||||||
|
extend.getMaxZ() - z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalCoordinate add(LocalCoordinate localCoordinate) {
|
||||||
|
this.x += localCoordinate.x;
|
||||||
|
this.y += localCoordinate.y;
|
||||||
|
this.z += localCoordinate.z;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalCoordinate add(double x, double y, double z) {
|
||||||
|
this.x += x;
|
||||||
|
this.y += y;
|
||||||
|
this.z += z;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Local{" + x + "," + y + "," + z + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ import org.bukkit.Bukkit;
|
|||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
import org.bukkit.util.BoundingBox;
|
import org.bukkit.util.BoundingBox;
|
||||||
import org.bukkit.util.Transformation;
|
import org.bukkit.util.Transformation;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
@@ -54,8 +55,7 @@ public class NavMesh {
|
|||||||
private static final Cuboid PLAYER_SHADOW = new Cuboid(0.2, 0, 0.2, 0.6, 1, 0.6);
|
private static final Cuboid PLAYER_SHADOW = new Cuboid(0.2, 0, 0.2, 0.6, 1, 0.6);
|
||||||
|
|
||||||
private static final Set<Pos> RELATIVE_BLOCKS = new HashSet<>();
|
private static final Set<Pos> RELATIVE_BLOCKS = new HashSet<>();
|
||||||
protected static final org.bukkit.block.data.BlockData PATH_BLOCK = Material.LIME_STAINED_GLASS.createBlockData();
|
protected static final BlockData PATH_BLOCK = Material.LIME_STAINED_GLASS.createBlockData();
|
||||||
protected static final Vector MIDDLE_VECTOR = new Vector(0.5, 0, 0.5);
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (int y = (int) -Math.ceil(PLAYER_FALL_DISTANCE); y <= Math.ceil(PLAYER_JUMP_HEIGHT); y++) {
|
for (int y = (int) -Math.ceil(PLAYER_FALL_DISTANCE); y <= Math.ceil(PLAYER_JUMP_HEIGHT); y++) {
|
||||||
@@ -93,8 +93,6 @@ public class NavMesh {
|
|||||||
|
|
||||||
walkable.values().forEach(this::checkNeighbouring);
|
walkable.values().forEach(this::checkNeighbouring);
|
||||||
|
|
||||||
System.out.println("NavMesh2: " + walkable.size() + " " + connections.size());
|
|
||||||
|
|
||||||
System.out.println("NavMesh initialized in " + (System.currentTimeMillis() - time) + " ms");
|
System.out.println("NavMesh initialized in " + (System.currentTimeMillis() - time) + " ms");
|
||||||
ready = true;
|
ready = true;
|
||||||
}, 20);
|
}, 20);
|
||||||
@@ -165,8 +163,8 @@ public class NavMesh {
|
|||||||
return new Pos(x + pos.x, y + pos.y, z + pos.z);
|
return new Pos(x + pos.x, y + pos.y, z + pos.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector toVector() {
|
public WorldCoordinate toWorld() {
|
||||||
return new Vector(x, y + floor, z);
|
return new WorldCoordinate(x, y + floor, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double floorPosition() {
|
public double floorPosition() {
|
||||||
@@ -245,8 +243,8 @@ public class NavMesh {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(Vector posVector) {
|
public void update(WorldCoordinate coordinate) {
|
||||||
Pos pos = toPos(posVector);
|
Pos pos = toPos(coordinate);
|
||||||
if (pos == null) return;
|
if (pos == null) return;
|
||||||
|
|
||||||
for (int x = -2; x <= 2; x++) {
|
for (int x = -2; x <= 2; x++) {
|
||||||
@@ -274,47 +272,57 @@ public class NavMesh {
|
|||||||
Block thisBlock = WORLD.getBlockAt(pos.x, pos.y, pos.z);
|
Block thisBlock = WORLD.getBlockAt(pos.x, pos.y, pos.z);
|
||||||
Block otherBlock = WORLD.getBlockAt(other.x, other.y, other.z);
|
Block otherBlock = WORLD.getBlockAt(other.x, other.y, other.z);
|
||||||
|
|
||||||
// TODO: Ladder connections!
|
if (thisBlock.getType() != Material.LADDER && otherBlock.getType() == Material.LADDER) {
|
||||||
if (thisBlock.getType() != Material.LADDER && otherBlock.getType() == Material.LADDER && pos.y > other.y) continue;
|
if ((relative.x != 0 || relative.z != 0) && pos.y != other.y) continue;
|
||||||
|
} else {
|
||||||
// Check if jumpable vertical distance
|
// Check if jumpable vertical distance
|
||||||
if (otherBlock.getType() != Material.LADDER && other.floorPosition() - pos.floorPosition() > PLAYER_JUMP_HEIGHT) continue;
|
if (other.floorPosition() - pos.floorPosition() > PLAYER_JUMP_HEIGHT) continue;
|
||||||
// Check if Ceiling is high enough for player walking down!
|
// Check if Ceiling is high enough for player walking down!
|
||||||
if (other.ceilingPosition() < pos.floorPosition() + PLAYER_HEIGHT) continue;
|
if (other.ceilingPosition() < pos.floorPosition() + PLAYER_HEIGHT) continue;
|
||||||
|
}
|
||||||
|
|
||||||
connections.computeIfAbsent(other, __ -> new LinkedHashSet<>()).add(pos);
|
connections.computeIfAbsent(other, __ -> new LinkedHashSet<>()).add(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pos toPos(Vector vector) {
|
private Pos toPos(WorldCoordinate coordinate) {
|
||||||
Pos pos = new Pos(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ());
|
Pos pos = new Pos(coordinate.getBlockX(), coordinate.getBlockY(), coordinate.getBlockZ());
|
||||||
if (walkable.containsKey(pos)) return walkable.get(pos);
|
if (walkable.containsKey(pos)) return walkable.get(pos);
|
||||||
pos = new Pos(pos.x, pos.y - 1, pos.z);
|
pos = new Pos(pos.x, pos.y - 1, pos.z);
|
||||||
if (walkable.containsKey(pos)) return walkable.get(pos);
|
if (walkable.containsKey(pos)) return walkable.get(pos);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Vector> walkable() {
|
public boolean isWalkable(WorldCoordinate coordinate) {
|
||||||
return walkable.values().stream().map(Pos::toVector).collect(Collectors.toList());
|
Pos pos = new Pos(coordinate.getBlockX(), coordinate.getBlockY(), coordinate.getBlockZ());
|
||||||
|
return walkable.containsKey(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Vector> pathToNearest(Vector fromVector, Vector toVector) {
|
public List<WorldCoordinate> walkable() {
|
||||||
Pos to = toPos(toVector);
|
return walkable.values().stream().map(Pos::toWorld).collect(Collectors.toList());
|
||||||
if (walkable.containsKey(to)) return path(fromVector, toVector);
|
}
|
||||||
|
|
||||||
|
public List<WorldCoordinate> pathToNearest(WorldCoordinate fromCoordinate, WorldCoordinate toCoordinate) {
|
||||||
|
Pos to = toPos(toCoordinate);
|
||||||
|
if (walkable.containsKey(to)) return path(toPos(fromCoordinate), to);
|
||||||
|
|
||||||
Pos nearestPos = walkable.values()
|
Pos nearestPos = walkable.values()
|
||||||
.stream()
|
.stream()
|
||||||
.min(Comparator.comparingDouble(pos -> pos.toVector().distanceSquared(toVector)))
|
.min(Comparator.comparingDouble(pos -> pos.toWorld().distanceSquared(toCoordinate)))
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (nearestPos == null) return Collections.emptyList();
|
if (nearestPos == null) return Collections.emptyList();
|
||||||
return path(fromVector, nearestPos.toVector());
|
return path(toPos(fromCoordinate), nearestPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Vector> path(Vector fromVector, Vector toVector) {
|
public List<WorldCoordinate> path(WorldCoordinate fromCoordinate, WorldCoordinate toCoordinate) {
|
||||||
|
Pos from = toPos(fromCoordinate);
|
||||||
|
Pos to = toPos(toCoordinate);
|
||||||
|
return path(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<WorldCoordinate> path(Pos from, Pos to) {
|
||||||
server.getEntities().forEach(REntity::die);
|
server.getEntities().forEach(REntity::die);
|
||||||
|
|
||||||
Pos from = toPos(fromVector);
|
|
||||||
Pos to = toPos(toVector);
|
|
||||||
if (from == null || to == null) {
|
if (from == null || to == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -366,15 +374,15 @@ public class NavMesh {
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Vector> vectors = path.stream().skip(1).map(Pos::toVector).collect(Collectors.toList());
|
List<WorldCoordinate> coordinates = path.stream().skip(1).map(Pos::toWorld).collect(Collectors.toList());
|
||||||
vectors.forEach(vector -> {
|
coordinates.forEach(coordinate -> {
|
||||||
RBlockDisplay block = new RBlockDisplay(server, vector.toLocation(WORLD));
|
RBlockDisplay block = new RBlockDisplay(server, coordinate.toLocation(WORLD));
|
||||||
block.setBlock(PATH_BLOCK);
|
block.setBlock(PATH_BLOCK);
|
||||||
block.setTransform(new Transformation(new Vector3f(0, 0, 0), new AxisAngle4f(0, 0, 0, 0), new Vector3f(1, 0.001F, 1), new AxisAngle4f(0, 0, 0, 0)));
|
block.setTransform(new Transformation(new Vector3f(0, 0, 0), new AxisAngle4f(0, 0, 0, 0), new Vector3f(1, 0.001F, 1), new AxisAngle4f(0, 0, 0, 0)));
|
||||||
});
|
});
|
||||||
|
|
||||||
vectors.forEach(vector -> vector.add(MIDDLE_VECTOR));
|
coordinates.forEach(vector -> vector.add(0.5, 0, 0.5));
|
||||||
return vectors;
|
return coordinates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 de.steamwar.fightsystem.fight.Fight;
|
||||||
|
import de.steamwar.fightsystem.fight.FightTeam;
|
||||||
|
import de.steamwar.fightsystem.utils.Region;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.util.NumberConversions;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class WorldCoordinate {
|
||||||
|
private double x;
|
||||||
|
private double y;
|
||||||
|
private double z;
|
||||||
|
|
||||||
|
public static WorldCoordinate from(Location location) {
|
||||||
|
return new WorldCoordinate(location.getX(), location.getY(), location.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WorldCoordinate from(Vector vector) {
|
||||||
|
return new WorldCoordinate(vector.getX(), vector.getY(), vector.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockX() {
|
||||||
|
return NumberConversions.floor(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockY() {
|
||||||
|
return NumberConversions.floor(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockZ() {
|
||||||
|
return NumberConversions.floor(z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double distanceSquared(WorldCoordinate coordinate) {
|
||||||
|
return NumberConversions.square(x - coordinate.x) + NumberConversions.square(y - coordinate.y) + NumberConversions.square(z - coordinate.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalCoordinate toLocal(FightTeam team) {
|
||||||
|
Region extend = team.getExtendRegion();
|
||||||
|
if (Fight.getUnrotated() == team) {
|
||||||
|
return new LocalCoordinate(
|
||||||
|
x - extend.getMinX(),
|
||||||
|
y - team.getSchemRegion().getMinY(),
|
||||||
|
z - extend.getMinZ()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new LocalCoordinate(
|
||||||
|
extend.getMaxX() - x,
|
||||||
|
y - team.getSchemRegion().getMinY(),
|
||||||
|
extend.getMaxZ() - z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldCoordinate add(WorldCoordinate worldCoordinate) {
|
||||||
|
this.x += worldCoordinate.x;
|
||||||
|
this.y += worldCoordinate.y;
|
||||||
|
this.z += worldCoordinate.z;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldCoordinate add(double x, double y, double z) {
|
||||||
|
this.x += x;
|
||||||
|
this.y += y;
|
||||||
|
this.z += z;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector toVector() {
|
||||||
|
return new Vector(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location toLocation(World world) {
|
||||||
|
return new Location(world, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "World{" + x + "," + y + "," + z + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-35
@@ -20,22 +20,18 @@
|
|||||||
package de.steamwar.fightsystem.ai.yoyonow;
|
package de.steamwar.fightsystem.ai.yoyonow;
|
||||||
|
|
||||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
import de.steamwar.entity.REntityServer;
|
|
||||||
import de.steamwar.fightsystem.ArenaMode;
|
|
||||||
import de.steamwar.fightsystem.ai.AI;
|
import de.steamwar.fightsystem.ai.AI;
|
||||||
import de.steamwar.fightsystem.ai.NavMesh;
|
import de.steamwar.fightsystem.ai.Action;
|
||||||
|
import de.steamwar.fightsystem.ai.WorldCoordinate;
|
||||||
import de.steamwar.fightsystem.ai.schematic.WarMachine;
|
import de.steamwar.fightsystem.ai.schematic.WarMachine;
|
||||||
import de.steamwar.fightsystem.ai.schematic.impl.MiniWarGear20;
|
import de.steamwar.fightsystem.ai.schematic.impl.MiniWarGear20;
|
||||||
import de.steamwar.fightsystem.fight.FightTeam;
|
import de.steamwar.fightsystem.fight.FightTeam;
|
||||||
import de.steamwar.fightsystem.states.FightState;
|
|
||||||
import de.steamwar.fightsystem.states.OneShotStateDependent;
|
|
||||||
import de.steamwar.sql.SchematicNode;
|
import de.steamwar.sql.SchematicNode;
|
||||||
import de.steamwar.sql.SteamwarUser;
|
import de.steamwar.sql.SteamwarUser;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.util.Vector;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
@@ -45,17 +41,9 @@ public class YoyoNowAI extends AI {
|
|||||||
|
|
||||||
private WarMachine selectedSchematic;
|
private WarMachine selectedSchematic;
|
||||||
|
|
||||||
private final REntityServer entityServer = new REntityServer();
|
|
||||||
private final NavMesh navMesh;
|
|
||||||
|
|
||||||
protected YoyoNowAI(FightTeam team) {
|
protected YoyoNowAI(FightTeam team) {
|
||||||
super(team, SteamwarUser.get("YoyoNow.AI"));
|
super(team, SteamwarUser.get("YoyoNow.AI"));
|
||||||
getEntity().setGlowing(true);
|
getEntity().setGlowing(true);
|
||||||
navMesh = new NavMesh(team);
|
|
||||||
|
|
||||||
new OneShotStateDependent(ArenaMode.All, FightState.PostSchemSetup, () -> {
|
|
||||||
Bukkit.getOnlinePlayers().forEach(entityServer::addPlayer);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,33 +58,30 @@ public class YoyoNowAI extends AI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Random random = new Random();
|
private Random random = new Random();
|
||||||
private Vector destination = null;
|
private List<Action> actions = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void plan() {
|
protected void plan() {
|
||||||
if (!navMesh.isReady()) return;
|
if (!navMesh.isReady()) return;
|
||||||
|
|
||||||
navMesh.update(getEntity().getLocation().toVector());
|
if (!actions.isEmpty()) {
|
||||||
|
Action.Result result = actions.get(0).step(this);
|
||||||
if (this.destination == null) {
|
if (result == Action.Result.FAILED) {
|
||||||
List<Vector> walkable = navMesh.walkable();
|
actions.clear();
|
||||||
Vector destination = walkable.get(random.nextInt(walkable.size()));
|
return;
|
||||||
List<Vector> path = navMesh.pathToNearest(getEntity().getLocation().toVector(), destination);
|
}
|
||||||
if (path.isEmpty()) return;
|
if (result == Action.Result.FINISHED) {
|
||||||
this.destination = untranslate(path.getLast());
|
actions.remove(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Location location = translate(destination);
|
List<WorldCoordinate> walkable = navMesh.walkable();
|
||||||
List<Vector> path = navMesh.pathToNearest(getEntity().getLocation().toVector(), location.toVector());
|
WorldCoordinate destination = walkable.get(random.nextInt(walkable.size()));
|
||||||
|
Action action = new Action.MoveAction(this, destination);
|
||||||
if (path.isEmpty()) {
|
if (!action.isCompletable()) return;
|
||||||
destination = null;
|
chat("Now moving to: " + destination);
|
||||||
return;
|
actions.add(action);
|
||||||
}
|
|
||||||
move(untranslate(path.get(0)));
|
|
||||||
if (path.get(0).equals(destination)) {
|
|
||||||
destination = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user