Add WorldCoordinate and LocalCoordinate to remove mixup

Improve NavMesh
Add Action
This commit is contained in:
2026-04-17 10:13:28 +02:00
parent bf26dfed56
commit f51bce6a69
6 changed files with 435 additions and 127 deletions
@@ -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,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;
}
} }
} }