destroyCommands;
-
/**
* 退出时是否丢弃背包物品
*/
@@ -140,8 +155,14 @@ public class FakeplayerConfig extends PluginConfig {
this.detectIp = file.getBoolean("detect-ip", false);
this.kaleTps = file.getInt("kale-tps", 0);
this.selfCommands = file.getStringList("self-commands");
- this.preparingCommands = file.getStringList("preparing-commands");
- this.destroyCommands = file.getStringList("destroy-commands");
+ this.preSpawnCommands = file.getStringList("pre-spawn-commands");
+ this.postSpawnCommands = file.getStringList("post-spawn-commands");
+ deprecated:
+ this.afterSpawnCommands = file.getStringList("after-spawn-commands");
+ this.postQuitCommands = file.getStringList("post-quit-commands");
+ this.afterQuitCommands = file.getStringList("after-quit-commands");
+// this.preparingCommands = file.getStringList("preparing-commands");
+// this.destroyCommands = file.getStringList("destroy-commands");
this.nameTemplate = file.getString("name-template", "");
this.dropInventoryOnQuiting = file.getBoolean("drop-inventory-on-quiting", true);
this.persistData = file.getBoolean("persist-data", true);
@@ -169,6 +190,19 @@ public class FakeplayerConfig extends PluginConfig {
if (!this.allowCommands.isEmpty()) {
log.warning("allow-commands is deprecated which will be removed at 0.4.0, you should use Permissions Plugin to assign permission groups to fake players.");
}
+
+ var preparingCommands = file.getStringList("preparing-commands");
+ if (!preparingCommands.isEmpty()) {
+ log.warning("preparing-commands is deprecated, use post-spawn-commands instead.");
+ this.postSpawnCommands.addAll(preparingCommands);
+ }
+
+ var destroyCommands = file.getStringList("destroy-commands");
+ if (!destroyCommands.isEmpty()) {
+ log.warning("destroy-commands is deprecated, use post-quit-commands instead.");
+ this.postQuitCommands.addAll(destroyCommands);
+ }
+
}
private @Nullable Duration getLifespan(@NotNull FileConfiguration file) {
diff --git a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/entity/FakePlayer.java b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/entity/FakePlayer.java
index 2f7a001..1bdb7c7 100644
--- a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/entity/FakePlayer.java
+++ b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/entity/FakePlayer.java
@@ -4,7 +4,6 @@ import io.github.hello09x.devtools.core.message.RuntimeMessageException;
import io.github.hello09x.devtools.core.utils.EntityUtils;
import io.github.hello09x.devtools.core.utils.SchedulerUtils;
import io.github.hello09x.devtools.core.utils.WorldUtils;
-import io.github.hello09x.fakeplayer.api.event.FakePlayerSpawnEvent;
import io.github.hello09x.fakeplayer.api.spi.Action;
import io.github.hello09x.fakeplayer.api.spi.NMSBridge;
import io.github.hello09x.fakeplayer.api.spi.NMSNetwork;
@@ -141,17 +140,6 @@ public class FakePlayer {
}
}
- {
- var event = this.callSpawnEvent();
- if (event.isCancelled() && config.getPreventKicking().ordinal() < PreventKicking.ON_SPAWNING.ordinal()) {
- throw new RuntimeMessageException(translatable(
- "fakeplayer.command.spawn.error.disallowed", RED,
- text(player.getName(), WHITE),
- event.getReason()
- ));
- }
- }
-
if (config.isDropInventoryOnQuiting()) {
// 跨服背包同步插件可能导致假人既丢弃了一份到地上,在重新生成的时候又回来了
// 因此在生成的时候清空一次背包
@@ -242,12 +230,6 @@ public class FakePlayer {
return event;
}
- private @NotNull FakePlayerSpawnEvent callSpawnEvent() {
- var event = new FakePlayerSpawnEvent(this.creator, this.player);
- Bukkit.getPluginManager().callEvent(event);
- return event;
- }
-
/**
* 判断是否是创建者
* 如果玩家下线再重新登陆, entityID 将会不一样导致 {@link Object#equals(Object)} 返回 {@code false}
diff --git a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerLifecycleListener.java b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerLifecycleListener.java
new file mode 100644
index 0000000..835bcd7
--- /dev/null
+++ b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerLifecycleListener.java
@@ -0,0 +1,85 @@
+package io.github.hello09x.fakeplayer.core.listener;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import io.github.hello09x.fakeplayer.core.Main;
+import io.github.hello09x.fakeplayer.core.config.FakeplayerConfig;
+import io.github.hello09x.fakeplayer.core.manager.FakeplayerManager;
+import org.bukkit.Bukkit;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author tanyaofei
+ * @since 2024/8/7
+ **/
+@Singleton
+public class FakeplayerLifecycleListener implements Listener {
+
+ private final FakeplayerManager manager;
+ private final FakeplayerConfig config;
+
+ @Inject
+ public FakeplayerLifecycleListener(FakeplayerManager manager, FakeplayerConfig config) {
+ this.manager = manager;
+ this.config = config;
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onPostSpawn(@NotNull PlayerJoinEvent event) {
+ var player = event.getPlayer();
+ if (this.manager.isNotFake(player)) {
+ // Not a fake player
+ return;
+ }
+
+ manager.dispatchCommands(player, config.getPostSpawnCommands());
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
+ public void onAfterSpawn(@NotNull PlayerJoinEvent event) {
+ var player = event.getPlayer();
+ if (this.manager.isNotFake(player)) {
+ // Not a fake player
+ return;
+ }
+
+ Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> {
+ if (player.isOnline()) {
+ manager.dispatchCommands(player, config.getAfterSpawnCommands());
+ manager.issueCommands(player, config.getSelfCommands());
+ }
+ }, 20);
+ }
+
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onPostQuit(@NotNull PlayerQuitEvent event) {
+ var player = event.getPlayer();
+ if (this.manager.isNotFake(player)) {
+ // Not a fake player
+ return;
+ }
+
+ manager.dispatchCommands(player, config.getPostQuitCommands());
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
+ public void onAfterQuit(@NotNull PlayerQuitEvent event) {
+ var player = event.getPlayer();
+ if (this.manager.isNotFake(player)) {
+ // Not a fake player
+ return;
+ }
+
+ Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> {
+ manager.dispatchCommands(player, config.getAfterQuitCommands());
+ }, 20);
+ }
+
+
+}
diff --git a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerListener.java b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerListener.java
index 252be96..dddceb8 100644
--- a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerListener.java
+++ b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerListener.java
@@ -1,6 +1,5 @@
package io.github.hello09x.fakeplayer.core.listener;
-import com.google.common.base.Throwables;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.github.hello09x.devtools.core.utils.ComponentUtils;
@@ -60,7 +59,7 @@ public class FakeplayerListener implements Listener {
* 拒绝真实玩家使用假人用过的 ID 登陆
*/
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onLogin(@NotNull PlayerLoginEvent event) {
+ public void rejectForUsedUUID(@NotNull PlayerLoginEvent event) {
var player = event.getPlayer();
if (InternalAddressGenerator.canBeGenerated(event.getAddress())) {
@@ -119,7 +118,7 @@ public class FakeplayerListener implements Listener {
* 死亡退出游戏
*/
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onDead(@NotNull PlayerDeathEvent event) {
+ public void kickOnDead(@NotNull PlayerDeathEvent event) {
var player = event.getPlayer();
if (manager.isNotFake(player)) {
return;
@@ -140,29 +139,24 @@ public class FakeplayerListener implements Listener {
/**
* 退出游戏掉落背包
*/
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onQuit(@NotNull PlayerQuitEvent event) {
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
+ public void cleanup(@NotNull PlayerQuitEvent event) {
var target = event.getPlayer();
if (manager.isNotFake(target)) {
return;
}
try {
- manager.dispatchCommands(target, config.getDestroyCommands());
- // 如果移除玩家后没有假人, 则更新命令列表
- // 这个方法需要在 cleanup 之前执行, 不然无法获取假人的创建者
if (manager.getCreator(target) instanceof Player creator && manager.countByCreator(creator) == 1) {
Bukkit.getScheduler().runTaskLater(Main.getInstance(), creator::updateCommands, 1); // 需要下 1 tick 移除后才正确刷新
}
- } catch (Throwable e) {
- log.warning("执行 destroy-commands 时发生错误: \n" + Throwables.getStackTraceAsString(e));
} finally {
manager.cleanup(target);
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
- public void autoFish(@NotNull PlayerFishEvent event) {
+ public void autoFishing(@NotNull PlayerFishEvent event) {
if (event.getState() != PlayerFishEvent.State.BITE) {
return;
}
diff --git a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/manager/FakeplayerManager.java b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/manager/FakeplayerManager.java
index 58e0dc8..1a5a40a 100644
--- a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/manager/FakeplayerManager.java
+++ b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/manager/FakeplayerManager.java
@@ -109,8 +109,10 @@ public class FakeplayerManager {
sn,
lifespan
);
+
var target = fp.getPlayer(); // 即使出现异常也不需要处理这个玩家, 最终会被 GC 掉
+ this.dispatchCommandsEarly(fp, this.config.getPreSpawnCommands());
return CompletableFuture
.supplyAsync(() -> {
var configs = configManager.getConfigs(creator);
@@ -126,15 +128,8 @@ public class FakeplayerManager {
);
})
.thenComposeAsync(fp::spawnAsync)
- .thenApply(nul -> {
- Bukkit.getScheduler().runTask(Main.getInstance(), () -> {
- this.playerList.add(fp);
- });
-
- Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> {
- this.dispatchCommands(target, config.getPreparingCommands());
- this.issueCommands(target, config.getSelfCommands());
- }, 20);
+ .thenApply(ignored -> {
+ Bukkit.getScheduler().runTask(Main.getInstance(), () -> playerList.add(fp));
return target;
});
}
@@ -451,7 +446,10 @@ public class FakeplayerManager {
return;
}
- for (var cmd : Commands.formatCommands(commands)) {
+ var p = target.getName();
+ var u = target.getUniqueId().toString();
+ var c = Objects.requireNonNull(this.getCreatorName(target));
+ for (var cmd : Commands.formatCommands(commands, "%p", p, "%u", u, "%c", c)) {
if (!target.performCommand(cmd)) {
log.warning(target.getName() + " failed to execute command: " + cmd);
} else {
@@ -460,6 +458,25 @@ public class FakeplayerManager {
}
}
+ public void dispatchCommandsEarly(@NotNull FakePlayer fp, @NotNull List commands) {
+ if (commands.isEmpty()) {
+ return;
+ }
+
+ var server = Bukkit.getServer();
+ var sender = Bukkit.getConsoleSender();
+ var p = fp.getName();
+ var u = fp.getUUID().toString();
+ var c = fp.getCreator().getName();
+ for (var cmd : Commands.formatCommands(commands, "%p", p, "%u", u, "%c", c)) {
+ if (!server.dispatchCommand(sender, cmd)) {
+ log.warning("Failed to execute command for %s: ".formatted(p) + cmd);
+ } else {
+ log.info("Dispatched command: " + cmd);
+ }
+ }
+ }
+
/**
* 以控制台身份对玩家执行命令
*
@@ -471,20 +488,15 @@ public class FakeplayerManager {
return;
}
- if (this.isNotFake(player)) {
- return;
- }
-
var server = Bukkit.getServer();
var sender = Bukkit.getConsoleSender();
- for (var cmd : Commands.formatCommands(
- commands,
- "%p", player.getName(),
- "%u", player.getUniqueId().toString(),
- "%c", Objects.requireNonNull(this.getCreatorName(player)))
- ) {
+
+ var p = player.getName();
+ var u = player.getUniqueId().toString();
+ var c = Objects.requireNonNull(this.getCreatorName(player));
+ for (var cmd : Commands.formatCommands(commands, "%p", p, "%u", u, "%c", c)) {
if (!server.dispatchCommand(sender, cmd)) {
- log.warning("Failed to execute command for %s: ".formatted(player.getName()) + cmd);
+ log.warning("Failed to execute command for %s: ".formatted(p) + cmd);
} else {
log.info("Dispatched command: " + cmd);
}
@@ -494,20 +506,20 @@ public class FakeplayerManager {
/**
* 让玩家打开假人背包
*
- * @param creator 玩家
+ * @param viewer 玩家
* @param target 假人
* @return 是否打开成功
*/
- public boolean openInventory(@NotNull Player creator, @NotNull Player target) {
+ public boolean openInventory(@NotNull Player viewer, @NotNull Player target) {
var fp = this.playerList.getByName(target.getName());
if (fp == null) {
return false;
}
- if (!creator.isOp() && !fp.isCreator(creator)) {
+ if (!viewer.isOp() && !fp.isCreator(viewer)) {
return false;
}
- this.invsee.openInventory(creator, target);
+ this.invsee.openInventory(viewer, target);
var pos = target.getLocation();
pos.getWorld().playSound(pos, Sound.BLOCK_CHEST_OPEN, SoundCategory.BLOCKS, 0.3f, 1.0f);
return true;
diff --git a/fakeplayer-core/src/main/resources/config.yml b/fakeplayer-core/src/main/resources/config.yml
index 52b8f1a..b14511a 100644
--- a/fakeplayer-core/src/main/resources/config.yml
+++ b/fakeplayer-core/src/main/resources/config.yml
@@ -111,51 +111,110 @@ kick-on-dead: true
# It's not recommended to enable this option, as it may cause the redstone machine to malfunction
kale-tps: 0
-# 预准备命令
-# 假人诞生时会以控制台的身份按顺序执行以下命令, 这些命令会比 `self-commands` 更早执行
-# 你可以用这个来实现权限组的分配之类的命令
-# 占位符:
-# %p: 假人名称
-# %u: 假人 uuid
-# %c: 创建者的名称
-# Server will execute the following commands after the fake player was spawned
-# You can add some commands to give them permission, such as '/lp user %p permission set xxx true'
-# placeholder:
-# %p: the name of the fake player
-# %u: the uuid of the fake player
-# %c: the name of creator
-preparing-commands:
+
+
+# Pre-Spawn-Commands
+# Server will execute the following commands BEFORE trying to spawn a fake player.
+# This is helpful for adding fake player into whitelist
+# Variables:
+# %p: the name of the fake player
+# %u: the UUID of the fake player
+# %c: the name of the creator
+# 服务器会在假人创建前执行这些命令
+# 这里可以添加类似于白名单的命令来保证后续创建过程的正常执行
+# 变量:
+# %p: 假人的名称
+# %u: 假人的 UUID
+# %c: 创建人的名称
+pre-spawn-commands:
- ''
- ''
-# 假人销毁时执行的命令
-# 与 `preparing-commands` 类似, 会在假人销毁时依次执行的命令
-# 也许可以用来销毁第三方插件的档案?
-# 占位符:
-# %p: 假人名称
-# %u: 假人 uuid
-# %c: 创建者的名称
-# Server will execute the following commands before the fake player was quited(PlayerQuitEvent)
-# you can add some commands to clean up data
-# placeholder:
-# %p: the name of the fake player
-# %u: the uuid of the fake player
-# %c: the name of creator
-destroy-commands:
+# Post-Spawn-Commands
+# Server will execute the following commands DURING spawning (in PlayerJoinEvent).
+# Variables:
+# %p: the name of the fake player
+# %u: the UUID of the fake player
+# %c: the name of the creator
+# 服务器会在假人正在加入游戏中执行这些命令
+# 变量:
+# %p: 假人的名称
+# %u: 假人的 UUID
+# %c: 创建人的名称
+post-spawn-commands:
- ''
- ''
-# 自执行命令
-# 假人在诞生时会以自己的身份按顺序执行命令
-# 你可以在这里做添加 /register 和 /login 命令来防止 `AuthMe` 等插件踢掉超时未登陆的玩家
-# The fake player will execute the following commands
-# You can add some command to make him to login
-# - '/register ANY_PASSWORD'
-# - '/login ANY_PASSWORD'
+# After-Spawn-Commands
+# Server will execute the following commands AFTER the fake player was spawned (after PlayerJoinEvent).
+# This is helpful for giving permission.
+# Variables:
+# %p: the name of the fake player
+# %u: the UUID of the fake player
+# %c: the name of the creator
+# 服务器会在假人创建后执行这里的命令
+# 这里可以添加一些权限组分配之类的命令
+# 变量:
+# %p: 假人的名称
+# %u: 假人的 UUID
+# %c: 创建人的名称
+after-spawn-commands:
+ - ''
+ - ''
+
+# Self-Commands
+# The fake player will execute the following commands AFTER they were joined the server.
+# This is helpful for them to execute some login command.
+# Ensure the password is complex enough otherwise the login plugin might reject it.
+# Variables:
+# %p: the name of the fake player
+# %u: the UUID of the fake player
+# %c: the name of the creator
+# 假人在加入游戏后会执行以下命令
+# 你可以添加一些登陆命令来让他们完成登陆过程
+# 变量:
+# %p: 假人的名称
+# %u: 假人的 UUID
+# %c: 创建人的名称
self-commands:
- ''
- ''
+# Post-Destroy-Commands
+# Server will execute the following commands when the fake player is quiting but not quited (in PlayerQuitEvent)
+# This is helpful for cleaning up their inventory if you want it
+# Variables:
+# %p: the name of the fake player
+# %u: the UUID of the fake player
+# %c: the name of the creator
+# 服务器会在假人正在退出时执行这些命令, 命令执行的那一刻假人还位于服务器
+# 你可以添加一些用来清空假人背包之类的命令
+# 变量:
+# %p: 假人的名称
+# %u: 假人的 UUID
+# %c: 创建人的名称
+post-quit-commands:
+ - ''
+ - ''
+
+# Post-Destroy-Commands
+# Server will execute the following commands AFTER the fake player was quited. (after PlayerQuitEvent)
+# Variables:
+# %p: the name of the fake player
+# %u: the UUID of the fake player
+# %c: the name of the creator
+# 销毁后命令
+# 服务器会在假人退出游戏之后执行这些命令
+# 你可以添加一些清理白名单、取消权限分配、清理某些插件数据等命令
+# 变量:
+# %p: 假人的名称
+# %u: 假人的 UUID
+# %c: 创建人的名称
+after-quit-commands:
+ - ''
+ - ''
+
+
# 允许玩家让假人执行的命令
# 在这里你可以放一些你服务器的命令,玩家就可以执行
# 例如添加 /sit 之后, 玩家可以通过 '/fp cmd myfakeplayer sit' 让假人坐下来
diff --git a/fakeplayer-v1_20_R1/pom.xml b/fakeplayer-v1_20_R1/pom.xml
index d4821d6..5724c44 100644
--- a/fakeplayer-v1_20_R1/pom.xml
+++ b/fakeplayer-v1_20_R1/pom.xml
@@ -12,8 +12,8 @@
fakeplayer-v1_20_R1
- 21
- 21
+ 17
+ 17
UTF-8
diff --git a/fakeplayer-v1_20_R2/pom.xml b/fakeplayer-v1_20_R2/pom.xml
index be3d6a0..b0628e1 100644
--- a/fakeplayer-v1_20_R2/pom.xml
+++ b/fakeplayer-v1_20_R2/pom.xml
@@ -12,8 +12,8 @@
fakeplayer-v1_20_R2
- 21
- 21
+ 17
+ 17
UTF-8
diff --git a/fakeplayer-v1_20_R3_R4/pom.xml b/fakeplayer-v1_20_R3_R4/pom.xml
index 3769355..61e7585 100644
--- a/fakeplayer-v1_20_R3_R4/pom.xml
+++ b/fakeplayer-v1_20_R3_R4/pom.xml
@@ -12,8 +12,8 @@
fakeplayer-v1_20_R3_R4
- 21
- 21
+ 17
+ 17
UTF-8
diff --git a/fakeplayer-v1_20_R5_R6/pom.xml b/fakeplayer-v1_20_R5_R6/pom.xml
index 4caa619..f361e3c 100644
--- a/fakeplayer-v1_20_R5_R6/pom.xml
+++ b/fakeplayer-v1_20_R5_R6/pom.xml
@@ -12,8 +12,8 @@
fakeplayer-v1_20_R5_R6
- 21
- 21
+ 17
+ 17
UTF-8