Compare commits

..

3 Commits

Author SHA1 Message Date
Helvetica Volubi ba6bd9f71c Update Luminol 2025-09-06 17:15:36 +08:00
Helvetica Volubi 4513cfd637 Fake Player command use brigadier (#57)
* pre update for new command system branch

* update ref
2025-09-05 03:30:27 +08:00
Helvetica Volubi d87cca6a97 fix: fix shutdown problem if bot was not removed in world 2025-09-04 21:38:52 +08:00
64 changed files with 1943 additions and 1207 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ jobs:
📦Version: `${{ env.mcversion }}` | Commit ${{ env.commit_id }} [![download](https://img.shields.io/github/downloads/LuminolMC/${{ env.project_id }}/${{ env.tag }}/total?color=red&style=flat-square)](https://github.com/LuminolMC/${{ env.project_id }}/releases/download/${{ env.tag }}/${{ env.jar }})
This release is automatically compiled by GitHub Actions
### Branch Info
${{ github.ref_name }}
> ${{ github.ref_name }}
### Commit Message
${{ env.commit_msg }}
artifacts: ${{ env.jar_dir }}
+1 -1
View File
@@ -4,7 +4,7 @@ mcVersion=1.21.8
release=2
# 0 for skip release, 1 for pre-release, 2 for release
luminolRef=e7176d188b9e4d3b1f258d56cda91e2d008282f4
luminolRef=0bf42dcc8d6c09fab78d1bdc5335cb38d7a1f5dc
org.gradle.configuration-cache=true
org.gradle.caching=true
@@ -5,10 +5,10 @@ Subject: [PATCH] Rebrand to Lophine
diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java
index 4be82092886728b7862b6e8951cf1c8ab917ca55..1b822e9576573c360b997e5cb4fa02278c88b74f 100644
index 5e341cdc1c6ff75552140ab16bc9ccd237d896d0..a780214137d10f2faa92042b2f32c77a6598fa8d 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -21,6 +21,7 @@ public class ConfigManager {
@@ -22,6 +22,7 @@ public class ConfigManager {
public static void initConfigs() {
configfiles.put("luminol", ConfigsInstance.of(new File("luminol_config"), "luminol", "me.earthme.luminol.config.modules"));
@@ -33,7 +33,7 @@ index 66ec0424a46dcd49cf44467357d80b1a2d84d3b2..04ae8de63af0a8abe578f14c8ef85fd4
private DisconnectionDetails disconnectionDetails;
private boolean encrypted;
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 0b7c014114f1f8648824fe7c9b75fde3ea79162a..55d3b9a612943fda29716b9e7969b82e43947669 100644
index 0b7c014114f1f8648824fe7c9b75fde3ea79162a..384b121df8bfc72b21b607e8fa4ae90efcc6f3e8 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -348,6 +348,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -45,14 +45,14 @@ index 0b7c014114f1f8648824fe7c9b75fde3ea79162a..55d3b9a612943fda29716b9e7969b82e
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
AtomicReference<S> atomicReference = new AtomicReference<>();
@@ -1066,6 +1068,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1039,6 +1041,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Folia end - region threading
LOGGER.info("Stopping server");
Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing
public void stopServer() {
+ this.getBotList().removeAll(); // Leaves - save or remove bot
// CraftBukkit start
if (this.server != null) {
if (false) this.server.spark.disable(); // Paper - spark // Luminol - Force disable builtin spark
// Folia start - region threading
// halt scheduler
// don't wait, we may be on a scheduler thread
@@ -1589,7 +1592,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
int i = this.pauseWhileEmptySeconds() * 20;
this.removeDisabledPluginsBlockingSleep(); // Paper - API to allow/disallow tick sleeping
@@ -5,14 +5,10 @@ import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
import net.minecraft.server.MinecraftServer;
import org.bukkit.Bukkit;
import org.leavesmc.leaves.bot.BotCommand;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.command.bot.BotCommand;
import java.util.List;
import java.util.Locale;
@ConfigClassInfo(configAttribution = EnumConfigCategory.FUNCTION, mainName = "fakeplayer")
public class FakeplayerConfig implements IConfigModule {
@@ -86,23 +82,28 @@ public class FakeplayerConfig implements IConfigModule {
public static ServerBot.TickType tickType = ServerBot.TickType.ENTITY_LIST;
public static void unregisterCommand(String name) {
name = name.toLowerCase(Locale.ENGLISH).trim();
MinecraftServer.getServer().server.getCommandMap().getKnownCommands().remove(name);
MinecraftServer.getServer().server.getCommandMap().getKnownCommands().remove("leaves:" + name);
MinecraftServer.getServer().server.syncCommands();
}
private BotCommand command = null;
private boolean registered = false;
public static int getSimulationDistance(ServerBot bot) {
return simulationDistance == -1 ? bot.getBukkitEntity().getSimulationDistance() : simulationDistance;
}
@Override
public void onLoaded(CommentedFileConfig configInstance) {
if (enable) {
Bukkit.getCommandMap().register("bot", "lophine", new BotCommand());
Actions.registerAll();
} else {
unregisterCommand("bot");
command = new BotCommand();
command.register();
registered = true;
}
}
@Override
public void onUnloaded(CommentedFileConfig configInstance) {
if (registered) {
command.unregister();
command = null;
}
}
}
@@ -1,504 +0,0 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.bot;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.AbstractBotConfig;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.bot.agent.Configs;
import org.leavesmc.leaves.bot.agent.actions.ServerBotAction;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.entity.bot.Bot;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
import org.leavesmc.leaves.event.bot.BotConfigModifyEvent;
import org.leavesmc.leaves.event.bot.BotCreateEvent;
import org.leavesmc.leaves.event.bot.BotRemoveEvent;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
import org.slf4j.Logger;
import java.util.*;
import static net.kyori.adventure.text.Component.text;
public class BotCommand extends Command {
private static final Logger LOGGER = LogUtils.getLogger();
public BotCommand() {
super("bot");
this.setPermission("lophine.bot");
this.setDescription("FakePlayer Command");
this.setUsage("/bot <create|remove|list|action|config|save|load> [args...]");
}
@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
if (!testPermission(sender)) {
return true;
}
if (!FakeplayerConfig.enable) {
sender.sendMessage(Component.text("Fakeplayer feature is disabled!").color(TextColor.color(255, 0, 0)));
return true;
}
if (args.length == 0) {
sender.sendMessage(Component.text("Usage: /bot <create|remove|list|action|config|save|load> [args...]").color(TextColor.color(255, 255, 0)));
return true;
}
String subCommand = args[0].toLowerCase();
switch (subCommand) {
case "create" -> {
return handleCreate(sender, args);
}
case "remove" -> {
return handleRemove(sender, args);
}
case "list" -> {
return handleList(sender, args);
}
case "action" -> {
return handleAction(sender, args);
}
case "config" -> {
return handleConfig(sender, args);
}
case "save" -> {
return handleSave(sender, args);
}
case "load" -> {
return handleLoad(sender, args);
}
default -> {
sender.sendMessage(Component.text("Unknown subcommand: " + subCommand).color(TextColor.color(255, 0, 0)));
sender.sendMessage(Component.text("Available commands: create, remove, list, action, config, save, load").color(TextColor.color(255, 255, 0)));
}
}
return true;
}
private boolean handleCreate(CommandSender sender, String[] args) {
if (args.length < 2) {
sender.sendMessage(text("Use /bot create <name> [skin_name] to create a fakeplayer", NamedTextColor.RED));
return true;
}
String botName = args[1];
String fullName = BotUtil.getFullName(botName);
if (this.canCreate(sender, fullName)) {
BotCreateState.Builder builder = BotCreateState.builder(botName, Bukkit.getWorlds().getFirst().getSpawnLocation())
.createReason(BotCreateEvent.CreateReason.COMMAND)
.creator(sender);
if (args.length >= 3) {
builder.skinName(args[2]);
}
if (sender instanceof Player player) {
builder.location(player.getLocation());
} else if (sender instanceof ConsoleCommandSender) {
if (args.length >= 7) {
try {
World world = Bukkit.getWorld(args[3]);
double x = Double.parseDouble(args[4]);
double y = Double.parseDouble(args[5]);
double z = Double.parseDouble(args[6]);
if (world != null) {
builder.location(new Location(world, x, y, z));
}
} catch (Exception e) {
LOGGER.warn("Can't build location", e);
}
}
}
builder.spawnWithSkin(null);
}
return true;
}
private boolean handleRemove(CommandSender sender, String[] args) {
if (args.length < 2 || args.length > 5) {
sender.sendMessage(text("Usage: /bot remove <name> [hour] [minute] [second]", NamedTextColor.RED));
return true;
}
String botName = args[1];
BotList botList = BotList.INSTANCE;
ServerBot bot = botList.getBotByName(BotUtil.getFullName(botName));
if (bot == null) {
sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED));
return true;
}
if (args.length == 3 && args[2].equals("cancel")) {
if (bot.removeTaskId == -1) {
sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED));
return true;
}
Bukkit.getScheduler().cancelTask(bot.removeTaskId);
bot.removeTaskId = -1;
sender.sendMessage(text("Remove cancel"));
return true;
}
if (args.length > 2) {
long time = 0;
int h;
long s = 0;
long m = 0;
try {
h = Integer.parseInt(args[2]);
if (h < 0) {
throw new NumberFormatException();
}
time += ((long) h) * 3600 * 20;
if (args.length > 3) {
m = Long.parseLong(args[3]);
if (m > 59 || m < 0) {
throw new NumberFormatException();
}
time += m * 60 * 20;
}
if (args.length > 4) {
s = Long.parseLong(args[4]);
if (s > 59 || s < 0) {
throw new NumberFormatException();
}
time += s * 20;
}
} catch (NumberFormatException e) {
sender.sendMessage(text("Usage: /bot remove <name> [hour] [minute] [second]", NamedTextColor.RED));
return true;
}
boolean isReschedule = bot.removeTaskId != -1;
if (isReschedule) {
Bukkit.getScheduler().cancelTask(bot.removeTaskId);
}
bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> {
bot.removeTaskId = -1;
botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false);
}, time).getTaskId();
sender.sendMessage("This fakeplayer will be removed in " + h + "h " + m + "m " + s + "s" + (isReschedule ? " (rescheduled)" : ""));
return true;
}
botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false);
sender.sendMessage(text("Removed fakeplayer: " + botName, NamedTextColor.GREEN));
return true;
}
private boolean handleList(CommandSender sender, String[] args) {
BotList botList = BotList.INSTANCE;
if (args.length < 2) {
Map<World, List<String>> botMap = new HashMap<>();
for (World world : Bukkit.getWorlds()) {
botMap.put(world, new ArrayList<>());
}
for (ServerBot bot : botList.bots) {
Bot bukkitBot = bot.getBukkitEntity();
botMap.get(bukkitBot.getWorld()).add(bukkitBot.getName());
}
sender.sendMessage("Total number: (" + botList.bots.size() + "/" + FakeplayerConfig.limit + ")");
for (World world : botMap.keySet()) {
sender.sendMessage(world.getName() + "(" + botMap.get(world).size() + "): " + formatPlayerNameList(botMap.get(world)));
}
} else {
World world = Bukkit.getWorld(args[1]);
if (world == null) {
sender.sendMessage(text("Unknown world", NamedTextColor.RED));
return true;
}
List<String> snowBotList = new ArrayList<>();
for (ServerBot bot : botList.bots) {
Bot bukkitBot = bot.getBukkitEntity();
if (bukkitBot.getWorld() == world) {
snowBotList.add(bukkitBot.getName());
}
}
sender.sendMessage(world.getName() + "(" + snowBotList.size() + "): " + formatPlayerNameList(snowBotList));
}
return true;
}
private boolean handleAction(CommandSender sender, String[] args) {
if (!FakeplayerConfig.canUseAction) {
sender.sendMessage(text("Bot action feature is disabled!", NamedTextColor.RED));
return true;
}
if (args.length < 3) {
sender.sendMessage(text("Use /bot action <name> <action> to make fakeplayer do action", NamedTextColor.RED));
return true;
}
ServerBot bot = BotList.INSTANCE.getBotByName(args[1]);
if (bot == null) {
sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED));
return true;
}
switch (args[2].toLowerCase()) {
case "list" -> {
sender.sendMessage(bot.getScoreboardName() + "'s action list:");
for (int i = 0; i < bot.getBotActions().size(); i++) {
sender.sendMessage(i + " " + bot.getBotActions().get(i).getName());
}
}
case "start" -> executeActionStart(bot, sender, args);
case "stop" -> executeActionStop(bot, sender, args);
default -> sender.sendMessage(text("Unknown action command. Use: list, start, stop", NamedTextColor.RED));
}
return true;
}
private void executeActionStart(ServerBot bot, CommandSender sender, String[] args) {
if (args.length < 4) {
sender.sendMessage(text("Invalid action", NamedTextColor.RED));
return;
}
ServerBotAction<?> action = Actions.getForName(args[3]);
if (action == null) {
sender.sendMessage(text("Invalid action", NamedTextColor.RED));
return;
}
CraftPlayer player;
if (sender instanceof CraftPlayer) {
player = (CraftPlayer) sender;
} else {
player = bot.getBukkitEntity();
}
String[] realArgs = Arrays.copyOfRange(args, 4, args.length);
ServerBotAction<?> newAction;
try {
newAction = action.create();
newAction.loadCommand(player.getHandle(), action.getArgument().parse(0, realArgs));
} catch (IllegalArgumentException e) {
sender.sendMessage(text("Action create error, please check your arguments, " + e.getMessage(), NamedTextColor.RED));
return;
}
if (bot.addBotAction(newAction, sender)) {
sender.sendMessage("Action " + action.getName() + " has been issued to " + bot.getName().getString());
}
}
private void executeActionStop(ServerBot bot, CommandSender sender, String[] args) {
if (args.length < 4) {
sender.sendMessage(text("Invalid index", NamedTextColor.RED));
return;
}
String index = args[3];
if (index.equals("all")) {
Set<ServerBotAction<?>> forRemoval = new HashSet<>();
for (int i = 0; i < bot.getBotActions().size(); i++) {
ServerBotAction<?> action = bot.getBotActions().get(i);
BotActionStopEvent event = new BotActionStopEvent(
bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender
);
event.callEvent();
if (!event.isCancelled()) {
forRemoval.add(action);
action.stop(bot, BotActionStopEvent.Reason.COMMAND);
}
}
bot.getBotActions().removeAll(forRemoval);
sender.sendMessage(bot.getScoreboardName() + "'s action list cleared.");
return;
}
try {
int i = Integer.parseInt(index);
if (i < 0 || i >= bot.getBotActions().size()) {
sender.sendMessage(text("Invalid index", NamedTextColor.RED));
return;
}
ServerBotAction<?> action = bot.getBotActions().get(i);
BotActionStopEvent event = new BotActionStopEvent(
bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender
);
event.callEvent();
if (!event.isCancelled()) {
action.stop(bot, BotActionStopEvent.Reason.COMMAND);
bot.getBotActions().remove(i);
sender.sendMessage(bot.getScoreboardName() + "'s " + action.getName() + " stopped.");
}
} catch (NumberFormatException e) {
sender.sendMessage(text("Invalid index", NamedTextColor.RED));
}
}
private boolean handleConfig(CommandSender sender, String[] args) {
if (!FakeplayerConfig.canModifyConfig) {
sender.sendMessage(text("Bot config feature is disabled!", NamedTextColor.RED));
return true;
}
if (args.length < 3) {
sender.sendMessage(text("Use /bot config <name> <config> to modify fakeplayer's config", NamedTextColor.RED));
return true;
}
ServerBot bot = BotList.INSTANCE.getBotByName(args[1]);
if (bot == null) {
sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED));
return true;
}
if (!Configs.getConfigNames().contains(args[2])) {
sender.sendMessage(text("This config is not accept", NamedTextColor.RED));
return true;
}
AbstractBotConfig<?> config = bot.getConfig(Objects.requireNonNull(Configs.getConfig(args[2])));
if (args.length < 4) {
config.getMessage().forEach(sender::sendMessage);
} else {
String[] realArgs = Arrays.copyOfRange(args, 3, args.length);
BotConfigModifyEvent event = new BotConfigModifyEvent(bot.getBukkitEntity(), config.getName(), realArgs, sender);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return true;
}
CommandArgumentResult result = config.getArgument().parse(0, realArgs);
try {
config.setFromCommand(result);
config.getChangeMessage().forEach(sender::sendMessage);
} catch (IllegalArgumentException e) {
sender.sendMessage(text(e.getMessage(), NamedTextColor.RED));
}
}
return true;
}
private boolean handleSave(CommandSender sender, String[] args) {
if (!FakeplayerConfig.canManualSaveAndLoad) {
sender.sendMessage(text("Bot save/load feature is disabled!", NamedTextColor.RED));
return true;
}
if (args.length < 2) {
sender.sendMessage(text("Use /bot save <name> to save a fakeplayer", NamedTextColor.RED));
return true;
}
BotList botList = BotList.INSTANCE;
ServerBot bot = botList.getBotByName(args[1]);
if (bot == null) {
sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED));
return true;
}
if (botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, true)) {
sender.sendMessage(bot.getScoreboardName() + " saved to " + bot.createState.realName());
}
return true;
}
private boolean handleLoad(CommandSender sender, String[] args) {
if (!FakeplayerConfig.canManualSaveAndLoad) {
sender.sendMessage(text("Bot save/load feature is disabled!", NamedTextColor.RED));
return true;
}
if (args.length < 2) {
sender.sendMessage(text("Use /bot load <name> to load a fakeplayer", NamedTextColor.RED));
return true;
}
String realName = args[1];
BotList botList = BotList.INSTANCE;
if (!botList.getSavedBotList().contains(realName)) {
sender.sendMessage(text("This fakeplayer is not saved", NamedTextColor.RED));
return true;
}
if (botList.loadNewBot(realName) == null) {
sender.sendMessage(text("Can't load bot, please check", NamedTextColor.RED));
} else {
sender.sendMessage(text("Successfully loaded fakeplayer: " + realName, NamedTextColor.GREEN));
}
return true;
}
@NotNull
private static String formatPlayerNameList(@NotNull List<String> list) {
if (list.isEmpty()) {
return "";
}
String string = list.toString();
return string.substring(1, string.length() - 1);
}
private boolean canCreate(CommandSender sender, @NotNull String name) {
BotList botList = BotList.INSTANCE;
if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) {
sender.sendMessage(text("This name is illegal", NamedTextColor.RED));
return false;
}
if (Bukkit.getPlayerExact(name) != null || botList.getBotByName(name) != null) {
sender.sendMessage(text("This player is in server", NamedTextColor.RED));
return false;
}
if (FakeplayerConfig.unableNames.contains(name)) {
sender.sendMessage(text("This name is not allowed", NamedTextColor.RED));
return false;
}
if (botList.bots.size() >= FakeplayerConfig.limit) {
sender.sendMessage(text("Fakeplayer limit is full", NamedTextColor.RED));
return false;
}
return true;
}
}
@@ -147,4 +147,4 @@ public record BotCreateState(String realName, String name, String skinName, Stri
return bot != null ? bot.getBukkitEntity() : null;
}
}
}
}
@@ -96,7 +96,12 @@ public class BotList {
}
public ServerBot loadNewBot(String realName) {
return this.loadNewBot(realName, this.dataStorage);
try {
return this.loadNewBot(realName, this.dataStorage);
} catch (Exception e) {
LOGGER.error("Failed to load bot {}", realName, e);
return null;
}
}
public ServerBot loadNewBot(String realName, IPlayerDataStorage playerIO) {
@@ -198,7 +203,7 @@ public class BotList {
this.server.server.getPluginManager().callEvent(event);
if (event.isCancelled() && event.getReason() != BotRemoveEvent.RemoveReason.INTERNAL) {
return true;
return false;
}
if (bot.removeTaskId != -1) {
@@ -240,18 +245,7 @@ public class BotList {
}
}
ServerLevel level = bot.level();
int chunkX = bot.getBlockX() >> 4;
int chunkZ = bot.getBlockZ() >> 4;
if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, chunkX, chunkZ)) {
level.removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
} else {
io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
level, chunkX, chunkZ, () -> {
level.removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
});
}
bot.level().removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
this.bots.remove(bot);
this.botsByName.remove(bot.getScoreboardName().toLowerCase(Locale.ROOT));
@@ -68,6 +68,6 @@ public class BotRecipeBook extends ServerRecipeBook {
@Override
public @NotNull Packed pack() {
return new ServerRecipeBook.Packed(this.bookSettings.copy(), List.of(), List.of());
return new Packed(this.bookSettings.copy(), List.of(), List.of());
}
}
@@ -69,10 +69,10 @@ import org.bukkit.command.CommandSender;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.agent.AbstractBotConfig;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.bot.agent.Configs;
import org.leavesmc.leaves.bot.agent.actions.ServerBotAction;
import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction;
import org.leavesmc.leaves.bot.agent.configs.AbstractBotConfig;
import org.leavesmc.leaves.entity.bot.CraftBot;
import org.leavesmc.leaves.event.bot.*;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
@@ -84,9 +84,9 @@ import java.util.function.Predicate;
public class ServerBot extends ServerPlayer {
private final List<ServerBotAction<?>> actions;
private final Map<Configs<?>, AbstractBotConfig<?>> configs;
private static final Logger LOGGER = LogUtils.getLogger();
private final List<AbstractBotAction<?>> actions;
private final Map<String, AbstractBotConfig<?, ?, ?>> configs;
private static final Logger LOGGER = LogUtils.getClassLogger();
public boolean resume = false;
public BotCreateState createState;
@@ -108,9 +108,9 @@ public class ServerBot extends ServerPlayer {
this.gameMode = new ServerBotGameMode(this);
this.actions = new ArrayList<>();
ImmutableMap.Builder<Configs<?>, AbstractBotConfig<?>> configBuilder = ImmutableMap.builder();
for (Configs<?> config : Configs.getConfigs()) {
configBuilder.put(config, config.createConfig(this));
ImmutableMap.Builder<String, AbstractBotConfig<?, ?, ?>> configBuilder = ImmutableMap.builder();
for (AbstractBotConfig<?, ?, ?> config : Configs.getConfigs()) {
configBuilder.put(config.getName(), config.create().setBot(this));
}
this.configs = configBuilder.build();
@@ -297,7 +297,7 @@ public class ServerBot extends ServerPlayer {
return this;
} else {
this.isChangingDimension = true;
fromLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
fromLevel.removePlayerImmediately(this, RemovalReason.CHANGED_DIMENSION);
this.unsetRemoved();
this.setServerLevel(toLevel);
this.teleportSetPosition(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
@@ -389,14 +389,14 @@ public class ServerBot extends ServerPlayer {
if (!this.actions.isEmpty()) {
ValueOutput.TypedOutputList<CompoundTag> actionNbt = nbt.list("actions", CompoundTag.CODEC);
for (ServerBotAction<?> action : this.actions) {
for (AbstractBotAction<?> action : this.actions) {
actionNbt.add(action.save(new CompoundTag()));
}
}
if (!this.configs.isEmpty()) {
ValueOutput.TypedOutputList<CompoundTag> configNbt = nbt.list("configs", CompoundTag.CODEC);
for (AbstractBotConfig<?> config : this.configs.values()) {
for (AbstractBotConfig<?, ?, ?> config : this.configs.values()) {
configNbt.add(config.save(new CompoundTag()));
}
}
@@ -429,9 +429,9 @@ public class ServerBot extends ServerPlayer {
if (nbt.list("actions", CompoundTag.CODEC).isPresent()) {
ValueInput.TypedInputList<CompoundTag> actionNbt = nbt.list("actions", CompoundTag.CODEC).orElseThrow();
actionNbt.forEach(actionTag -> {
ServerBotAction<?> action = Actions.getForName(actionTag.getString("actionName").orElseThrow());
AbstractBotAction<?> action = Actions.getForName(actionTag.getString("actionName").orElseThrow());
if (action != null) {
ServerBotAction<?> newAction = action.create();
AbstractBotAction<?> newAction = action.create();
newAction.load(actionTag);
this.actions.add(newAction);
}
@@ -441,9 +441,9 @@ public class ServerBot extends ServerPlayer {
if (nbt.list("configs", CompoundTag.CODEC).isPresent()) {
ValueInput.TypedInputList<CompoundTag> configNbt = nbt.list("configs", CompoundTag.CODEC).orElseThrow();
for (CompoundTag configTag : configNbt) {
Configs<?> configKey = Configs.getConfig(configTag.getString("configName").orElseThrow());
if (configKey != null) {
this.configs.get(configKey).load(configTag);
AbstractBotConfig<?, ?, ?> config = Configs.getConfig(configTag.getString("configName").orElseThrow());
if (config != null) {
config.load(configTag);
}
}
}
@@ -641,11 +641,11 @@ public class ServerBot extends ServerPlayer {
private void runAction() {
if (FakeplayerConfig.canUseAction) {
this.actions.forEach(action -> action.tryTick(this));
this.actions.removeIf(ServerBotAction::isCancelled);
this.actions.removeIf(AbstractBotAction::isCancelled);
}
}
public boolean addBotAction(ServerBotAction<?> action, CommandSender sender) {
public boolean addBotAction(AbstractBotAction<?> action, CommandSender sender) {
if (!FakeplayerConfig.canUseAction) {
return false;
}
@@ -659,7 +659,7 @@ public class ServerBot extends ServerPlayer {
return true;
}
public List<ServerBotAction<?>> getBotActions() {
public List<AbstractBotAction<?>> getBotActions() {
return actions;
}
@@ -670,11 +670,15 @@ public class ServerBot extends ServerPlayer {
}
@SuppressWarnings("unchecked")
public <E> AbstractBotConfig<E> getConfig(Configs<E> config) {
return (AbstractBotConfig<E>) Objects.requireNonNull(this.configs.get(config));
public <O, I, E extends AbstractBotConfig<O, I, E>> AbstractBotConfig<O, I, E> getConfig(@NotNull AbstractBotConfig<O, I, E> config) {
return (AbstractBotConfig<O, I, E>) Objects.requireNonNull(this.configs.get(config.getName()));
}
public <E> E getConfigValue(Configs<E> config) {
public Collection<AbstractBotConfig<?, ?, ?>> getAllConfigs() {
return configs.values();
}
public <O, I, E extends AbstractBotConfig<O, I, E>> O getConfigValue(@NotNull AbstractBotConfig<O, I, E> config) {
return this.getConfig(config).getValue();
}
@@ -1,84 +0,0 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.bot.agent;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import java.util.List;
public abstract class AbstractBotConfig<E> {
private final String name;
private final CommandArgument argument;
protected ServerBot bot;
public AbstractBotConfig(String name, CommandArgument argument) {
this.name = name;
this.argument = argument;
}
public AbstractBotConfig<E> setBot(ServerBot bot) {
this.bot = bot;
return this;
}
public abstract E getValue();
public abstract void setValue(E value) throws IllegalArgumentException;
@SuppressWarnings("unchecked")
public void setFromCommand(@NotNull CommandArgumentResult result) throws IllegalArgumentException {
if (argument == CommandArgument.EMPTY) {
throw new IllegalArgumentException("No argument for " + this.getName());
}
try {
this.setValue((E) result.read(argument.getArgumentTypes().getFirst().getType()));
} catch (ClassCastException e) {
throw new IllegalArgumentException("Invalid argument type for " + this.getName() + ": " + e.getMessage());
}
}
public List<String> getMessage() {
return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + ": " + this.getValue());
}
public List<String> getChangeMessage() {
return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + " changed: " + this.getValue());
}
public String getName() {
return name;
}
public CommandArgument getArgument() {
return argument;
}
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
nbt.putString("configName", this.name);
return nbt;
}
public abstract void load(@NotNull CompoundTag nbt);
}
@@ -30,10 +30,10 @@ import java.util.Set;
public class Actions {
private static final Map<String, ServerBotAction<?>> actionsByName = new HashMap<>();
private static final Map<Class<?>, ServerBotAction<?>> actionsByClass = new HashMap<>();
private static final Map<String, AbstractBotAction<?>> actionsByName = new HashMap<>();
private static final Map<Class<?>, AbstractBotAction<?>> actionsByClass = new HashMap<>();
public static void registerAll() {
static {
register(new ServerAttackAction(), AttackAction.class);
register(new ServerBreakBlockAction(), BreakBlockAction.class);
register(new ServerDropAction(), DropAction.class);
@@ -55,7 +55,7 @@ public class Actions {
register(new ServerSwapAction(), SwapAction.class);
}
public static boolean register(@NotNull ServerBotAction<?> action, Class<? extends BotAction<?>> type) {
public static boolean register(@NotNull AbstractBotAction<?> action, Class<?> type) {
if (!actionsByName.containsKey(action.getName())) {
actionsByName.put(action.getName(), action);
actionsByClass.put(type, action);
@@ -64,14 +64,22 @@ public class Actions {
return false;
}
public static boolean register(@NotNull AbstractBotAction<?> action) {
return register(action, action.getClass());
}
public static boolean unregister(@NotNull String name) {
// TODO add in custom action api
return true;
AbstractBotAction<?> action = actionsByName.remove(name);
if (action != null) {
actionsByClass.remove(action.getClass());
return true;
}
return false;
}
@NotNull
@Contract(pure = true)
public static Collection<ServerBotAction<?>> getAll() {
public static Collection<AbstractBotAction<?>> getAll() {
return actionsByName.values();
}
@@ -81,12 +89,12 @@ public class Actions {
}
@Nullable
public static ServerBotAction<?> getForName(String name) {
public static AbstractBotAction<?> getForName(String name) {
return actionsByName.get(name);
}
@Nullable
public static ServerBotAction<?> getForClass(@NotNull Class<?> type) {
public static AbstractBotAction<?> getForClass(@NotNull Class<?> type) {
return actionsByClass.get(type);
}
}
@@ -20,63 +20,40 @@ package org.leavesmc.leaves.bot.agent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.configs.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@SuppressWarnings("unused")
public class Configs<E> {
@SuppressWarnings({"unused"})
public class Configs {
private static final Map<Class<?>, AbstractBotConfig<?, ?, ?>> configs = new HashMap<>();
private static final Map<String, Configs<?>> configs = new HashMap<>();
public static final Configs<Boolean> SKIP_SLEEP = register(SkipSleepConfig.class, SkipSleepConfig::new);
public static final Configs<Boolean> ALWAYS_SEND_DATA = register(AlwaysSendDataConfig.class, AlwaysSendDataConfig::new);
public static final Configs<Boolean> SPAWN_PHANTOM = register(SpawnPhantomConfig.class, SpawnPhantomConfig::new);
public static final Configs<Integer> SIMULATION_DISTANCE = register(SimulationDistanceConfig.class, SimulationDistanceConfig::new);
public static final Configs<ServerBot.TickType> TICK_TYPE = register(TickTypeConfig.class, TickTypeConfig::new);
public static final Configs<Boolean> ENABLE_LOCATOR_BAR = register(LocatorBarConfig.class, LocatorBarConfig::new);
private final Class<? extends AbstractBotConfig<E>> configClass;
private final Supplier<? extends AbstractBotConfig<E>> configCreator;
private Configs(Class<? extends AbstractBotConfig<E>> configClass, Supplier<? extends AbstractBotConfig<E>> configCreator) {
this.configClass = configClass;
this.configCreator = configCreator;
}
public Class<? extends AbstractBotConfig<E>> getConfigClass() {
return configClass;
}
public AbstractBotConfig<E> createConfig(ServerBot bot) {
return configCreator.get().setBot(bot);
}
public static final SkipSleepConfig SKIP_SLEEP = register(new SkipSleepConfig());
public static final AlwaysSendDataConfig ALWAYS_SEND_DATA = register(new AlwaysSendDataConfig());
public static final SpawnPhantomConfig SPAWN_PHANTOM = register(new SpawnPhantomConfig());
public static final SimulationDistanceConfig SIMULATION_DISTANCE = register(new SimulationDistanceConfig());
public static final TickTypeConfig TICK_TYPE = register(new TickTypeConfig());
public static final LocatorBarConfig ENABLE_LOCATOR_BAR = register(new LocatorBarConfig());
@Nullable
public static Configs<?> getConfig(String name) {
return configs.get(name);
public static AbstractBotConfig<?, ?, ?> getConfig(String name) {
return configs.values().stream()
.filter(config -> config.getName().equals(name))
.findFirst()
.orElse(null);
}
@NotNull
@Contract(pure = true)
public static Collection<Configs<?>> getConfigs() {
public static Collection<AbstractBotConfig<?, ?, ?>> getConfigs() {
return configs.values();
}
@NotNull
@Contract(pure = true)
public static Collection<String> getConfigNames() {
return configs.keySet();
}
@NotNull
private static <E> Configs<E> register(Class<? extends AbstractBotConfig<E>> configClass, Supplier<? extends AbstractBotConfig<E>> configCreator) {
Configs<E> config = new Configs<>(configClass, configCreator);
configs.put(config.createConfig(null).getName(), config);
return config;
@SuppressWarnings("unchecked")
private static <Value, Type, E extends AbstractBotConfig<Value, Type, E>> @NotNull E register(AbstractBotConfig<Value, Type, E> instance) {
configs.put(instance.getClass(), instance);
return (E) instance;
}
}
@@ -17,34 +17,33 @@
package org.leavesmc.leaves.bot.agent.actions;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.logging.LogUtils;
import net.minecraft.core.UUIDUtil;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.WrappedArgument;
import org.leavesmc.leaves.event.bot.BotActionExecuteEvent;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
import org.leavesmc.leaves.util.UpdateSuppressionException;
import org.slf4j.Logger;
import java.util.List;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
@SuppressWarnings("unchecked")
public abstract class ServerBotAction<E extends ServerBotAction<E>> {
public abstract class AbstractBotAction<E extends AbstractBotAction<E>> {
private final String name;
private final CommandArgument argument;
private final Map<Integer, List<Pair<String, WrappedArgument<?>>>> arguments;
private final Supplier<E> creator;
private UUID uuid;
private int currentFork = 0;
private int initialTickDelay;
private int initialTickInterval;
@@ -57,13 +56,13 @@ public abstract class ServerBotAction<E extends ServerBotAction<E>> {
private Consumer<E> onFail;
private Consumer<E> onSuccess;
private Consumer<E> onStop;
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogUtils.getClassLogger();
public ServerBotAction(String name, CommandArgument argument, Supplier<E> creator) {
public AbstractBotAction(String name, Supplier<E> creator) {
this.name = name;
this.argument = argument;
this.uuid = UUID.randomUUID();
this.creator = creator;
this.arguments = new HashMap<>();
this.cancel = false;
this.setStartDelayTick(0);
@@ -75,12 +74,40 @@ public abstract class ServerBotAction<E extends ServerBotAction<E>> {
public abstract Object asCraft();
public void provideActionData(@NotNull ActionData data) {
}
public String getActionDataString() {
ActionData data = new ActionData(new ArrayList<>());
provideActionData(data);
return data.raw.stream()
.map(pair -> pair.getLeft() + "=" + pair.getRight())
.reduce((a, b) -> a + ", " + b)
.orElse("No arguments");
}
public void init() {
this.tickToNext = initialTickDelay;
this.numberRemaining = this.getDoNumber();
this.setCancelled(false);
}
public void fork(int fork) {
currentFork = fork;
}
public <T> WrappedArgument<T> addArgument(String name, ArgumentType<T> type) {
WrappedArgument<T> argument = new WrappedArgument<>(name, type);
this.arguments
.computeIfAbsent(currentFork, k -> new ArrayList<>())
.add(Pair.of(name, argument));
return argument;
}
public Map<Integer, List<Pair<String, WrappedArgument<?>>>> getArguments() {
return this.arguments;
}
public void tryTick(ServerBot bot) {
if (this.numberRemaining == 0) {
this.stop(bot, BotActionStopEvent.Reason.DONE);
@@ -168,19 +195,8 @@ public abstract class ServerBotAction<E extends ServerBotAction<E>> {
}
}
public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) {
}
public void setSuggestion(int n, BiFunction<CommandSender, String, Pair<List<String>, String>> suggestion) {
this.argument.setSuggestion(n, suggestion);
}
public void setSuggestion(int n, Pair<List<String>, String> suggestion) {
this.setSuggestion(n, (sender, arg) -> suggestion);
}
public void setSuggestion(int n, List<String> tabComplete) {
this.setSuggestion(n, Pair.of(tabComplete, null));
@SuppressWarnings("RedundantThrows")
public void loadCommand(@NotNull CommandContext context) throws CommandSyntaxException {
}
@NotNull
@@ -188,10 +204,6 @@ public abstract class ServerBotAction<E extends ServerBotAction<E>> {
return this.creator.get();
}
public CommandArgument getArgument() {
return this.argument;
}
public String getName() {
return this.name;
}
@@ -251,4 +263,12 @@ public abstract class ServerBotAction<E extends ServerBotAction<E>> {
public void setOnStop(Consumer<E> onStop) {
this.onStop = onStop;
}
public record ActionData(
List<Pair<String, String>> raw
) {
public void add(String key, String value) {
raw.add(Pair.of(key, value));
}
}
}
@@ -17,14 +17,12 @@
package org.leavesmc.leaves.bot.agent.actions;
import org.leavesmc.leaves.command.CommandArgument;
import java.util.function.Supplier;
public abstract class ServerStateBotAction<E extends ServerStateBotAction<E>> extends ServerBotAction<E> {
public abstract class AbstractStateBotAction<E extends AbstractStateBotAction<E>> extends AbstractBotAction<E> {
public ServerStateBotAction(String name, CommandArgument argument, Supplier<E> creator) {
super(name, argument, creator);
public AbstractStateBotAction(String name, Supplier<E> creator) {
super(name, creator);
this.setDoNumber(-1);
}
}
@@ -0,0 +1,59 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.CommandContext;
import java.util.function.Supplier;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static org.leavesmc.leaves.command.ArgumentNode.ArgumentSuggestions.strings;
public abstract class AbstractTimerBotAction<E extends AbstractTimerBotAction<E>> extends AbstractBotAction<E> {
public AbstractTimerBotAction(String name, Supplier<E> creator) {
super(name, creator);
this.addArgument("delay", integer(0))
.suggests(strings("0", "5", "10", "20"))
.setOptional(true);
this.addArgument("interval", integer(0))
.suggests(strings("20", "0", "5", "10"))
.setOptional(true);
this.addArgument("do_number", integer(-1))
.suggests(((context, builder) -> builder.suggest("-1", Component.literal("do infinite times"))))
.setOptional(true);
}
@Override
public void loadCommand(@NotNull CommandContext context) {
this.setStartDelayTick(context.getIntegerOrDefault("delay", 0));
this.setDoIntervalTick(context.getIntegerOrDefault("interval", 20));
this.setDoNumber(context.getIntegerOrDefault("do_number", 1));
}
@Override
public void provideActionData(@NotNull ActionData data) {
super.provideActionData(data);
data.add("delay", String.valueOf(this.getStartDelayTick()));
data.add("interval", String.valueOf(this.getDoIntervalTick()));
data.add("do_number", String.valueOf(this.getDoNumber()));
data.add("remaining_do_number", String.valueOf(this.getDoNumberRemaining()));
}
}
@@ -18,33 +18,37 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionResult;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
import java.util.List;
import java.util.function.Supplier;
public abstract class ServerUseBotAction<T extends ServerUseBotAction<T>> extends ServerTimerBotAction<T> {
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
public abstract class AbstractUseBotAction<T extends AbstractUseBotAction<T>> extends AbstractTimerBotAction<T> {
private int useTickTimeout = -1;
private int alreadyUsedTick = 0;
private int useItemRemainingTicks = 0;
public ServerUseBotAction(String name, Supplier<T> supplier) {
super(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), supplier);
this.setSuggestion(3, Pair.of(List.of("-1"), "[UseTickTimeout]"));
public AbstractUseBotAction(String name, Supplier<T> supplier) {
super(name, supplier);
this.addArgument("use_timeout", integer(-1))
.suggests((context, builder) -> {
builder.suggest("-1", Component.literal("no use timeout"));
builder.suggest("3", Component.literal("minimum bow shoot time"));
builder.suggest("10", Component.literal("minimum trident shoot time"));
})
.setOptional(true);
}
@Override
public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) {
super.loadCommand(player, result);
this.useTickTimeout = result.readInt(-1);
public void loadCommand(@NotNull CommandContext context) {
super.loadCommand(context);
this.useTickTimeout = context.getIntegerOrDefault("use_timeout", -1);
}
@Override
@@ -110,6 +114,13 @@ public abstract class ServerUseBotAction<T extends ServerUseBotAction<T>> extend
this.alreadyUsedTick++;
}
@Override
public void provideActionData(@NotNull ActionData data) {
super.provideActionData(data);
data.add("use_timeout", String.valueOf(this.useTickTimeout));
data.add("already_used_tick", String.valueOf(this.alreadyUsedTick));
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
@@ -22,7 +22,7 @@ import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftAttackAction;
public class ServerAttackAction extends ServerTimerBotAction<ServerAttackAction> {
public class ServerAttackAction extends AbstractTimerBotAction<ServerAttackAction> {
public ServerAttackAction() {
super("attack", ServerAttackAction::new);
@@ -30,7 +30,7 @@ import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftBreakBlockAction;
public class ServerBreakBlockAction extends ServerTimerBotAction<ServerBreakBlockAction> {
public class ServerBreakBlockAction extends AbstractTimerBotAction<ServerBreakBlockAction> {
public ServerBreakBlockAction() {
super("break", ServerBreakBlockAction::new);
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftDropAction;
public class ServerDropAction extends ServerTimerBotAction<ServerDropAction> {
public class ServerDropAction extends AbstractTimerBotAction<ServerDropAction> {
public ServerDropAction() {
super("drop", ServerDropAction::new);
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftFishAction;
public class ServerFishAction extends ServerTimerBotAction<ServerFishAction> {
public class ServerFishAction extends AbstractTimerBotAction<ServerFishAction> {
public ServerFishAction() {
super("fish", ServerFishAction::new);
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftJumpAction;
public class ServerJumpAction extends ServerTimerBotAction<ServerJumpAction> {
public class ServerJumpAction extends AbstractTimerBotAction<ServerJumpAction> {
public ServerJumpAction() {
super("jump", ServerJumpAction::new);
@@ -17,40 +17,61 @@
package org.leavesmc.leaves.bot.agent.actions;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.coordinates.Coordinates;
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
import net.minecraft.commands.arguments.selector.EntitySelector;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Player;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.entity.bot.actions.CraftLookAction;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class ServerLookAction extends ServerBotAction<ServerLookAction> {
public class ServerLookAction extends AbstractBotAction<ServerLookAction> {
private static final Vector ZERO_VECTOR = new Vector(0, 0, 0);
private static final DecimalFormat DF = new DecimalFormat("0.0");
private Vector pos = ZERO_VECTOR;
private ServerPlayer target = null;
public ServerLookAction() {
super("look", CommandArgument.of(CommandArgumentType.STRING, CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE), ServerLookAction::new);
this.setSuggestion(0, (sender, arg) -> sender instanceof Player player ?
Pair.of(Stream.concat(Arrays.stream(MinecraftServer.getServer().getPlayerNames()), Stream.of(DF.format(player.getX()))).toList(), "<Player>|<X>") :
Pair.of(Stream.concat(Arrays.stream(MinecraftServer.getServer().getPlayerNames()), Stream.of("0")).toList(), "<Player>|<X>")
);
this.setSuggestion(1, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getY())), "<Y>") : Pair.of(List.of("0"), "<Y>"));
this.setSuggestion(2, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getZ())), "<Z>") : Pair.of(List.of("0"), "<Z>"));
super("look", ServerLookAction::new);
declarePlayerBranch();
declareLocationBranch();
}
private void declarePlayerBranch() {
this.fork(0);
this.addArgument("player", EntityArgument.player())
.setOptional(true);
}
private void declareLocationBranch() {
this.fork(1);
this.addArgument("location", Vec3Argument.vec3(false));
}
@Override
public void loadCommand(@NotNull CommandContext context) throws CommandSyntaxException {
EntitySelector selector = context.getArgumentOrDefault("player", EntitySelector.class, null);
Coordinates location = context.getArgumentOrDefault("location", Coordinates.class, null);
CommandSourceStack source = context.getSource();
if (selector == null && location == null) {
Entity sender = source.getEntityOrException();
this.setPos(new Vector(sender.getX(), sender.getY(), sender.getZ()));
} else if (selector != null) {
ServerPlayer player = selector.findSinglePlayer(source);
this.setTarget(player);
} else {
Vec3 vector = location.getPosition(source);
this.setPos(new Vector(vector.x, vector.y, vector.z));
}
}
@Override
@@ -94,6 +115,16 @@ public class ServerLookAction extends ServerBotAction<ServerLookAction> {
return target;
}
@Override
public void provideActionData(@NotNull ActionData data) {
super.provideActionData(data);
if (target != null) {
data.add("target", target.getName().getString());
} else {
data.add("position", String.format("(%.2f, %.2f, %.2f)", pos.getX(), pos.getY(), pos.getZ()));
}
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
if (target != null) {
@@ -104,22 +135,6 @@ public class ServerLookAction extends ServerBotAction<ServerLookAction> {
return true;
}
@Override
public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) {
String nameOrX = result.readString(player.getScoreboardName());
ServerPlayer player1 = player.getServer().getPlayerList().getPlayerByName(nameOrX);
if (player1 != null) {
this.setTarget(player1);
return;
}
try {
Vector vector = result.readVectorYZ(Double.parseDouble(nameOrX));
this.setPos(vector);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid vector");
}
}
@Override
public Object asCraft() {
return new CraftLookAction(this);
@@ -23,16 +23,15 @@ import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.entity.Vehicle;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.entity.bot.actions.CraftMountAction;
import java.util.Comparator;
import java.util.List;
public class ServerMountAction extends ServerBotAction<ServerMountAction> {
public class ServerMountAction extends AbstractBotAction<ServerMountAction> {
public ServerMountAction() {
super("mount", CommandArgument.EMPTY, ServerMountAction::new);
super("mount", ServerMountAction::new);
}
@Override
@@ -40,8 +39,8 @@ public class ServerMountAction extends ServerBotAction<ServerMountAction> {
Location center = bot.getBukkitEntity().getLocation();
List<Vehicle> vehicles = center.getNearbyEntitiesByType(
Vehicle.class,
3,
vehicle -> manhattanDistance(bot, ((CraftEntity) vehicle).getHandle()) <= 2
4,
vehicle -> manhattanDistance(bot, ((CraftEntity) vehicle).getHandle()) <= 3
).stream().sorted(Comparator.comparingDouble(
(vehicle) -> center.distanceSquared(vehicle.getLocation())
)).toList();
@@ -17,39 +17,46 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.server.level.ServerPlayer;
import org.apache.commons.lang3.tuple.Pair;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.entity.bot.action.MoveAction.MoveDirection;
import org.leavesmc.leaves.entity.bot.actions.CraftMoveAction;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ServerMoveAction extends ServerStateBotAction<ServerMoveAction> {
import static java.util.stream.Collectors.toMap;
import static org.leavesmc.leaves.command.ArgumentNode.ArgumentSuggestions.strings;
private static final Pair<List<String>, String> suggestions = Pair.of(
Arrays.stream(MoveDirection.values()).map((it) -> it.name).toList(),
"<Direction>"
);
public class ServerMoveAction extends AbstractStateBotAction<ServerMoveAction> {
private static final Map<String, MoveDirection> NAME_TO_DIRECTION = Arrays.stream(MoveDirection.values()).collect(toMap(
it -> it.name,
it -> it
));
private MoveDirection direction = MoveDirection.FORWARD;
public ServerMoveAction() {
super("move", CommandArgument.of(CommandArgumentType.ofEnum(MoveDirection.class)), ServerMoveAction::new);
this.setSuggestion(0, suggestions);
super("move", ServerMoveAction::new);
this.addArgument("direction", StringArgumentType.word())
.suggests(strings(
Arrays.stream(MoveDirection.values())
.map((it) -> it.name)
.toList()
));
}
@Override
public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) {
this.direction = result.read(MoveDirection.class);
public void loadCommand(@NotNull CommandContext context) throws CommandSyntaxException {
String raw = context.getArgument("direction", String.class);
MoveDirection direction = NAME_TO_DIRECTION.get(raw);
if (direction == null) {
throw new IllegalArgumentException("Invalid direction");
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
}
this.direction = direction;
}
@Override
@@ -74,6 +81,12 @@ public class ServerMoveAction extends ServerStateBotAction<ServerMoveAction> {
return true;
}
@Override
public void provideActionData(@NotNull ActionData data) {
super.provideActionData(data);
data.add("direction", direction.name);
}
public MoveDirection getDirection() {
return direction;
}
@@ -17,42 +17,44 @@
package org.leavesmc.leaves.bot.agent.actions;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Player;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.entity.bot.actions.CraftRotationAction;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Objects;
public class ServerRotationAction extends ServerBotAction<ServerRotationAction> {
public class ServerRotationAction extends AbstractBotAction<ServerRotationAction> {
private static final DecimalFormat DF = new DecimalFormat("0.00");
public ServerRotationAction() {
super("rotation", CommandArgument.of(CommandArgumentType.FLOAT, CommandArgumentType.FLOAT), ServerRotationAction::new);
this.setSuggestion(0, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getYaw())), "[yaw]") : Pair.of(List.of("0"), "<yaw>"));
this.setSuggestion(1, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getPitch())), "[pitch]") : Pair.of(List.of("0"), "<pitch>"));
super("rotation", ServerRotationAction::new);
this.addArgument("yaw", FloatArgumentType.floatArg(-180, 180))
.suggests((context, builder) -> builder.suggest(
DF.format(context.getSource().getEntityOrException().getYRot()),
Component.literal("current player yaw")
))
.setOptional(true);
this.addArgument("pitch", FloatArgumentType.floatArg(-90, 90))
.suggests((context, builder) -> builder.suggest(
DF.format(context.getSource().getEntityOrException().getXRot()),
Component.literal("current player pitch")
))
.setOptional(true);
}
private float yaw = 0.0f;
private float pitch = 0.0f;
@Override
public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) {
try {
this.yaw = result.readFloat(Objects.requireNonNull(player).getYRot());
this.pitch = result.readFloat(player.getXRot());
} catch (Exception e) {
throw new IllegalArgumentException("No valid rotation specified", e);
}
public void loadCommand(@NotNull CommandContext context) throws CommandSyntaxException {
this.yaw = context.getFloatOrDefault("yaw", context.getSource().getEntityOrException().getYRot());
this.pitch = context.getFloatOrDefault("pitch", context.getSource().getEntityOrException().getXRot());
}
public void setYaw(float yaw) {
@@ -71,6 +73,13 @@ public class ServerRotationAction extends ServerBotAction<ServerRotationAction>
return this.pitch;
}
@Override
public void provideActionData(@NotNull ActionData data) {
super.provideActionData(data);
data.add("yaw", DF.format(this.yaw));
data.add("pitch", DF.format(this.pitch));
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
@@ -19,14 +19,13 @@ package org.leavesmc.leaves.bot.agent.actions;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.entity.bot.actions.CraftSneakAction;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
public class ServerSneakAction extends ServerStateBotAction<ServerSneakAction> {
public class ServerSneakAction extends AbstractStateBotAction<ServerSneakAction> {
public ServerSneakAction() {
super("sneak", CommandArgument.EMPTY, ServerSneakAction::new);
super("sneak", ServerSneakAction::new);
}
@Override
@@ -21,13 +21,12 @@ import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.entity.bot.actions.CraftSwapAction;
public class ServerSwapAction extends ServerBotAction<ServerSwapAction> {
public class ServerSwapAction extends AbstractBotAction<ServerSwapAction> {
public ServerSwapAction() {
super("swap", CommandArgument.EMPTY, ServerSwapAction::new);
super("swap", ServerSwapAction::new);
}
@Override
@@ -20,13 +20,12 @@ package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.entity.bot.actions.CraftSwimAction;
public class ServerSwimAction extends ServerStateBotAction<ServerSwimAction> {
public class ServerSwimAction extends AbstractStateBotAction<ServerSwimAction> {
public ServerSwimAction() {
super("swim", CommandArgument.EMPTY, ServerSwimAction::new);
super("swim", ServerSwimAction::new);
}
@Override
@@ -1,49 +0,0 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.server.level.ServerPlayer;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
import java.util.function.Supplier;
public abstract class ServerTimerBotAction<E extends ServerTimerBotAction<E>> extends ServerBotAction<E> {
public ServerTimerBotAction(String name, Supplier<E> creator) {
this(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), creator);
}
public ServerTimerBotAction(String name, CommandArgument argument, Supplier<E> creator) {
super(name, argument, creator);
this.setSuggestion(0, Pair.of(List.of("0"), "[TickDelay]"));
this.setSuggestion(1, Pair.of(List.of("20"), "[TickInterval]"));
this.setSuggestion(2, Pair.of(List.of("1", "-1"), "[DoNumber]"));
}
@Override
public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) {
this.setStartDelayTick(result.readInt(0));
this.setDoIntervalTick(result.readInt(20));
this.setDoNumber(result.readInt(1));
}
}
@@ -23,7 +23,7 @@ import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftUseItemAction;
public class ServerUseItemAction extends ServerUseBotAction<ServerUseItemAction> {
public class ServerUseItemAction extends AbstractUseBotAction<ServerUseItemAction> {
public ServerUseItemAction() {
super("use", ServerUseItemAction::new);
@@ -34,7 +34,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem;
import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useItemOn;
import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useItemTo;
public class ServerUseItemAutoAction extends ServerUseBotAction<ServerUseItemAutoAction> {
public class ServerUseItemAutoAction extends AbstractUseBotAction<ServerUseItemAutoAction> {
public ServerUseItemAutoAction() {
super("use_auto", ServerUseItemAutoAction::new);
@@ -24,7 +24,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOffhandAction;
import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem;
public class ServerUseItemOffhandAction extends ServerUseBotAction<ServerUseItemOffhandAction> {
public class ServerUseItemOffhandAction extends AbstractUseBotAction<ServerUseItemOffhandAction> {
public ServerUseItemOffhandAction() {
super("use_offhand", ServerUseItemOffhandAction::new);
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnAction;
public class ServerUseItemOnAction extends ServerUseBotAction<ServerUseItemOnAction> {
public class ServerUseItemOnAction extends AbstractUseBotAction<ServerUseItemOnAction> {
public ServerUseItemOnAction() {
super("use_on", ServerUseItemOnAction::new);
@@ -25,7 +25,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnOffhandAction;
import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useItemOn;
public class ServerUseItemOnOffhandAction extends ServerUseBotAction<ServerUseItemOnOffhandAction> {
public class ServerUseItemOnOffhandAction extends AbstractUseBotAction<ServerUseItemOnOffhandAction> {
public ServerUseItemOnOffhandAction() {
super("use_on_offhand", ServerUseItemOnOffhandAction::new);
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToAction;
public class ServerUseItemToAction extends ServerUseBotAction<ServerUseItemToAction> {
public class ServerUseItemToAction extends AbstractUseBotAction<ServerUseItemToAction> {
public ServerUseItemToAction() {
super("use_to", ServerUseItemToAction::new);
@@ -25,7 +25,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToOffhandAction;
import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useItemTo;
public class ServerUseItemToOffhandAction extends ServerUseBotAction<ServerUseItemToOffhandAction> {
public class ServerUseItemToOffhandAction extends AbstractUseBotAction<ServerUseItemToOffhandAction> {
public ServerUseItemToOffhandAction() {
super("use_to_offhand", ServerUseItemToOffhandAction::new);
@@ -0,0 +1,119 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.bot.agent.configs;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.kyori.adventure.text.Component;
import net.minecraft.nbt.CompoundTag;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.WrappedArgument;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Supplier;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.event.HoverEvent.showText;
import static net.kyori.adventure.text.format.NamedTextColor.AQUA;
public abstract class AbstractBotConfig<Value, Type, E extends AbstractBotConfig<Value, Type, E>> {
private final String name;
private final WrappedArgument<Type> argument;
private final Supplier<E> creator;
protected ServerBot bot;
public AbstractBotConfig(String name, ArgumentType<Type> type, Supplier<E> creator) {
this.name = name;
this.argument = new WrappedArgument<>(name, type);
if (shouldApplySuggestions()) {
this.argument.suggests(this::applySuggestions);
}
this.creator = creator;
}
@SuppressWarnings("RedundantThrows")
public void applySuggestions(final CommandContext context, final SuggestionsBuilder builder) throws CommandSyntaxException {
}
public AbstractBotConfig<Value, Type, E> setBot(ServerBot bot) {
this.bot = bot;
return this;
}
public E create() {
return creator.get();
}
public abstract Value getValue();
public abstract void setValue(Value value) throws CommandSyntaxException;
public abstract Value loadFromCommand(@NotNull CommandContext context) throws CommandSyntaxException;
public List<Pair<String, String>> getExtraData() {
return List.of();
}
public String getName() {
return name;
}
public Component getNameComponent() {
Component result = text(getName(), AQUA);
if (!getExtraData().isEmpty()) {
result = result.hoverEvent(showText(
getExtraData().stream()
.map(pair -> text(pair.getKey() + "=" + pair.getValue()))
.reduce((a, b) -> a.append(text(", ")).append(b))
.orElseGet(() -> text(""))
));
}
return result;
}
public WrappedArgument<Type> getArgument() {
return argument;
}
public ServerBot getBot() {
return bot;
}
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
nbt.putString("configName", this.name);
return nbt;
}
public abstract void load(@NotNull CompoundTag nbt);
private boolean shouldApplySuggestions() {
for (Method method : getClass().getDeclaredMethods()) {
if (method.getName().equals("applySuggestions")) {
return method.getDeclaringClass() != AbstractBotConfig.class;
}
}
return false;
}
}
@@ -17,23 +17,17 @@
package org.leavesmc.leaves.bot.agent.configs;
import com.mojang.brigadier.arguments.BoolArgumentType;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.AbstractBotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
public class AlwaysSendDataConfig extends AbstractBotConfig<Boolean> {
public static final String NAME = "always_send_data";
import org.leavesmc.leaves.command.CommandContext;
public class AlwaysSendDataConfig extends AbstractBotConfig<Boolean, Boolean, AlwaysSendDataConfig> {
private boolean value;
public AlwaysSendDataConfig() {
super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false")));
super("always_send_data", BoolArgumentType.bool(), AlwaysSendDataConfig::new);
this.value = FakeplayerConfig.canSendDataAlways;
}
@@ -47,16 +41,21 @@ public class AlwaysSendDataConfig extends AbstractBotConfig<Boolean> {
this.value = value;
}
@Override
public Boolean loadFromCommand(@NotNull CommandContext context) {
return context.getBoolean(getName());
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putBoolean(NAME, this.getValue());
nbt.putBoolean(getName(), this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSendDataAlways));
this.setValue(nbt.getBooleanOr(getName(), FakeplayerConfig.canSendDataAlways));
}
}
@@ -17,25 +17,19 @@
package org.leavesmc.leaves.bot.agent.configs;
import com.mojang.brigadier.arguments.BoolArgumentType;
import fun.bm.lophine.config.modules.experiment.CommandConfig;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.waypoints.ServerWaypointManager;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.AbstractBotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
public class LocatorBarConfig extends AbstractBotConfig<Boolean> {
public static final String NAME = "enable_locator_bar";
import org.leavesmc.leaves.command.CommandContext;
public class LocatorBarConfig extends AbstractBotConfig<Boolean, Boolean, LocatorBarConfig> {
private boolean value;
public LocatorBarConfig() {
super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false")));
super("enable_locator_bar", BoolArgumentType.bool(), LocatorBarConfig::new);
this.value = FakeplayerConfig.enableLocatorBar && CommandConfig.waypoint;
}
@@ -45,7 +39,7 @@ public class LocatorBarConfig extends AbstractBotConfig<Boolean> {
}
@Override
public void setValue(Boolean value) throws IllegalArgumentException {
public void setValue(@NotNull Boolean value) throws IllegalArgumentException {
this.value = value;
ServerWaypointManager manager = this.bot.level().getWaypointManager();
if (value) {
@@ -55,15 +49,20 @@ public class LocatorBarConfig extends AbstractBotConfig<Boolean> {
}
}
@Override
public Boolean loadFromCommand(@NotNull CommandContext context) {
return context.getBoolean(getName());
}
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putBoolean(NAME, this.getValue());
nbt.putBoolean(getName(), this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.enableLocatorBar));
this.setValue(nbt.getBooleanOr(getName(), FakeplayerConfig.enableLocatorBar && CommandConfig.waypoint));
}
}
@@ -17,22 +17,28 @@
package org.leavesmc.leaves.bot.agent.configs;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.nbt.CompoundTag;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.AbstractBotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentType;
import org.leavesmc.leaves.command.CommandContext;
import java.util.List;
import static net.minecraft.network.chat.Component.literal;
public class SimulationDistanceConfig extends AbstractBotConfig<Integer> {
public static final String NAME = "simulation_distance";
public class SimulationDistanceConfig extends AbstractBotConfig<Integer, Integer, SimulationDistanceConfig> {
public SimulationDistanceConfig() {
super(NAME, CommandArgument.of(CommandArgumentType.INTEGER).setSuggestion(0, Pair.of(List.of("2", "10"), "<INT 2 - 32>")));
super("simulation_distance", IntegerArgumentType.integer(2, 32), SimulationDistanceConfig::new);
}
@Override
public void applySuggestions(CommandContext context, @NotNull SuggestionsBuilder builder) {
builder.suggest("2", literal("Minimum simulation distance"));
builder.suggest("8");
builder.suggest("12");
builder.suggest("16");
builder.suggest("32", literal("Maximum simulation distance"));
}
@Override
@@ -42,22 +48,24 @@ public class SimulationDistanceConfig extends AbstractBotConfig<Integer> {
@Override
public void setValue(Integer value) {
if (value < 2 || value > 32) {
throw new IllegalArgumentException("simulation_distance must be a number between 2 and 32, got: " + value);
}
this.bot.getBukkitEntity().setSimulationDistance(value);
}
@Override
public Integer loadFromCommand(@NotNull CommandContext context) {
return context.getInteger(getName());
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putInt(NAME, this.getValue());
nbt.putInt(getName(), this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.setValue(nbt.getIntOr(NAME, FakeplayerConfig.getSimulationDistance(this.bot)));
this.setValue(nbt.getIntOr(getName(), FakeplayerConfig.getSimulationDistance(this.bot)));
}
}
@@ -17,21 +17,16 @@
package org.leavesmc.leaves.bot.agent.configs;
import com.mojang.brigadier.arguments.BoolArgumentType;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.AbstractBotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentType;
import org.leavesmc.leaves.command.CommandContext;
import java.util.List;
public class SkipSleepConfig extends AbstractBotConfig<Boolean> {
public static final String NAME = "skip_sleep";
public class SkipSleepConfig extends AbstractBotConfig<Boolean, Boolean, SkipSleepConfig> {
public SkipSleepConfig() {
super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false")));
super("skip_sleep", BoolArgumentType.bool(), SkipSleepConfig::new);
}
@Override
@@ -44,15 +39,20 @@ public class SkipSleepConfig extends AbstractBotConfig<Boolean> {
bot.fauxSleeping = value;
}
@Override
public Boolean loadFromCommand(@NotNull CommandContext context) {
return context.getBoolean(getName());
}
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putBoolean(NAME, this.getValue());
nbt.putBoolean(getName(), this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSkipSleep));
this.setValue(nbt.getBooleanOr(getName(), FakeplayerConfig.canSkipSleep));
}
}
@@ -17,23 +17,20 @@
package org.leavesmc.leaves.bot.agent.configs;
import com.mojang.brigadier.arguments.BoolArgumentType;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.nbt.CompoundTag;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.AbstractBotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentType;
import org.leavesmc.leaves.command.CommandContext;
import java.util.List;
public class SpawnPhantomConfig extends AbstractBotConfig<Boolean> {
public static final String NAME = "spawn_phantom";
public class SpawnPhantomConfig extends AbstractBotConfig<Boolean, Boolean, SpawnPhantomConfig> {
private boolean value;
public SpawnPhantomConfig() {
super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false")));
super("spawn_phantom", BoolArgumentType.bool(), SpawnPhantomConfig::new);
this.value = FakeplayerConfig.canSpawnPhantom;
}
@@ -48,22 +45,24 @@ public class SpawnPhantomConfig extends AbstractBotConfig<Boolean> {
}
@Override
public List<String> getMessage() {
return List.of(
bot.getScoreboardName() + "'s spawn_phantom: " + this.getValue(),
bot.getScoreboardName() + "'s not_sleeping_ticks: " + bot.notSleepTicks
);
public List<Pair<String, String>> getExtraData() {
return List.of(Pair.of("not_sleeping_ticks", String.valueOf(bot.notSleepTicks)));
}
@Override
public Boolean loadFromCommand(@NotNull CommandContext context) {
return context.getBoolean(getName());
}
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putBoolean(NAME, this.getValue());
nbt.putBoolean(getName(), this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSpawnPhantom));
this.setValue(nbt.getBooleanOr(getName(), FakeplayerConfig.canSpawnPhantom));
}
}
@@ -17,28 +17,39 @@
package org.leavesmc.leaves.bot.agent.configs;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.AbstractBotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
public class TickTypeConfig extends AbstractBotConfig<ServerBot.TickType> {
private static final String NAME = "tick_type";
private static final CommandArgumentType<ServerBot.TickType> TICK_TYPE_ARGUMENT = CommandArgumentType.ofEnum(ServerBot.TickType.class);
import org.leavesmc.leaves.command.CommandContext;
public class TickTypeConfig extends AbstractBotConfig<ServerBot.TickType, String, TickTypeConfig> {
private ServerBot.TickType value;
public TickTypeConfig() {
super(NAME, CommandArgument.of(TICK_TYPE_ARGUMENT).setSuggestion(0, List.of("network", "entity_list")));
super("tick_type", StringArgumentType.word(), TickTypeConfig::new);
this.value = FakeplayerConfig.tickType;
}
@Override
public void applySuggestions(CommandContext context, @NotNull SuggestionsBuilder builder) {
builder.suggest("network");
builder.suggest("entity_list");
}
@Override
public ServerBot.TickType loadFromCommand(@NotNull CommandContext context) throws CommandSyntaxException {
String raw = context.getString(getName());
return switch (raw) {
case "network" -> ServerBot.TickType.NETWORK;
case "entity_list" -> ServerBot.TickType.ENTITY_LIST;
default -> throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
};
}
@Override
public ServerBot.TickType getValue() {
return value;
@@ -53,12 +64,17 @@ public class TickTypeConfig extends AbstractBotConfig<ServerBot.TickType> {
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putString(NAME, this.getValue().toString());
nbt.putString(getName(), this.getValue().toString());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.setValue(TICK_TYPE_ARGUMENT.parse(nbt.getStringOr(NAME, FakeplayerConfig.tickType.name())));
String raw = nbt.getStringOr(getName(), FakeplayerConfig.tickType.name());
this.setValue(switch (raw) {
case "network" -> ServerBot.TickType.NETWORK;
case "entity_list" -> ServerBot.TickType.ENTITY_LIST;
default -> throw new IllegalStateException("Unexpected bot tick type value: " + raw);
});
}
}
@@ -1,67 +0,0 @@
package org.leavesmc.leaves.command;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
public class CommandArgument {
public static final CommandArgument EMPTY = new CommandArgument();
private static final Pair<List<String>, String> EMPTY_SUGGESTION_RESULT = Pair.of(List.of(), null);
private static final BiFunction<CommandSender, String, Pair<List<String>, String>> EMPTY_SUGGESTION = (sender, arg) -> EMPTY_SUGGESTION_RESULT;
private final List<BiFunction<CommandSender, String, Pair<List<String>, String>>> suggestions;
private final List<CommandArgumentType<?>> argumentTypes;
private CommandArgument(CommandArgumentType<?>... argumentTypes) {
this.argumentTypes = List.of(argumentTypes);
this.suggestions = new ArrayList<>();
for (int i = 0; i < argumentTypes.length; i++) {
suggestions.add(EMPTY_SUGGESTION);
}
}
public static CommandArgument of(CommandArgumentType<?>... argumentTypes) {
return new CommandArgument(argumentTypes);
}
public List<CommandArgumentType<?>> getArgumentTypes() {
return argumentTypes;
}
public CommandArgument setSuggestion(int n, BiFunction<CommandSender, String, Pair<List<String>, String>> suggestion) {
this.suggestions.set(n, suggestion);
return this;
}
public CommandArgument setSuggestion(int n, Pair<List<String>, String> suggestion) {
return this.setSuggestion(n, (sender, arg) -> suggestion);
}
public CommandArgument setSuggestion(int n, List<String> tabComplete) {
return this.setSuggestion(n, Pair.of(tabComplete, null));
}
public Pair<List<String>, String> suggestion(int n, CommandSender sender, String arg) {
if (suggestions.size() > n) {
return suggestions.get(n).apply(sender, arg);
} else {
return EMPTY_SUGGESTION.apply(sender, arg);
}
}
public CommandArgumentResult parse(int index, String @NotNull [] args) {
Object[] result = new Object[argumentTypes.size()];
Arrays.fill(result, null);
for (int i = index, j = 0; i < args.length && j < result.length; i++, j++) {
result[j] = argumentTypes.get(j).parse(args[i]);
}
return new CommandArgumentResult(new ArrayList<>(Arrays.asList(result)));
}
}
@@ -1,92 +0,0 @@
package org.leavesmc.leaves.command;
import net.minecraft.core.BlockPos;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
public class CommandArgumentResult {
private final List<Object> result;
public CommandArgumentResult(List<Object> result) {
this.result = result;
}
public int readInt(int def) {
return Objects.requireNonNullElse(read(Integer.class), def);
}
public double readDouble(double def) {
return Objects.requireNonNullElse(read(Double.class), def);
}
public float readFloat(float def) {
return Objects.requireNonNullElse(read(Float.class), def);
}
public String readString(String def) {
return Objects.requireNonNullElse(read(String.class), def);
}
public boolean readBoolean(boolean def) {
return Objects.requireNonNullElse(read(Boolean.class), def);
}
public BlockPos readPos() {
Integer[] pos = {read(Integer.class), read(Integer.class), read(Integer.class)};
for (Integer po : pos) {
if (po == null) {
return null;
}
}
return new BlockPos(pos[0], pos[1], pos[2]);
}
public @Nullable Vector readVector() {
Double[] pos = {read(Double.class), read(Double.class), read(Double.class)};
for (Double po : pos) {
if (po == null) {
return null;
}
}
return new Vector(pos[0], pos[1], pos[2]);
}
public @NotNull Vector readVectorYZ(double x) {
Double[] pos = {x, read(Double.class), read(Double.class)};
for (Double po : pos) {
if (po == null) {
throw new IllegalArgumentException("Failed to read vector!");
}
}
return new Vector(pos[0], pos[1], pos[2]);
}
public Object readObject() {
if (result.isEmpty()) {
return null;
}
return result.removeFirst();
}
public <T> T read(Class<T> tClass, T def) {
return Objects.requireNonNullElse(read(tClass), def);
}
public <T> T read(Class<T> tClass) {
if (result.isEmpty()) {
return null;
}
Object obj = result.removeFirst();
if (tClass.isInstance(obj)) {
return tClass.cast(obj);
} else {
return null;
}
}
}
@@ -1,59 +0,0 @@
package org.leavesmc.leaves.command;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
public abstract class CommandArgumentType<E> {
public static final CommandArgumentType<String> STRING = CommandArgumentType.string();
public static final CommandArgumentType<Integer> INTEGER = CommandArgumentType.of(Integer.class, Integer::parseInt);
public static final CommandArgumentType<Double> DOUBLE = CommandArgumentType.of(Double.class, Double::parseDouble);
public static final CommandArgumentType<Float> FLOAT = CommandArgumentType.of(Float.class, Float::parseFloat);
public static final CommandArgumentType<Boolean> BOOLEAN = CommandArgumentType.of(Boolean.class, Boolean::parseBoolean);
private final Class<E> type;
private CommandArgumentType(Class<E> type) {
this.type = type;
}
@NotNull
@Contract(value = "_, _ -> new", pure = true)
public static <E> CommandArgumentType<E> of(Class<E> type, Function<String, E> parse) {
return new CommandArgumentType<>(type) {
@Override
public E parse(@NotNull String arg) {
try {
return parse.apply(arg);
} catch (Exception ignore) {
return null;
}
}
};
}
@NotNull
@Contract(value = "_ -> new", pure = true)
public static <E extends Enum<E>> CommandArgumentType<E> ofEnum(Class<E> type) {
return of(type, (string -> Enum.valueOf(type, string.toUpperCase())));
}
@NotNull
@Contract(value = " -> new", pure = true)
private static CommandArgumentType<String> string() {
return new CommandArgumentType<>(String.class) {
@Override
public String parse(@NotNull String arg) {
return arg;
}
};
}
public Class<E> getType() {
return type;
}
public abstract E parse(@NotNull String arg);
}
@@ -0,0 +1,46 @@
package org.leavesmc.leaves.command.bot;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.network.chat.Component;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.CustomArgumentType;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
public class BotArgument implements CustomArgumentType<ServerBot, String> {
@Override
public ArgumentType<String> getBaseArgumentType() {
return StringArgumentType.word();
}
@Override
public ServerBot transform(String value) throws CommandSyntaxException {
ServerBot bot = BotList.INSTANCE.getBotByName(value);
if (bot == null) {
throw new CommandSyntaxException(
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument(),
Component.literal("Bot with name '" + value + "' does not exist")
);
}
return bot;
}
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException {
Collection<ServerBot> bots = BotList.INSTANCE.bots;
if (bots.isEmpty()) {
return builder
.suggest("<NO BOT EXISTS>", Component.literal("There are no bots in the server, create one first."))
.buildFuture();
}
bots.stream().map(ServerBot::getScoreboardName).forEach(builder::suggest);
return builder.buildFuture();
}
}
@@ -0,0 +1,58 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot;
import com.mojang.brigadier.builder.ArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.RootNode;
import org.leavesmc.leaves.command.bot.subcommands.*;
import static org.leavesmc.leaves.command.CommandUtils.registerPermissions;
public class BotCommand extends RootNode {
private static final String PERM_BASE = "bukkit.command.bot";
public BotCommand() {
super("bot", PERM_BASE);
this.children(
ListCommand::new,
ConfigCommand::new,
RemoveCommand::new,
LoadCommand::new,
SaveCommand::new,
ActionCommand::new,
CreateCommand::new
);
}
@Override
protected ArgumentBuilder<CommandSourceStack, ?> compile() {
registerPermissions(PERM_BASE, this.children);
return super.compile();
}
public static boolean hasPermission(@NotNull CommandSender sender) {
return sender.hasPermission(PERM_BASE);
}
public static boolean hasPermission(@NotNull CommandSender sender, String... subcommand) {
return hasPermission(PERM_BASE, sender, subcommand);
}
}
@@ -0,0 +1,34 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot;
import net.minecraft.commands.CommandSourceStack;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.LiteralNode;
public abstract class BotSubcommand extends LiteralNode {
protected BotSubcommand(String name) {
super(name);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return BotCommand.hasPermission(source.getSender(), this.name);
}
}
@@ -0,0 +1,59 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.commands.CommandSourceStack;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.CustomArgumentNode;
import org.leavesmc.leaves.command.bot.BotSubcommand;
import org.leavesmc.leaves.command.bot.subcommands.action.ListCommand;
import org.leavesmc.leaves.command.bot.subcommands.action.StartCommand;
import org.leavesmc.leaves.command.bot.subcommands.action.StopCommand;
public class ActionCommand extends BotSubcommand {
public ActionCommand() {
super("action");
children(BotArgument::new);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return FakeplayerConfig.canUseAction && super.requires(source);
}
public static class BotArgument extends CustomArgumentNode<ServerBot, String> {
private BotArgument() {
super("bot", new org.leavesmc.leaves.command.bot.BotArgument());
children(
StartCommand::new,
StopCommand::new,
ListCommand::new
);
}
public static @NotNull ServerBot getBot(@NotNull CommandContext context) throws CommandSyntaxException {
return context.getCustomArgument(BotArgument.class);
}
}
}
@@ -0,0 +1,151 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.minecraft.commands.CommandSourceStack;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.Configs;
import org.leavesmc.leaves.bot.agent.configs.AbstractBotConfig;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.CustomArgumentNode;
import org.leavesmc.leaves.command.LiteralNode;
import org.leavesmc.leaves.command.bot.BotSubcommand;
import java.util.Collection;
import java.util.function.Supplier;
import static io.papermc.paper.adventure.PaperAdventure.asAdventure;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.JoinConfiguration.spaces;
import static net.kyori.adventure.text.format.NamedTextColor.AQUA;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class ConfigCommand extends BotSubcommand {
public ConfigCommand() {
super("config");
children(BotArgument::new);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return FakeplayerConfig.canModifyConfig && super.requires(source);
}
private static class BotArgument extends CustomArgumentNode<ServerBot, String> {
private BotArgument() {
super("bot", new org.leavesmc.leaves.command.bot.BotArgument());
Configs.getConfigs().stream()
.map(this::configNodeCreator)
.forEach(this::children);
}
@Contract(pure = true)
private @NotNull Supplier<LiteralNode> configNodeCreator(AbstractBotConfig<?, ?, ?> config) {
return () -> new ConfigNode<>(config);
}
public static @NotNull ServerBot getBot(@NotNull CommandContext context) throws CommandSyntaxException {
return context.getCustomArgument(BotArgument.class);
}
@Override
protected boolean execute(CommandContext context) throws CommandSyntaxException {
ServerBot bot = BotArgument.getBot(context);
CommandSender sender = context.getSender();
Collection<AbstractBotConfig<?, ?, ?>> botConfigs = bot.getAllConfigs();
sender.sendMessage(join(spaces(),
text("Bot", GRAY),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("configs:", GRAY)
));
for (AbstractBotConfig<?, ?, ?> botConfig : botConfigs) {
sender.sendMessage(join(spaces(),
botConfig.getNameComponent(),
text("=", GRAY),
text(String.valueOf(botConfig.getValue()), AQUA)
));
}
return true;
}
}
private static class ConfigNode<Value> extends LiteralNode {
private final AbstractBotConfig<Value, ?, ?> config;
private ConfigNode(@NotNull AbstractBotConfig<Value, ?, ?> config) {
super(config.getName());
this.config = config;
}
@Override
protected ArgumentBuilder<CommandSourceStack, ?> compileBase() {
RequiredArgumentBuilder<CommandSourceStack, ?> argument = config.getArgument()
.compile()
.executes(mojangCtx -> {
CommandContext ctx = new CommandContext(mojangCtx);
return executeSet(ctx) ? 1 : 0;
});
return super.compileBase()
.then(argument);
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
ServerBot bot = BotArgument.getBot(context);
AbstractBotConfig<Value, ?, ?> botConfig = bot.getConfig(config);
context.getSender().sendMessage(join(spaces(),
text("Bot", GRAY),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("config", GRAY),
botConfig.getNameComponent(),
text("is", GRAY),
text(String.valueOf(bot.getConfig(config).getValue()), AQUA)
));
return true;
}
private boolean executeSet(CommandContext context) throws CommandSyntaxException {
ServerBot bot = BotArgument.getBot(context);
AbstractBotConfig<Value, ?, ?> botConfig = bot.getConfig(config);
try {
botConfig.setValue(botConfig.loadFromCommand(context));
} catch (ClassCastException e) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
}
context.getSender().sendMessage(join(spaces(),
text("Bot", GRAY),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("config", GRAY),
botConfig.getNameComponent(),
text("changed to", GRAY),
text(String.valueOf(botConfig.getValue()), AQUA)
));
return true;
}
}
}
@@ -0,0 +1,168 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.DimensionArgument;
import net.minecraft.commands.arguments.coordinates.Coordinates;
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.BotCreateState;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.bot.BotSubcommand;
import org.leavesmc.leaves.event.bot.BotCreateEvent;
import static net.kyori.adventure.text.Component.text;
import static net.minecraft.commands.arguments.DimensionArgument.getDimension;
public class CreateCommand extends BotSubcommand {
public CreateCommand() {
super("create");
children(NameArgument::new);
}
protected static boolean handleCreateCommand(@NotNull CommandContext context) throws CommandSyntaxException {
CommandSender sender = context.getSender();
String name = context.getArgument(NameArgument.class);
if (!canCreate(sender, name)) {
return false;
}
String skinName = context.getArgumentOrDefault(SkinNameArgument.class, name);
World world;
try {
world = getDimension(context.getMojangContext(), "world").getWorld();
} catch (IllegalArgumentException e) {
if (!(sender instanceof Entity entity)) {
sender.sendMessage(text("Must specify world and location when executed by console", NamedTextColor.RED));
return false;
}
world = entity.getWorld();
}
Location location = Bukkit.getWorlds().getFirst().getSpawnLocation();
Coordinates coords = context.getArgumentOrDefault(LocationArgument.class, null);
if (coords != null) {
Vec3 vec3 = coords.getPosition(context.getSource());
location = new Location(world, vec3.x, vec3.y, vec3.z);
} else if (sender instanceof Entity entity) {
location = entity.getLocation();
}
BotCreateState
.builder(name, location)
.createReason(BotCreateEvent.CreateReason.COMMAND)
.skinName(skinName)
.creator(sender)
.spawnWithSkin(null);
return true;
}
private static boolean canCreate(CommandSender sender, @NotNull String name) {
BotList botList = BotList.INSTANCE;
if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) {
sender.sendMessage(text("This name is illegal, bot name must be 4-16 characters and contain only letters, numbers, and underscores.", NamedTextColor.RED));
return false;
}
if (Bukkit.getPlayerExact(name) != null || botList.getBotByName(name) != null) {
sender.sendMessage(text("This bot is already in server", NamedTextColor.RED));
return false;
}
if (FakeplayerConfig.unableNames.contains(name)) {
sender.sendMessage(text("This name is not allowed in this server", NamedTextColor.RED));
return false;
}
if (botList.bots.size() >= FakeplayerConfig.limit) {
sender.sendMessage(text("Bot number limit exceeded", NamedTextColor.RED));
return false;
}
return true;
}
private static class NameArgument extends ArgumentNode<String> {
private NameArgument() {
super("name", StringArgumentType.word());
children(SkinNameArgument::new);
}
@Override
protected boolean execute(CommandContext context) throws CommandSyntaxException {
return handleCreateCommand(context);
}
}
private static class SkinNameArgument extends ArgumentNode<String> {
private SkinNameArgument() {
super("skin_name", StringArgumentType.word());
children(WorldArgument::new);
}
@Override
protected boolean execute(CommandContext context) throws CommandSyntaxException {
return handleCreateCommand(context);
}
}
private static class WorldArgument extends ArgumentNode<ResourceLocation> {
private WorldArgument() {
super("world", DimensionArgument.dimension());
children(LocationArgument::new);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return source.getSender() instanceof ConsoleCommandSender;
}
}
private static class LocationArgument extends ArgumentNode<Coordinates> {
private LocationArgument() {
super("location", Vec3Argument.vec3(true));
}
@Override
protected boolean execute(CommandContext context) throws CommandSyntaxException {
return handleCreateCommand(context);
}
}
}
@@ -0,0 +1,115 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import io.papermc.paper.adventure.PaperAdventure;
import net.kyori.adventure.text.Component;
import net.minecraft.commands.arguments.DimensionArgument;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftWorld;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.bot.BotSubcommand;
import java.util.List;
import java.util.Objects;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.JoinConfiguration.noSeparators;
import static net.kyori.adventure.text.event.HoverEvent.showText;
import static net.kyori.adventure.text.format.NamedTextColor.*;
import static net.minecraft.commands.arguments.DimensionArgument.getDimension;
public class ListCommand extends BotSubcommand {
public ListCommand() {
super("list");
children(WorldArgument::new);
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
Component msg = Bukkit.getWorlds().stream()
.map(ListCommand::getBotListMessage)
.filter(Objects::nonNull)
.reduce((a, b) -> a.append(text("\n")).append(b))
.orElseGet(() -> text("No bots on the server", GRAY));
context.getSender().sendMessage(join(noSeparators(),
text("Total bot number: ", GRAY),
text(BotList.INSTANCE.bots.size(), AQUA).hoverEvent(showText(text("current bot count"))),
text("/", GRAY),
text(FakeplayerConfig.limit, AQUA).hoverEvent(showText(text("bot count limit")))
));
context.getSender().sendMessage(msg);
return true;
}
protected static @Nullable Component getBotListMessage(@NotNull World world) {
List<ServerBot> botsInLevel = BotList.INSTANCE.bots.stream()
.filter((bot) -> bot.getBukkitEntity().getWorld().equals(world))
.toList();
if (botsInLevel.isEmpty()) {
return null;
}
Component botsMsg = botsInLevel.stream()
.map(Player::getDisplayName)
.map(PaperAdventure::asAdventure)
.reduce((a, b) -> a.append(text(", ", GRAY)).append(b))
.get();
String worldLocation = ((CraftWorld) world).getHandle().dimension().location().toString();
return join(noSeparators(),
text(world.getName(), AQUA).hoverEvent(showText(text(worldLocation))),
text(" (" + botsInLevel.size() + ")\n", GRAY),
botsMsg
);
}
private static class WorldArgument extends ArgumentNode<ResourceLocation> {
private WorldArgument() {
super("world", DimensionArgument.dimension());
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
ServerLevel dimension = getDimension(context.getMojangContext(), "world");
Component botListMessage = getBotListMessage(dimension.getWorld());
CommandSender sender = context.getSender();
if (botListMessage == null) {
sender.sendMessage(text("No bots in that world", RED));
} else {
sender.sendMessage(text("Bot in ").append(botListMessage));
}
return true;
}
}
}
@@ -0,0 +1,96 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.commands.CommandSourceStack;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.bot.BotSubcommand;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import static io.papermc.paper.adventure.PaperAdventure.asAdventure;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.JoinConfiguration.spaces;
public class LoadCommand extends BotSubcommand {
public LoadCommand() {
super("load");
children(BotNameArgument::new);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return FakeplayerConfig.canManualSaveAndLoad && super.requires(source);
}
private static class BotNameArgument extends ArgumentNode<String> {
private BotNameArgument() {
super("bot_name", StringArgumentType.word());
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
String botName = context.getArgument(BotNameArgument.class);
BotList botList = BotList.INSTANCE;
CommandSender sender = context.getSender();
if (!botList.getSavedBotList().contains(botName)) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
}
ServerBot bot = botList.loadNewBot(botName);
if (bot == null) {
sender.sendMessage(text("Failed to load bot, please check log", NamedTextColor.RED));
return false;
}
sender.sendMessage(join(
spaces(),
text("Successfully loaded bot", NamedTextColor.GRAY),
asAdventure(bot.getDisplayName())
));
return true;
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(CommandContext context, @NotNull SuggestionsBuilder builder) {
BotList botList = BotList.INSTANCE;
Set<String> bots = botList.getSavedBotList().keySet();
if (bots.isEmpty()) {
return builder
.suggest("<NO SAVED BOT EXISTS>", net.minecraft.network.chat.Component.literal("There are no bots saved before, save one first."))
.buildFuture();
}
bots.forEach(builder::suggest);
return builder.buildFuture();
}
}
}
@@ -0,0 +1,178 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.papermc.paper.adventure.PaperAdventure;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.CustomArgumentNode;
import org.leavesmc.leaves.command.bot.BotSubcommand;
import org.leavesmc.leaves.event.bot.BotRemoveEvent;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.JoinConfiguration.spaces;
import static net.kyori.adventure.text.format.NamedTextColor.*;
import static net.minecraft.network.chat.Component.literal;
public class RemoveCommand extends BotSubcommand {
public RemoveCommand() {
super("remove");
children(BotArgument::new);
}
private static boolean removeBot(@NotNull ServerBot bot, @Nullable CommandSender sender) {
boolean success = BotList.INSTANCE.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false);
if (!success) {
sender = sender == null ? Bukkit.getConsoleSender() : sender;
sender.sendMessage(text("Bot remove canceled by a plugin", RED));
}
return success;
}
private static class BotArgument extends CustomArgumentNode<ServerBot, String> {
private BotArgument() {
super("bot", new org.leavesmc.leaves.command.bot.BotArgument());
children(RemoveTimeArgument::new);
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
ServerBot bot = context.getCustomArgument(BotArgument.class);
return removeBot(bot, context.getSender());
}
}
private static class RemoveTimeArgument extends ArgumentNode<String> {
private RemoveTimeArgument() {
super("remove_time", StringArgumentType.word());
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
String removeTimeStr = context.getArgument("remove_time", String.class);
int removeTimeSeconds = parseRemoveTime(removeTimeStr);
ServerBot bot = context.getCustomArgument(BotArgument.class);
CommandSender sender = context.getSender();
boolean isReschedule = bot.removeTaskId != -1;
if (isReschedule) {
Bukkit.getScheduler().cancelTask(bot.removeTaskId);
}
bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> {
bot.removeTaskId = -1;
removeBot(bot, sender);
}, removeTimeSeconds * 20L).getTaskId();
sender.sendMessage(join(spaces(),
text("Bot", GRAY),
PaperAdventure.asAdventure(bot.getDisplayName()),
text("scheduled for removal in", GRAY),
text(formatSeconds(removeTimeSeconds), AQUA),
text(isReschedule ? "(rescheduled)" : "", GRAY)
));
return true;
}
private static int parseRemoveTime(String timeStr) throws CommandSyntaxException {
if (timeStr == null || timeStr.trim().isEmpty()) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
}
if (!timeStr.matches("^[\\d\\shmsHMS]+$")) {
throw new CommandSyntaxException(
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException(),
literal("Invalid time format: " + timeStr)
);
}
String remaining = timeStr.replaceAll("\\d+[hmsHMS]", "").trim();
if (!remaining.isEmpty() && remaining.matches(".*\\d+.*")) {
throw new CommandSyntaxException(
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException(),
literal("Found trailing numbers without unit: " + timeStr)
);
}
Matcher matcher = Pattern.compile("(\\d+)([hmsHMS])").matcher(timeStr);
int seconds = 0;
boolean foundMatch = false;
while (matcher.find()) {
foundMatch = true;
int value = Integer.parseInt(matcher.group(1));
switch (matcher.group(2).toLowerCase()) {
case "h":
seconds += value * 3600;
break;
case "m":
seconds += value * 60;
break;
case "s":
seconds += value;
break;
}
}
if (!foundMatch) {
throw new CommandSyntaxException(
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException(),
literal("No valid time units found in: " + timeStr)
);
}
return seconds;
}
private static @NotNull String formatSeconds(int totalSeconds) {
int h = totalSeconds / 3600;
int m = (totalSeconds % 3600) / 60;
int s = totalSeconds % 60;
StringBuilder sb = new StringBuilder();
if (h > 0) {
sb.append(h).append("h");
}
if (m > 0) {
sb.append(m).append("m");
}
if (s > 0) {
sb.append(s).append("s");
}
if (sb.isEmpty()) {
sb.append("0s");
}
return sb.toString();
}
}
}
@@ -0,0 +1,75 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.commands.CommandSourceStack;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.CustomArgumentNode;
import org.leavesmc.leaves.command.bot.BotSubcommand;
import org.leavesmc.leaves.event.bot.BotRemoveEvent;
import static io.papermc.paper.adventure.PaperAdventure.asAdventure;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.JoinConfiguration.spaces;
public class SaveCommand extends BotSubcommand {
public SaveCommand() {
super("save");
children(BotArgument::new);
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return FakeplayerConfig.canManualSaveAndLoad && super.requires(source);
}
private static class BotArgument extends CustomArgumentNode<ServerBot, String> {
private BotArgument() {
super("bot", new org.leavesmc.leaves.command.bot.BotArgument());
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
ServerBot bot = context.getCustomArgument(BotArgument.class);
CommandSender sender = context.getSender();
BotList botList = BotList.INSTANCE;
boolean success = botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, true);
if (success) {
sender.sendMessage(join(spaces(),
text("Successfully saved bot", NamedTextColor.GRAY),
asAdventure(bot.getDisplayName()),
text("as " + bot.createState.realName(), NamedTextColor.GRAY)
));
} else {
sender.sendMessage(text("Bot save canceled by a plugin", NamedTextColor.RED));
}
return success;
}
}
}
@@ -0,0 +1,70 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands.action;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.LiteralNode;
import org.leavesmc.leaves.command.bot.subcommands.ActionCommand;
import java.util.List;
import static io.papermc.paper.adventure.PaperAdventure.asAdventure;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.JoinConfiguration.spaces;
import static net.kyori.adventure.text.event.HoverEvent.showText;
import static net.kyori.adventure.text.format.NamedTextColor.AQUA;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class ListCommand extends LiteralNode {
public ListCommand() {
super("list");
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
ServerBot bot = ActionCommand.BotArgument.getBot(context);
CommandSender sender = context.getSender();
List<AbstractBotAction<?>> actions = bot.getBotActions();
if (actions.isEmpty()) {
sender.sendMessage(text("This bot has no active actions", GRAY));
return true;
}
sender.sendMessage(
asAdventure(bot.getDisplayName())
.append(text("'s action list:", GRAY))
);
for (int i = 0; i < actions.size(); i++) {
AbstractBotAction<?> action = actions.get(i);
sender.sendMessage(join(spaces(),
text(i, GRAY),
text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString())))
));
}
return true;
}
}
@@ -0,0 +1,132 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands.action;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.commands.CommandSourceStack;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.LiteralNode;
import org.leavesmc.leaves.command.WrappedArgument;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import static io.papermc.paper.adventure.PaperAdventure.asAdventure;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.JoinConfiguration.spaces;
import static net.kyori.adventure.text.event.HoverEvent.showText;
import static net.kyori.adventure.text.format.NamedTextColor.AQUA;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static org.leavesmc.leaves.command.bot.subcommands.ActionCommand.BotArgument.getBot;
public class StartCommand extends LiteralNode {
public StartCommand() {
super("start");
Actions.getAll()
.stream()
.map(this::actionNodeCreator)
.forEach(this::children);
}
private boolean handleStartCommand(CommandContext context, @NotNull AbstractBotAction<?> action) throws CommandSyntaxException {
ServerBot bot = getBot(context);
CommandSender sender = context.getSender();
action.loadCommand(context);
if (bot.addBotAction(action, sender)) {
sender.sendMessage(join(spaces(),
text("Action", GRAY),
text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString()))),
text("has been issued to", GRAY),
asAdventure(bot.getDisplayName())
));
}
return true;
}
@Contract(pure = true)
private @NotNull Supplier<LiteralNode> actionNodeCreator(AbstractBotAction<?> action) {
return () -> new ActionLiteralNode(action);
}
private class ActionLiteralNode extends LiteralNode {
private final AbstractBotAction<?> action;
private ActionLiteralNode(@NotNull AbstractBotAction<?> action) {
super(action.getName());
this.action = action;
}
@Override
protected ArgumentBuilder<CommandSourceStack, ?> compile() {
ArgumentBuilder<CommandSourceStack, ?> builder = super.compile();
Map<Integer, List<Pair<String, WrappedArgument<?>>>> arguments = action.getArguments();
Command<CommandSourceStack> executor = context -> {
if (handleStartCommand(new CommandContext(context), action)) {
return Command.SINGLE_SUCCESS;
} else {
return 0;
}
};
for (Map.Entry<Integer, List<Pair<String, WrappedArgument<?>>>> entry : arguments.entrySet()) {
List<Pair<String, WrappedArgument<?>>> value = entry.getValue();
ArgumentBuilder<CommandSourceStack, ?> branchArgumentBuilder = null;
for (Pair<String, WrappedArgument<?>> stringWrappedArgumentPair : value.reversed()) {
WrappedArgument<?> argument = stringWrappedArgumentPair.getRight();
if (branchArgumentBuilder == null) {
branchArgumentBuilder = argument.compile().executes(executor);
} else {
branchArgumentBuilder = argument.compile().then(branchArgumentBuilder);
if (argument.isOptional()) {
branchArgumentBuilder = branchArgumentBuilder.executes(executor);
}
}
}
if (value.getFirst().getRight().isOptional() || value.isEmpty()) {
builder = builder.executes(executor);
}
if (branchArgumentBuilder != null) {
builder = builder.then(branchArgumentBuilder);
}
}
if (arguments.isEmpty()) {
builder = builder.executes(executor);
}
return builder;
}
}
}
@@ -0,0 +1,161 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.command.bot.subcommands.action;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.network.chat.Component;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.LiteralNode;
import org.leavesmc.leaves.command.bot.subcommands.ActionCommand;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import static io.papermc.paper.adventure.PaperAdventure.asAdventure;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.JoinConfiguration.spaces;
import static net.kyori.adventure.text.event.HoverEvent.showText;
import static net.kyori.adventure.text.format.NamedTextColor.*;
public class StopCommand extends LiteralNode {
public StopCommand() {
super("stop");
children(StopIndexArgument::new);
children(StopAll::new);
}
private static class StopIndexArgument extends ArgumentNode<Integer> {
private StopIndexArgument() {
super("index", IntegerArgumentType.integer(0));
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException {
ServerBot bot = ActionCommand.BotArgument.getBot(context);
for (int i = 0; i < bot.getBotActions().size(); i++) {
AbstractBotAction<?> action = bot.getBotActions().get(i);
builder.suggest(String.valueOf(i), Component.literal(action.getName()));
}
return builder.buildFuture();
}
@Override
protected boolean execute(CommandContext context) throws CommandSyntaxException {
ServerBot bot = ActionCommand.BotArgument.getBot(context);
CommandSender sender = context.getSender();
int index = context.getArgument(StopIndexArgument.class);
int maxIndex = bot.getBotActions().size() - 1;
if (maxIndex < 0) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
}
if (index > maxIndex) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh().create(index, maxIndex);
}
AbstractBotAction<?> action = bot.getBotActions().get(index);
BotActionStopEvent event = new BotActionStopEvent(
bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender
);
event.callEvent();
if (!event.isCancelled()) {
action.stop(bot, BotActionStopEvent.Reason.COMMAND);
bot.getBotActions().remove(index);
sender.sendMessage(join(spaces(),
text("Already stopped", GRAY),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("action", GRAY),
text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString())))
));
} else {
sender.sendMessage(text("Action stop cancelled by a plugin", RED));
}
return true;
}
}
private static class StopAll extends LiteralNode {
private StopAll() {
super("all");
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
ServerBot bot = ActionCommand.BotArgument.getBot(context);
List<AbstractBotAction<?>> actions = bot.getBotActions();
CommandSender sender = context.getSender();
if (actions.isEmpty()) {
sender.sendMessage(text("This bot has no active actions", GRAY));
return true;
}
Set<AbstractBotAction<?>> canceled = new HashSet<>();
Set<AbstractBotAction<?>> forRemoval = new HashSet<>();
for (AbstractBotAction<?> action : actions) {
BotActionStopEvent event = new BotActionStopEvent(
bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender
);
event.callEvent();
if (!event.isCancelled()) {
forRemoval.add(action);
action.stop(bot, BotActionStopEvent.Reason.COMMAND);
} else {
canceled.add(action);
}
}
bot.getBotActions().removeAll(forRemoval);
if (canceled.isEmpty()) {
sender.sendMessage(join(spaces(),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("'s action list cleared", GRAY)
));
} else {
sender.sendMessage(join(spaces(),
text("Tried to clear", GRAY),
asAdventure(bot.getDisplayName()).append(text("'s", GRAY)),
text("'s action list, but following actions' stop was canceled by plugin:", GRAY)
));
for (AbstractBotAction<?> action : canceled) {
context.getSender().sendMessage(
text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString())))
);
}
}
return true;
}
}
}
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.actions.ServerBotAction;
import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction;
import org.leavesmc.leaves.entity.bot.action.BotAction;
import org.leavesmc.leaves.entity.bot.actions.CraftBotAction;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
@@ -57,13 +57,17 @@ public class CraftBot extends CraftPlayer implements Bot {
@Override
public <T extends BotAction<T>> void addAction(@NotNull T action) {
switch (action) {
case CraftBotAction act -> this.getHandle().addBotAction(act.getHandle(), null);
default ->
throw new IllegalArgumentException("Action " + action.getClass().getName() + " is not a valid BotAction type!");
if (action instanceof CraftBotAction<?, ?> act) {
this.getHandle().addBotAction(act.getHandle(), null);
} else {
throw new IllegalArgumentException("Action " + action.getClass().getName() + " is not a valid BotAction type!");
}
}
public void addAction(@NotNull AbstractBotAction<?> action) {
this.getHandle().addBotAction(action, null);
}
@Override
public BotAction<?> getAction(int index) {
return (BotAction<?>) this.getHandle().getBotActions().get(index).asCraft();
@@ -81,15 +85,14 @@ public class CraftBot extends CraftPlayer implements Bot {
@Override
public void stopAllActions() {
for (ServerBotAction<?> action : this.getHandle().getBotActions()) {
for (AbstractBotAction<?> action : this.getHandle().getBotActions()) {
action.stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN);
}
}
@Override
public boolean remove(boolean save) {
BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save);
return true;
return BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save);
}
@Override
@@ -26,7 +26,7 @@ import org.leavesmc.leaves.bot.BotCreateState;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.bot.agent.actions.ServerBotAction;
import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction;
import org.leavesmc.leaves.entity.bot.action.BotAction;
import org.leavesmc.leaves.event.bot.BotCreateEvent;
@@ -72,7 +72,7 @@ public class CraftBotManager implements BotManager {
@SuppressWarnings("unchecked")
@Override
public <T extends BotAction<T>> T newAction(@NotNull Class<T> type) {
ServerBotAction<?> action = Actions.getForClass(type);
AbstractBotAction<?> action = Actions.getForClass(type);
if (action == null) {
throw new IllegalArgumentException("No action registered for type: " + type.getName());
} else {
@@ -19,14 +19,14 @@ package org.leavesmc.leaves.entity.bot.actions;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.actions.ServerBotAction;
import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction;
import org.leavesmc.leaves.entity.bot.action.BotAction;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
public abstract class CraftBotAction<T extends BotAction<T>, S extends ServerBotAction<S>> implements BotAction<T> {
public abstract class CraftBotAction<T extends BotAction<T>, S extends AbstractBotAction<S>> implements BotAction<T> {
protected final S serverAction;
protected final Function<S, T> creator;
@@ -40,7 +40,7 @@ public abstract class CraftBotAction<T extends BotAction<T>, S extends ServerBot
this.creator = creator;
}
public ServerBotAction<?> getHandle() {
public AbstractBotAction<?> getHandle() {
return serverAction;
}
@@ -17,12 +17,12 @@
package org.leavesmc.leaves.entity.bot.actions;
import org.leavesmc.leaves.bot.agent.actions.ServerTimerBotAction;
import org.leavesmc.leaves.bot.agent.actions.AbstractTimerBotAction;
import org.leavesmc.leaves.entity.bot.action.TimerBotAction;
import java.util.function.Function;
public class CraftTimerBotAction<T extends TimerBotAction<T>, S extends ServerTimerBotAction<S>> extends CraftBotAction<T, S> implements TimerBotAction<T> {
public class CraftTimerBotAction<T extends TimerBotAction<T>, S extends AbstractTimerBotAction<S>> extends CraftBotAction<T, S> implements TimerBotAction<T> {
public CraftTimerBotAction(S serverAction, Function<S, T> creator) {
super(serverAction, creator);