From 6fa873204bc1bc0950e6d967b449935302563531 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 6 Jan 2024 15:22:55 +0100 Subject: [PATCH] Emergency Movement Signed-off-by: Lixfel --- .../de/steamwar/fightsystem/FightSystem.java | 2 +- .../src/de/steamwar/fightsystem/ai/AI.java | 10 +- .../fightsystem/ai/chaos/ChaosAI.java | 4 +- .../fightsystem/ai/{ => lixfel}/LixfelAI.java | 90 +++++++-------- .../ai/{ => lixfel}/LixfelPathplanner.java | 105 ++++++++++++------ 5 files changed, 129 insertions(+), 82 deletions(-) rename FightSystem_Core/src/de/steamwar/fightsystem/ai/{ => lixfel}/LixfelAI.java (78%) rename FightSystem_Core/src/de/steamwar/fightsystem/ai/{ => lixfel}/LixfelPathplanner.java (67%) diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index 875c926..65e584d 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -22,7 +22,7 @@ package de.steamwar.fightsystem; import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.core.Core; import de.steamwar.fightsystem.ai.DummyAI; -import de.steamwar.fightsystem.ai.LixfelAI; +import de.steamwar.fightsystem.ai.lixfel.LixfelAI; import de.steamwar.fightsystem.commands.*; import de.steamwar.fightsystem.countdown.*; import de.steamwar.fightsystem.event.HellsBells; diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java index 3d894a5..22b0898 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java @@ -296,8 +296,14 @@ public abstract class AI { } private void run() { - if(queue.isEmpty()) - plan(); + if(queue.isEmpty()) { + try { + plan(); + } catch (Throwable t) { + stop(); + throw t; + } + } if(!queue.isEmpty() && --queue.peek().delay == 0) queue.poll().run(); diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/ChaosAI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/ChaosAI.java index b34f9b9..f561463 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/ChaosAI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/ChaosAI.java @@ -22,7 +22,7 @@ package de.steamwar.fightsystem.ai.chaos; import com.sk89q.worldedit.extent.clipboard.Clipboard; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.ai.AI; -import de.steamwar.fightsystem.ai.LixfelPathplanner; +import de.steamwar.fightsystem.ai.lixfel.LixfelPathplanner; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; import de.steamwar.sql.SchematicData; @@ -78,7 +78,7 @@ public class ChaosAI extends AI { throw new IllegalStateException(e); } - pathplanner = new LixfelPathplanner(clipboard); + pathplanner = new LixfelPathplanner(this, clipboard); return schem; } diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/lixfel/LixfelAI.java similarity index 78% rename from FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java rename to FightSystem_Core/src/de/steamwar/fightsystem/ai/lixfel/LixfelAI.java index 42e9191..f50fb74 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelAI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/lixfel/LixfelAI.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2023 SteamWar.de-Serverteam + * Copyright (C) 2024 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 @@ -17,20 +17,19 @@ * along with this program. If not, see . */ -package de.steamwar.fightsystem.ai; +package de.steamwar.fightsystem.ai.lixfel; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.world.block.BlockState; import de.steamwar.fightsystem.Config; -import de.steamwar.fightsystem.ai.lixfel.PlanResult; +import de.steamwar.fightsystem.ai.AI; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; import de.steamwar.sql.SchematicData; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; import org.bukkit.Material; -import org.bukkit.Particle; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Waterlogged; import org.bukkit.util.Vector; @@ -42,10 +41,10 @@ import java.util.function.Supplier; public class LixfelAI extends AI { private final Random random = new Random(); - private final List> plans = new ArrayList<>(); + private List> plans; private LixfelPathplanner pathplanner; - private Vector plannedPosition; + private MovementEmergency movementEmergency; public LixfelAI(FightTeam team, String user) { super(team, SteamwarUser.get(user)); @@ -62,13 +61,16 @@ public class LixfelAI extends AI { throw new IllegalStateException(e); } - pathplanner = new LixfelPathplanner(clipboard); + pathplanner = new LixfelPathplanner(this, clipboard); + movementEmergency = new MovementEmergency(); - plans.clear(); - plans.add(new MovementEmergency()); + //TODO low priority plan to reconnect split walkable grid + plans = new ArrayList<>(); + plans.add(movementEmergency); plans.add(new ReadyPlan()); plans.add(new TimedInteraction(200.0, -1, new Vector(25, 15, 25))); plans.add(new TimedInteraction(210.0, 420, new Vector(21, 15, 24))); + List cannons = new ArrayList<>(Arrays.asList( new Cannon(new Vector(11,25,11), 10,23,14, 12,23,14, 10,23,12, 12,23,12, 12,24,15, 10,24,14, 12,23,15, 12,24,14, 11,24,12, 10,24,15, 12,24,12, 10,23,15, 10,24,12), new Cannon(new Vector(39,25,11), 38,24,15, 38,23,12, 40,23,14, 40,24,12, 40,23,15, 40,24,14, 39,24,12, 38,23,14, 40,24,15, 40,23,12, 38,23,15, 38,24,12, 38,24,14), @@ -86,7 +88,7 @@ public class LixfelAI extends AI { @Override public void move(Vector pos) { - plannedPosition = pos; + movementEmergency.setPlannedPosition(pos); super.move(pos); } @@ -109,26 +111,6 @@ public class LixfelAI extends AI { } } - private PlanResult inRange(double rating, Vector target, Supplier plan) { - return inRangeAndTime(rating, 0, target, plan); - } - - private PlanResult inRangeAndTime(double rating, int time, Vector target, Supplier plan) { - Vector position = getPosition(); - boolean inRange = new Vector(0, getEntity().getEyeHeight(), 0).add(position).distance(target) <= AI.INTERACTION_RANGE; - if(inRange) - return time <= 0 ? plan.get() : PlanResult.EMPTY; - - List path = new ArrayList<>(pathplanner.planToRange(position, new Vector(0, -getEntity().getEyeHeight(), 0).add(target), 5.0)); - for(Vector v : path) - Config.world.spawnParticle(Particle.DRIP_LAVA, translate(v, false), 1); - - if(path.isEmpty() || time > path.size()*AI.MOVEMENT_DELAY) - return PlanResult.EMPTY; - - return new PlanResult(rating, () -> move(path.get(0))); - } - private class ReadyPlan implements Supplier { @Override public PlanResult get() { @@ -140,23 +122,43 @@ public class LixfelAI extends AI { } private class MovementEmergency implements Supplier { + private Vector plannedPosition; + + public void setPlannedPosition(Vector plannedPosition) { + this.plannedPosition = plannedPosition; + } @Override public PlanResult get() { - //TODO defunct ladder - if(plannedPosition == null || getPosition().equals(plannedPosition) || pathplanner.getLadders().contains(plannedPosition)) + if(plannedPosition == null) return PlanResult.EMPTY; - pathplanner.getWalkable().remove(plannedPosition); //TODO neighbour-table update - if(getEntity().getVelocity().getY() < 0) { - return new PlanResult(1000.0, () -> setTNT(getPosition().subtract(new Vector(0, 1.0, 0)))); - } else { - pathplanner.getWalkable().add(getPosition()); //TODO idealized position, neighbour-table update - //TODO handle offgrid location - //TODO update grid - return new PlanResult(10.0, () -> { + Vector position = getPosition(); + if(position.getY() == plannedPosition.getY()) + return PlanResult.EMPTY; - }); + boolean falling = getEntity().getVelocity().getY() < 0; + if(position.getY() > plannedPosition.getY()) { + if(!falling) { //Update grid accordingly + pathplanner.removePosition(plannedPosition); + position.setX(position.getBlockX() + 0.5); + position.setZ(position.getBlockZ() + 0.5); + pathplanner.addPosition(position); + } + return PlanResult.EMPTY; + } + + if(pathplanner.isLadder(plannedPosition) && getEntity().getVelocity().getY() >= -0.23) //alternative measure: falling speed + return PlanResult.EMPTY; + + pathplanner.removePosition(plannedPosition); //obviously not walkable anymore + if(falling) { + return new PlanResult(1000.0, () -> setTNT(position.subtract(new Vector(0, 1.0, 0)))); + } else { + position.setX(position.getBlockX() + 0.5); + position.setZ(position.getBlockZ() + 0.5); + pathplanner.addPosition(position); + return PlanResult.EMPTY; } } } @@ -180,7 +182,7 @@ public class LixfelAI extends AI { timeReference = getEntity().getTicksLived(); int timeRemaining = time - (getEntity().getTicksLived() - timeReference); - return inRangeAndTime(rating, timeRemaining, vector, () -> new PlanResult(rating, () -> { + return pathplanner.inRangeAndTime(rating, timeRemaining, vector, () -> new PlanResult(rating, () -> { interact(vector); plans.remove(this); })); @@ -234,7 +236,7 @@ public class LixfelAI extends AI { }); } - return tnt.entrySet().stream().filter(entry -> !entry.getValue()).map(entry -> inRangeAndTime(90.0, FightState.infight() ? 0 : 1, entry.getKey(), () -> new PlanResult(100.0, () -> { + return tnt.entrySet().stream().filter(entry -> !entry.getValue()).map(entry -> pathplanner.inRangeAndTime(90.0, FightState.infight() ? 0 : 20*Config.PreFightDuration/AI.MOVEMENT_DELAY, entry.getKey(), () -> new PlanResult(100.0, () -> { setTNT(entry.getKey()); entry.setValue(true); @@ -244,7 +246,7 @@ public class LixfelAI extends AI { if(activator == null) return PlanResult.EMPTY; - return inRange(110.0, activator, () -> new PlanResult(120.0, () -> { + return pathplanner.inRange(110.0, activator, () -> new PlanResult(120.0, () -> { interact(activator); fired(); })); diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelPathplanner.java b/FightSystem_Core/src/de/steamwar/fightsystem/ai/lixfel/LixfelPathplanner.java similarity index 67% rename from FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelPathplanner.java rename to FightSystem_Core/src/de/steamwar/fightsystem/ai/lixfel/LixfelPathplanner.java index 16f092a..9862934 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/ai/LixfelPathplanner.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/ai/lixfel/LixfelPathplanner.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2023 SteamWar.de-Serverteam + * Copyright (C) 2024 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 @@ -17,13 +17,14 @@ * along with this program. If not, see . */ -package de.steamwar.fightsystem.ai; +package de.steamwar.fightsystem.ai.lixfel; 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 de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.ai.AI; import de.steamwar.fightsystem.utils.WorldeditWrapper; import org.bukkit.Material; import org.bukkit.block.Banner; @@ -32,6 +33,7 @@ import org.bukkit.block.data.type.*; import org.bukkit.util.Vector; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; public class LixfelPathplanner { @@ -76,14 +78,15 @@ public class LixfelPathplanner { return new Vector(vector.getX() + offset, vector.getY() + height, vector.getZ() + offset); } - private final Vector clipboardToSchem; + private final AI ai; private final BlockVector3 diff; - private final List ladders = new ArrayList<>(); - private final List walkable = new ArrayList<>(); + private final Set ladders = new HashSet<>(); private final Map neighbours = new HashMap<>(); - public LixfelPathplanner(Clipboard clipboard) { - clipboardToSchem = new Vector(Config.BluePasteRegion.getSizeX(), Config.BluePasteRegion.getSizeY(), Config.BluePasteRegion.getSizeZ()) + public LixfelPathplanner(AI ai, Clipboard clipboard) { + this.ai = ai; + + 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)); @@ -92,18 +95,8 @@ public class LixfelPathplanner { fillWalkable(clipboard); } - public Vector clipboardToSchem(BlockVector3 vector) { - return toBukkit(vector.subtract(diff), 0.0, 0.0); - } - - public List getLadders() { - return ladders; - } - public List getWalkable() { - return walkable; - } - private void fillWalkable(Clipboard clipboard) { + List walkable = new ArrayList<>(); Region region = clipboard.getRegion(); clipboard.getRegion().forEach(vector -> { BlockVector3 below = vector.subtract(0, 1, 0); @@ -132,36 +125,82 @@ public class LixfelPathplanner { } } - @Deprecated - public Vector walkableNearby(double eyeHeight, double distance, List nearby) { - List 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 Vector clipboardToSchem(BlockVector3 vector) { + return toBukkit(vector.subtract(diff), 0.0, 0.0); + } + + public boolean isLadder(Vector vector) { + return ladders.contains(vector); + } + + public void addPosition(Vector vector) { + Vector[] n = neighbours.keySet().stream().filter(neighbour -> neighbouring(neighbour, vector)).toArray(Vector[]::new); + neighbours.put(vector, n); + + for(Vector neighbour : n) { + neighbours.computeIfPresent(neighbour, (neighBour, array) -> { + array = Arrays.copyOf(array, array.length+1); + array[array.length-1] = vector; + return array; + }); + } + } + + public void removePosition(Vector vector) { + ladders.remove(vector); + Vector[] n = neighbours.remove(vector); + if(n == null) + return; + + for(Vector neighbour : n) { + neighbours.computeIfPresent(neighbour, (neighBour, array) -> { + for(int i = 0; i < array.length; i++) { + if(array[i] == vector) { + Vector[] newArray = Arrays.copyOf(array, array.length-1); + System.arraycopy(array, i+1, newArray, i, newArray.length-i); + return newArray; + } + } + throw new IllegalStateException(); + }); + } + } + + public PlanResult inRange(double rating, Vector target, Supplier plan) { + return inRangeAndTime(rating, 0, target, plan); + } + + public PlanResult inRangeAndTime(double rating, int time, Vector target, Supplier plan) { + Vector position = ai.getPosition(); + double eyeHeight = ai.getEntity().getEyeHeight(); + boolean inRange = new Vector(0, eyeHeight, 0).add(position).distance(target) <= AI.INTERACTION_RANGE; + if(inRange) + return time <= 0 ? plan.get() : PlanResult.EMPTY; + + List path = new ArrayList<>(planToRange(position, new Vector(0, -eyeHeight, 0).add(target), 5.0)); + if(path.isEmpty() || time > path.size()*AI.MOVEMENT_DELAY) + return PlanResult.EMPTY; + + return new PlanResult(rating, () -> ai.move(path.get(0))); } public List planToAnywhere(Vector start, Vector destination) { - Vector intermediate = walkable.stream().filter(vector -> neighbouring(vector, destination)).findAny().orElse(null); + Vector intermediate = neighbours.keySet().stream().filter(vector -> neighbouring(vector, destination)).findAny().orElse(null); if(intermediate == null) return Collections.emptyList(); - List plan = new ArrayList<>(plan(start, intermediate)); + List plan = new ArrayList<>(plan(start, Collections.singletonList(intermediate))); plan.add(destination); return plan; } public List planToRange(Vector start, Vector destination, double range) { - return plan(start, walkable.stream().filter(vector -> vector.distance(destination) <= range).collect(Collectors.toList())); + return plan(start, neighbours.keySet().stream().filter(vector -> vector.distance(destination) <= range).collect(Collectors.toList())); } - public List plan(Vector start, Vector destination) { - return plan(start, Collections.singletonList(destination)); - } - - public List plan(Vector start, List destinations) { + private List plan(Vector start, List destinations) { for(Vector destination : destinations) if(neighbouring(start, destination)) return Collections.singletonList(destination);