Changes to support AIs, Add DummyAI

This commit is contained in:
Lixfel
2024-08-18 18:02:02 +02:00
parent acbe6f2f5d
commit 7afdfd89b0
14 changed files with 268 additions and 240 deletions

View File

@ -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

View File

@ -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

View File

@ -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();
});
@ -68,7 +80,9 @@ public abstract class AI {
return ais.get(uuid);
}
private final FightTeam team;
@Getter
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()
);
}

View File

@ -0,0 +1,55 @@
/*
* 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.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)
);
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);
}
}

View File

@ -19,42 +19,42 @@
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;
private static final Random random = new Random();
public LixfelAI(FightTeam team, String user) {
super(team, SteamwarUser.get(user));
public DummyAI(FightTeam team) {
super(team, SteamwarUser.get("public"));
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(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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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.OBSIDIAN);
replaceSync(Material.TNT, Material.RED_WOOL);
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), freezer::disable, 3);
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), team::teleportToSpawn, 40);
}
@Override

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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());
}