Commits vergleichen
18 Commits
Autor | SHA1 | Datum | |
---|---|---|---|
7b26b7bf91 | |||
|
ab08f5c131 | ||
|
cffa60e43d | ||
|
578c0056cd | ||
|
f10238cae2 | ||
|
69a0e02ab9 | ||
|
45fd36907c | ||
|
c3e39296b5 | ||
|
2452d72f2c | ||
39ccb113a8 | |||
|
71ddeb4ac8 | ||
|
c507e7ec97 | ||
efb2537097 | |||
|
8e963bffef | ||
5a0614974e | |||
|
2151134dcc | ||
|
fc7b8b6233 | ||
78ae119719 |
@ -22,6 +22,8 @@ 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.ai.chaos.ChaosAI;
|
||||
import de.steamwar.fightsystem.commands.*;
|
||||
import de.steamwar.fightsystem.countdown.*;
|
||||
import de.steamwar.fightsystem.event.HellsBells;
|
||||
@ -42,6 +44,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 +170,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 ChaosAI(Fight.getBlueTeam());
|
||||
new LixfelAI(Fight.getRedTeam(), SteamwarUser.get(-1));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,13 +35,13 @@ import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Note;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.Lectern;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Openable;
|
||||
import org.bukkit.block.data.Powerable;
|
||||
import org.bukkit.block.data.*;
|
||||
import org.bukkit.block.data.type.Comparator;
|
||||
import org.bukkit.block.data.type.NoteBlock;
|
||||
import org.bukkit.block.data.type.Repeater;
|
||||
import org.bukkit.block.data.type.Switch;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -68,7 +68,7 @@ public abstract class AI {
|
||||
return ais.get(uuid);
|
||||
}
|
||||
|
||||
private final FightTeam team;
|
||||
protected final FightTeam team;
|
||||
private final LivingEntity entity;
|
||||
private final BukkitTask task;
|
||||
private final Queue<Action> queue = new ArrayDeque<>();
|
||||
@ -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();
|
||||
@ -155,8 +171,10 @@ public abstract class AI {
|
||||
return;
|
||||
|
||||
Location location = translate(pos, true);
|
||||
if(interactionDistanceViolation(location))
|
||||
if(interactionDistanceViolation(location)) {
|
||||
chat("2 smoll pepe hönds!");
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = location.getBlock();
|
||||
if(block.getType() == Material.AIR)
|
||||
@ -170,8 +188,10 @@ public abstract class AI {
|
||||
@Override
|
||||
public void run() {
|
||||
Location location = translate(pos, true);
|
||||
if(interactionDistanceViolation(location))
|
||||
if(interactionDistanceViolation(location)) {
|
||||
chat("Ich komme da nicht dran!");
|
||||
return;
|
||||
}
|
||||
|
||||
interact(location.getBlock());
|
||||
}
|
||||
@ -205,15 +225,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -249,12 +271,34 @@ public abstract class AI {
|
||||
|
||||
powerable.setPowered(false);
|
||||
block.setBlockData(powerable);
|
||||
updateButton(block);
|
||||
}, type.name().endsWith("STONE_BUTTON") ? 20 : 30);
|
||||
}
|
||||
|
||||
powerable.setPowered(!isPowered);
|
||||
}
|
||||
block.setBlockData(data);
|
||||
if(data instanceof Switch) {
|
||||
updateButton(block);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButton(Block block) {
|
||||
Switch sw = (Switch) block.getBlockData();
|
||||
FaceAttachable.AttachedFace face = sw.getAttachedFace();
|
||||
if (face == FaceAttachable.AttachedFace.FLOOR) {
|
||||
update(block.getRelative(BlockFace.DOWN));
|
||||
} else if (face == FaceAttachable.AttachedFace.CEILING) {
|
||||
update(block.getRelative(BlockFace.UP));
|
||||
} else {
|
||||
update(block.getRelative(sw.getFacing().getOppositeFace()));
|
||||
}
|
||||
}
|
||||
|
||||
private void update(Block block) {
|
||||
BlockData data = block.getBlockData();
|
||||
block.setType(Material.BARRIER);
|
||||
block.setBlockData(data);
|
||||
}
|
||||
|
||||
private void run() {
|
||||
@ -265,7 +309,7 @@ public abstract class AI {
|
||||
queue.poll().run();
|
||||
}
|
||||
|
||||
private Location translate(Vector pos, boolean blockPos) {
|
||||
public Location translate(Vector pos, boolean blockPos) {
|
||||
Region extend = team.getExtendRegion();
|
||||
if(Fight.getUnrotated() == team)
|
||||
return new Location(
|
||||
|
@ -19,42 +19,102 @@
|
||||
|
||||
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.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.*;
|
||||
|
||||
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));
|
||||
public LixfelAI(FightTeam team, SteamwarUser user) {
|
||||
super(team, user);
|
||||
this.team = team;
|
||||
navMesh = new NavMesh(team, entityServer);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SchematicNode chooseSchematic() {
|
||||
if (false) {
|
||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB());
|
||||
SchematicNode schem = publics.get(new Random().nextInt(publics.size()));
|
||||
pathplanner = new LixfelPathplanner(schem);
|
||||
return schem;
|
||||
}
|
||||
// return SchematicNode.byIdAndUser(SteamwarUser.get(0), 111476);
|
||||
return SchematicNode.byIdAndUser(SteamwarUser.get(0), 98711);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptJoinRequest(Player player, FightTeam team) {
|
||||
if (team == this.team) {
|
||||
entityServer.addPlayer(player);
|
||||
}
|
||||
return super.acceptJoinRequest(player, team);
|
||||
}
|
||||
|
||||
private Vector source = null;
|
||||
private Vector destination = null;
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
protected void plan() {
|
||||
setReady();
|
||||
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);
|
||||
|
||||
if (navMesh == null) return;
|
||||
if (!getEntity().isOnGround() && getEntity().getLocation().getBlock().getType() != Material.LADDER) return;
|
||||
|
||||
if (source == null || destination == null) {
|
||||
source = getEntity().getLocation().toVector();
|
||||
List<Vector> walkableBlocks = navMesh.getWalkableBlocks(source);
|
||||
if (walkableBlocks.isEmpty()) return;
|
||||
destination = walkableBlocks.get(random.nextInt(walkableBlocks.size()));
|
||||
index = 0;
|
||||
}
|
||||
|
||||
List<Vector> oldRoute = navMesh.path(source, destination);
|
||||
navMesh.update(getEntity().getLocation().toVector());
|
||||
List<Vector> path = navMesh.path(source, destination);
|
||||
// TODO: New Route detection
|
||||
if (path.isEmpty()) {
|
||||
source = null;
|
||||
destination = null;
|
||||
chat("no route");
|
||||
return;
|
||||
}
|
||||
if (!oldRoute.equals(path)) {
|
||||
source = getEntity().getLocation().toVector();
|
||||
index = 0;
|
||||
chat("new route");
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,30 +19,60 @@
|
||||
|
||||
package de.steamwar.fightsystem.ai;
|
||||
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
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 com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.utils.WorldeditWrapper;
|
||||
import de.steamwar.sql.SchematicData;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.*;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LixfelPathplanner {
|
||||
|
||||
private static BlockType getBlockType(Clipboard clipboard, BlockVector3 vector) {
|
||||
return clipboard.getBlock(vector).getBlockType();
|
||||
private static double blockHeight(Clipboard clipboard, BlockVector3 vector) {
|
||||
BaseBlock block = clipboard.getFullBlock(vector);
|
||||
if(block.getBlockType().getMaterial().isFullCube())
|
||||
return 1.0;
|
||||
BlockData data = BukkitAdapter.adapt(block);
|
||||
Material material = data.getMaterial();
|
||||
if(material.isSolid()) {
|
||||
if(material.isInteractable()) {
|
||||
if(data instanceof Stairs)
|
||||
return 1.0;
|
||||
else if(data instanceof Fence)
|
||||
return 1.5;
|
||||
else if(data instanceof Bed)
|
||||
return 0.5625;
|
||||
} else {
|
||||
if(data instanceof Slab)
|
||||
return ((Slab)data).getType() == Slab.Type.BOTTOM ? 0.5 : 1.0;
|
||||
else if(data instanceof Wall)
|
||||
return 1.5;
|
||||
else if(data instanceof GlassPane || material == Material.IRON_BARS)
|
||||
return 1.0;
|
||||
}
|
||||
} else {
|
||||
if(material == Material.LADDER || material == Material.SCAFFOLDING)
|
||||
return -1.0;
|
||||
else if(material.name().endsWith("_CARPET"))
|
||||
return 0.0625;
|
||||
}
|
||||
|
||||
private static boolean nonsolid(Clipboard clipboard, BlockVector3 vector) {
|
||||
return !getBlockType(clipboard, vector).getMaterial().isSolid();
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private static Vector toBukkit(BlockVector3 vector) {
|
||||
return new Vector(vector.getX() + 0.5, vector.getY(), vector.getZ() + 0.5);
|
||||
private static Vector toBukkit(BlockVector3 vector, double height) {
|
||||
return new Vector(vector.getX() + 0.5, vector.getY() + height, vector.getZ() + 0.5);
|
||||
}
|
||||
|
||||
private final List<Vector> walkable = new ArrayList<>();
|
||||
@ -61,25 +91,33 @@ public class LixfelPathplanner {
|
||||
}
|
||||
|
||||
private void fillWalkable(Clipboard clipboard) {
|
||||
BlockVector3 min = clipboard.getRegion().getMinimumPoint().subtract(Config.PreperationArea, 0, Config.PreperationArea); //TODO assumes nonextended Schematic with maximal size
|
||||
Vector clipboardToSchem = new Vector(Config.BluePasteRegion.getSizeX(), Config.BluePasteRegion.getSizeY(), Config.BluePasteRegion.getSizeZ())
|
||||
.subtract(WorldeditWrapper.impl.getDimensions(clipboard))
|
||||
.multiply(0.5)
|
||||
.add(new Vector(Config.PreperationArea, 0, Config.PreperationArea));
|
||||
BlockVector3 diff = clipboard.getRegion().getMinimumPoint().subtract(clipboardToSchem.getBlockX(), clipboardToSchem.getBlockY(), clipboardToSchem.getBlockZ());
|
||||
|
||||
Region region = clipboard.getRegion();
|
||||
clipboard.getRegion().forEach(vector -> {
|
||||
BlockVector3 below = vector.subtract(0, 1, 0);
|
||||
if(!region.contains(below))
|
||||
BlockVector3 above = vector.add(0, 1, 0);
|
||||
|
||||
double aboveHeight = region.contains(above) ? blockHeight(clipboard, above) : 0.0;
|
||||
if(aboveHeight > 0.0)
|
||||
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)));
|
||||
}
|
||||
double belowHeight = region.contains(below) ? blockHeight(clipboard, below) : 0.0;
|
||||
double height = blockHeight(clipboard, vector);
|
||||
if(height == 0.0 && Math.abs(belowHeight) < 1.0)
|
||||
return;
|
||||
|
||||
if(height >= 1.0 && region.contains(above))
|
||||
return;
|
||||
|
||||
if(height < 0.0)
|
||||
height = 0.0;
|
||||
|
||||
walkable.add(toBukkit(vector.subtract(diff), height));
|
||||
});
|
||||
|
||||
for(Vector vector : walkable) {
|
||||
@ -87,24 +125,42 @@ public class LixfelPathplanner {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Vector walkableNearby(double eyeHeight, double distance, List<Vector> nearby) {
|
||||
List<Vector> moddedNearby = nearby.stream().map(n -> n.clone().subtract(new Vector(0, eyeHeight, 0))).collect(Collectors.toList());
|
||||
return walkable.stream()
|
||||
.filter(vector -> moddedNearby.stream()
|
||||
.allMatch(n -> n.distance(vector) <= distance && !neighbouring(n, vector)))
|
||||
.findAny().orElse(null);
|
||||
}
|
||||
|
||||
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);
|
||||
List<Vector> plan = new ArrayList<>(plan(start, intermediate));
|
||||
plan.add(destination);
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
public List<Vector> planToRange(Vector start, Vector destination, double range) {
|
||||
return plan(start, walkable.stream().filter(vector -> vector.distance(destination) <= range).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public List<Vector> plan(Vector start, Vector destination) {
|
||||
return plan(start, Collections.singletonList(destination));
|
||||
}
|
||||
|
||||
public List<Vector> plan(Vector start, List<Vector> destinations) {
|
||||
for(Vector destination : destinations)
|
||||
if(neighbouring(start, destination))
|
||||
return Collections.singletonList(destination);
|
||||
|
||||
Map<Vector, Vector> approach = new HashMap<>();
|
||||
Set<Vector> checking = Collections.singleton(destination);
|
||||
Set<Vector> checking = new HashSet<>(destinations);
|
||||
|
||||
while(!checking.isEmpty()) {
|
||||
Set<Vector> toCheck = new HashSet<>();
|
||||
@ -122,7 +178,7 @@ public class LixfelPathplanner {
|
||||
List<Vector> path = new ArrayList<>();
|
||||
path.add(firstStep);
|
||||
|
||||
while(path.get(path.size()-1) != destination) {
|
||||
while(!destinations.contains(path.get(path.size()-1))) {
|
||||
path.add(approach.get(path.get(path.size()-1)));
|
||||
}
|
||||
|
||||
|
58
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/Cannon.java
Normale Datei
58
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/Cannon.java
Normale Datei
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.chaos;
|
||||
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class Cannon {
|
||||
public final Vector[] tnt;
|
||||
public final Vector button;
|
||||
public final Vector load;
|
||||
public final Vector escape;
|
||||
public final String name;
|
||||
|
||||
public Cannon(String name, Vector[] tnt, Vector button, Vector load, Vector escape) {
|
||||
this.tnt = tnt;
|
||||
this.button = button;
|
||||
this.load = load;
|
||||
this.escape = escape;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Vector[] getTnt() {
|
||||
return tnt;
|
||||
}
|
||||
|
||||
public Vector getButton() {
|
||||
return button;
|
||||
}
|
||||
|
||||
public Vector getLoad() {
|
||||
return load;
|
||||
}
|
||||
|
||||
public Vector getEscape() {
|
||||
return escape;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
267
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/ChaosAI.java
Normale Datei
267
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/ChaosAI.java
Normale Datei
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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.chaos;
|
||||
|
||||
import de.steamwar.entity.REntityServer;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.ai.AI;
|
||||
import de.steamwar.fightsystem.ai.chaos.gears.Gear;
|
||||
import de.steamwar.fightsystem.ai.chaos.gears.TheUnderground;
|
||||
import de.steamwar.fightsystem.ai.navmesh.NavMesh;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ChaosAI extends AI {
|
||||
private State state = State.PRE_PREPARE;
|
||||
private static final Gear gear = TheUnderground.THE_UNDERGROUND;
|
||||
|
||||
private Cannon currentCannon;
|
||||
private boolean igniteMg = false;
|
||||
private static final Random random = new Random();
|
||||
public static Map<FightTeam, Boolean> onePrepares = new HashMap<>();
|
||||
private boolean prepares = false;
|
||||
private static final Map<FightTeam, Set<Cannon>> cannonsShot = new HashMap<>();
|
||||
private final NavMesh navMesh;
|
||||
private final REntityServer entityServer = new REntityServer();
|
||||
|
||||
|
||||
private Vector source = null;
|
||||
private Vector destination = null;
|
||||
private int index = 0;
|
||||
private State nextState = null;
|
||||
|
||||
public ChaosAI(FightTeam team) {
|
||||
this(team, SteamwarUser.get(14533));
|
||||
}
|
||||
|
||||
|
||||
public ChaosAI(FightTeam team, SteamwarUser user) {
|
||||
super(team, user);
|
||||
this.navMesh = new NavMesh(team, entityServer);
|
||||
if (Boolean.FALSE.equals(onePrepares.getOrDefault(team, false))) {
|
||||
prepares = true;
|
||||
onePrepares.put(team, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchematicNode chooseSchematic() {
|
||||
return SchematicNode.getSchematicNode(gear.getSchematicId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void plan() {
|
||||
switch (state) {
|
||||
case PRE_PREPARE:
|
||||
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> {
|
||||
state = State.PREPARE;
|
||||
}, 20 * 15);
|
||||
state = State.WAIT;
|
||||
break;
|
||||
case PREPARE:
|
||||
prepare();
|
||||
break;
|
||||
case PREPARE_READY:
|
||||
if (prepares) {
|
||||
for (Vector prepareButton : gear.getPrepareButtons()) {
|
||||
interact(prepareButton);
|
||||
}
|
||||
}
|
||||
setReady();
|
||||
state = State.WAIT_TILL_START;
|
||||
break;
|
||||
case WAIT_TILL_START:
|
||||
if (FightState.ingame()) {
|
||||
state = State.IGNITE_MG;
|
||||
startFight();
|
||||
}
|
||||
break;
|
||||
case IGNITE_MG:
|
||||
if(igniteMg) {
|
||||
if (prepares) {
|
||||
interact(gear.getMgButton());
|
||||
chat("Die MG ist angezündet!");
|
||||
}
|
||||
state = State.WAIT;
|
||||
currentCannon = randomCannon();
|
||||
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> state = State.FIGHT, 20 * 9);
|
||||
}
|
||||
break;
|
||||
case FIGHT:
|
||||
fireCannon(currentCannon);
|
||||
break;
|
||||
case LOAD_CANNON:
|
||||
loadCannon(currentCannon);
|
||||
break;
|
||||
case ESCAPE:
|
||||
cannonsShot.computeIfAbsent(team, fightTeam -> new HashSet<>()).remove(currentCannon);
|
||||
currentCannon = randomCannon();
|
||||
|
||||
state = State.WAIT;
|
||||
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> state = State.FIGHT, 20 * 4);
|
||||
break;
|
||||
case WAIT:
|
||||
break;
|
||||
case WALK:
|
||||
if (!getEntity().isOnGround() && getEntity().getLocation().getBlock().getType() != Material.LADDER) return;
|
||||
|
||||
List<Vector> oldRoute = navMesh.path(source, destination);
|
||||
navMesh.update(getEntity().getLocation().toVector());
|
||||
List<Vector> path = navMesh.path(source, destination);
|
||||
|
||||
if (path.isEmpty()) {
|
||||
source = null;
|
||||
destination = null;
|
||||
state = nextState;
|
||||
chat("no route");
|
||||
return;
|
||||
}
|
||||
if (!oldRoute.equals(path)) {
|
||||
source = getEntity().getLocation().toVector();
|
||||
index = 0;
|
||||
chat("new route");
|
||||
return;
|
||||
}
|
||||
if (index == 0) {
|
||||
chat(source + " -> " + destination + " = " + path.size());
|
||||
}
|
||||
|
||||
Vector loc = path.get(index++);
|
||||
move(toAIPosition(loc));
|
||||
|
||||
if (index == path.size()) {
|
||||
source = null;
|
||||
destination = null;
|
||||
state = nextState;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void moveTo(Vector vec, State nextState) {
|
||||
source = getEntity().getLocation().toVector();
|
||||
Vector currentBest = null;
|
||||
for (Vector vector : navMesh.getWalkableBlocks(source)) {
|
||||
if (currentBest == null || vector.distanceSquared(vec) < currentBest.distanceSquared(vec)) {
|
||||
currentBest = vector;
|
||||
}
|
||||
}
|
||||
|
||||
destination = currentBest;
|
||||
index = 0;
|
||||
state = State.WALK;
|
||||
this.nextState = nextState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
chat("gege wp eZ win 4nus");
|
||||
if (currentCannon != null) {
|
||||
cannonsShot.computeIfAbsent(team, fightTeam -> new HashSet<>()).remove(currentCannon);
|
||||
}
|
||||
super.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptJoinRequest(Player player, FightTeam team) {
|
||||
if (team == this.team) {
|
||||
entityServer.addPlayer(player);
|
||||
}
|
||||
return team == this.team;
|
||||
}
|
||||
|
||||
public void fireCannon(Cannon cannon) {
|
||||
chat("Ich feuere " + cannon.name + " an!");
|
||||
moveTo(cannon.load.clone().add(new Vector(0.5, 0, 0.5)), State.LOAD_CANNON);
|
||||
}
|
||||
|
||||
private void loadCannon(Cannon cannon) {
|
||||
if (cannon.button == null) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (Vector vector : cannon.tnt) {
|
||||
setTNT(vector);
|
||||
}
|
||||
for (int j = 0; j < 30; j++) {
|
||||
getBlock(gear.getMgButton());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Vector vector : cannon.tnt) {
|
||||
setTNT(vector);
|
||||
}
|
||||
interact(cannon.button);
|
||||
}
|
||||
|
||||
if (currentCannon.escape != null) {
|
||||
moveTo(cannon.getEscape().clone().add(new Vector(0.5, 0, 0.5)), State.ESCAPE);
|
||||
}
|
||||
}
|
||||
|
||||
private Cannon randomCannon() {
|
||||
Cannon nextCannon = gear.getCannons()[random.nextInt(gear.getCannons().length)];
|
||||
while (cannonsShot.computeIfAbsent(team, fightTeam -> new HashSet<>()).contains(nextCannon)) {
|
||||
nextCannon = gear.getCannons()[random.nextInt(gear.getCannons().length)];
|
||||
}
|
||||
|
||||
cannonsShot.computeIfAbsent(team, fightTeam -> new HashSet<>()).add(nextCannon);
|
||||
|
||||
return nextCannon;
|
||||
}
|
||||
|
||||
public void startFight() {
|
||||
chat("gl&hf");
|
||||
if (prepares) {
|
||||
chat("Ich zünde die MG an!");
|
||||
}
|
||||
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> {
|
||||
igniteMg = true;
|
||||
}, 20L * gear.getMgTime());
|
||||
}
|
||||
|
||||
public void prepare() {
|
||||
chat("Prepare your fkin 4nus");
|
||||
if (prepares) {
|
||||
moveTo(gear.getBridge().clone().add(new Vector(0.5, 0, 0.5)), State.PREPARE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
PRE_PREPARE,
|
||||
PREPARE,
|
||||
PREPARE_READY,
|
||||
WAIT_TILL_START,
|
||||
IGNITE_MG,
|
||||
FIGHT,
|
||||
LOAD_CANNON,
|
||||
ESCAPE,
|
||||
WAIT,
|
||||
WALK,
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.chaos.gears;
|
||||
|
||||
import de.steamwar.fightsystem.ai.chaos.Cannon;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class Gear {
|
||||
|
||||
private final Cannon[] cannons;
|
||||
private final Vector bridge;
|
||||
private final Vector[] prepareButtons;
|
||||
private final Vector mgButton;
|
||||
private final int mgTime;
|
||||
private final int schematicId;
|
||||
|
||||
protected Gear(Cannon[] cannons, Vector bridge, Vector[] prepareButtons, Vector mgButton, int mgTime, int schematicId) {
|
||||
this.cannons = cannons;
|
||||
this.bridge = bridge;
|
||||
this.prepareButtons = prepareButtons;
|
||||
this.mgButton = mgButton;
|
||||
this.mgTime = mgTime;
|
||||
this.schematicId = schematicId;
|
||||
}
|
||||
|
||||
public Cannon[] getCannons() {
|
||||
return cannons;
|
||||
}
|
||||
|
||||
public Vector getBridge() {
|
||||
return bridge;
|
||||
}
|
||||
|
||||
public Vector[] getPrepareButtons() {
|
||||
return prepareButtons;
|
||||
}
|
||||
|
||||
public Vector getMgButton() {
|
||||
return mgButton;
|
||||
}
|
||||
|
||||
public int getMgTime() {
|
||||
return mgTime;
|
||||
}
|
||||
|
||||
public int getSchematicId() {
|
||||
return schematicId;
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.chaos.gears;
|
||||
|
||||
import de.steamwar.fightsystem.ai.chaos.Cannon;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class TheUnderground {
|
||||
|
||||
public static final Vector[] DS_LEFT_TNT = new Vector[] {
|
||||
new Vector(40, 24, 12),
|
||||
new Vector(39, 24, 12),
|
||||
new Vector(38, 23, 12),
|
||||
new Vector(38, 24, 12),
|
||||
new Vector(40, 23, 15),
|
||||
new Vector(40, 23, 14),
|
||||
new Vector(40, 24, 15),
|
||||
new Vector(40, 24, 14),
|
||||
new Vector(38, 23, 15),
|
||||
new Vector(38, 23, 14),
|
||||
new Vector(38, 24, 15),
|
||||
new Vector(38, 24, 14),
|
||||
new Vector(40, 23, 12),
|
||||
};
|
||||
public static final Vector DS_LEFT_BUTTON = new Vector(39, 25, 11);
|
||||
public static final Vector DS_LEFT_LOAD = new Vector(39, 25, 13);
|
||||
public static final Vector DS_LEFT_ESCAPE = new Vector(31, 26, 13);
|
||||
public static final Cannon DS_LEFT = new Cannon("Downstäb Links", DS_LEFT_TNT, DS_LEFT_BUTTON, DS_LEFT_LOAD, DS_LEFT_ESCAPE);
|
||||
|
||||
public static final Vector[] DS_RIGHT_TNT = new Vector[] {
|
||||
new Vector(12, 24, 12),
|
||||
new Vector(11, 24, 12),
|
||||
new Vector(10, 23, 12),
|
||||
new Vector(10, 24, 12),
|
||||
new Vector(10, 23, 15),
|
||||
new Vector(10, 23, 14),
|
||||
new Vector(10, 24, 15),
|
||||
new Vector(10, 24, 14),
|
||||
new Vector(12, 23, 14),
|
||||
new Vector(12, 24, 14),
|
||||
new Vector(12, 23, 15),
|
||||
new Vector(12, 24, 15),
|
||||
new Vector(12, 23, 12),
|
||||
};
|
||||
public static final Vector DS_RIGHT_BUTTON = new Vector(11, 25, 11);
|
||||
public static final Vector DS_RIGHT_LOAD = new Vector(11, 25, 13);
|
||||
public static final Vector DS_RIGHT_ESCAPE = new Vector(19, 26, 13);
|
||||
public static final Cannon DS_RIGHT = new Cannon("Downstäb Rechts", DS_RIGHT_TNT, DS_RIGHT_BUTTON, DS_RIGHT_LOAD, DS_RIGHT_ESCAPE);
|
||||
|
||||
public static final Vector[] STATIC_LEFT_TNT = new Vector[] {
|
||||
new Vector(37, 17, 15),
|
||||
new Vector(37, 17, 14),
|
||||
new Vector(37, 18, 16),
|
||||
new Vector(37, 18, 15),
|
||||
new Vector(37, 18, 14),
|
||||
new Vector(36, 17, 16),
|
||||
new Vector(36, 17, 15),
|
||||
new Vector(36, 17, 14),
|
||||
new Vector(36, 18, 16),
|
||||
new Vector(36, 18, 15),
|
||||
new Vector(36, 18, 14),
|
||||
new Vector(37, 17, 16),
|
||||
};
|
||||
public static final Vector STATIC_LEFT_BUTTON = new Vector(38, 18, 11);
|
||||
public static final Vector STATIC_LEFT_LOAD = new Vector(37, 17, 12);
|
||||
public static final Vector STATIC_LEFT_ESCAPE = new Vector(30, 13, 9);
|
||||
public static final Cannon STATIC_LEFT = new Cannon("Lupf Links", STATIC_LEFT_TNT, STATIC_LEFT_BUTTON, STATIC_LEFT_LOAD, STATIC_LEFT_ESCAPE);
|
||||
|
||||
public static final Vector[] STATIC_RIGHT_TNT = new Vector[] {
|
||||
new Vector(14, 17, 15),
|
||||
new Vector(14, 18, 16),
|
||||
new Vector(14, 18, 15),
|
||||
new Vector(14, 17, 14),
|
||||
new Vector(14, 18, 14),
|
||||
new Vector(13, 17, 16),
|
||||
new Vector(13, 17, 15),
|
||||
new Vector(13, 17, 14),
|
||||
new Vector(13, 18, 16),
|
||||
new Vector(13, 18, 15),
|
||||
new Vector(13, 18, 14),
|
||||
new Vector(14, 17, 16),
|
||||
};
|
||||
public static final Vector STATIC_RIGHT_BUTTON = new Vector(12, 18, 11);
|
||||
public static final Vector STATIC_RIGHT_LOAD = new Vector(13, 17, 12);
|
||||
public static final Vector STATIC_RIGHT_ESCAPE = new Vector(20, 13, 9);
|
||||
public static final Cannon STATIC_RIGHT = new Cannon("Lupf Rechts", STATIC_RIGHT_TNT, STATIC_RIGHT_BUTTON, STATIC_RIGHT_LOAD, STATIC_RIGHT_ESCAPE);
|
||||
|
||||
public static final Vector[] AK_TNT = new Vector[] {
|
||||
new Vector(10, 7, 17),
|
||||
new Vector(10, 11, 17),
|
||||
new Vector(10, 10, 17),
|
||||
new Vector(10, 8, 17),
|
||||
new Vector(10, 9, 17),
|
||||
new Vector(10, 7, 15),
|
||||
new Vector(11, 7, 15),
|
||||
new Vector(12, 7, 15),
|
||||
new Vector(10, 10, 15),
|
||||
new Vector(11, 10, 15),
|
||||
new Vector(12, 10, 15),
|
||||
new Vector(12, 8, 15),
|
||||
new Vector(12, 9, 15),
|
||||
new Vector(11, 8, 15),
|
||||
new Vector(11, 9, 15),
|
||||
new Vector(10, 8, 15),
|
||||
new Vector(10, 9, 15),
|
||||
new Vector(10, 7, 19),
|
||||
new Vector(11, 7, 19),
|
||||
new Vector(12, 7, 19),
|
||||
new Vector(12, 8, 19),
|
||||
new Vector(11, 8, 19),
|
||||
new Vector(10, 8, 19),
|
||||
new Vector(10, 10, 19),
|
||||
new Vector(11, 10, 19),
|
||||
new Vector(12, 10, 19),
|
||||
new Vector(12, 9, 19),
|
||||
new Vector(11, 9, 19),
|
||||
new Vector(10, 9, 19),
|
||||
new Vector(10, 6, 17),
|
||||
};
|
||||
public static final Vector AK_BUTTON = new Vector(9, 9, 16);
|
||||
public static final Vector AK_LOAD = new Vector(11, 8, 17);
|
||||
public static final Cannon AK = new Cannon("Arschkratzer", AK_TNT, AK_BUTTON, AK_LOAD, null);
|
||||
|
||||
public static final Vector[] HA_RIGHT_TNT = new Vector[] {
|
||||
new Vector(23, 5, 9),
|
||||
new Vector(23, 6, 9),
|
||||
new Vector(23, 7, 9),
|
||||
new Vector(23, 8, 9),
|
||||
};
|
||||
|
||||
public static final Vector[] HA_LEFT_TNT = new Vector[] {
|
||||
new Vector(27, 5, 9),
|
||||
new Vector(27, 6, 9),
|
||||
new Vector(27, 7, 9),
|
||||
new Vector(27, 8, 9),
|
||||
};
|
||||
|
||||
public static final Vector HA_RIGHT_LOAD = new Vector(23, 8, 8);
|
||||
public static final Vector HA_LEFT_LOAD = new Vector(27, 8, 8);
|
||||
|
||||
public static final Cannon HA_LEFT = new Cannon("Halbautomatik Links", HA_LEFT_TNT, null, HA_LEFT_LOAD, null);
|
||||
public static final Cannon HA_RIGHT = new Cannon("Halbautomatik Rechts", HA_RIGHT_TNT, null, HA_RIGHT_LOAD, null);
|
||||
|
||||
public static final Vector BRIDGE_POS = new Vector(25, 14, 24);
|
||||
public static final Vector BRIDGE_SHIELDS = new Vector(25, 15, 25);
|
||||
public static final Vector BRIDGE_MG = new Vector(21, 15, 24);
|
||||
|
||||
public static final Gear THE_UNDERGROUND = new Gear(
|
||||
new Cannon[] {
|
||||
DS_LEFT,
|
||||
DS_RIGHT,
|
||||
STATIC_LEFT,
|
||||
STATIC_RIGHT,
|
||||
AK,
|
||||
HA_LEFT,
|
||||
HA_RIGHT,
|
||||
//Cannon.AK,
|
||||
},
|
||||
BRIDGE_POS,
|
||||
new Vector[] {
|
||||
BRIDGE_SHIELDS
|
||||
},
|
||||
BRIDGE_MG,
|
||||
21,
|
||||
124667
|
||||
);
|
||||
}
|
25
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/recorder.lua
Normale Datei
25
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/recorder.lua
Normale Datei
@ -0,0 +1,25 @@
|
||||
event(events.PlaceBlock, function(e)
|
||||
if storage.player.get("recording") then
|
||||
pos = {math.floor(e.x), math.floor(e.y) - 100, math.floor(e.z)}
|
||||
storage.player.get("recordingPos")[storage.player.get("recordingCount")] = pos
|
||||
storage.player.set("recordingCount", storage.player.get("recordingCount") + 1)
|
||||
end
|
||||
end)
|
||||
|
||||
command("recorder", function(args)
|
||||
if storage.player.get("recording") then
|
||||
storage.player.set("recording", false)
|
||||
storage.player.set("recordingCount", 0)
|
||||
print("private static final Vector[] positions = new Vector[] {")
|
||||
for k, v in pairs(storage.player.get("recordingPos")) do
|
||||
print("new Vector(" .. v[1] .. ", " .. v[2] .. ", " .. v[3] .. "),")
|
||||
end
|
||||
print("};")
|
||||
print("Stopped recording")
|
||||
else
|
||||
storage.player.set("recording", true)
|
||||
storage.player.set("recordingCount", 0)
|
||||
storage.player.set("recordingPos", {})
|
||||
print("Started recording")
|
||||
end
|
||||
end)
|
343
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/sw.def.lua
Normale Datei
343
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/sw.def.lua
Normale Datei
@ -0,0 +1,343 @@
|
||||
-- This file is a part of the SteamWar software.
|
||||
--
|
||||
-- Copyright (C) 2021 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/>.
|
||||
|
||||
---
|
||||
--- This file contains the definitions for the SteamWar.de script API.
|
||||
--- It is used by the IDE to provide code completion and type checking.
|
||||
--- Created by Chaoscaot
|
||||
---
|
||||
|
||||
inventory = {}
|
||||
|
||||
---@param title string
|
||||
---@param size number
|
||||
---@return Inventory
|
||||
function inventory.create(title, size) return nil end
|
||||
|
||||
---@alias InventoryClick 'LEFT' | 'SHIFT_LEFT' | 'RIGHT' | 'SHIFT_RIGHT' | 'MIDDLE' | 'NUMBER_KEY'
|
||||
|
||||
---@class Inventory
|
||||
local Inventory = {}
|
||||
|
||||
---@overload fun(slot: number, material: string, name: string, handler: fun(click: InventoryClick)): void
|
||||
---@overload fun(slot: number, material: string, name: string, handler: fun(click: InventoryClick), lore: string[]): void
|
||||
---@overload fun(slot: number, material: string, name: string, handler: fun(click: InventoryClick), lore: string[], enchanted: boolean): void
|
||||
---@param slot number
|
||||
---@param material string
|
||||
---@param name string
|
||||
---@param handler fun(click: InventoryClick): void
|
||||
---@param lore string[]
|
||||
---@param enchanted boolean
|
||||
---@param amount number
|
||||
---@return void
|
||||
function Inventory.item(slot, material, name, handler, lore, enchanted, amount) end
|
||||
|
||||
---@param handler fun(): void
|
||||
---@return void
|
||||
function Inventory.setCloseHandler(handler) end
|
||||
|
||||
---@return void
|
||||
function Inventory.open() end
|
||||
|
||||
player = {}
|
||||
|
||||
---@return string
|
||||
---Get the name of the player.
|
||||
function player.name() return "" end
|
||||
|
||||
---@return void
|
||||
function player.chat(...) end
|
||||
|
||||
---@return void
|
||||
---Send a message to the actionbar of the player.
|
||||
function player.actionbar(...) end
|
||||
|
||||
---@overload fun(): number
|
||||
---@param newX number
|
||||
function player.x(newX) end
|
||||
|
||||
---@overload fun(): number
|
||||
---@param newY number
|
||||
function player.y(newY) end
|
||||
|
||||
---@overload fun(): number
|
||||
---@param newZ number
|
||||
function player.z(newZ) end
|
||||
|
||||
---@overload fun(): number
|
||||
---@param newYaw number
|
||||
function player.yaw(newYaw) end
|
||||
|
||||
---@overload fun(): number
|
||||
---@param newPitch number
|
||||
function player.pitch(newPitch) end
|
||||
|
||||
---@return boolean
|
||||
function player.sneaking() return nil end
|
||||
|
||||
---@return boolean
|
||||
function player.sprinting() return nil end
|
||||
|
||||
---@overload fun(): number
|
||||
---@param newSlot number
|
||||
function player.slot(newSlot) end
|
||||
|
||||
---@return string
|
||||
function player.item() return nil end
|
||||
|
||||
---@return string
|
||||
function player.offHandItem() return nil end
|
||||
|
||||
---@return void
|
||||
function player.closeInventory() end
|
||||
|
||||
---@field nextBool fun(): boolean
|
||||
random = {}
|
||||
|
||||
---@overload fun(): number
|
||||
---@overload fun(bound: number): number
|
||||
---@param origin number
|
||||
---@param bound number
|
||||
---@return number
|
||||
function random.nextInt(origin, bound) return nil end
|
||||
|
||||
---@overload fun(): number
|
||||
---@overload fun(bound: number): number
|
||||
---@param origin number
|
||||
---@param bound number
|
||||
---@return number
|
||||
function random.nextDouble(origin, bound) return nil end
|
||||
|
||||
---@return boolean
|
||||
function random.nextBool() return nil end
|
||||
|
||||
---@alias RegionType 'wg' | 'mwg' | 'as' | 'ws' | 'ws_inner' | 'ws_rumpf' | 'ws_rahmen' | 'spawn'
|
||||
|
||||
---@class iregion
|
||||
---@field tnt tnt
|
||||
---@field trace trace
|
||||
local iregion = {}
|
||||
|
||||
---@class region: iregion
|
||||
region = {}
|
||||
|
||||
---@return string
|
||||
function iregion.name() return nil end
|
||||
|
||||
---@return RegionType
|
||||
function iregion.type() return nil end
|
||||
|
||||
---@return boolean
|
||||
function iregion.fire() return nil end
|
||||
|
||||
---@return boolean
|
||||
function iregion.freeze() return nil end
|
||||
|
||||
---@return boolean
|
||||
function iregion.protect() return nil end
|
||||
|
||||
---@return string
|
||||
function iregion.loader() return nil end
|
||||
|
||||
---@alias TNTMode 'ALLOW' | 'DENY' | 'ONLY_TB'
|
||||
|
||||
---@class tnt
|
||||
local tnt = {}
|
||||
|
||||
---@return TNTMode
|
||||
function tnt.mode() return nil end
|
||||
|
||||
---@return boolean
|
||||
function tnt.enabled() return nil end
|
||||
|
||||
---@return boolean
|
||||
function tnt.onlyTb() return nil end
|
||||
|
||||
---@class trace
|
||||
local trace = {}
|
||||
|
||||
---@return boolean
|
||||
function trace.active() return nil end
|
||||
|
||||
---@return boolean
|
||||
function trace.auto() return nil end
|
||||
|
||||
---@return string
|
||||
function trace.status() return nil end
|
||||
|
||||
---@return number
|
||||
function trace.time() return nil end
|
||||
|
||||
---@param name string
|
||||
---@return iregion
|
||||
function region.get(name) return nil end
|
||||
|
||||
---@return iregion[]
|
||||
function region.list() return nil end
|
||||
|
||||
---@class Position
|
||||
---@field x number
|
||||
---@field y number
|
||||
---@field z number
|
||||
|
||||
---@class server
|
||||
---@field tps tps
|
||||
server = {}
|
||||
|
||||
---@return string
|
||||
function server.time() return nil end
|
||||
|
||||
---@return number
|
||||
function server.ticks() return nil end
|
||||
|
||||
---@param position Position
|
||||
---@return string
|
||||
function getBlockAt(position) return nil end
|
||||
|
||||
---@param position Position
|
||||
---@param material string
|
||||
---@return void
|
||||
function setBlockAt(position, material) return nil end
|
||||
|
||||
---@class tps
|
||||
local tps = {}
|
||||
|
||||
---@return number
|
||||
function tps.oneSecond() return nil end
|
||||
|
||||
---@return number
|
||||
function tps.tenSecond() return nil end
|
||||
|
||||
---@return number
|
||||
function tps.oneMinute() return nil end
|
||||
|
||||
---@return number
|
||||
function tps.fiveMinute() return nil end
|
||||
|
||||
---@return number
|
||||
function tps.tenMinute() return nil end
|
||||
|
||||
---@return number
|
||||
function tps.current() return nil end
|
||||
|
||||
---@return number
|
||||
function tps.limit() return nil end
|
||||
|
||||
---@class storage
|
||||
---@field global storageLib
|
||||
---@field player storageLib
|
||||
---@field region storageLib
|
||||
storage = {}
|
||||
|
||||
---@class storageLib
|
||||
local storageLib = {}
|
||||
|
||||
---@param key string
|
||||
---@return any
|
||||
function storageLib.get(key) return nil end
|
||||
|
||||
---@param key string
|
||||
---@param value any
|
||||
---@return void
|
||||
function storageLib.set(key, value) end
|
||||
|
||||
---@param key string
|
||||
---@return boolean
|
||||
function storageLib.has(key) return nil end
|
||||
|
||||
---@param key string
|
||||
---@return void
|
||||
function storageLib.remove(key) end
|
||||
|
||||
---@param key string
|
||||
---@return Accessor
|
||||
function storageLib.accessor(key) return nil end
|
||||
|
||||
---@class Accessor
|
||||
---@overload fun(): any
|
||||
---@overload fun(value: any)
|
||||
|
||||
---@class Selection
|
||||
---@field max Position
|
||||
---@field min Position
|
||||
|
||||
---@class _worldedit
|
||||
_worldedit = {}
|
||||
|
||||
---@overload fun(pos: Position[]): void
|
||||
---@return Selection
|
||||
function _worldedit.selection() return nil end
|
||||
|
||||
---@param msg string
|
||||
---@param callback fun(value: string): void
|
||||
---@return void
|
||||
function input(msg, callback) end
|
||||
|
||||
---@param ticks number
|
||||
---@param callback fun(): void
|
||||
---@return void
|
||||
function delayed(ticks, callback) end
|
||||
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param z number
|
||||
---@return Position
|
||||
function pos(x, y, z) return nil end
|
||||
|
||||
---@return void
|
||||
function exec(...) end
|
||||
|
||||
---@param obj any
|
||||
---@return number
|
||||
function length(obj) return 0 end
|
||||
|
||||
---@param separator string
|
||||
---@param table any[]
|
||||
---@return string
|
||||
function join(separator, table) return "" end
|
||||
|
||||
---@class EventType
|
||||
---@class events
|
||||
---@field DoubleSwap EventType
|
||||
---@field PlaceBlock EventType
|
||||
---@field BreakBlock EventType
|
||||
---@field RightClick EventType
|
||||
---@field LeftClick EventType
|
||||
---@field TNTSpawn EventType
|
||||
---@field TNTExplode EventType
|
||||
---@field TNTExplodeInBuild EventType
|
||||
---@field SelfJoin EventType
|
||||
---@field SelfLeave EventType
|
||||
---@field DropItem EventType
|
||||
---@field EntityDeath EventType
|
||||
events = {}
|
||||
|
||||
|
||||
---@param id EventType
|
||||
---@param handler fun(params: any): void
|
||||
---@return void
|
||||
function event(id, handler) end
|
||||
|
||||
---@param command string
|
||||
---@param handler fun(params: string[]): void
|
||||
---@return void
|
||||
function command(command, handler) end
|
||||
|
||||
---@param trigger string
|
||||
---@param handler fun(pressed: boolean): void
|
||||
---@return void
|
||||
function hotkey(trigger, handler) end
|
386
FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java
Normale Datei
386
FightSystem_Core/src/de/steamwar/fightsystem/ai/navmesh/NavMesh.java
Normale Datei
@ -0,0 +1,386 @@
|
||||
/*
|
||||
* 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.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;
|
||||
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.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.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
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_SHADOW = new BoundingBox(0.2, 0, 0.2, 0.8, 1, 0.8);
|
||||
private static final Set<Pos> 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;
|
||||
private List<REntity> 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(), () -> {
|
||||
long time = System.currentTimeMillis();
|
||||
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");
|
||||
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, () -> {
|
||||
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;
|
||||
}
|
||||
|
||||
public Pos(Vector vector) {
|
||||
this.x = vector.getBlockX();
|
||||
this.y = vector.getBlockY();
|
||||
this.z = vector.getBlockZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return x + "," + y + "," + 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);
|
||||
}
|
||||
|
||||
public Vector toVector() {
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
|
||||
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<Pos, Double> floorBlock = new HashMap<>();
|
||||
private Map<Pos, Double> ceilingOffset = new HashMap<>();
|
||||
private Map<Pos, Set<Pos>> neighbourConnections = new HashMap<>();
|
||||
private Map<Pos, Set<Pos>> reverseNeighbourConnections = 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;
|
||||
|
||||
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;
|
||||
|
||||
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(pos, y + floorHeight);
|
||||
this.ceilingOffset.put(pos, ceilingOffset);
|
||||
}
|
||||
|
||||
private void checkNeighbouring(Pos pos, double posFloorHeight) {
|
||||
Set<Pos> 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean overlaps(VoxelShape voxelShape, double x, double y, double z) {
|
||||
PLAYER_SHADOW.shift(x, y, z);
|
||||
boolean overlaps = voxelShape.overlaps(PLAYER_SHADOW);
|
||||
PLAYER_SHADOW.shift(-x, -y, -z);
|
||||
return overlaps;
|
||||
}
|
||||
|
||||
private void iterateWalkableBlocks(BiConsumer<Vector, Double> consumer) {
|
||||
floorBlock.forEach((pos, aDouble) -> {
|
||||
Vector vector = new Vector(pos.x + 0.5, aDouble, pos.z + 0.5);
|
||||
consumer.accept(vector, ceilingOffset.get(pos));
|
||||
});
|
||||
}
|
||||
|
||||
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 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<Vector> getAllWalkableBlocks() {
|
||||
return floorBlock.keySet().stream().map(Pos::toVector).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Vector> getWalkableBlocks(Vector fromVector) {
|
||||
Pos from = toPos(fromVector);
|
||||
if (from == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Set<Pos> checked = new HashSet<>();
|
||||
List<Pos> 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.add(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<Vector> 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<Pos> checking = new ArrayList<>(Arrays.asList(to));
|
||||
Map<Pos, Pos> route = new HashMap<>();
|
||||
while (!checking.isEmpty()) {
|
||||
Set<Pos> toCheck = new HashSet<>();
|
||||
for (Pos pos : checking) {
|
||||
boolean foundFrom = false;
|
||||
Set<Pos> 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<Pos> path = new ArrayList<>();
|
||||
path.add(from);
|
||||
|
||||
while (path.get(path.size() - 1) != to) {
|
||||
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<Vector> vectors = path.stream().map(p -> {
|
||||
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<Vector> 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);
|
||||
|
||||
if (true) return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
checking.clear();
|
||||
checking.addAll(toCheck);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
@ -76,6 +76,10 @@ public class Region {
|
||||
return maxX - minX;
|
||||
}
|
||||
|
||||
public int getSizeY() {
|
||||
return maxY - minY;
|
||||
}
|
||||
|
||||
public int getSizeZ() {
|
||||
return maxZ - minZ;
|
||||
}
|
||||
|
0
gradlew
vendored
Ausführbare Datei → Normale Datei
0
gradlew
vendored
Ausführbare Datei → Normale Datei
In neuem Issue referenzieren
Einen Benutzer sperren