Commits vergleichen
27 Commits
master
...
lixfel-ai-
Autor | SHA1 | Datum | |
---|---|---|---|
65c85c46b5 | |||
dd1cd9cf37 | |||
cd7dc57914 | |||
618b987d6a | |||
c622e8b4f6 | |||
f130af5465 | |||
dcfe141beb | |||
5cc2e7e7b5 | |||
db365b161b | |||
6177050548 | |||
6fa873204b | |||
f104ccc49a | |||
97a9acdf53 | |||
b09de0a70f | |||
a309541e7e | |||
3b6cde085d | |||
21ee580c0f | |||
b3c5186699 | |||
39ccb113a8 | |||
|
71ddeb4ac8 | ||
|
c507e7ec97 | ||
efb2537097 | |||
|
8e963bffef | ||
5a0614974e | |||
|
2151134dcc | ||
|
fc7b8b6233 | ||
78ae119719 |
@ -21,6 +21,7 @@ package de.steamwar.fightsystem;
|
||||
|
||||
import com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import de.steamwar.core.Core;
|
||||
import de.steamwar.fightsystem.ai.lixfel.LixfelAI;
|
||||
import de.steamwar.fightsystem.commands.*;
|
||||
import de.steamwar.fightsystem.countdown.*;
|
||||
import de.steamwar.fightsystem.event.HellsBells;
|
||||
@ -181,6 +182,12 @@ public class FightSystem extends JavaPlugin {
|
||||
}else if(Config.mode == ArenaMode.PREPARE) {
|
||||
Fight.getUnrotated().setSchem(SchematicNode.getSchematicNode(Config.PrepareSchemID));
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(this, () -> {
|
||||
FightWorld.forceLoad();
|
||||
new LixfelAI(Fight.getBlueTeam());
|
||||
new LixfelAI(Fight.getRedTeam());
|
||||
}, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,6 +83,8 @@ SCHEM_PRIVATE=§ePrivate {0}
|
||||
SCHEM_NO_PRIVATE=§7No private {0} present
|
||||
SCHEM_PRIVATE_FORBIDDEN=§7No private {0} allowed
|
||||
|
||||
ADD_AI_TITLE=Add AI
|
||||
|
||||
|
||||
# Countdowns
|
||||
COUNTDOWN_MINUTES=§e{0} §7Minutes {1}
|
||||
@ -114,6 +116,7 @@ RESPAWN=§eRespawn
|
||||
REMOVE_PLAYERS=§cKick player
|
||||
CHOOSE_SCHEMATIC=§eChoose {0}
|
||||
SCHEMATIC_REQUIRED=§cChoose a schematic first
|
||||
ADD_AI=§eAdd AI
|
||||
|
||||
KIT_PREVIEW_EDIT=§7Edit kit
|
||||
KIT_PREVIEW_CHOOSE=§aSelect kit
|
||||
|
@ -77,6 +77,8 @@ SCHEM_PRIVATE=§ePrivates {0}
|
||||
SCHEM_NO_PRIVATE=§7Kein privates {0} vorhanden
|
||||
SCHEM_PRIVATE_FORBIDDEN=§7Kein privates {0} erlaubt
|
||||
|
||||
ADD_AI_TITLE=KI hinzufügen
|
||||
|
||||
|
||||
# Countdowns
|
||||
COUNTDOWN_MINUTES=§e{0} §7Minuten {1}
|
||||
@ -107,6 +109,7 @@ RESPAWN=§eRespawn
|
||||
REMOVE_PLAYERS=§cSpieler rauswerfen
|
||||
CHOOSE_SCHEMATIC=§e{0} wählen
|
||||
SCHEMATIC_REQUIRED=§cZuerst muss eine Schematic gewählt sein
|
||||
ADD_AI=§eKI hinzufügen
|
||||
|
||||
KIT_PREVIEW_EDIT=§7Kit bearbeiten
|
||||
KIT_PREVIEW_CHOOSE=§aKit wählen
|
||||
|
@ -19,32 +19,38 @@
|
||||
|
||||
package de.steamwar.fightsystem.ai;
|
||||
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import de.steamwar.fightsystem.ArenaMode;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.fight.Fight;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import de.steamwar.fightsystem.fight.JoinRequest;
|
||||
import de.steamwar.fightsystem.listener.Chat;
|
||||
import de.steamwar.fightsystem.record.GlobalRecorder;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.fightsystem.states.OneShotStateDependent;
|
||||
import de.steamwar.fightsystem.utils.Region;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
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.FaceAttachable;
|
||||
import org.bukkit.block.data.Openable;
|
||||
import org.bukkit.block.data.Powerable;
|
||||
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;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
@ -55,10 +61,16 @@ 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<>();
|
||||
public static void printPos() {
|
||||
ais.values().forEach(ai -> ai.chat(ai.entity.isValid() + " " + ai.entity.isDead() + " " + ai.entity.getLocation()));
|
||||
}
|
||||
|
||||
static {
|
||||
new OneShotStateDependent(ArenaMode.All, FightState.Spectate, () -> {
|
||||
new OneShotStateDependent(ArenaMode.AntiReplay, FightState.Spectate, () -> {
|
||||
ais.values().forEach(AI::stop);
|
||||
ais.clear();
|
||||
});
|
||||
@ -67,8 +79,10 @@ public abstract class AI {
|
||||
public static AI getAI(UUID uuid) {
|
||||
return ais.get(uuid);
|
||||
}
|
||||
@Getter
|
||||
|
||||
private final FightTeam team;
|
||||
protected final FightTeam team;
|
||||
@Getter
|
||||
private final LivingEntity entity;
|
||||
private final BukkitTask task;
|
||||
private final Queue<Action> queue = new ArrayDeque<>();
|
||||
@ -83,11 +97,15 @@ public abstract class AI {
|
||||
task = Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), this::run, 1, 1);
|
||||
ais.put(entity.getUniqueId(), this);
|
||||
team.addMember(entity, user);
|
||||
|
||||
if(FightState.Schem.contains(FightState.getFightState()))
|
||||
Bukkit.getScheduler().runTask(FightSystem.getPlugin(), () -> schematic(team.getClipboard()));
|
||||
}
|
||||
|
||||
public abstract SchematicNode chooseSchematic();
|
||||
public abstract void schematic(Clipboard clipboard);
|
||||
|
||||
public boolean acceptJoinRequest(Player player, FightTeam team) {
|
||||
public boolean acceptJoinRequest(JoinRequest.Enquirer enquirer, FightTeam team) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -101,11 +119,7 @@ public abstract class AI {
|
||||
task.cancel();
|
||||
}
|
||||
|
||||
public LivingEntity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
protected void setReady() {
|
||||
public void setReady() {
|
||||
if(FightState.getFightState() != FightState.POST_SCHEM_SETUP)
|
||||
return;
|
||||
|
||||
@ -115,12 +129,12 @@ public abstract class AI {
|
||||
team.setReady(true);
|
||||
}
|
||||
|
||||
protected void chat(String message) {
|
||||
public void chat(String message) {
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + "» " + message);
|
||||
Chat.broadcastChat("PARTICIPANT_CHAT", team.getColoredName(), entity.getName(), message);
|
||||
}
|
||||
|
||||
protected Vector getPosition() {
|
||||
public Vector getPosition() {
|
||||
Location location = entity.getLocation();
|
||||
Region extend = team.getExtendRegion();
|
||||
if(Fight.getUnrotated() == team)
|
||||
@ -137,26 +151,28 @@ public abstract class AI {
|
||||
);
|
||||
}
|
||||
|
||||
protected Material getBlock(Vector pos) {
|
||||
public Material getBlock(Vector pos) {
|
||||
queue.add(new Action(1));
|
||||
return translate(pos, true).getBlock().getType();
|
||||
return translate(pos).getBlock().getType();
|
||||
}
|
||||
|
||||
protected boolean isPowered(Vector pos) {
|
||||
public BlockData getBlockData(Vector pos) {
|
||||
queue.add(new Action(1));
|
||||
return translate(pos, true).getBlock().isBlockPowered();
|
||||
return translate(pos).getBlock().getBlockData();
|
||||
}
|
||||
|
||||
protected void setTNT(Vector pos) {
|
||||
public void setTNT(Vector pos) {
|
||||
queue.add(new Action(1) {
|
||||
@Override
|
||||
public void run() {
|
||||
if(FightState.getFightState() != FightState.RUNNING)
|
||||
return;
|
||||
|
||||
Location location = translate(pos, true);
|
||||
if(interactionDistanceViolation(location))
|
||||
Location location = translate(pos);
|
||||
if(interactionDistanceViolation(location)) {
|
||||
chat("InteractionDistanceViolation: setTNT");
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = location.getBlock();
|
||||
if(block.getType() == Material.AIR)
|
||||
@ -165,24 +181,26 @@ public abstract class AI {
|
||||
});
|
||||
}
|
||||
|
||||
protected void interact(Vector pos) {
|
||||
public void interact(Vector pos) {
|
||||
queue.add(new Action(1) {
|
||||
@Override
|
||||
public void run() {
|
||||
Location location = translate(pos, true);
|
||||
if(interactionDistanceViolation(location))
|
||||
Location location = translate(pos);
|
||||
if(interactionDistanceViolation(location)) {
|
||||
chat("InteractionDistanceViolation: interact");
|
||||
return;
|
||||
}
|
||||
|
||||
interact(location.getBlock());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void interact(Vector pos, int n) {
|
||||
public void interact(Vector pos, int n) {
|
||||
queue.add(new Action(1) {
|
||||
@Override
|
||||
public void run() {
|
||||
Location location = translate(pos, true);
|
||||
Location location = translate(pos);
|
||||
if (interactionDistanceViolation(location))
|
||||
return;
|
||||
Block block = location.getBlock();
|
||||
@ -199,13 +217,18 @@ public abstract class AI {
|
||||
});
|
||||
}
|
||||
|
||||
protected void move(Vector pos) {
|
||||
queue.add(new Action(2) {
|
||||
public void move(Vector pos) {
|
||||
queue.add(new Action(MOVEMENT_DELAY) {
|
||||
@Override
|
||||
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) {
|
||||
if(!entity.isOnGround() && location.getBlock().getType() != Material.LADDER) {
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, "Entity falling");
|
||||
return;
|
||||
}
|
||||
|
||||
Location target = translate(pos);
|
||||
if(Math.abs(location.getX() - target.getX()) > 1.0 || Math.abs(location.getY() - target.getY()) > 1.5 || Math.abs(location.getZ() - target.getZ()) > 1.0) {
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + ": Overdistance movement " + location.toVector() + " " + target.toVector());
|
||||
return;
|
||||
}
|
||||
@ -213,13 +236,16 @@ public abstract class AI {
|
||||
if(!team.getFightPlayer(entity).canEntern() && !team.getExtendRegion().inRegion(target))
|
||||
return;
|
||||
|
||||
entity.teleport(target, PlayerTeleportEvent.TeleportCause.COMMAND);
|
||||
if(!entity.teleport(target, PlayerTeleportEvent.TeleportCause.PLUGIN))
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, "Entity not teleported: " + entity.isValid());
|
||||
|
||||
GlobalRecorder.getInstance().entityMoves(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean interactionDistanceViolation(Location location) {
|
||||
return location.distance(entity.getEyeLocation()) > 5;
|
||||
return location.distance(entity.getEyeLocation()) > INTERACTION_RANGE;
|
||||
}
|
||||
|
||||
private void interact(Block block) {
|
||||
@ -249,23 +275,51 @@ 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() {
|
||||
if(queue.isEmpty())
|
||||
if(queue.isEmpty()) {
|
||||
try {
|
||||
plan();
|
||||
} catch (Throwable t) {
|
||||
stop();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
if(!queue.isEmpty() && --queue.peek().delay == 0)
|
||||
queue.poll().run();
|
||||
}
|
||||
|
||||
private Location translate(Vector pos, boolean blockPos) {
|
||||
public Location translate(Vector pos) {
|
||||
Region extend = team.getExtendRegion();
|
||||
if(Fight.getUnrotated() == team)
|
||||
return new Location(
|
||||
@ -277,9 +331,9 @@ public abstract class AI {
|
||||
else
|
||||
return new Location(
|
||||
Config.world,
|
||||
extend.getMaxX() - pos.getX() - (blockPos ? 1 : 0),
|
||||
extend.getMaxX() - pos.getX(),
|
||||
pos.getY() + team.getSchemRegion().getMinY(),
|
||||
extend.getMaxZ() - pos.getZ() - (blockPos ? 1 : 0)
|
||||
extend.getMaxZ() - pos.getZ()
|
||||
);
|
||||
}
|
||||
|
||||
|
61
FightSystem_Core/src/de/steamwar/fightsystem/ai/AIManager.java
Normale Datei
61
FightSystem_Core/src/de/steamwar/fightsystem/ai/AIManager.java
Normale Datei
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.comphenix.tinyprotocol.Reflection;
|
||||
import de.steamwar.core.Core;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.ai.chaos.ChaosAI;
|
||||
import de.steamwar.fightsystem.ai.lixfel.LixfelAI;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class AIManager {
|
||||
private static final List<AIManager> AIs = Arrays.asList(
|
||||
new AIManager(DummyAI.class, Material.STONE, () -> true),
|
||||
new AIManager(LixfelAI.class, Material.TNT, () -> Core.getVersion() >= 14),
|
||||
new AIManager(ChaosAI.class, Material.SLIME_BLOCK, () -> Config.SchematicType.toDB().equals("miniwargear19") && Core.getVersion() >= 14)
|
||||
);
|
||||
|
||||
public static List<AIManager> availableAIs() {
|
||||
return AIs.stream().filter(manager -> manager.available.getAsBoolean()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private final Class<? extends AI> aiClass;
|
||||
@Getter
|
||||
private final Material icon;
|
||||
private final BooleanSupplier available;
|
||||
|
||||
public String name() {
|
||||
return aiClass.getSimpleName();
|
||||
}
|
||||
|
||||
public void join(FightTeam team) {
|
||||
Reflection.getConstructor(aiClass, FightTeam.class).invoke(team);
|
||||
}
|
||||
}
|
@ -19,42 +19,40 @@
|
||||
|
||||
package de.steamwar.fightsystem.ai;
|
||||
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.fightsystem.utils.FightStatistics;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class LixfelAI extends AI {
|
||||
public class DummyAI extends AI {
|
||||
|
||||
private final Random random = new Random();
|
||||
private LixfelPathplanner pathplanner;
|
||||
public DummyAI(FightTeam team) {
|
||||
super(team, SteamwarUser.get("public"));
|
||||
|
||||
public LixfelAI(FightTeam team, String user) {
|
||||
super(team, SteamwarUser.get(user));
|
||||
FightStatistics.unrank();
|
||||
getEntity().setInvulnerable(true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SchematicNode chooseSchematic() {
|
||||
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 publics.get(new Random().nextInt(publics.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schematic(Clipboard clipboard) {
|
||||
//does nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void plan() {
|
||||
if(FightState.getFightState() == FightState.POST_SCHEM_SETUP)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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 de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.sql.SchematicData;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class LixfelPathplanner {
|
||||
|
||||
private static BlockType getBlockType(Clipboard clipboard, BlockVector3 vector) {
|
||||
return clipboard.getBlock(vector).getBlockType();
|
||||
}
|
||||
|
||||
private static boolean nonsolid(Clipboard clipboard, BlockVector3 vector) {
|
||||
return !getBlockType(clipboard, vector).getMaterial().isSolid();
|
||||
}
|
||||
|
||||
private static Vector toBukkit(BlockVector3 vector) {
|
||||
return new Vector(vector.getX() + 0.5, vector.getY(), vector.getZ() + 0.5);
|
||||
}
|
||||
|
||||
private final List<Vector> walkable = new ArrayList<>();
|
||||
private final Map<Vector, Vector[]> neighbours = new HashMap<>();
|
||||
|
||||
public LixfelPathplanner(SchematicNode schem) {
|
||||
try {
|
||||
fillWalkable(new SchematicData(schem).load());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Vector> getWalkable() {
|
||||
return walkable;
|
||||
}
|
||||
|
||||
private void fillWalkable(Clipboard clipboard) {
|
||||
BlockVector3 min = clipboard.getRegion().getMinimumPoint().subtract(Config.PreperationArea, 0, Config.PreperationArea); //TODO assumes nonextended Schematic with maximal size
|
||||
Region region = clipboard.getRegion();
|
||||
clipboard.getRegion().forEach(vector -> {
|
||||
BlockVector3 below = vector.subtract(0, 1, 0);
|
||||
if(!region.contains(below))
|
||||
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)));
|
||||
}
|
||||
});
|
||||
|
||||
for(Vector vector : walkable) {
|
||||
neighbours.put(vector, walkable.stream().filter(neighbour -> neighbouring(neighbour, vector)).filter(neighbour -> neighbour != vector).toArray(Vector[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
plan.add(destination);
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
public List<Vector> plan(Vector start, Vector destination) {
|
||||
if(neighbouring(start, destination))
|
||||
return Collections.singletonList(destination);
|
||||
|
||||
Map<Vector, Vector> approach = new HashMap<>();
|
||||
Set<Vector> checking = Collections.singleton(destination);
|
||||
|
||||
while(!checking.isEmpty()) {
|
||||
Set<Vector> toCheck = new HashSet<>();
|
||||
for(Vector current : checking) {
|
||||
Vector firstStep = Arrays.stream(neighbours.get(current))
|
||||
.filter(vector -> !approach.containsKey(vector))
|
||||
.filter(next -> {
|
||||
approach.put(next, current);
|
||||
toCheck.add(next);
|
||||
return neighbouring(next, start);
|
||||
})
|
||||
.findAny().orElse(null);
|
||||
|
||||
if(firstStep != null) {
|
||||
List<Vector> path = new ArrayList<>();
|
||||
path.add(firstStep);
|
||||
|
||||
while(path.get(path.size()-1) != destination) {
|
||||
path.add(approach.get(path.get(path.size()-1)));
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
checking = toCheck;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean neighbouring(Vector a, Vector b) {
|
||||
return Math.abs(a.getX() - b.getX()) <= 1 && Math.abs(a.getY() - b.getY()) <= 1 && Math.abs(a.getZ() - b.getZ()) <= 1;
|
||||
}
|
||||
}
|
31
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/Bridge.java
Normale Datei
31
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/Bridge.java
Normale Datei
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 Bridge {
|
||||
private Bridge() {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
172
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/Cannon.java
Normale Datei
172
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/Cannon.java
Normale Datei
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
private 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 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);
|
||||
}
|
211
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/ChaosAI.java
Normale Datei
211
FightSystem_Core/src/de/steamwar/fightsystem/ai/chaos/ChaosAI.java
Normale Datei
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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 com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.ai.AI;
|
||||
import de.steamwar.fightsystem.ai.lixfel.LixfelPathplanner;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import de.steamwar.fightsystem.fight.JoinRequest;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ChaosAI extends AI {
|
||||
private LixfelPathplanner pathplanner;
|
||||
private State state = State.PRE_PREPARE;
|
||||
private static final Cannon[] cannons = new Cannon[] {
|
||||
Cannon.DS_LEFT,
|
||||
Cannon.DS_RIGHT,
|
||||
Cannon.STATIC_LEFT,
|
||||
Cannon.STATIC_RIGHT,
|
||||
Cannon.HA_LEFT,
|
||||
Cannon.HA_RIGHT,
|
||||
//Cannon.AK,
|
||||
};
|
||||
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<>();
|
||||
|
||||
|
||||
public ChaosAI(FightTeam team) {
|
||||
this(team, SteamwarUser.get(14533));
|
||||
}
|
||||
|
||||
|
||||
public ChaosAI(FightTeam team, SteamwarUser user) {
|
||||
super(team, user);
|
||||
if (Boolean.FALSE.equals(onePrepares.getOrDefault(team, false))) {
|
||||
prepares = true;
|
||||
onePrepares.put(team, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchematicNode chooseSchematic() {
|
||||
return SchematicNode.getSchematicNode(111476);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schematic(Clipboard clipboard) {
|
||||
pathplanner = new LixfelPathplanner(this, clipboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void plan() {
|
||||
switch (state) {
|
||||
case PRE_PREPARE:
|
||||
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> {
|
||||
state = State.PREPARE;
|
||||
}, 20 * 15);
|
||||
state = State.PREPARING_PREPARE;
|
||||
break;
|
||||
case WAIT_TILL_IGNITE:
|
||||
case PREPARING_PREPARE:
|
||||
break;
|
||||
case PREPARE:
|
||||
prepare();
|
||||
state = State.PREPARE_READY;
|
||||
break;
|
||||
case PREPARE_READY:
|
||||
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(Bridge.BRIDGE_MG);
|
||||
chat("Die MG ist angezündet!");
|
||||
}
|
||||
state = State.WAIT_TILL_IGNITE;
|
||||
currentCannon = randomCannon();
|
||||
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> state = State.FIGHT, 20 * 9);
|
||||
}
|
||||
break;
|
||||
case FIGHT:
|
||||
fireCannon(currentCannon);
|
||||
break;
|
||||
case ESCAPE:
|
||||
chat("Ich flüchte!");
|
||||
if (currentCannon.escape != null) {
|
||||
pathplanner.planToAnywhere(getPosition(), currentCannon.escape.add(new Vector(0.5, 0, 0.5))).forEach(this::move);
|
||||
}
|
||||
|
||||
cannonsShot.computeIfAbsent(team, fightTeam -> new HashSet<>()).remove(currentCannon);
|
||||
currentCannon = randomCannon();
|
||||
|
||||
state = State.FIGHT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@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(JoinRequest.Enquirer enquirer, FightTeam team) {
|
||||
return team == this.team;
|
||||
}
|
||||
|
||||
public void fireCannon(Cannon cannon) {
|
||||
chat("Ich feuere " + cannon.name + " an!");
|
||||
pathplanner.planToAnywhere(getPosition(), cannon.load.add(new Vector(0.5, 0, 0.5))).forEach(this::move);
|
||||
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(Bridge.BRIDGE_MG);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Vector vector : cannon.tnt) {
|
||||
setTNT(vector);
|
||||
}
|
||||
interact(cannon.button);
|
||||
}
|
||||
|
||||
state = State.ESCAPE;
|
||||
}
|
||||
|
||||
private Cannon randomCannon() {
|
||||
Cannon nextCannon = cannons[random.nextInt(cannons.length)];
|
||||
while (cannonsShot.computeIfAbsent(team, fightTeam -> new HashSet<>()).contains(nextCannon)) {
|
||||
nextCannon = cannons[random.nextInt(cannons.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;
|
||||
}, 20 * 21);
|
||||
}
|
||||
|
||||
public void prepare() {
|
||||
chat("Prepare your fkin 4nus");
|
||||
if (prepares) {
|
||||
pathplanner.planToAnywhere(getPosition(), Bridge.BRIDGE_POS.add(new Vector(0.5, 0, 0.5))).forEach(this::move);
|
||||
interact(Bridge.BRIDGE_SHIELDS);
|
||||
pathplanner.planToAnywhere(getPosition(), Bridge.BRIDGE_POS.add(new Vector(0.5, 0, 0.5)).subtract(new Vector(2, 0, 0))).forEach(this::move);
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
PRE_PREPARE,
|
||||
PREPARING_PREPARE,
|
||||
PREPARE,
|
||||
PREPARE_READY,
|
||||
WAIT_TILL_START,
|
||||
IGNITE_MG,
|
||||
WAIT_TILL_IGNITE,
|
||||
FIGHT,
|
||||
ESCAPE,
|
||||
|
||||
}
|
||||
}
|
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
|
254
FightSystem_Core/src/de/steamwar/fightsystem/ai/lixfel/LixfelAI.java
Normale Datei
254
FightSystem_Core/src/de/steamwar/fightsystem/ai/lixfel/LixfelAI.java
Normale Datei
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.ai.AI;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Waterlogged;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class LixfelAI extends AI {
|
||||
|
||||
private static final Vector CENTER_BLOCK = new Vector(0.5, 0.5, 0.5);
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
private List<Supplier<PlanResult>> plans = new ArrayList<>();
|
||||
private LixfelPathplanner pathplanner;
|
||||
private LixfelCircuit circuit;
|
||||
private MovementEmergency movementEmergency;
|
||||
|
||||
public LixfelAI(FightTeam team) {
|
||||
super(team, SteamwarUser.get("Lixfel.AI"));
|
||||
getEntity().setGlowing(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchematicNode chooseSchematic() {
|
||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.SchematicType.toDB());
|
||||
return publics.stream().filter(s -> s.getName().equals("TheUnderground")).findAny().orElseGet(() -> publics.get(random.nextInt(publics.size())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schematic(Clipboard clipboard) {
|
||||
pathplanner = new LixfelPathplanner(this, clipboard);
|
||||
circuit = new LixfelCircuit(this, pathplanner, clipboard);
|
||||
movementEmergency = new MovementEmergency();
|
||||
|
||||
//TODO low priority plan to reconnect split walkable grid
|
||||
plans = new ArrayList<>();
|
||||
plans.add(movementEmergency);
|
||||
//plans.add(new ReadyPlan());
|
||||
|
||||
List<Vector> waterBlocks = findBlocks(clipboard, data -> data.getMaterial() == Material.WATER || (data instanceof Waterlogged && ((Waterlogged)data).isWaterlogged()));
|
||||
|
||||
if(team.getSchematic() == 111476) { //MWG TheUnderground
|
||||
plans.add(new TimedInteraction(200.0, -1, new Vector(25.5, 15.5, 25.5)));
|
||||
plans.add(new TimedInteraction(210.0, 420, new Vector(21.5, 15.5, 24.5)));
|
||||
|
||||
List<Cannon> cannons = new ArrayList<>(Arrays.asList(
|
||||
new Cannon(new Vector(11.5,25.5,11.5), 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.5,25.5,11.5), 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.5,18.5,11.5), 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),
|
||||
new Cannon(new Vector(38.5,18.5,11.5), 37,18,14, 36,18,14, 36,17,15, 37,17,15, 37,18,15, 36,18,15, 36,17,14, 37,17,14, 36,17,16, 37,17,16, 36,18,16, 37,18,16),
|
||||
new Cannon(new Vector(9.5,9.5,16.5), 10,11,17, 10,8,19, 10,8,17, 11,8,19, 11,8,15, 10,8,15, 12,8,15, 10,10,15, 12,7,19, 11,10,15, 12,8,19, 12,7,15, 11,7,15, 10,9,17, 11,9,19, 12,9,19, 10,9,19, 10,7,17, 11,7,19, 10,7,15, 10,7,19, 10,10,17, 12,9,15, 10,9,15, 11,9,15, 12,10,19, 11,10,19, 12,10,15, 10,6,17, 10,10,19),
|
||||
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)
|
||||
));
|
||||
plans.addAll(cannons);
|
||||
assignWater(cannons, waterBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move(Vector pos) {
|
||||
movementEmergency.setPlannedPosition(pos);
|
||||
super.move(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void plan() {
|
||||
plans.stream().map(Supplier::get).filter(result -> result != PlanResult.EMPTY).max(Comparator.comparingDouble(PlanResult::getRating)).ifPresent(PlanResult::act);
|
||||
}
|
||||
|
||||
private List<Vector> findBlocks(Clipboard clipboard, Predicate<BlockData> predicate) {
|
||||
List<Vector> blocks = new ArrayList<>();
|
||||
clipboard.getRegion().forEach(block -> {
|
||||
if(predicate.test(BukkitAdapter.adapt(clipboard.getBlock(block))))
|
||||
blocks.add(pathplanner.clipboardToSchem(block));
|
||||
});
|
||||
return blocks;
|
||||
}
|
||||
|
||||
private void assignWater(List<Cannon> cannons, List<Vector> waterBlocks) {
|
||||
for(Vector water : waterBlocks) {
|
||||
cannons.stream().filter(cannon -> cannon.getTNTMinY() >= water.getBlockY()).min(Comparator.comparingDouble(c -> c.waterDistance(water))).ifPresent(cannon -> cannon.addWater(water.add(CENTER_BLOCK)));
|
||||
}
|
||||
}
|
||||
|
||||
private class ReadyPlan implements Supplier<PlanResult> {
|
||||
@Override
|
||||
public PlanResult get() {
|
||||
return new PlanResult(Double.MIN_VALUE, () -> {
|
||||
setReady();
|
||||
plans.remove(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Setter
|
||||
private class MovementEmergency implements Supplier<PlanResult> {
|
||||
private Vector plannedPosition;
|
||||
|
||||
@Override
|
||||
public PlanResult get() {
|
||||
if(plannedPosition == null)
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
Vector position = getPosition();
|
||||
if(position.getY() >= plannedPosition.getY())
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
if(pathplanner.isLadder(plannedPosition) && getEntity().getVelocity().getY() >= -0.23)
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
pathplanner.removePosition(plannedPosition); //obviously not walkable anymore
|
||||
if(!getEntity().isOnGround()) {
|
||||
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);
|
||||
setPlannedPosition(position);
|
||||
return PlanResult.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 pathplanner.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;
|
||||
|
||||
public Cannon(Vector activator, int... tntpos) {
|
||||
this.activator = activator;
|
||||
|
||||
for(int i = 0; i < tntpos.length; i+=3) {
|
||||
tnt.put(new Vector(tntpos[i], tntpos[i+1], tntpos[i+2]).add(CENTER_BLOCK), false);
|
||||
}
|
||||
|
||||
minY = tnt.keySet().stream().min(Comparator.comparingInt(Vector::getBlockY)).orElse(new Vector()).getBlockY();
|
||||
}
|
||||
|
||||
public int getTNTMinY() {
|
||||
return minY;
|
||||
}
|
||||
|
||||
public void addWater(Vector vector) {
|
||||
water.add(vector);
|
||||
}
|
||||
|
||||
public double waterDistance(Vector vector) {
|
||||
return tnt.keySet().stream().mapToDouble(t -> t.distance(vector)).min().orElse(Double.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanResult get() {
|
||||
//TODO smarter ratings
|
||||
if(getEntity().getTicksLived() < freeAt || water.isEmpty())
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
if(lastCannonCheck < freeAt) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if(activator == null && tnt.values().stream().allMatch(b -> b))
|
||||
fired();
|
||||
}))).max(Comparator.comparingDouble(PlanResult::getRating)).orElseGet(() -> {
|
||||
if(activator == null)
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
return pathplanner.inRange(110.0, activator, () -> new PlanResult(120.0, () -> {
|
||||
interact(activator);
|
||||
fired();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void fired() {
|
||||
freeAt = getEntity().getTicksLived() + 80;
|
||||
for(Map.Entry<Vector, Boolean> entry : tnt.entrySet())
|
||||
entry.setValue(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.ai.AI;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.Dropper;
|
||||
import org.bukkit.block.data.*;
|
||||
import org.bukkit.block.data.type.Comparator;
|
||||
import org.bukkit.block.data.type.Observer;
|
||||
import org.bukkit.block.data.type.*;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LixfelCircuit {
|
||||
private final Map<Vector, Component> grid = new HashMap<>();
|
||||
|
||||
public LixfelCircuit(AI ai, LixfelPathplanner pathplanner, Clipboard clipboard) {
|
||||
for(BlockVector3 block : clipboard.getRegion()) {
|
||||
BlockData data = getData(clipboard, block);
|
||||
Set<BlockVector3> sources = getSources(clipboard, block, data);
|
||||
|
||||
//TODO other triggers without sources
|
||||
if(!sources.isEmpty() || data instanceof Switch || data instanceof NoteBlock || data instanceof TripwireHook || data instanceof Lectern || data.getMaterial().name().endsWith("PRESSURE_PLATE"))
|
||||
grid.put(pathplanner.clipboardToSchem(block), new Component(sources.stream().map(pathplanner::clipboardToSchem).collect(Collectors.toSet()), data));
|
||||
}
|
||||
|
||||
for(Vector block : grid.keySet())
|
||||
Config.world.getBlockAt(ai.translate(block).add(0, ai.getTeam().getExtendRegion().getSizeY(), 0)).setType(Material.STONE);
|
||||
for(Map.Entry<Vector, Component> entry : grid.entrySet())
|
||||
if(entry.getValue().isOpenEnd())
|
||||
Config.world.getBlockAt(ai.translate(entry.getKey()).add(0, ai.getTeam().getExtendRegion().getSizeY(), 0)).setType(Material.GLASS);
|
||||
|
||||
Map<Vector, Map<Integer, List<Vector>>> cannonMap = new HashMap<>();
|
||||
for (Map.Entry<Vector, Component> openEndEntry : grid.entrySet()) {
|
||||
Component openEnd = openEndEntry.getValue();
|
||||
if(!openEnd.isOpenEnd())
|
||||
continue;
|
||||
|
||||
List<DelayedPos> list = new ArrayList<>();
|
||||
fillTriggers(ai, new HashSet<>(), list, openEndEntry.getKey(), 0);
|
||||
|
||||
for(DelayedPos pos : list) {
|
||||
cannonMap.computeIfAbsent(pos.pos, p -> new HashMap<>()).computeIfAbsent(pos.delay, delay -> new ArrayList<>()).add(openEndEntry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for(Map.Entry<Vector, Map<Integer, List<Vector>>> entry : cannonMap.entrySet()) {
|
||||
Map<Integer, List<Vector>> ends = entry.getValue();
|
||||
List<Vector> propellants = null;
|
||||
List<Vector> projectiles = new ArrayList<>();
|
||||
int visited = 0;
|
||||
for(int time = 0; time < 100; time++) {
|
||||
List<Vector> step = ends.get(time);
|
||||
if(step == null)
|
||||
continue;
|
||||
|
||||
if(propellants == null) {
|
||||
//if(step.size() >= 4)
|
||||
propellants = step;
|
||||
} else {
|
||||
projectiles.addAll(step);
|
||||
}
|
||||
|
||||
visited++;
|
||||
if(visited == ends.size())
|
||||
break;
|
||||
}
|
||||
|
||||
if(propellants != null /*&& !projectiles.isEmpty()*/) {
|
||||
Config.world.getBlockAt(ai.translate(entry.getKey()).add(0, ai.getTeam().getExtendRegion().getSizeY(), 0)).setType(Material.RED_WOOL);
|
||||
for(Vector propellant : propellants)
|
||||
Config.world.getBlockAt(ai.translate(propellant).add(0, ai.getTeam().getExtendRegion().getSizeY(), 0)).setType(Material.GREEN_WOOL);
|
||||
for(Vector projectile : projectiles)
|
||||
Config.world.getBlockAt(ai.translate(projectile).add(0, ai.getTeam().getExtendRegion().getSizeY(), 0)).setType(Material.BLUE_WOOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillTriggers(AI ai, Set<Vector> seen, List<DelayedPos> list, Vector pos, int delay) {
|
||||
if(!seen.add(pos))
|
||||
return;
|
||||
|
||||
Component component = grid.get(pos);
|
||||
if(component == null)
|
||||
return;
|
||||
|
||||
Config.world.getBlockAt(ai.translate(pos).add(0, ai.getTeam().getExtendRegion().getSizeY(), 0)).setType(Material.GREEN_STAINED_GLASS);
|
||||
delay += component.delay();
|
||||
if(component.isTrigger())
|
||||
list.add(new DelayedPos(pos, delay));
|
||||
|
||||
for(Vector source : component.getSources())
|
||||
fillTriggers(ai, seen, list, source, delay);
|
||||
}
|
||||
|
||||
private static final BlockVector3[] directNeighbours = new BlockVector3[]{BlockVector3.UNIT_X, BlockVector3.UNIT_Y, BlockVector3.UNIT_Z, BlockVector3.UNIT_MINUS_X, BlockVector3.UNIT_MINUS_Y, BlockVector3.UNIT_MINUS_Z};
|
||||
private Set<BlockVector3> getSources(Clipboard clipboard, BlockVector3 block, BlockData data) {
|
||||
Set<BlockVector3> sources = new HashSet<>();
|
||||
|
||||
if(data instanceof Observer) {
|
||||
BlockFace face = ((Directional) data).getFacing();
|
||||
BlockVector3 source = block.add(face.getModX(), face.getModY(), face.getModZ());
|
||||
BlockData sourceData = getData(clipboard, source);
|
||||
if(sourceData instanceof AnaloguePowerable || sourceData instanceof Powerable || sourceData instanceof Lightable || sourceData instanceof Hopper || sourceData instanceof Piston || sourceData instanceof TechnicalPiston || data instanceof Dispenser || data instanceof Dropper || sourceData.getMaterial().isAir() || sourceData.getMaterial() == Material.TNT)
|
||||
sources.add(source);
|
||||
} else if (data instanceof Repeater || data instanceof RedstoneWallTorch) {
|
||||
BlockFace face = ((Directional) data).getFacing(); //TODO correct directions?
|
||||
powers(clipboard, block, sources, BlockVector3.at(face.getModX(), face.getModY(), face.getModZ()));
|
||||
} else if (data instanceof Comparator) {
|
||||
BlockFace face = ((Directional) data).getFacing();
|
||||
BlockVector3 neighbour = BlockVector3.at(face.getModX(), face.getModY(), face.getModZ());
|
||||
powers(clipboard, block, sources, neighbour);
|
||||
powersDirectly(clipboard, block, sources, BlockVector3.at(face.getModZ(), 0, face.getModX()), BlockVector3.at(-face.getModZ(), 0, -face.getModX()));
|
||||
BlockVector3 source = block.add(neighbour);
|
||||
BlockData sourceData = getData(clipboard, source);
|
||||
if(sourceData instanceof Furnace || sourceData instanceof Chest || sourceData instanceof NoteBlock || sourceData instanceof Lectern || sourceData instanceof Hopper)
|
||||
sources.add(source);
|
||||
} else if (data instanceof RedstoneWire) {
|
||||
powers(clipboard, block, sources, directNeighbours);
|
||||
powersRedstone(clipboard, block, sources, BlockVector3.at(-1, -1, 0), BlockVector3.at(1, -1, 0), BlockVector3.at(0, -1, -1), BlockVector3.at(0, -1, 1));
|
||||
} else if (data instanceof Piston) {
|
||||
BlockFace face = ((Directional) data).getFacing();
|
||||
for (BlockVector3 neighbour : directNeighbours)
|
||||
if (neighbour.getX() != face.getModX() || neighbour.getY() != face.getModY() || neighbour.getZ() != face.getModZ())
|
||||
powers(clipboard, block, sources, neighbour);
|
||||
|
||||
powers(clipboard, block, sources, BlockVector3.at(0, 2, 0));
|
||||
} else if (data instanceof Powerable || data instanceof Dispenser || data instanceof Dropper || data.getMaterial().isAir() || data.getMaterial() == Material.TNT || data.getMaterial().name().endsWith("PRESSURE_PLATE")) {
|
||||
powers(clipboard, block, sources, directNeighbours);
|
||||
} else if (data.getMaterial() == Material.REDSTONE_TORCH) {
|
||||
powers(clipboard, block, sources, BlockVector3.UNIT_MINUS_Y);
|
||||
}
|
||||
|
||||
sources.remove(block);
|
||||
return sources;
|
||||
}
|
||||
|
||||
private void powers(Clipboard clipboard, BlockVector3 block, Set<BlockVector3> sources, BlockVector3... eligibleNeighbours) {
|
||||
for(BlockVector3 neighbour : eligibleNeighbours) {
|
||||
BlockVector3 source = block.add(neighbour);
|
||||
if(powers(clipboard, source, block))
|
||||
sources.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean powers(Clipboard clipboard, BlockVector3 block, BlockVector3 target) {
|
||||
BlockData data = getData(clipboard, block);
|
||||
if(data.getMaterial().isOccluding() || data.getMaterial() == Material.REDSTONE_LAMP) {
|
||||
for (BlockVector3 neighbour : directNeighbours) {
|
||||
BlockVector3 source = block.add(neighbour);
|
||||
if(powersDirectly(source, getData(clipboard, source), block))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return powersDirectly(block, data, target);
|
||||
}
|
||||
|
||||
private void powersDirectly(Clipboard clipboard, BlockVector3 block, Set<BlockVector3> sources, BlockVector3... eligibleNeighbours) {
|
||||
for(BlockVector3 neighbour : eligibleNeighbours) {
|
||||
BlockVector3 source = block.add(neighbour);
|
||||
if(powersDirectly(source, getData(clipboard, source), block))
|
||||
sources.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean powersDirectly(BlockVector3 block, BlockData data, BlockVector3 target) {
|
||||
if(data instanceof Comparator || data instanceof Repeater || data instanceof Observer) {
|
||||
BlockFace face = ((Directional)data).getFacing().getOppositeFace();
|
||||
return target.equals(block.add(face.getModX(), face.getModY(), face.getModZ()));
|
||||
}
|
||||
|
||||
return powersRedstone(block, data, target) || data.getMaterial() == Material.REDSTONE_BLOCK || data instanceof RedstoneWallTorch || data instanceof Switch || data instanceof TripwireHook || data.getMaterial() == Material.REDSTONE_TORCH;
|
||||
}
|
||||
|
||||
private void powersRedstone(Clipboard clipboard, BlockVector3 block, Set<BlockVector3> sources, BlockVector3... eligibleNeighbours) {
|
||||
for(BlockVector3 neighbour : eligibleNeighbours) {
|
||||
BlockVector3 source = block.add(neighbour);
|
||||
if(powersRedstone(source, getData(clipboard, source), block))
|
||||
sources.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean powersRedstone(BlockVector3 block, BlockData data, BlockVector3 target) {
|
||||
if (!(data instanceof RedstoneWire))
|
||||
return false;
|
||||
|
||||
RedstoneWire wire = (RedstoneWire) data;
|
||||
BlockVector3 delta = target.subtract(block);
|
||||
for(BlockFace face : wire.getAllowedFaces()) {
|
||||
if(face.getModX() != delta.getX() || face.getModZ() != delta.getZ())
|
||||
continue;
|
||||
|
||||
RedstoneWire.Connection connection = wire.getFace(face);
|
||||
return connection == RedstoneWire.Connection.UP || (delta.getY() == 0 && connection == RedstoneWire.Connection.SIDE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private BlockData getData(Clipboard clipboard, BlockVector3 v) {
|
||||
if (!clipboard.getRegion().contains(v))
|
||||
return Material.AIR.createBlockData();
|
||||
|
||||
return BukkitAdapter.adapt(clipboard.getBlock(v));
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class DelayedPos {
|
||||
Vector pos;
|
||||
int delay;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
private static class Component {
|
||||
private final Set<Vector> sources;
|
||||
private final BlockData data;
|
||||
|
||||
public int delay() {
|
||||
if(data instanceof Repeater)
|
||||
return ((Repeater)data).getDelay();
|
||||
if(data instanceof Observer || data instanceof Piston || data instanceof Comparator)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isOpenEnd() {
|
||||
Material material = data.getMaterial();
|
||||
return material.isAir() || material == Material.TNT || material == Material.DISPENSER;
|
||||
}
|
||||
|
||||
public boolean isTrigger() {
|
||||
//TODO "open" observer
|
||||
return data instanceof Switch || data instanceof NoteBlock || data instanceof TripwireHook || data instanceof Lectern || data.getMaterial().name().endsWith("PRESSURE_PLATE");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
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 {
|
||||
|
||||
private static final double INTERACTION_RANGE = AI.INTERACTION_RANGE - 0.05;
|
||||
|
||||
private static double blockHeight(Clipboard clipboard, BlockVector3 vector) {
|
||||
BlockData data = BukkitAdapter.adapt(clipboard.getFullBlock(vector));
|
||||
Material material = data.getMaterial();
|
||||
if(material.isOccluding())
|
||||
return 1.0;
|
||||
|
||||
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 TrapDoor)
|
||||
return -1.0;
|
||||
} 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 Banner || material.name().endsWith("PRESSURE_PLATE"))
|
||||
return 0.0;
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
} else {
|
||||
if(material == Material.LADDER || material == Material.SCAFFOLDING)
|
||||
return -1.0;
|
||||
else if(material.name().endsWith("_CARPET"))
|
||||
return 0.0625;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private static Vector toBukkit(BlockVector3 vector, double height) {
|
||||
return new Vector(vector.getX() + 0.5, vector.getY() + height, vector.getZ() + 0.5);
|
||||
}
|
||||
|
||||
private final AI ai;
|
||||
private final BlockVector3 diff;
|
||||
private final Set<Vector> ladders = new HashSet<>();
|
||||
private final Map<Vector, List<Vector>> neighbours = new HashMap<>();
|
||||
|
||||
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));
|
||||
diff = clipboard.getRegion().getMinimumPoint().subtract(clipboardToSchem.getBlockX(), clipboardToSchem.getBlockY(), clipboardToSchem.getBlockZ());
|
||||
|
||||
fillWalkable(clipboard);
|
||||
}
|
||||
|
||||
private void fillWalkable(Clipboard clipboard) {
|
||||
List<Vector> walkable = new ArrayList<>();
|
||||
Region region = clipboard.getRegion();
|
||||
clipboard.getRegion().forEach(vector -> {
|
||||
BlockVector3 below = vector.subtract(0, 1, 0);
|
||||
BlockVector3 above = vector.add(0, 1, 0);
|
||||
|
||||
double aboveHeight = region.contains(above) ? blockHeight(clipboard, above) : 0.0;
|
||||
if(aboveHeight > 0.0)
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
Vector block = toBukkit(vector.subtract(diff), Math.max(height, 0.0));
|
||||
walkable.add(block);
|
||||
if(height < 0.0)
|
||||
ladders.add(block);
|
||||
});
|
||||
|
||||
for(Vector vector : walkable) {
|
||||
neighbours.put(vector, walkable.stream().filter(neighbour -> neighbouring(neighbour, vector)).filter(neighbour -> neighbour != vector).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
public Vector clipboardToSchem(BlockVector3 vector) {
|
||||
return toBukkit(vector.subtract(diff), 0.0);
|
||||
}
|
||||
|
||||
public boolean isLadder(Vector vector) {
|
||||
return ladders.contains(vector);
|
||||
}
|
||||
|
||||
public void addPosition(Vector vector) {
|
||||
List<Vector> n = neighbours.keySet().stream().filter(neighbour -> neighbouring(neighbour, vector)).collect(Collectors.toList());
|
||||
neighbours.put(vector, new ArrayList<>(n));
|
||||
|
||||
for(Vector neighbour : n) {
|
||||
neighbours.computeIfPresent(neighbour, (neighBour, list) -> {
|
||||
list.add(vector);
|
||||
return list;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void removePosition(Vector vector) {
|
||||
ladders.remove(vector);
|
||||
List<Vector> n = neighbours.remove(vector);
|
||||
if(n == null)
|
||||
return;
|
||||
|
||||
for(Vector neighbour : n) {
|
||||
neighbours.computeIfPresent(neighbour, (neighBour, list) -> {
|
||||
list.remove(vector);
|
||||
return list;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public PlanResult inRange(double rating, Vector target, Supplier<PlanResult> plan) {
|
||||
return inRangeAndTime(rating, 0, target, plan);
|
||||
}
|
||||
|
||||
public PlanResult inRangeAndTime(double rating, int time, Vector target, Supplier<PlanResult> plan) {
|
||||
Vector position = ai.getPosition();
|
||||
double eyeHeight = ai.getEntity().getEyeHeight();
|
||||
boolean inRange = new Vector(0, eyeHeight, 0).add(position).distance(target) <= INTERACTION_RANGE;
|
||||
if(inRange)
|
||||
return time <= 0 ? plan.get() : PlanResult.EMPTY;
|
||||
|
||||
List<Vector> path = new ArrayList<>(planToRange(position, new Vector(0, -eyeHeight, 0).add(target), INTERACTION_RANGE));
|
||||
if(path.isEmpty() || time > path.size()*AI.MOVEMENT_DELAY)
|
||||
return PlanResult.EMPTY;
|
||||
|
||||
return new PlanResult(rating, () -> ai.move(path.get(0)));
|
||||
}
|
||||
|
||||
public List<Vector> planToAnywhere(Vector start, Vector destination) {
|
||||
Vector intermediate = neighbours.keySet().stream().filter(vector -> neighbouring(vector, destination)).findAny().orElse(null);
|
||||
|
||||
if(intermediate == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
List<Vector> plan = new ArrayList<>(plan(start, Collections.singletonList(intermediate)));
|
||||
plan.add(destination);
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
public List<Vector> planToRange(Vector start, Vector destination, double range) {
|
||||
return plan(start, neighbours.keySet().stream().filter(vector -> vector.distance(destination) <= range).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private 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 = new HashSet<>(destinations);
|
||||
|
||||
while(!checking.isEmpty()) {
|
||||
Set<Vector> toCheck = new HashSet<>();
|
||||
for(Vector current : checking) {
|
||||
Vector firstStep = neighbours.get(current).stream()
|
||||
.filter(vector -> !approach.containsKey(vector))
|
||||
.filter(next -> {
|
||||
approach.put(next, current);
|
||||
toCheck.add(next);
|
||||
return neighbouring(next, start);
|
||||
})
|
||||
.findAny().orElse(null);
|
||||
|
||||
if(firstStep != null) {
|
||||
List<Vector> path = new ArrayList<>();
|
||||
path.add(firstStep);
|
||||
|
||||
while(!destinations.contains(path.get(path.size()-1))) {
|
||||
path.add(approach.get(path.get(path.size()-1)));
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
checking = toCheck;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean neighbouring(Vector a, Vector b) {
|
||||
return Math.abs(a.getX() - b.getX()) <= 1 && Math.abs(a.getY() - b.getY()) <= 1 && Math.abs(a.getZ() - b.getZ()) <= 1;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ package de.steamwar.fightsystem.commands;
|
||||
import de.steamwar.fightsystem.ArenaMode;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.ai.AIManager;
|
||||
import de.steamwar.fightsystem.fight.*;
|
||||
import de.steamwar.fightsystem.listener.PersonalKitCreator;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
@ -38,9 +39,8 @@ import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GUI {
|
||||
private GUI(){}
|
||||
@ -53,7 +53,7 @@ public class GUI {
|
||||
String name = team.getLeader() != null ? team.getLeader().getEntity().getName() : team.getName();
|
||||
inv.setItem(pos, SWItem.getDye(colorCode), colorCode, msg.parse("JOIN_REQUEST_TEAM", p, team.getColor() + name), click -> {
|
||||
p.closeInventory();
|
||||
new JoinRequest(p, team);
|
||||
JoinRequest.forPlayer(p, team);
|
||||
});
|
||||
}
|
||||
|
||||
@ -89,11 +89,29 @@ public class GUI {
|
||||
inv.open();
|
||||
}
|
||||
|
||||
public static void chooseJoinRequests(Player p){
|
||||
List<SWListInv.SWListEntry<Player>> players = JoinRequest.openRequests(p, Fight.getPlayerTeam(p));
|
||||
SWListInv<Player> inv = new SWListInv<>(p, msg.parse("REQUESTS_TITLE", p), players, (ClickType click, Player player) -> {
|
||||
public static void addAI(Player p) {
|
||||
SWListInv<AIManager> inv = new SWListInv<>(
|
||||
p, msg.parse("ADD_AI_TITLE", p),
|
||||
AIManager.availableAIs().stream().map(manager -> new SWListInv.SWListEntry<>(new SWItem(manager.getIcon(), manager.name()), manager)).collect(Collectors.toList()),
|
||||
(click, manager) -> {
|
||||
FightTeam team = Fight.getPlayerTeam(p);
|
||||
if(FightState.PreLeaderSetup.contains(FightState.getFightState())) {
|
||||
manager.join(Fight.getOpposite(team));
|
||||
} else {
|
||||
JoinRequest.forAI(manager, team);
|
||||
}
|
||||
p.closeInventory();
|
||||
RequestsCommand.onJoinRequest(p, player, click.isLeftClick() ? JoinRequest::accept : JoinRequest::decline);
|
||||
}
|
||||
);
|
||||
inv.setCallback(-999, (ClickType click) -> p.closeInventory());
|
||||
inv.open();
|
||||
}
|
||||
|
||||
public static void chooseJoinRequests(Player p){
|
||||
List<SWListInv.SWListEntry<JoinRequest>> players = JoinRequest.openRequests(p, Fight.getPlayerTeam(p));
|
||||
SWListInv<JoinRequest> inv = new SWListInv<>(p, msg.parse("REQUESTS_TITLE", p), players, (ClickType click, JoinRequest request) -> {
|
||||
p.closeInventory();
|
||||
RequestsCommand.onJoinRequest(p, request, click.isLeftClick() ? JoinRequest::accept : JoinRequest::decline);
|
||||
});
|
||||
inv.setCallback(-999, (ClickType click) -> p.closeInventory());
|
||||
inv.open();
|
||||
|
@ -20,6 +20,7 @@
|
||||
package de.steamwar.fightsystem.commands;
|
||||
|
||||
import de.steamwar.fightsystem.ArenaMode;
|
||||
import de.steamwar.fightsystem.ai.AI;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.fightsystem.states.StateDependentCommand;
|
||||
import org.bukkit.command.Command;
|
||||
|
@ -59,13 +59,7 @@ public class RequestsCommand implements CommandExecutor {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void onJoinRequest(Player player, Player target, BiConsumer<JoinRequest, FightTeam> handleJoinRequest) {
|
||||
JoinRequest request = JoinRequest.get(target);
|
||||
if(request == null) {
|
||||
FightSystem.getMessage().send("NO_JOIN_REQUEST", player);
|
||||
return;
|
||||
}
|
||||
|
||||
public static void onJoinRequest(Player player, JoinRequest request, BiConsumer<JoinRequest, FightTeam> handleJoinRequest) {
|
||||
FightTeam team = Fight.getPlayerTeam(player);
|
||||
if(!request.required(team)) {
|
||||
FightSystem.getMessage().send("NO_CONFIRMATION", player);
|
||||
|
@ -34,6 +34,7 @@ import de.steamwar.fightsystem.utils.WorldeditWrapper;
|
||||
import de.steamwar.sql.SchematicData;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SchematicType;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.Location;
|
||||
@ -52,6 +53,7 @@ public class FightSchematic extends StateDependent {
|
||||
private final Region region;
|
||||
private final boolean rotate;
|
||||
|
||||
@Getter
|
||||
private Clipboard clipboard = null;
|
||||
private int schematic = 0;
|
||||
|
||||
@ -137,6 +139,7 @@ public class FightSchematic extends StateDependent {
|
||||
private void paste(){
|
||||
FreezeWorld freezer = new FreezeWorld();
|
||||
|
||||
team.teleportToSpawn();
|
||||
Vector dims = WorldeditWrapper.impl.getDimensions(clipboard);
|
||||
WorldeditWrapper.impl.pasteClipboard(
|
||||
clipboard,
|
||||
@ -149,11 +152,11 @@ public class FightSchematic extends StateDependent {
|
||||
new AffineTransform().rotateY(rotate ? 180 : 0)
|
||||
);
|
||||
FightSystem.getHullHider().initialize(team);
|
||||
team.getPlayers().forEach(fightPlayer -> fightPlayer.ifAI(ai -> ai.schematic(clipboard)));
|
||||
if(ArenaMode.Check.contains(Config.mode) && !team.isBlue())
|
||||
replaceSync(Material.TNT, Material.RED_WOOL);
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), freezer::disable, 3);
|
||||
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), team::teleportToSpawn, 40);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,6 +23,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import de.steamwar.fightsystem.ArenaMode;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.ai.AIManager;
|
||||
import de.steamwar.fightsystem.commands.GUI;
|
||||
import de.steamwar.fightsystem.countdown.Countdown;
|
||||
import de.steamwar.fightsystem.events.TeamLeaveEvent;
|
||||
@ -70,9 +71,11 @@ public class FightTeam {
|
||||
static {
|
||||
setKitButton(notReadyKit, true);
|
||||
|
||||
if(!ArenaMode.RankedEvent.contains(Config.mode)){
|
||||
if(ArenaMode.VariableTeams.contains(Config.mode)){
|
||||
notReadyKit.setItem(2, "REQUESTS", new ItemBuilder(Material.PAPER).build(), GUI::chooseJoinRequests);
|
||||
notReadyKit.setItem(3, "REMOVE_PLAYERS", new ItemBuilder(SWItem.getMaterial("FIREWORK_CHARGE")).build(), GUI::chooseRemove);
|
||||
if(!AIManager.availableAIs().isEmpty())
|
||||
notReadyKit.setItem(6, "ADD_AI", new ItemBuilder(Material.REDSTONE).build(), GUI::addAI);
|
||||
}
|
||||
|
||||
if(Config.test())
|
||||
@ -452,6 +455,10 @@ public class FightTeam {
|
||||
return schematic.getId();
|
||||
}
|
||||
|
||||
public Clipboard getClipboard() {
|
||||
return schematic.getClipboard();
|
||||
}
|
||||
|
||||
public double getCurrentHearts() {
|
||||
return players.values().stream().filter(FightPlayer::isLiving).mapToDouble(fp -> fp.getEntity().getHealth()).sum();
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
package de.steamwar.fightsystem.fight;
|
||||
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.ai.AIManager;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.inventory.SWItem;
|
||||
import de.steamwar.inventory.SWListInv;
|
||||
@ -28,44 +29,66 @@ import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class JoinRequest {
|
||||
|
||||
private static final Map<Player, JoinRequest> activeRequests = new HashMap<>();
|
||||
private static final Map<Player, JoinRequest> playerRequests = new HashMap<>();
|
||||
private static final List<JoinRequest> activeRequests = new ArrayList<>();
|
||||
public static JoinRequest get(Player player) {
|
||||
return playerRequests.get(player);
|
||||
}
|
||||
|
||||
public static List<SWListInv.SWListEntry<Player>> openRequests(Player p, FightTeam team) {
|
||||
return activeRequests.values().stream().filter(
|
||||
public static List<SWListInv.SWListEntry<JoinRequest>> openRequests(Player p, FightTeam team) {
|
||||
return activeRequests.stream().filter(
|
||||
request -> request.waitOnApproval.contains(team)
|
||||
).map(request -> {
|
||||
SWItem item = SWItem.getPlayerSkull(request.player);
|
||||
item.setLore(Arrays.asList(
|
||||
AtomicReference<SWItem> item = new AtomicReference<>();
|
||||
request.enquirer.ifPlayer(player -> item.set(SWItem.getPlayerSkull(player)));
|
||||
request.enquirer.ifAI(manager -> item.set(new SWItem(manager.getIcon(), manager.name())));
|
||||
item.get().setLore(Arrays.asList(
|
||||
FightSystem.getMessage().parse("REQUESTS_LEFT_CLICK", p),
|
||||
FightSystem.getMessage().parse("REQUESTS_RIGHT_CLICK", p)
|
||||
));
|
||||
return new SWListInv.SWListEntry<>(item, request.player);
|
||||
return new SWListInv.SWListEntry<>(item.get(), request);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static void clearRequests() {
|
||||
playerRequests.clear();
|
||||
activeRequests.clear();
|
||||
}
|
||||
|
||||
public static JoinRequest get(Player player) {
|
||||
return activeRequests.get(player);
|
||||
public static void forPlayer(Player player, FightTeam team) {
|
||||
new JoinRequest(new Enquirer() {
|
||||
@Override public String name() { return player.getName(); }
|
||||
@Override public void ifPlayer(Consumer<Player> function) { function.accept(player); }
|
||||
@Override public void ifAI(Consumer<AIManager> function) {}
|
||||
}, team, FightState.ingame() ? Fight.teams() : Collections.singleton(team));
|
||||
}
|
||||
|
||||
private final Player player;
|
||||
public static void forAI(AIManager manager, FightTeam team) {
|
||||
new JoinRequest(new Enquirer() {
|
||||
@Override public String name() { return manager.name(); }
|
||||
@Override public void ifPlayer(Consumer<Player> function) {}
|
||||
@Override public void ifAI(Consumer<AIManager> function) { function.accept(manager); }
|
||||
}, team, Collections.singleton(Fight.getOpposite(team)));
|
||||
}
|
||||
|
||||
private final Enquirer enquirer;
|
||||
private final FightTeam team;
|
||||
private final Set<FightTeam> waitOnApproval;
|
||||
|
||||
public JoinRequest(Player player, FightTeam team) {
|
||||
this.player = player;
|
||||
private JoinRequest(Enquirer enquirer, FightTeam team, Collection<FightTeam> waitOnApproval) {
|
||||
this.enquirer = enquirer;
|
||||
this.team = team;
|
||||
this.waitOnApproval = new HashSet<>(FightState.ingame() ? Fight.teams() : Collections.singleton(team));
|
||||
this.waitOnApproval = new HashSet<>(waitOnApproval);
|
||||
Set<FightTeam> alreadyAccepted = new HashSet<>();
|
||||
|
||||
activeRequests.put(player, this);
|
||||
enquirer.ifPlayer(player -> playerRequests.put(player, this));
|
||||
activeRequests.add(this);
|
||||
for(FightTeam t : waitOnApproval) {
|
||||
FightPlayer leader = t.getLeader();
|
||||
if(leader == null)
|
||||
@ -74,14 +97,14 @@ public class JoinRequest {
|
||||
if(leader.getEntity() == null)
|
||||
continue;
|
||||
|
||||
leader.ifPlayer(leaderPlayer -> FightSystem.getMessage().sendPrefixless("JOIN_REQUEST_NOTIFICATION", leaderPlayer, "REQUESTS", new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/requests"), player.getName(), team.getColoredName()));
|
||||
leader.ifPlayer(leaderPlayer -> FightSystem.getMessage().sendPrefixless("JOIN_REQUEST_NOTIFICATION", leaderPlayer, "REQUESTS", new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/requests"), enquirer.name(), team.getColoredName()));
|
||||
leader.ifAI(ai -> {
|
||||
if(ai.acceptJoinRequest(player, team))
|
||||
if(ai.acceptJoinRequest(enquirer, team))
|
||||
alreadyAccepted.add(t);
|
||||
});
|
||||
}
|
||||
|
||||
FightSystem.getMessage().sendPrefixless("JOIN_REQUEST_CONFIRMATION", player, ChatMessageType.ACTION_BAR);
|
||||
enquirer.ifPlayer(player -> FightSystem.getMessage().sendPrefixless("JOIN_REQUEST_CONFIRMATION", player, ChatMessageType.ACTION_BAR));
|
||||
alreadyAccepted.forEach(this::accept);
|
||||
}
|
||||
|
||||
@ -93,18 +116,26 @@ public class JoinRequest {
|
||||
waitOnApproval.remove(acceptor);
|
||||
|
||||
if(waitOnApproval.isEmpty()) {
|
||||
team.addMember(player);
|
||||
activeRequests.remove(player);
|
||||
enquirer.ifPlayer(team::addMember);
|
||||
enquirer.ifAI(manager -> manager.join(team));
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
public void decline(FightTeam declinor) {
|
||||
FightSystem.getMessage().sendPrefixless("REQUEST_YOUR_DECLINED", player, ChatMessageType.ACTION_BAR);
|
||||
waitOnApproval.forEach(t -> t.broadcast("REQUEST_DECLINED", player.getName()));
|
||||
silentDecline();
|
||||
enquirer.ifPlayer(player -> FightSystem.getMessage().sendPrefixless("REQUEST_YOUR_DECLINED", player, ChatMessageType.ACTION_BAR));
|
||||
waitOnApproval.forEach(t -> t.broadcast("REQUEST_DECLINED", enquirer.name()));
|
||||
close();
|
||||
}
|
||||
|
||||
public void silentDecline() {
|
||||
activeRequests.remove(player);
|
||||
public void close() {
|
||||
enquirer.ifPlayer(playerRequests::remove);
|
||||
activeRequests.remove(this);
|
||||
}
|
||||
|
||||
public interface Enquirer {
|
||||
String name();
|
||||
void ifPlayer(Consumer<Player> function);
|
||||
void ifAI(Consumer<AIManager> function);
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,6 @@ public class JoinRequestListener implements Listener {
|
||||
public void onLeave(PlayerQuitEvent event) {
|
||||
JoinRequest request = JoinRequest.get(event.getPlayer());
|
||||
if(request != null)
|
||||
request.silentDecline();
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
@ -70,7 +71,7 @@ public class Recording implements Listener {
|
||||
return stack;
|
||||
}
|
||||
|
||||
public static boolean isNotSent(Player p){
|
||||
public static boolean isNotSent(LivingEntity p){
|
||||
FightPlayer fp = Fight.getFightPlayer(p);
|
||||
return fp == null || !fp.isLiving() || FightState.getFightState() == FightState.SPECTATE;
|
||||
}
|
||||
@ -193,10 +194,10 @@ public class Recording implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onEntityDamage(EntityDamageEvent e) {
|
||||
if(e.getEntityType() != EntityType.PLAYER)
|
||||
if(!e.getEntityType().isAlive())
|
||||
return;
|
||||
|
||||
Player p = (Player) e.getEntity();
|
||||
LivingEntity p = (LivingEntity) e.getEntity();
|
||||
if(isNotSent(p))
|
||||
return;
|
||||
|
||||
@ -208,10 +209,10 @@ public class Recording implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onEntityCombust(EntityCombustEvent e) {
|
||||
if(e.getEntityType() != EntityType.PLAYER)
|
||||
if(!e.getEntityType().isAlive())
|
||||
return;
|
||||
|
||||
Player p = (Player) e.getEntity();
|
||||
LivingEntity p = (LivingEntity) e.getEntity();
|
||||
if(isNotSent(p))
|
||||
return;
|
||||
|
||||
|
@ -204,7 +204,7 @@ public interface Recorder {
|
||||
write(0x0a, e.getEntityId(), start, offHand);
|
||||
}
|
||||
|
||||
default void damageAnimation(Player p) {
|
||||
default void damageAnimation(LivingEntity p) {
|
||||
write(0x0b, p.getEntityId());
|
||||
}
|
||||
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren