mirror of
https://github.com/LeavesMC/Leaves.git
synced 2025-12-19 14:59:32 +00:00
Fakeplayer API update
This commit is contained in:
@@ -669,12 +669,14 @@ index 0000000000000000000000000000000000000000..a369b468d4793b36dd0944a1368a70e0
|
|||||||
+}
|
+}
|
||||||
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java
|
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..e2e0b9fe697ab3b89373264d20d013cb9f65dd40
|
index 0000000000000000000000000000000000000000..07f6d81c4cd897230bbd6712dac09b8995431104
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java
|
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java
|
||||||
@@ -0,0 +1,51 @@
|
@@ -0,0 +1,66 @@
|
||||||
+package org.leavesmc.leaves.event.bot;
|
+package org.leavesmc.leaves.event.bot;
|
||||||
+
|
+
|
||||||
|
+import net.kyori.adventure.text.Component;
|
||||||
|
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
+import org.bukkit.event.HandlerList;
|
+import org.bukkit.event.HandlerList;
|
||||||
+import org.jetbrains.annotations.NotNull;
|
+import org.jetbrains.annotations.NotNull;
|
||||||
+import org.jetbrains.annotations.Nullable;
|
+import org.jetbrains.annotations.Nullable;
|
||||||
@@ -686,13 +688,46 @@ index 0000000000000000000000000000000000000000..e2e0b9fe697ab3b89373264d20d013cb
|
|||||||
+public class BotJoinEvent extends BotEvent {
|
+public class BotJoinEvent extends BotEvent {
|
||||||
+ private static final HandlerList handlers = new HandlerList();
|
+ private static final HandlerList handlers = new HandlerList();
|
||||||
+
|
+
|
||||||
+ private String joinMessage;
|
+ private Component joinMessage;
|
||||||
+
|
+
|
||||||
+ public BotJoinEvent(@NotNull Bot who, @Nullable final String joinMessage) {
|
+ public BotJoinEvent(@NotNull final Bot who, @Nullable final Component joinMessage) {
|
||||||
+ super(who);
|
+ super(who);
|
||||||
+ this.joinMessage = joinMessage;
|
+ this.joinMessage = joinMessage;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
+ public BotJoinEvent(@NotNull final Bot who, @Nullable final String joinMessage) {
|
||||||
|
+ super(who);
|
||||||
|
+ this.joinMessage = joinMessage != null ? LegacyComponentSerializer.legacySection().deserialize(joinMessage) : null;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public void joinMessage(@Nullable final Component joinMessage) {
|
||||||
|
+ this.joinMessage = joinMessage;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ @Nullable
|
||||||
|
+ public Component joinMessage() {
|
||||||
|
+ return joinMessage;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /**
|
||||||
|
+ * Gets the join message to send to all online players
|
||||||
|
+ *
|
||||||
|
+ * @return string join message. Can be null
|
||||||
|
+ */
|
||||||
|
+ @Nullable
|
||||||
|
+ public String getJoinMessage() {
|
||||||
|
+ return this.joinMessage == null ? null : LegacyComponentSerializer.legacySection().serialize(this.joinMessage);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /**
|
||||||
|
+ * Sets the join message to send to all online players
|
||||||
|
+ *
|
||||||
|
+ * @param joinMessage join message. If null, no message will be sent
|
||||||
|
+ */
|
||||||
|
+ public void setJoinMessage(@Nullable String joinMessage) {
|
||||||
|
+ this.joinMessage = joinMessage != null ? LegacyComponentSerializer.legacySection().deserialize(joinMessage) : null;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
+ @Override
|
+ @Override
|
||||||
+ @NotNull
|
+ @NotNull
|
||||||
+ public HandlerList getHandlers() {
|
+ public HandlerList getHandlers() {
|
||||||
@@ -703,35 +738,17 @@ index 0000000000000000000000000000000000000000..e2e0b9fe697ab3b89373264d20d013cb
|
|||||||
+ public static HandlerList getHandlerList() {
|
+ public static HandlerList getHandlerList() {
|
||||||
+ return handlers;
|
+ return handlers;
|
||||||
+ }
|
+ }
|
||||||
+
|
|
||||||
+
|
|
||||||
+ /**
|
|
||||||
+ * Gets the join message to send to all online players
|
|
||||||
+ *
|
|
||||||
+ * @return string join message. Can be null
|
|
||||||
+ */
|
|
||||||
+ @Nullable
|
|
||||||
+ public String getJoinMessage() {
|
|
||||||
+ return joinMessage;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /**
|
|
||||||
+ * Sets the join message to send to all online players
|
|
||||||
+ *
|
|
||||||
+ * @param joinMessage join message. If null, no message will be sent
|
|
||||||
+ */
|
|
||||||
+ public void setJoinMessage(@Nullable String joinMessage) {
|
|
||||||
+ this.joinMessage = joinMessage;
|
|
||||||
+ }
|
|
||||||
+}
|
+}
|
||||||
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java
|
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..6aee942d7db322196504d386a009e22e2aa16230
|
index 0000000000000000000000000000000000000000..7af990a5ee020dfdbec2efc6aecf6393a4223730
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java
|
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java
|
||||||
@@ -0,0 +1,79 @@
|
@@ -0,0 +1,105 @@
|
||||||
+package org.leavesmc.leaves.event.bot;
|
+package org.leavesmc.leaves.event.bot;
|
||||||
+
|
+
|
||||||
|
+import net.kyori.adventure.text.Component;
|
||||||
|
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
+import org.bukkit.command.CommandSender;
|
+import org.bukkit.command.CommandSender;
|
||||||
+import org.bukkit.event.Cancellable;
|
+import org.bukkit.event.Cancellable;
|
||||||
+import org.bukkit.event.HandlerList;
|
+import org.bukkit.event.HandlerList;
|
||||||
@@ -751,10 +768,12 @@ index 0000000000000000000000000000000000000000..6aee942d7db322196504d386a009e22e
|
|||||||
+ DEATH,
|
+ DEATH,
|
||||||
+ INTERNAL
|
+ INTERNAL
|
||||||
+ }
|
+ }
|
||||||
|
+
|
||||||
+ private static final HandlerList handlers = new HandlerList();
|
+ private static final HandlerList handlers = new HandlerList();
|
||||||
+
|
+
|
||||||
+ private final RemoveReason reason;
|
+ private final RemoveReason reason;
|
||||||
+ private final Optional<CommandSender> remover;
|
+ private final Optional<CommandSender> remover;
|
||||||
|
+ private Component removeMessage;
|
||||||
+ private boolean cancel = false;
|
+ private boolean cancel = false;
|
||||||
+
|
+
|
||||||
+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason) {
|
+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason) {
|
||||||
@@ -762,9 +781,14 @@ index 0000000000000000000000000000000000000000..6aee942d7db322196504d386a009e22e
|
|||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover) {
|
+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover) {
|
||||||
|
+ this(who, reason, remover, null);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover, @Nullable Component removeMessage) {
|
||||||
+ super(who);
|
+ super(who);
|
||||||
+ this.reason = reason;
|
+ this.reason = reason;
|
||||||
+ this.remover = Optional.ofNullable(remover);
|
+ this.remover = Optional.ofNullable(remover);
|
||||||
|
+ this.removeMessage = removeMessage;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ /**
|
+ /**
|
||||||
@@ -788,6 +812,23 @@ index 0000000000000000000000000000000000000000..6aee942d7db322196504d386a009e22e
|
|||||||
+ return remover;
|
+ return remover;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
+ public Component removeMessage() {
|
||||||
|
+ return removeMessage;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public void removeMessage(Component removeMessage) {
|
||||||
|
+ this.removeMessage = removeMessage;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ @Nullable
|
||||||
|
+ public String getRemoveMessage() {
|
||||||
|
+ return this.removeMessage == null ? null : LegacyComponentSerializer.legacySection().serialize(this.removeMessage);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public void setRemoveMessage(@Nullable String removeMessage) {
|
||||||
|
+ this.removeMessage = removeMessage != null ? LegacyComponentSerializer.legacySection().deserialize(removeMessage) : null;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
+ @Override
|
+ @Override
|
||||||
+ public boolean isCancelled() {
|
+ public boolean isCancelled() {
|
||||||
+ return cancel;
|
+ return cancel;
|
||||||
|
|||||||
@@ -1555,10 +1555,10 @@ index 0000000000000000000000000000000000000000..0db337866c71283464d026a4f230016b
|
|||||||
+}
|
+}
|
||||||
diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java
|
diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..f05e769328ee4b32c0cb21da65f8502d7167353b
|
index 0000000000000000000000000000000000000000..31de3025586331839870796ad9191738b96b4ef8
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java
|
+++ b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java
|
||||||
@@ -0,0 +1,757 @@
|
@@ -0,0 +1,772 @@
|
||||||
+package org.leavesmc.leaves.bot;
|
+package org.leavesmc.leaves.bot;
|
||||||
+
|
+
|
||||||
+import com.google.common.collect.Lists;
|
+import com.google.common.collect.Lists;
|
||||||
@@ -1567,11 +1567,13 @@ index 0000000000000000000000000000000000000000..f05e769328ee4b32c0cb21da65f8502d
|
|||||||
+import com.google.gson.JsonObject;
|
+import com.google.gson.JsonObject;
|
||||||
+import com.mojang.authlib.GameProfile;
|
+import com.mojang.authlib.GameProfile;
|
||||||
+import com.mojang.authlib.properties.Property;
|
+import com.mojang.authlib.properties.Property;
|
||||||
|
+import io.papermc.paper.adventure.PaperAdventure;
|
||||||
+import io.papermc.paper.event.entity.EntityKnockbackEvent;
|
+import io.papermc.paper.event.entity.EntityKnockbackEvent;
|
||||||
+import net.minecraft.Util;
|
+import net.minecraft.Util;
|
||||||
+import net.minecraft.core.BlockPos;
|
+import net.minecraft.core.BlockPos;
|
||||||
+import net.minecraft.network.Connection;
|
+import net.minecraft.network.Connection;
|
||||||
+import net.minecraft.network.PacketSendListener;
|
+import net.minecraft.network.PacketSendListener;
|
||||||
|
+import net.minecraft.network.chat.Component;
|
||||||
+import net.minecraft.network.protocol.Packet;
|
+import net.minecraft.network.protocol.Packet;
|
||||||
+import net.minecraft.network.protocol.PacketFlow;
|
+import net.minecraft.network.protocol.PacketFlow;
|
||||||
+import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
+import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||||
@@ -1608,7 +1610,6 @@ index 0000000000000000000000000000000000000000..f05e769328ee4b32c0cb21da65f8502d
|
|||||||
+import net.minecraft.world.phys.EntityHitResult;
|
+import net.minecraft.world.phys.EntityHitResult;
|
||||||
+import net.minecraft.world.phys.Vec3;
|
+import net.minecraft.world.phys.Vec3;
|
||||||
+import org.bukkit.Bukkit;
|
+import org.bukkit.Bukkit;
|
||||||
+import org.bukkit.ChatColor;
|
|
||||||
+import org.bukkit.Location;
|
+import org.bukkit.Location;
|
||||||
+import org.bukkit.Material;
|
+import org.bukkit.Material;
|
||||||
+import org.bukkit.command.CommandSender;
|
+import org.bukkit.command.CommandSender;
|
||||||
@@ -1742,11 +1743,12 @@ index 0000000000000000000000000000000000000000..f05e769328ee4b32c0cb21da65f8502d
|
|||||||
+ server.getPlayerList().addNewBot(bot);
|
+ server.getPlayerList().addNewBot(bot);
|
||||||
+ bots.add(bot);
|
+ bots.add(bot);
|
||||||
+
|
+
|
||||||
+ BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitPlayer(), ChatColor.YELLOW + state.name + " joined the game");
|
+ BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitPlayer(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())));
|
||||||
+ server.server.getPluginManager().callEvent(event1);
|
+ server.server.getPluginManager().callEvent(event1);
|
||||||
+
|
+
|
||||||
+ if (event1.getJoinMessage() != null) {
|
+ net.kyori.adventure.text.Component joinMessage = event1.joinMessage();
|
||||||
+ Bukkit.broadcastMessage(event1.getJoinMessage());
|
+ if (joinMessage != null && !joinMessage.equals(net.kyori.adventure.text.Component.empty())) {
|
||||||
|
+ server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(joinMessage), false);
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ return bot;
|
+ return bot;
|
||||||
@@ -1828,20 +1830,28 @@ index 0000000000000000000000000000000000000000..f05e769328ee4b32c0cb21da65f8502d
|
|||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public void onRemove(BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover) {
|
+ public void onRemove(BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover) {
|
||||||
+ if (!new BotRemoveEvent(this.getBukkitPlayer(), reason, remover).callEvent()) {
|
+ BotRemoveEvent event = new BotRemoveEvent(this.getBukkitPlayer(), reason, remover, PaperAdventure.asAdventure(Component.translatable("multiplayer.player.left", this.getDisplayName())));
|
||||||
|
+ this.server.server.getPluginManager().callEvent(event);
|
||||||
|
+
|
||||||
|
+ if (event.isCancelled()) {
|
||||||
+ return;
|
+ return;
|
||||||
+ }
|
+ }
|
||||||
+ dropAll();
|
+
|
||||||
|
+ this.dropAll();
|
||||||
+ if (this.removeTaskId != -1) {
|
+ if (this.removeTaskId != -1) {
|
||||||
+ Bukkit.getScheduler().cancelTask(this.removeTaskId);
|
+ Bukkit.getScheduler().cancelTask(this.removeTaskId);
|
||||||
+ this.removeTaskId = -1;
|
+ this.removeTaskId = -1;
|
||||||
+ }
|
+ }
|
||||||
+ bots.remove(this);
|
+ bots.remove(this);
|
||||||
+ server.getPlayerList().removeBot(this);
|
+ this.server.getPlayerList().removeBot(this);
|
||||||
+ remove(RemovalReason.DISCARDED);
|
+ this.remove(RemovalReason.DISCARDED);
|
||||||
+ this.setDead();
|
+ this.setDead();
|
||||||
+ this.removeTab();
|
+ this.removeTab();
|
||||||
+ Bukkit.broadcastMessage(ChatColor.YELLOW + this.getName().getString() + " left the game"); // TODO i18n
|
+
|
||||||
|
+ net.kyori.adventure.text.Component removeMessage = event.removeMessage();
|
||||||
|
+ if (removeMessage != null && !removeMessage.equals(net.kyori.adventure.text.Component.empty())) {
|
||||||
|
+ server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(removeMessage), false);
|
||||||
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ private void removeTab() {
|
+ private void removeTab() {
|
||||||
@@ -2147,7 +2157,8 @@ index 0000000000000000000000000000000000000000..f05e769328ee4b32c0cb21da65f8502d
|
|||||||
+ BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity());
|
+ BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity());
|
||||||
+ server.server.getPluginManager().callEvent(event);
|
+ server.server.getPluginManager().callEvent(event);
|
||||||
+ if (!event.isCancelled()) {
|
+ if (!event.isCancelled()) {
|
||||||
+ player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, container), getDisplayName()));
|
+ Component menuName = this.getDisplayName();
|
||||||
|
+ player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, container), menuName != null ? menuName : Component.literal(this.createState.name)));
|
||||||
+ return InteractionResult.SUCCESS;
|
+ return InteractionResult.SUCCESS;
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
@@ -2195,15 +2206,18 @@ index 0000000000000000000000000000000000000000..f05e769328ee4b32c0cb21da65f8502d
|
|||||||
+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fake_player.leaves.json").toFile();
|
+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fake_player.leaves.json").toFile();
|
||||||
+ if (!file.isFile()) {
|
+ if (!file.isFile()) {
|
||||||
+ try {
|
+ try {
|
||||||
+ file.createNewFile();
|
+ if (!file.createNewFile()) {
|
||||||
|
+ throw new IOException("Failed to create fakeplayer file: " + file);
|
||||||
|
+ }
|
||||||
+ } catch (IOException e) {
|
+ } catch (IOException e) {
|
||||||
+ e.printStackTrace();
|
+ LeavesLogger.LOGGER.severe("Failed to save fakeplayer", e);
|
||||||
|
+ return;
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ try (BufferedWriter bfw = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
|
+ try (BufferedWriter bfw = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
|
||||||
+ bfw.write(new Gson().toJson(fakePlayerList));
|
+ bfw.write(new Gson().toJson(fakePlayerList));
|
||||||
+ } catch (IOException e) {
|
+ } catch (IOException e) {
|
||||||
+ e.printStackTrace();
|
+ LeavesLogger.LOGGER.severe("Failed to save fakeplayer", e);
|
||||||
+ }
|
+ }
|
||||||
+ } else {
|
+ } else {
|
||||||
+ removeAllBot(BotRemoveEvent.RemoveReason.INTERNAL);
|
+ removeAllBot(BotRemoveEvent.RemoveReason.INTERNAL);
|
||||||
@@ -2220,22 +2234,23 @@ index 0000000000000000000000000000000000000000..f05e769328ee4b32c0cb21da65f8502d
|
|||||||
+ try (BufferedReader bfr = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
|
+ try (BufferedReader bfr = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
|
||||||
+ fakePlayerList = new Gson().fromJson(bfr, JsonObject.class);
|
+ fakePlayerList = new Gson().fromJson(bfr, JsonObject.class);
|
||||||
+ } catch (IOException e) {
|
+ } catch (IOException e) {
|
||||||
+ e.printStackTrace();
|
+ LeavesLogger.LOGGER.severe("Failed to load fakeplayer", e);
|
||||||
+ }
|
+ }
|
||||||
+ for (Map.Entry<String, JsonElement> entry : fakePlayerList.entrySet()) {
|
+ for (Map.Entry<String, JsonElement> entry : fakePlayerList.entrySet()) {
|
||||||
+ BotUtil.loadBot(entry);
|
+ BotUtil.loadBot(entry);
|
||||||
+ }
|
+ }
|
||||||
+ file.delete();
|
+ if (!file.delete()) {
|
||||||
|
+ LeavesLogger.LOGGER.warning("Failed to delete " + file);
|
||||||
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public static boolean removeAllBot(BotRemoveEvent.RemoveReason reason) {
|
+ public static void removeAllBot(BotRemoveEvent.RemoveReason reason) {
|
||||||
+ Iterator<ServerBot> iterator = bots.iterator();
|
+ Iterator<ServerBot> iterator = bots.iterator();
|
||||||
+ while (iterator.hasNext()) {
|
+ while (iterator.hasNext()) {
|
||||||
+ ServerBot bot = iterator.next();
|
+ ServerBot bot = iterator.next();
|
||||||
+ bot.onRemove(reason);
|
+ bot.onRemove(reason);
|
||||||
+ }
|
+ }
|
||||||
+ return true;
|
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public static List<ServerBot> getBots() {
|
+ public static List<ServerBot> getBots() {
|
||||||
|
|||||||
Reference in New Issue
Block a user