Signed-off-by: Lixfel <agga-games@gmx.de>
Dieser Commit ist enthalten in:
Ursprung
97a9acdf53
Commit
f104ccc49a
@ -47,6 +47,6 @@ dependencies {
|
||||
compileOnly 'io.netty:netty-all:4.1.68.Final'
|
||||
compileOnly 'com.mojang:authlib:1.5.25'
|
||||
|
||||
compileOnly swdep("FastAsyncWorldEdit-1.18")
|
||||
compileOnly swdep("WorldEdit-1.15")
|
||||
compileOnly swdep("SpigotCore")
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ import java.util.logging.Level;
|
||||
|
||||
public abstract class AI {
|
||||
|
||||
public static final int MOVEMENT_DELAY = 4;
|
||||
public static final double INTERACTION_RANGE = 5.0;
|
||||
|
||||
private static final Map<UUID, AI> ais = new HashMap<>();
|
||||
@ -209,7 +210,7 @@ public abstract class AI {
|
||||
}
|
||||
|
||||
public void move(Vector pos) {
|
||||
queue.add(new Action(4) {
|
||||
queue.add(new Action(MOVEMENT_DELAY) {
|
||||
@Override
|
||||
public void run() {
|
||||
Location location = entity.getLocation();
|
||||
|
@ -20,51 +20,41 @@
|
||||
package de.steamwar.fightsystem.ai;
|
||||
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.registry.state.BooleanProperty;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.registry.state.Property;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.ai.lixfel.PlanResult;
|
||||
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.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Waterlogged;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class LixfelAI extends AI {
|
||||
|
||||
private static final Random random = new Random();
|
||||
private final Random random = new Random();
|
||||
private final List<Supplier<PlanResult>> plans = new ArrayList<>();
|
||||
|
||||
private final BukkitTask timerTask;
|
||||
private int currentTime;
|
||||
private LixfelPathplanner pathplanner;
|
||||
private List<Vector> setup;
|
||||
private int preRunningStart;
|
||||
private List<TimedVector> timedStart;
|
||||
private List<Cannon> cannons;
|
||||
private Cannon currentCannon;
|
||||
private Vector plannedPosition;
|
||||
|
||||
public LixfelAI(FightTeam team, String user) {
|
||||
super(team, SteamwarUser.get(user));
|
||||
timerTask = Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), () -> currentTime++, 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchematicNode chooseSchematic() {
|
||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB());
|
||||
SchematicNode schem = publics.get(random.nextInt(publics.size()));
|
||||
schem = publics.stream().filter(s -> s.getName().equals("TheUnderground")).findAny().orElse(schem);
|
||||
SchematicNode schem = publics.stream().filter(s -> s.getName().equals("TheUnderground")).findAny().orElseGet(() -> publics.get(random.nextInt(publics.size())));
|
||||
Clipboard clipboard;
|
||||
try {
|
||||
clipboard = new SchematicData(schem).load();
|
||||
@ -73,9 +63,13 @@ public class LixfelAI extends AI {
|
||||
}
|
||||
|
||||
pathplanner = new LixfelPathplanner(clipboard);
|
||||
setup = new ArrayList<>(Collections.singletonList(new Vector(25, 15, 25)));
|
||||
timedStart = new ArrayList<>(Collections.singletonList(new TimedVector(420L, new Vector(21, 15, 24))));
|
||||
cannons = new ArrayList<>(Arrays.asList(
|
||||
|
||||
plans.clear();
|
||||
plans.add(new 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<Cannon> 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),
|
||||
new Cannon(new Vector(12,18,11), 13,17,15, 13,17,16, 13,18,16, 13,18,14, 14,18,14, 14,17,15, 14,17,16, 14,18,16, 13,17,14, 14,17,14, 13,18,15, 14,18,15),
|
||||
@ -84,125 +78,120 @@ public class LixfelAI extends AI {
|
||||
new Cannon(null, 23,5,9, 23,6,9, 23,7,9, 23,8,9),
|
||||
new Cannon(null, 27,5,9, 27,6,9, 27,7,9, 27,8,9)
|
||||
));
|
||||
assignWater(clipboard);
|
||||
chooseCannon();
|
||||
assignWater(cannons, clipboard);
|
||||
plans.addAll(cannons);
|
||||
|
||||
return schem;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void plan() {
|
||||
switch (FightState.getFightState()) {
|
||||
case POST_SCHEM_SETUP:
|
||||
if(prepareSchem() && scanEnemy())
|
||||
setReady();
|
||||
break;
|
||||
case PRE_RUNNING:
|
||||
if(start() && scanEnemy())
|
||||
ensureInRange(currentCannon.tnt.keySet().iterator().next());
|
||||
break;
|
||||
case RUNNING:
|
||||
while(currentCannon.shoot() && chooseCannon() && scanEnemy()) {}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
public void move(Vector pos) {
|
||||
plannedPosition = pos;
|
||||
super.move(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if(!timerTask.isCancelled())
|
||||
timerTask.cancel();
|
||||
|
||||
super.stop();
|
||||
protected void plan() {
|
||||
plans.stream().map(Supplier::get).filter(result -> result != PlanResult.EMPTY).max(Comparator.comparingDouble(PlanResult::getRating)).ifPresent(PlanResult::act);
|
||||
}
|
||||
|
||||
private void assignWater(Clipboard clipboard) {
|
||||
BooleanProperty waterlogged = new BooleanProperty("waterlogged", Arrays.asList(false, true));
|
||||
|
||||
private void assignWater(List<Cannon> cannons, Clipboard clipboard) {
|
||||
List<Vector> waterSources = new ArrayList<>();
|
||||
clipboard.getRegion().forEach(block -> {
|
||||
BaseBlock state = clipboard.getFullBlock(block);
|
||||
if(state.getBlockType().getMaterial().isLiquid() || Boolean.TRUE.equals(state.getState(waterlogged)))
|
||||
BlockState state = clipboard.getBlock(block);
|
||||
Property<?> waterlogged = state.getBlockType().getPropertyMap().get("waterlogged");
|
||||
if(state.getBlockType().getMaterial().isLiquid() || (waterlogged != null && Boolean.TRUE.equals(state.getState(waterlogged))))
|
||||
waterSources.add(pathplanner.clipboardToSchem(block));
|
||||
});
|
||||
|
||||
for(Vector water : waterSources) {
|
||||
cannons.stream().min(Comparator.comparingDouble(c -> c.waterDistance(water))).ifPresent(cannon -> cannon.addWater(water));
|
||||
cannons.stream().filter(cannon -> cannon.getTNTMinY() >= water.getBlockY()).min(Comparator.comparingDouble(c -> c.waterDistance(water))).ifPresent(cannon -> cannon.addWater(water));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean prepareSchem() {
|
||||
return doWith(setup, this::interact);
|
||||
private PlanResult inRange(double rating, Vector target, Supplier<PlanResult> plan) {
|
||||
return inRangeAndTime(rating, 0, target, plan);
|
||||
}
|
||||
|
||||
private boolean scanEnemy() {
|
||||
//TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean start() {
|
||||
if(preRunningStart == 0)
|
||||
preRunningStart = currentTime;
|
||||
|
||||
if(timedStart.isEmpty())
|
||||
return true;
|
||||
|
||||
long time = currentTime - preRunningStart;
|
||||
TimedVector vector = timedStart.get(0);
|
||||
if(!ensureInRange(vector.vector))
|
||||
return false;
|
||||
|
||||
if(time >= vector.getTime()) {
|
||||
interact(timedStart.remove(0).vector);
|
||||
} else {
|
||||
scanEnemy();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean chooseCannon() {
|
||||
List<Cannon> availableCannons = cannons.stream().filter(Cannon::available).collect(Collectors.toList());
|
||||
if(availableCannons.isEmpty())
|
||||
return false;
|
||||
|
||||
currentCannon = availableCannons.get(random.nextInt(availableCannons.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean doWith(List<Vector> targets, Consumer<Vector> method) {
|
||||
if(targets.isEmpty())
|
||||
return true;
|
||||
|
||||
if(ensureInRange(targets.get(0)))
|
||||
method.accept(targets.remove(0));
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean ensureInRange(Vector target) {
|
||||
private PlanResult inRangeAndTime(double rating, int time, Vector target, Supplier<PlanResult> plan) {
|
||||
Vector position = getPosition();
|
||||
boolean inRange = new Vector(0, getEntity().getEyeHeight(), 0).add(position).distance(target) <= 5;
|
||||
boolean inRange = new Vector(0, getEntity().getEyeHeight(), 0).add(position).distance(target) <= AI.INTERACTION_RANGE;
|
||||
if(inRange)
|
||||
return true;
|
||||
return time <= 0 ? plan.get() : PlanResult.EMPTY;
|
||||
|
||||
List<Vector> 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()) {
|
||||
move(new Vector(0, 1.2, 0).add(position));
|
||||
setTNT(position);
|
||||
} else {
|
||||
move(path.get(0));
|
||||
}
|
||||
if(path.isEmpty() || time > path.size()*AI.MOVEMENT_DELAY)
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
return false;
|
||||
return new PlanResult(rating, () -> move(path.get(0)));
|
||||
}
|
||||
|
||||
private class Cannon {
|
||||
private class ReadyPlan implements Supplier<PlanResult> {
|
||||
@Override
|
||||
public PlanResult get() {
|
||||
return new PlanResult(Double.MIN_VALUE, () -> {
|
||||
setReady();
|
||||
plans.remove(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class MovementEmergency implements Supplier<PlanResult> {
|
||||
|
||||
@Override
|
||||
public PlanResult get() {
|
||||
//TODO defunct ladder
|
||||
if(plannedPosition == null || getPosition().equals(plannedPosition) || pathplanner.getLadders().contains(plannedPosition))
|
||||
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, () -> {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TimedInteraction implements Supplier<PlanResult> {
|
||||
|
||||
private final double rating;
|
||||
private final int time;
|
||||
private final Vector vector;
|
||||
|
||||
private int timeReference = 0;
|
||||
public TimedInteraction(double rating, int time, Vector vector) {
|
||||
this.rating = rating;
|
||||
this.vector = vector;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanResult get() {
|
||||
if(!FightState.ingame())
|
||||
timeReference = getEntity().getTicksLived();
|
||||
|
||||
int timeRemaining = time - (getEntity().getTicksLived() - timeReference);
|
||||
return inRangeAndTime(rating, timeRemaining, vector, () -> new PlanResult(rating, () -> {
|
||||
interact(vector);
|
||||
plans.remove(this);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private class Cannon implements Supplier<PlanResult> {
|
||||
private final List<Vector> water = new ArrayList<>();
|
||||
private final Vector activator;
|
||||
private final Map<Vector, Boolean> tnt = new HashMap<>();
|
||||
private final int minY;
|
||||
private int freeAt;
|
||||
private int lastCannonCheck = -1;
|
||||
|
||||
@ -212,6 +201,12 @@ public class LixfelAI extends AI {
|
||||
for(int i = 0; i < tntpos.length; i+=3) {
|
||||
tnt.put(new Vector(tntpos[i], tntpos[i+1], tntpos[i+2]), false);
|
||||
}
|
||||
|
||||
minY = tnt.keySet().stream().min(Comparator.comparingInt(Vector::getBlockY)).orElse(new Vector()).getBlockY();
|
||||
}
|
||||
|
||||
public int getTNTMinY() {
|
||||
return minY;
|
||||
}
|
||||
|
||||
public void addWater(Vector vector) {
|
||||
@ -219,75 +214,47 @@ public class LixfelAI extends AI {
|
||||
}
|
||||
|
||||
public double waterDistance(Vector vector) {
|
||||
return tnt.keySet().stream().mapToDouble(t -> {
|
||||
double distance = t.distance(vector);
|
||||
if(t.getY() < vector.getY())
|
||||
distance += 10.0; //It is unlikely that TNT is loaded from below into the cannon
|
||||
return distance;
|
||||
}).min().orElse(Double.MAX_VALUE);
|
||||
return tnt.keySet().stream().mapToDouble(t -> t.distance(vector)).min().orElse(Double.MAX_VALUE);
|
||||
}
|
||||
|
||||
public boolean available() {
|
||||
return currentTime >= freeAt && !water.isEmpty();
|
||||
}
|
||||
|
||||
public boolean shoot() {
|
||||
for(Vector w : water)
|
||||
Config.world.spawnParticle(Particle.VILLAGER_HAPPY, translate(w, true).add(0.5, 0.5, 0.5), 1);
|
||||
|
||||
if(currentTime < freeAt || water.isEmpty())
|
||||
return true;
|
||||
@Override
|
||||
public PlanResult get() {
|
||||
//TODO smarter ratings
|
||||
if(getEntity().getTicksLived() < freeAt || water.isEmpty())
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
if(lastCannonCheck < freeAt) {
|
||||
Vector w = water.get(random.nextInt(water.size()));
|
||||
BlockData data = getBlockData(w);
|
||||
if(data.getMaterial() == Material.WATER || (data instanceof Waterlogged && ((Waterlogged)data).isWaterlogged()))
|
||||
lastCannonCheck = freeAt;
|
||||
else
|
||||
water.remove(w);
|
||||
return false;
|
||||
return new PlanResult(80.0, () -> {
|
||||
Vector w = water.get(random.nextInt(water.size()));
|
||||
BlockData data = getBlockData(w);
|
||||
if(data.getMaterial() == Material.WATER || (data instanceof Waterlogged && ((Waterlogged)data).isWaterlogged()))
|
||||
lastCannonCheck = freeAt;
|
||||
else
|
||||
water.remove(w);
|
||||
});
|
||||
}
|
||||
|
||||
for(Map.Entry<Vector, Boolean> entry : tnt.entrySet()) {
|
||||
if(!entry.getValue()) {
|
||||
if(ensureInRange(entry.getKey())) {
|
||||
setTNT(entry.getKey());
|
||||
entry.setValue(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return tnt.entrySet().stream().filter(entry -> !entry.getValue()).map(entry -> inRangeAndTime(90.0, FightState.infight() ? 0 : 1, entry.getKey(), () -> new PlanResult(100.0, () -> {
|
||||
setTNT(entry.getKey());
|
||||
entry.setValue(true);
|
||||
|
||||
if(activator != null) {
|
||||
if(!ensureInRange(activator))
|
||||
return false;
|
||||
if(activator == null && tnt.values().stream().allMatch(b -> b))
|
||||
fired();
|
||||
}))).max(Comparator.comparingDouble(PlanResult::getRating)).orElseGet(() -> {
|
||||
if(activator == null)
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
interact(activator);
|
||||
}
|
||||
return inRange(110.0, activator, () -> new PlanResult(120.0, () -> {
|
||||
interact(activator);
|
||||
fired();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
freeAt = currentTime + 80;
|
||||
private void fired() {
|
||||
freeAt = getEntity().getTicksLived() + 80;
|
||||
for(Map.Entry<Vector, Boolean> entry : tnt.entrySet())
|
||||
entry.setValue(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TimedVector {
|
||||
private final long time;
|
||||
private final Vector vector;
|
||||
|
||||
public TimedVector(long time, Vector vector) {
|
||||
this.time = time;
|
||||
this.vector = vector;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public Vector getVector() {
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ public class LixfelPathplanner {
|
||||
|
||||
private final Vector clipboardToSchem;
|
||||
private final BlockVector3 diff;
|
||||
private final List<Vector> ladders = new ArrayList<>();
|
||||
private final List<Vector> walkable = new ArrayList<>();
|
||||
private final Map<Vector, Vector[]> neighbours = new HashMap<>();
|
||||
|
||||
@ -95,6 +96,9 @@ public class LixfelPathplanner {
|
||||
return toBukkit(vector.subtract(diff), 0.0, 0.0);
|
||||
}
|
||||
|
||||
public List<Vector> getLadders() {
|
||||
return ladders;
|
||||
}
|
||||
public List<Vector> getWalkable() {
|
||||
return walkable;
|
||||
}
|
||||
@ -117,10 +121,10 @@ public class LixfelPathplanner {
|
||||
if(height >= 1.0 && region.contains(above))
|
||||
return;
|
||||
|
||||
Vector block = toBukkit(vector.subtract(diff), 0.5, Math.max(height, 0.0));
|
||||
walkable.add(block);
|
||||
if(height < 0.0)
|
||||
height = 0.0;
|
||||
|
||||
walkable.add(toBukkit(vector.subtract(diff), 0.5, height));
|
||||
ladders.add(block);
|
||||
});
|
||||
|
||||
for(Vector vector : walkable) {
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* 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
|
||||
* 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.lixfel;
|
||||
|
||||
|
||||
public class PlanResult {
|
||||
public static final PlanResult EMPTY = new PlanResult(Double.NEGATIVE_INFINITY, () -> {});
|
||||
|
||||
private final double rating;
|
||||
private final Runnable action;
|
||||
|
||||
public PlanResult(double rating, Runnable action) {
|
||||
this.rating = rating;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public double getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void act() {
|
||||
action.run();
|
||||
}
|
||||
}
|
In neuem Issue referenzieren
Einen Benutzer sperren