1. 更改命令的实现方式,更符合 minecraft 的风格

2. 添加 `self-commands` 配置
3. 配置项新增是否拾取物品
This commit is contained in:
tanyaofei 2023-07-26 18:36:24 +08:00
parent 5ddaec7eda
commit 61c0381326
44 changed files with 756 additions and 1501 deletions

View File

@ -13,6 +13,8 @@
## 命令
+ /fp ? - 查看帮助手册
+ /fp reload - 重载配置文件
+ /fp create - 创建一个假人
+ /fp kill - 移除假人
+ /fp tp - 传送到假人身边
@ -24,6 +26,8 @@
+ /fp config - 玩家个性化配置
+ /fp config set - 设置个性化配置
+ /fp config get - 查看个性化配置
+ /fp drop - 丢弃手上物品
+ /fp dropinv - 丢弃背包物品
+ /fp attack - 让假人点击鼠标左键 **(实验性)**
+ /fp use - 让假人点击鼠标右键 **(实验性)**
@ -61,7 +65,7 @@
```yml
# 配置文件版本
# 不要修改这个值
version: 7
version: 8
# 服务器最多存在多少个假人
# 默认: 1000
@ -77,6 +81,7 @@ player-limit: 1
# 注意:
# 1. 如果包含除 英文字母、数字、下划线以外的字符,原生自带命令将不被支持
# 2. 长度超过 16 位将会被截取
# 3. 不能以 - 开头
name-template: ''
# 跟随下线
@ -84,10 +89,15 @@ name-template: ''
# 如果玩家只是切换服务器, 那么不会触发跟随下线
follow-quiting: true
# 是否开启 bungeeCord 跟随下线
# 如果开启则玩家在切换服务器时不会因为在当前服务器下线而导致跟随下线
# 此配置仅在 `follow-quiting``true` 时生效
bungee: true
# 是否检测 IP
# 如果启用, 则一个 IP 只能创建 `maximum` 个假人
# 能够避免玩家开小号疯狂创建假人
detect-ip: false
detect-ip: true
# 服务器最近 5 分钟平均 TPS 低于这个值清除所有假人
# 每 60 秒检测一次
@ -95,9 +105,10 @@ detect-ip: false
kale-tps: 0
# 是否模拟登陆
# 真实的玩家登陆流程是 "预登陆" -> "登陆" -> "加入游戏", 而假人插件默认情况下跳过了这三个步骤直接生成实体, 如果设置为 true 将会模拟这三个步骤
# 因此有一些需要在 "登陆" 时生成玩家档案的插件发生异常比如 LuckPerms, 如果服务器没有出现严重的错误不需要理会这些异常, 只是这些插件无法对假人进行操作而已
# 开启也不一定能解决所有问题, 甚至可能衍生出更加奇怪的报错并且会让第三方插件生成大量的假人数据, 或者由于和一些 "新加入玩家限制行动之类的插件" 起冲突而导致假人飞走
# 真实的玩家登陆流程是 "预登陆" -> "登陆" -> "加入游戏", 而假人插件默认情况下跳过了前两个步骤直接加入游戏
# 有一些需要在 "登陆" 时生成玩家档案的插件发生异常比如 LuckPerms
# 如果服务器没有出现严重的错误不需要理会这些异常, 只是这些插件无法对假人进行操作而已
# 开启也不一定能解决所有问题, 也可能因为一些 "限制新加入玩家" 的插件而导致假人出现问题, 并且会创建更多的第三方插件数据
simulate-login: false
# 预准备命令
@ -109,7 +120,7 @@ simulate-login: false
# %u: 假人 uuid
# %c: 创建者的名称
preparing-commands:
- '/give %p poppy'
- ''
- ''
# 假人销毁时执行的命令
@ -123,5 +134,12 @@ destroy-commands:
- ''
- ''
# 自执行命令
# 假人在诞生时会以自己的身份按顺序执行命令
# 你可以在这里做一些 /register 之类的命令
self-commands:
- ''
- ''
```

23
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>io.github.hello09x</groupId>
<artifactId>fakeplayer</artifactId>
<version>1_20_R1-0.1.1</version>
<version>1_20_R1-0.1.2</version>
<packaging>jar</packaging>
<name>fakeplayer</name>
@ -31,7 +31,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
@ -43,6 +43,14 @@
</configuration>
</execution>
</executions>
<configuration>
<relocations>
<relocation>
<pattern>dev.jorel.commandapi</pattern>
<shadedPattern>io.github.hello09x.shaded.commandapi</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
@ -58,7 +66,8 @@
<configuration>
<srgIn>org.spigotmc:minecraft-server:${craftbukkit.version}:txt:maps-mojang</srgIn>
<reverse>true</reverse>
<remappedDependencies>org.spigotmc:spigot:${craftbukkit.version}:jar:remapped-mojang
<remappedDependencies>
org.spigotmc:spigot:${craftbukkit.version}:jar:remapped-mojang
</remappedDependencies>
<remappedArtifactAttached>true</remappedArtifactAttached>
<remappedClassifierName>remapped-obf</remappedClassifierName>
@ -138,6 +147,13 @@
<scope>provided</scope>
</dependency>
<!-- https://github.com/JorelAli/CommandAPI -->
<dependency>
<groupId>dev.jorel</groupId>
<artifactId>commandapi-bukkit-shade</artifactId>
<version>9.0.3</version>
</dependency>
<!-- 远程仓库中没有, 需要使用 spigot BuildTool.jar 在本地仓库中安装 -->
<!-- https://www.spigotmc.org/wiki/buildtools/ -->
<!-- https://blog.jeff-media.com/nms-use-mojang-mappings-for-your-spigot-plugins/ -->
@ -150,4 +166,5 @@
</dependency>
</dependencies>
</project>

View File

@ -1,6 +1,8 @@
package io.github.hello09x.fakeplayer;
import io.github.hello09x.fakeplayer.command.RootCommand;
import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.CommandAPIBukkitConfig;
import io.github.hello09x.fakeplayer.command.Commands;
import io.github.hello09x.fakeplayer.listener.PlayerListeners;
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
import io.github.hello09x.fakeplayer.optional.BungeeCordServer;
@ -13,14 +15,18 @@ public final class Main extends JavaPlugin {
@Getter
private static Main instance;
@Override
public void onLoad() {
CommandAPI.onLoad(new CommandAPIBukkitConfig(this).silentLogs(true));
}
@Override
public void onEnable() {
// Plugin startup logic
instance = this;
{
getServer().getPluginCommand("fakeplayer").setExecutor(RootCommand.instance);
}
instance = this;
Commands.register();
CommandAPI.onEnable();
{
getServer().getMessenger().registerIncomingPluginChannel(Main.getInstance(), "BungeeCord", BungeeCordServer.instance);
@ -39,6 +45,8 @@ public final class Main extends JavaPlugin {
getServer().getMessenger().unregisterIncomingPluginChannel(this);
getServer().getMessenger().unregisterOutgoingPluginChannel(this);
}
CommandAPI.onDisable();
}
private void registerListeners() {

View File

@ -0,0 +1,108 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.arguments.Argument;
import dev.jorel.commandapi.arguments.ArgumentSuggestions;
import dev.jorel.commandapi.arguments.CustomArgument;
import dev.jorel.commandapi.arguments.StringArgument;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public abstract class AbstractCommand {
protected final FakeplayerManager fakeplayerManager = FakeplayerManager.instance;
public static Argument<Player> targetArgument(@NotNull String nodeName) {
return new CustomArgument<>(new StringArgument(nodeName), info -> {
var sender = info.sender();
var name = info.currentInput();
var target = sender.isOp()
? FakeplayerManager.instance.get(name)
: FakeplayerManager.instance.get(sender, name);
if (target == null) {
var targets = FakeplayerManager.instance.getAll(sender);
if (targets.size() != 1) {
throw CustomArgument.CustomArgumentException.fromString("你需要指定假人");
}
target = targets.get(0);
}
return target;
}).replaceSuggestions(ArgumentSuggestions.strings(info -> {
var sender = info.sender();
var arg = info.currentArg();
var fakes = sender.isOp()
? FakeplayerManager.instance.getAll()
: FakeplayerManager.instance.getAll(sender);
var names = fakes.stream().map(Player::getName);
if (!arg.isEmpty()) {
names = names.filter(n -> n.toLowerCase().contains(arg));
}
return names.toArray(String[]::new);
}));
}
public static Argument<List<Player>> multiTargetArgument(@NotNull String nodeName) {
return new CustomArgument<List<Player>, String>(new StringArgument(nodeName), info -> {
var sender = info.sender();
var name = info.currentInput();
if (name.equals("--all")) {
return sender.isOp()
? FakeplayerManager.instance.getAll()
: FakeplayerManager.instance.getAll(sender);
}
var one = sender.isOp()
? FakeplayerManager.instance.get(name)
: FakeplayerManager.instance.get(sender, name);
if (one == null) {
return Collections.emptyList();
}
return Collections.singletonList(one);
}).replaceSuggestions(ArgumentSuggestions.strings(info -> {
var sender = info.sender();
var arg = info.currentArg().toLowerCase();
var fakes = sender.isOp()
? FakeplayerManager.instance.getAll()
: FakeplayerManager.instance.getAll(sender);
var names = fakes.stream().map(Player::getName);
if (!arg.isEmpty()) {
names = names.filter(n -> n.toLowerCase().contains(arg));
}
names = Stream.concat(Stream.of("--all"), names);
return names.toArray(String[]::new);
}));
}
protected @NotNull Player getTarget(@NotNull CommandSender sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
return Optional
.ofNullable((Player) args.getUnchecked("target"))
.or(() -> {
var all = FakeplayerManager.instance.getAll(sender);
if (all.size() != 1) {
return Optional.empty();
}
return Optional.of(all.get(0));
})
.orElseThrow(() -> CommandAPI.failWithString("你需要指定假人"));
}
}

View File

@ -0,0 +1,41 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;
import io.github.hello09x.fakeplayer.entity.action.Action;
import io.github.hello09x.fakeplayer.entity.action.ActionSetting;
import io.github.hello09x.fakeplayer.entity.action.PlayerActionManager;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
public class ActionCommand extends AbstractCommand {
public final static ActionCommand instance = new ActionCommand();
private final PlayerActionManager actionManager = PlayerActionManager.instance;
public void action(@NotNull CommandSender sender, @NotNull CommandArguments args, @NotNull Action action, @NotNull ActionSetting setting) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
actionManager.setAction(target, action, setting);
}
public void sneak(@NotNull CommandSender sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
var sneaking = !target.isSneaking();
target.setSneaking(sneaking);
sender.sendMessage(textOfChildren(
text(target.getName(), WHITE),
text("现在", GRAY),
text(sneaking ? "潜行中" : "取消了潜行", GRAY)
));
}
}

View File

@ -0,0 +1,168 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.IntegerArgument;
import dev.jorel.commandapi.arguments.MultiLiteralArgument;
import dev.jorel.commandapi.executors.CommandExecutor;
import io.github.hello09x.fakeplayer.entity.action.Action;
import io.github.hello09x.fakeplayer.entity.action.ActionSetting;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static io.github.hello09x.fakeplayer.command.AbstractCommand.multiTargetArgument;
import static io.github.hello09x.fakeplayer.command.AbstractCommand.targetArgument;
public class Commands {
private final static String PERMISSION_SPAWN = "fakeplayer.spawn";
private final static String PERMISSION_PROFILE = "fakeplayer.profile";
private final static String PERMISSION_TP = "fakeplayer.tp";
private final static String PERMISSION_EXP = "fakeplayer.exp";
private final static String PERMISSION_ACTION = "fakeplayer.action";
private final static String PERMISSION_EXPERIMENTAL_ACTION = "fakeplayer.experimental.action";
private final static String PERMISSION_ADMIN = "fakeplayer.admin";
public static void register() {
new CommandAPICommand("fakeplayer")
.withAliases("fp")
.withHelp(
"假人相关命令",
"fakeplayer 可以用来创建一个模拟为玩家的假人, 能保持附近区块的刷新、触发怪物生成。同时还提供了一些操作命令让你控制假人的物品、动作等等。"
)
.withUsage(
"§6/fp create §7- §f创建假人",
"§6/fp kill [假人] §7- §f移除假人",
"§6/fp list [页码] [数量] §7- §f查看所有假人",
"§6/fp tp [假人] §7- §f传送到假人身边",
"§6/fp tphere [假人] §7- §f将假人传送到身边",
"§6/fp tps [假人] §7- §f与假人交换位置",
"§6/fp config get <配置项> §7- §f查看配置项",
"§6/fp config set <配置项> <配置值> §7- §f设置配置项",
"§6/fp health [假人] §7- §f查看生命值",
"§6/fp exp [假人] §7- §f查看经验值",
"§6/fp expme [假人] §7- §f转移经验值",
"§6/fp drop [假人] [-a|--all] §7- §f丢弃手上物品",
"§6/fp dropinv [假人] §7- §f丢弃背包物品",
"§6/fp sneak [假人] §7- §f开启/取消潜行",
"§6/fp attack <once|continuous|interval|stop> [假人] §7- §f鼠标左键",
"§6/fp use <once|continuous|interval|stop> [假人] §7- §f鼠标右键"
)
.withSubcommands(
new CommandAPICommand("help")
.withAliases("?")
.withOptionalArguments(new IntegerArgument("page", 1))
.executesPlayer(HelpCommand.instance::help),
new CommandAPICommand("create")
.withPermission(PERMISSION_SPAWN)
.executesPlayer(SpawnCommand.instance::create),
new CommandAPICommand("kill")
.withPermission(PERMISSION_SPAWN)
.withOptionalArguments(multiTargetArgument("targets"))
.executes(SpawnCommand.instance::kill),
new CommandAPICommand("list")
.withPermission(PERMISSION_SPAWN)
.withOptionalArguments(new IntegerArgument("page", 1), new IntegerArgument("size", 1))
.executes(SpawnCommand.instance::list),
new CommandAPICommand("exp")
.withPermission(PERMISSION_PROFILE)
.withOptionalArguments(targetArgument("target"))
.executes(ProfileCommand.instance::exp),
new CommandAPICommand("health")
.withPermission(PERMISSION_PROFILE)
.withOptionalArguments(targetArgument("target"))
.executes(ProfileCommand.instance::health),
new CommandAPICommand("tp")
.withPermission(PERMISSION_TP)
.withOptionalArguments(targetArgument("target"))
.executesPlayer(TpCommand.instance::tp),
new CommandAPICommand("tphere")
.withPermission(PERMISSION_TP)
.withOptionalArguments(targetArgument("target"))
.executesPlayer(TpCommand.instance::tphere),
new CommandAPICommand("tps")
.withPermission(PERMISSION_TP)
.withOptionalArguments(targetArgument("target"))
.executesPlayer(TpCommand.instance::tps),
new CommandAPICommand("config")
.withSubcommands(
new CommandAPICommand("get")
.withArguments(ConfigCommand.configArgument("config"))
.executesPlayer(ConfigCommand.instance::getConfig),
new CommandAPICommand("set")
.withArguments(
ConfigCommand.configArgument("config"),
ConfigCommand.configValueArgument("config", "value")
)
.executesPlayer(ConfigCommand.instance::setConfig)
),
new CommandAPICommand("attack")
.withPermission(PERMISSION_EXPERIMENTAL_ACTION)
.withSubcommands(buildActionCommand(Action.ATTACK)),
new CommandAPICommand("use")
.withPermission(PERMISSION_EXPERIMENTAL_ACTION)
.withSubcommands(buildActionCommand(Action.USE)),
new CommandAPICommand("drop")
.withPermission(PERMISSION_ACTION)
.withOptionalArguments(
targetArgument("target"),
new MultiLiteralArgument("all", List.of("-a", "--all"))
)
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(
sender,
args,
args.getOptional("all").isPresent() ? Action.DROP_STACK : Action.DROP_ITEM,
ActionSetting.once())),
new CommandAPICommand("dropinv")
.withPermission(PERMISSION_ACTION)
.withOptionalArguments(targetArgument("target"))
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(sender, args, Action.DROP_INVENTORY, ActionSetting.once())),
new CommandAPICommand("sneak")
.withPermission(PERMISSION_ACTION)
.withOptionalArguments(targetArgument("target"))
.executes(ActionCommand.instance::sneak),
new CommandAPICommand("expme")
.withPermission(PERMISSION_EXP)
.withOptionalArguments(targetArgument("target"))
.executesPlayer(ExpCommand.instance::expme),
new CommandAPICommand("reload")
.withPermission(PERMISSION_ADMIN)
.executes(ReloadCommand.instance::reload)
).register();
}
private static CommandAPICommand[] buildActionCommand(@NotNull Action action) {
return new CommandAPICommand[]{
new CommandAPICommand("once")
.withOptionalArguments(targetArgument("target"))
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(sender, args, action, ActionSetting.once())
),
new CommandAPICommand("continuous")
.withOptionalArguments(targetArgument("target"))
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(sender, args, action, ActionSetting.continuous())
),
new CommandAPICommand("stop")
.withOptionalArguments(targetArgument("target"))
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(sender, args, action, ActionSetting.stop())
),
new CommandAPICommand("interval")
.withOptionalArguments(
new IntegerArgument("interval", 1),
targetArgument("target")
)
.executes((sender, args) -> {
int interval = (int) args.getOptional("interval").orElse(1);
ActionCommand.instance.action(sender, args, action, ActionSetting.interval(interval));
})
};
}
}

View File

@ -0,0 +1,83 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.arguments.Argument;
import dev.jorel.commandapi.arguments.ArgumentSuggestions;
import dev.jorel.commandapi.arguments.CustomArgument;
import dev.jorel.commandapi.arguments.StringArgument;
import dev.jorel.commandapi.executors.CommandArguments;
import io.github.hello09x.fakeplayer.repository.UserConfigRepository;
import io.github.hello09x.fakeplayer.repository.model.Config;
import io.github.hello09x.fakeplayer.repository.model.Configs;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Objects;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.*;
public class ConfigCommand extends AbstractCommand {
public final static ConfigCommand instance = new ConfigCommand();
private final UserConfigRepository repository = UserConfigRepository.instance;
public static Argument<Config<Object>> configArgument(String nodeName) {
return new CustomArgument<>(new StringArgument(nodeName), info -> {
var arg = info.currentInput();
try {
return Configs.valueOf(arg);
} catch (Exception e) {
throw CustomArgument.CustomArgumentException.fromMessageBuilder(new CustomArgument.MessageBuilder("未知的配置项: ").appendArgInput());
}
}).replaceSuggestions(ArgumentSuggestions.strings(Arrays.stream(Configs.values()).map(Config::name).toList()));
}
public static Argument<Object> configValueArgument(String configNodeName, String nodeName) {
return new CustomArgument<>(new StringArgument(nodeName), info -> {
@SuppressWarnings("unchecked")
var config = Objects.requireNonNull((Config<Object>) info.previousArgs().get(configNodeName));
var arg = info.currentInput();
if (!config.options().contains(arg)) {
throw CustomArgument.CustomArgumentException.fromMessageBuilder(new CustomArgument.MessageBuilder("未知的配置值: ").appendArgInput());
}
return config.mapper().apply(arg);
}).replaceSuggestions(ArgumentSuggestions.strings(info -> {
var config = Objects.requireNonNull((Config<?>) info.previousArgs().get(configNodeName));
var arg = info.currentArg().toLowerCase();
var options = config.options().stream();
if (!arg.isEmpty()) {
options = options.filter(o -> o.toLowerCase().contains(arg));
}
return options.toArray(String[]::new);
}));
}
public void getConfig(@NotNull Player sender, @NotNull CommandArguments args) {
Config<Object> config = Objects.requireNonNull(args.getUnchecked("config"));
var value = String.valueOf(repository.selectOrDefault(sender.getUniqueId(), config));
sender.sendMessage(
textOfChildren(
text(config.label(), GOLD),
text(": ", GRAY),
text(value, WHITE)
)
);
}
public void setConfig(@NotNull Player sender, @NotNull CommandArguments args) {
Config<Object> config = Objects.requireNonNull(args.getUnchecked("config"));
Object value = Objects.requireNonNull(args.getUnchecked("value"));
repository.saveOrUpdate(sender.getUniqueId(), config, value);
sender.sendMessage(textOfChildren(
text(config.label(), GOLD),
text("变更为 ", GRAY),
text(value.toString(), WHITE),
text(" , 下次创建假人时生效", GRAY)
));
}
}

View File

@ -0,0 +1,33 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.DARK_GREEN;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class ExpCommand extends AbstractCommand {
public final static ExpCommand instance = new ExpCommand();
public void expme(@NotNull Player sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
var exp = target.getTotalExperience();
target.setTotalExperience(0);
sender.setTotalExperience(sender.getTotalExperience() + exp);
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(" 转移 ", GRAY),
text(exp, DARK_GREEN),
text(" 点经验值给你", GRAY)
));
}
}

View File

@ -0,0 +1,17 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.executors.CommandArguments;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class HelpCommand extends AbstractCommand {
public final static HelpCommand instance = new HelpCommand();
public void help(@NotNull Player sender, @NotNull CommandArguments args) {
var page = (int) args.getOptional("page").orElse(1);
sender.performCommand("help fakeplayer " + page);
}
}

View File

@ -1,15 +1,12 @@
package io.github.hello09x.fakeplayer.command.player.profile;
package io.github.hello09x.fakeplayer.command;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Optional;
import static net.kyori.adventure.text.Component.text;
@ -17,42 +14,27 @@ import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.*;
import static org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH;
public class HealthCommand extends AbstractCommand {
public class ProfileCommand extends AbstractCommand {
public final static ProfileCommand instance = new ProfileCommand();
public static final HealthCommand instance = new HealthCommand(
"获取假人生命值",
"/fp health [假人]",
"fakeplayer.profile"
);
public HealthCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
public static double round(double num, double base) {
if (num % base == 0) {
return num;
}
return Math.floor(num / base) * base;
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
public void exp(@NotNull CommandSender sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
if (target == null) {
return false;
}
var level = target.getLevel();
var total = target.getTotalExperience();
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(" 当前 ", GRAY),
text(level, DARK_GREEN),
text(" 级, 共 ", GRAY),
text(total, DARK_GREEN),
text(" 点经验值", GRAY)
));
}
public void health(@NotNull CommandSender sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
var health = target.getHealth();
double max = Optional.ofNullable(target.getAttribute(GENERIC_MAX_HEALTH))
.map(AttributeInstance::getValue)
@ -73,8 +55,6 @@ public class HealthCommand extends AbstractCommand {
color = DARK_RED;
}
var h = BigDecimal.valueOf(health).setScale(1, RoundingMode.DOWN);
sender.sendMessage(textOfChildren(
text(target.getName()),
text(" 当前生命值: ", GRAY),
@ -82,7 +62,13 @@ public class HealthCommand extends AbstractCommand {
text("/", color),
text(max, color)
));
return true;
}
private static double round(double num, double base) {
if (num % base == 0) {
return num;
}
return Math.floor(num / base) * base;
}
}

View File

@ -0,0 +1,22 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.executors.CommandArguments;
import io.github.hello09x.fakeplayer.properties.FakeplayerProperties;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class ReloadCommand extends AbstractCommand {
public final static ReloadCommand instance = new ReloadCommand();
private final FakeplayerProperties properties = FakeplayerProperties.instance;
public void reload(@NotNull CommandSender sender, @NotNull CommandArguments args) {
properties.reload();
sender.sendMessage(text("重载配置文件完成", GRAY));
}
}

View File

@ -1,62 +0,0 @@
package io.github.hello09x.fakeplayer.command;
import io.github.hello09x.fakeplayer.command.admin.ReloadCommand;
import io.github.hello09x.fakeplayer.command.player.action.*;
import io.github.hello09x.fakeplayer.command.player.config.ConfigCommand;
import io.github.hello09x.fakeplayer.command.player.exp.ExpmeCommand;
import io.github.hello09x.fakeplayer.command.player.profile.ExpCommand;
import io.github.hello09x.fakeplayer.command.player.profile.HealthCommand;
import io.github.hello09x.fakeplayer.command.player.spawn.CreateCommand;
import io.github.hello09x.fakeplayer.command.player.spawn.KillCommand;
import io.github.hello09x.fakeplayer.command.player.spawn.ListCommand;
import io.github.hello09x.fakeplayer.command.player.tp.TpHereCommand;
import io.github.hello09x.fakeplayer.command.player.tp.TpSwapCommand;
import io.github.hello09x.fakeplayer.command.player.tp.TpToCommand;
import io.github.tanyaofei.plugin.toolkit.command.ParentCommand;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class RootCommand extends ParentCommand {
public final static RootCommand instance = new RootCommand(
"假人相关命令",
null
);
static {
// spawn
instance.register("create", CreateCommand.instance);
instance.register("kill", KillCommand.instance);
instance.register("list", ListCommand.instance);
// tp
instance.register("tp", TpToCommand.instance);
instance.register("tphere", TpHereCommand.instance);
instance.register("tps", TpSwapCommand.instance);
// action
instance.register("attack", AttackCommand.instance);
instance.register("use", UseCommand.instance);
instance.register("sneak", SneakCommand.instance);
instance.register("drop", DropCommand.instance);
instance.register("dropinv", DropInvCommand.instance);
// exp
instance.register("expme", ExpmeCommand.instance);
// profile
instance.register("health", HealthCommand.instance);
instance.register("exp", ExpCommand.instance);
// config
instance.register("config", ConfigCommand.instance);
// admin
instance.register("reload", ReloadCommand.instance);
}
protected RootCommand(@NotNull String description, @Nullable String permission) {
super(description, permission);
}
}

View File

@ -0,0 +1,79 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;
import io.github.tanyaofei.plugin.toolkit.database.Page;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
public class SpawnCommand extends AbstractCommand {
public final static SpawnCommand instance = new SpawnCommand();
public void create(@NotNull Player sender, CommandArguments args) {
var fakePlayer = fakeplayerManager.spawn(sender, sender.getLocation());
if (fakePlayer != null) {
sender.sendMessage(textOfChildren(
text("你创建了假人 ", GRAY),
text(fakePlayer.getName())
));
}
}
public void kill(@NotNull CommandSender sender, CommandArguments args) throws WrapperCommandSyntaxException {
@SuppressWarnings("unchecked")
var targets = (List<Player>) Optional.ofNullable(args.get("targets")).orElse(Collections.emptyList());
int count = 0;
for (var target : targets) {
if (fakeplayerManager.remove(target.getName())) {
count++;
}
}
sender.sendMessage(textOfChildren(
text("你移除了 ", GRAY),
text(count, WHITE),
text(" 个假人", GRAY)
));
}
public void list(@NotNull CommandSender sender, @NotNull CommandArguments args) {
var page = (int) args.getOptional("page").orElse(1);
var size = (int) args.getOptional("size").orElse(10);
var fakers = sender.isOp()
? fakeplayerManager.getAll()
: fakeplayerManager.getAll(sender);
var total = fakers.size();
var pages = total == 0 ? 1 : (int) Math.ceil((double) total / size);
var p = new Page<>(
fakers.subList((page - 1) * size, Math.min(total, page * size)),
total,
pages,
page,
size
);
sender.sendMessage(p.toComponent(
"假人",
record -> textOfChildren(
text(record.getName()),
text(" - "),
text(Optional.ofNullable(fakeplayerManager.getCreator(record)).orElse("<不在线>"))
),
String.format("/fp list %d %d", page - 1, size),
String.format("/fp list %d %d", page + 1, size)
));
}
}

View File

@ -0,0 +1,46 @@
package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.jetbrains.annotations.NotNull;
import static org.bukkit.Sound.ENTITY_ENDERMAN_TELEPORT;
public class TpCommand extends AbstractCommand {
public final static TpCommand instance = new TpCommand();
public void tp(@NotNull Player sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
this.teleport(sender, target);
}
public void tphere(@NotNull Player sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
this.teleport(target, sender);
}
public void tps(@NotNull Player sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
var l1 = sender.getLocation();
var l2 = target.getLocation();
target.teleport(l1, PlayerTeleportEvent.TeleportCause.PLUGIN);
l1.getWorld().playSound(l1, Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1.0F, 1.0F);
sender.teleport(l2, PlayerTeleportEvent.TeleportCause.PLUGIN);
l2.getWorld().playSound(l2, Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1.0F, 1.0F);
}
private void teleport(@NotNull Player from, @NotNull Player to) {
from.teleport(to.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
to.getLocation().getWorld().playSound(to.getLocation(), ENTITY_ENDERMAN_TELEPORT, 1.0F, 1.0F);
}
}

View File

@ -1,40 +0,0 @@
package io.github.hello09x.fakeplayer.command.admin;
import io.github.hello09x.fakeplayer.properties.FakeplayerProperties;
import io.github.tanyaofei.plugin.toolkit.command.ExecutableCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class ReloadCommand extends ExecutableCommand {
public final static ReloadCommand instance = new ReloadCommand(
"重载配置文件",
"/fp reload",
"fakeplayer.admin"
);
private final FakeplayerProperties properties = FakeplayerProperties.instance;
public ReloadCommand(@NotNull String description, @NotNull String usage, @Nullable String permission) {
super(description, usage, permission);
}
@Override
protected boolean execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
properties.reload();
sender.sendMessage(text("重载成功", GRAY));
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return null;
}
}

View File

@ -1,105 +0,0 @@
package io.github.hello09x.fakeplayer.command.player;
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
import io.github.tanyaofei.plugin.toolkit.command.ExecutableCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
public abstract class AbstractCommand extends ExecutableCommand {
private final FakeplayerManager manager = FakeplayerManager.instance;
public AbstractCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
public @Nullable Player getTarget(
@NotNull CommandSender sender,
@NotNull String[] args
) {
if (args.length != 0) {
var name = Arrays.stream(args).filter(arg -> !arg.startsWith("-")).findFirst().orElse(null);
if (name != null) {
return sender.isOp()
? manager.get(args[0])
: manager.get(sender, args[0]);
}
}
if (!(sender instanceof Player p)) {
return null;
}
var lookAt = p.getTargetEntity(32);
if (!(lookAt instanceof Player target) || !manager.isFake(target)) {
var creations = manager.getAll(sender);
if (creations.size() == 1) {
return creations.get(0);
}
return null;
}
return p.isOp() || Objects.equals(p.getName(), manager.getCreator(target))
? target
: null;
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (args.length != 1) {
return new ArrayList<>();
}
var fakers = sender.isOp()
? manager.getAll()
: manager.getAll(sender);
if (fakers.isEmpty()) {
return new ArrayList<>();
}
var input = args[0];
var names = fakers.stream().map(Player::getName);
if (!input.isEmpty()) {
names = names.filter(name -> name.toLowerCase().contains(input));
}
return names.collect(Collectors.toList());
}
protected @Nullable String getArgs(@NotNull String[] args, @NotNull String name) {
for (var arg : args) {
if (!arg.startsWith(name + ":")) {
continue;
}
return arg.split(":")[1];
}
return null;
}
protected @NotNull String getArgs(@NotNull String[] args, @NotNull String name, @NotNull String defaultValue) {
return Optional.ofNullable(getArgs(args, name)).orElse(defaultValue);
}
protected boolean hasFlag(@NotNull String[] args, @NotNull String flag) {
return Arrays.stream(args).anyMatch(i -> i.contains(flag));
}
}

View File

@ -1,41 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.action;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import io.github.hello09x.fakeplayer.entity.action.ActionSetting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class AbstractActionCommand extends AbstractCommand {
public AbstractActionCommand(@NotNull String description, @NotNull String usage, @Nullable String permission) {
super(description, usage, permission);
}
public @Nullable ActionSetting getActionSettings(String[] args) {
var n = getArgs(args, "-n", "1");
var i = getArgs(args, "-i", "1");
var keep = hasFlag(args, "--keep") || hasFlag(args, "-k");
int times;
if (keep) {
times = -1;
} else {
try {
times = Integer.parseInt(n);
} catch (NumberFormatException e) {
return null;
}
}
int interval;
try {
interval = Integer.parseInt(i);
} catch (NumberFormatException e) {
return null;
}
return new ActionSetting(
times,
interval
);
}
}

View File

@ -1,120 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.action;
import io.github.hello09x.fakeplayer.entity.action.Action;
import io.github.hello09x.fakeplayer.entity.action.ActionSetting;
import io.github.hello09x.fakeplayer.entity.action.PlayerActionManager;
import io.github.tanyaofei.plugin.toolkit.command.help.Helps;
import net.kyori.adventure.text.Component;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class AttackCommand extends AbstractActionCommand {
public final static Component help = Helps.help(
"控制假人点击鼠标左键",
"攻击面前的实体、破坏面前的方块",
new Helps.Content("用法", "/fp attack [假人] [-n:次数] [-i:间隔] [-k|--keep] [-s|--stop]"),
new Helps.Content("参数", List.of(
"-n:次数 - 指定次数, 只有成功攻击或破坏才计数",
"-i:间隔 - 指定间隔, 单位为 ticks",
"--keep - 永不停止",
"--stop - 立即停止"
)),
new Helps.Content("例子", List.of(
"/fp attack hello09x -n:5 -i:20 - 点击每 20 ticks 触发鼠标左键, 共 5 次",
"/fp attach hello09x -i:20 --keep - 永久(真的很久)地每 20 ticks 触发鼠标左键",
"/fp attack hello09x --stop - 取消触发鼠标左键"
))
);
public final static AttackCommand instance = new AttackCommand(
"控制假人点击鼠标左键",
"/fp attack [假人] [-n:次数] [-i:间隔] [-k|--keep] [-s|--stop]",
"fakeplayer.experimental.action"
);
private final PlayerActionManager manager = PlayerActionManager.instance;
public AttackCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
public @NotNull Component getHelp(int page) {
return help;
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
var target = getTarget(sender, args);
if (target == null) {
return false;
}
if (hasFlag(args, "--stop") || hasFlag(args, "-s")) {
manager.setAction(target, Action.ATTACK, ActionSetting.stop());
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(" 已停止左键", GRAY)
));
return true;
}
var setting = getActionSettings(args);
if (setting == null) {
return false;
}
manager.setAction(target, Action.ATTACK, setting);
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(" 开始左键")
));
return true;
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (args.length < 2) {
return super.onTabComplete(sender, command, label, args);
}
var suggestion = Stream.of(
"-n:",
"-i:",
"--keep",
"--stop"
);
if (!args[args.length - 1].isEmpty()) {
suggestion = suggestion.filter(s -> s.startsWith(args[args.length - 1]));
}
return suggestion.collect(Collectors.toList());
}
}

View File

@ -1,57 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.action;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import io.github.hello09x.fakeplayer.entity.action.Action;
import io.github.hello09x.fakeplayer.entity.action.ActionSetting;
import io.github.hello09x.fakeplayer.entity.action.PlayerActionManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
public class DropCommand extends AbstractCommand {
private final static PlayerActionManager manager = PlayerActionManager.instance;
public final static DropCommand instance = new DropCommand(
"丢弃手上的物品",
"/fp drop [假人] [-a|--all]",
"fakeplayer.action"
);
public DropCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
var target = getTarget(sender, args);
if (target == null) {
return false;
}
var dropAll = hasFlag(args, "-a") || hasFlag(args, "--all");
manager.setAction(target, dropAll ? Action.DROP_STACK : Action.DROP_ITEM, ActionSetting.once());
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length == 2) {
return Collections.singletonList("--all");
}
return super.onTabComplete(sender, command, label, args);
}
}

View File

@ -1,46 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.action;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import io.github.hello09x.fakeplayer.entity.action.Action;
import io.github.hello09x.fakeplayer.entity.action.ActionSetting;
import io.github.hello09x.fakeplayer.entity.action.PlayerActionManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class DropInvCommand extends AbstractCommand {
private final static PlayerActionManager manager = PlayerActionManager.instance;
public final static DropInvCommand instance = new DropInvCommand(
"丢弃背包的物品",
"/fp dropinv [假人]",
"fakeplayer.action"
);
public DropInvCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
var target = getTarget(sender, args);
if (target == null) {
return false;
}
manager.setAction(target, Action.DROP_INVENTORY, ActionSetting.once());
return true;
}
}

View File

@ -1,48 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.action;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
public class SneakCommand extends AbstractCommand {
public final static SneakCommand instance = new SneakCommand(
"让假人潜行或取消",
"/fp sneak [假人]",
"fakeplayer.action"
);
public SneakCommand(@NotNull String description, @NotNull String usage, @Nullable String permission) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
var target = getTarget(sender, args);
if (target == null) {
return false;
}
var sneaking = !target.isSneaking();
target.setSneaking(sneaking);
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(sneaking ? "潜行" : "取消潜行", WHITE)
));
return true;
}
}

View File

@ -1,116 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.action;
import io.github.hello09x.fakeplayer.entity.action.Action;
import io.github.hello09x.fakeplayer.entity.action.ActionSetting;
import io.github.hello09x.fakeplayer.entity.action.PlayerActionManager;
import io.github.tanyaofei.plugin.toolkit.command.help.Helps;
import net.kyori.adventure.text.Component;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
public class UseCommand extends AbstractActionCommand {
public final static Component help = Helps.help(
"控制假人点击鼠标右键",
"与面前的实体/方块交互、使用手上的消耗品、防止手上的方块",
new Helps.Content("用法", "/fp use [假人] [-n:次数] [-i:间隔] [-k|--keep] [-s|--stop]"),
new Helps.Content("参数", List.of(
"-n:次数 - 指定次数, 只有成功攻击或破坏才计数",
"-i:间隔 - 指定间隔, 单位为 ticks",
"--keep - 永不停止",
"--stop - 立即停止"
)),
new Helps.Content("例子", List.of(
"/fp use hello09x -n:5 -i:20 - 点击每 20 ticks 触发鼠标右键, 共 5 次",
"/fp use hello09x -i:20 --keep - 永久(真的很久)地每 20 ticks 触发触发鼠标右键",
"/fp use hello09x --stop - 取消触发鼠标右键"
))
);
public final static UseCommand instance = new UseCommand(
"控制假人点击鼠标右键",
"/fp use [假人] [-n:次数] [-i:间隔] [--keep] [--stop]",
"fakeplayer.experimental.action"
);
private final PlayerActionManager manager = PlayerActionManager.instance;
public UseCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
public @NotNull Component getHelp(int page) {
return help;
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
var target = getTarget(sender, args);
if (target == null) {
return false;
}
if (hasFlag(args, "--stop") || hasFlag(args, "-s")) {
manager.setAction(target, Action.USE, ActionSetting.stop());
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(" 已停止右键", GRAY)
));
return true;
}
var settings = getActionSettings(args);
if (settings == null) {
return false;
}
manager.setAction(target, Action.USE, settings);
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(" 开始右键", WHITE)
));
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 2) {
return super.onTabComplete(sender, command, label, args);
}
var suggestion = Stream.of(
"-n:",
"-i:",
"--keep",
"--stop"
);
if (!args[args.length - 1].isEmpty()) {
suggestion = suggestion.filter(s -> s.startsWith(args[args.length - 1]));
}
return suggestion.collect(Collectors.toList());
}
}

View File

@ -1,65 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.config;
import io.github.hello09x.fakeplayer.repository.UserConfigRepository;
import io.github.hello09x.fakeplayer.repository.model.Config;
import io.github.hello09x.fakeplayer.repository.model.Configs;
import io.github.tanyaofei.plugin.toolkit.command.ExecutableCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public abstract class AbstractConfigCommand extends ExecutableCommand {
protected final UserConfigRepository repository = UserConfigRepository.instance;
public AbstractConfigCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
public @Nullable Config<Object> getConfig(
@NotNull Player sender,
@NotNull String[] args
) {
if (args.length < 1) {
return null;
}
try {
return Configs.valueOf(args[0]);
} catch (IllegalArgumentException e) {
return null;
}
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (args.length != 1) {
return new ArrayList<>();
}
var prefix = args[0].toLowerCase();
var options = Arrays.stream(Configs.values()).map(Config::name);
if (!prefix.isEmpty()) {
options = options.filter(name -> name.toLowerCase().contains(prefix));
}
return options.collect(Collectors.toList());
}
}

View File

@ -1,23 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.config;
import io.github.tanyaofei.plugin.toolkit.command.ParentCommand;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ConfigCommand extends ParentCommand {
public final static ConfigCommand instance = new ConfigCommand(
"假人玩家个性化配置",
null
);
static {
instance.register("get", ConfigGetCommand.instance);
instance.register("set", ConfigSetCommand.instance);
}
protected ConfigCommand(@NotNull String description, @Nullable String permission) {
super(description, permission);
}
}

View File

@ -1,58 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.config;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.*;
public class ConfigGetCommand extends AbstractConfigCommand {
public final static ConfigGetCommand instance = new ConfigGetCommand(
"获取假人参数配置",
"/fp config get <配置项>",
null
);
public ConfigGetCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (!(sender instanceof Player p)) {
sender.sendMessage(text("你不是玩家...", RED));
return true;
}
var config = getConfig(p, args);
if (config == null) {
return false;
}
var value = String.valueOf(repository.selectOrDefault(p.getUniqueId(), config));
sender.sendMessage(textOfChildren(
text(config.label(), GOLD),
text(": ", GRAY),
text(value, WHITE)
));
return true;
}
}

View File

@ -1,92 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.config;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
public class ConfigSetCommand extends AbstractConfigCommand {
public final static ConfigSetCommand instance = new ConfigSetCommand(
"修改假人参数配置",
"/fp config set <配置项> <配置值>",
null
);
public ConfigSetCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (args.length != 2) {
return false;
}
if (!(sender instanceof Player p)) {
sender.sendMessage(text("你不是玩家...", RED));
return true;
}
var config = getConfig(p, args);
if (config == null) {
return false;
}
Object value;
try {
value = config.mapper().apply(args[1]);
} catch (Throwable e) {
sender.sendMessage(text("配置值有误"));
return true;
}
repository.saveOrUpdate(p.getUniqueId(), config, value);
sender.sendMessage(text("修改假人配置成功, 在下一次创建假人时生效", GRAY));
return true;
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (args.length != 2 || !(sender instanceof Player p)) {
return super.onTabComplete(sender, command, label, args);
}
var config = getConfig(p, args);
if (config == null) {
return Collections.emptyList();
}
var prefix = args[1].toLowerCase(Locale.ROOT);
var options = config.options().stream();
if (!prefix.isEmpty()) {
options = options.filter(opt -> opt.toLowerCase().contains(prefix));
}
return options.toList();
}
}

View File

@ -1,58 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.exp;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.*;
public class ExpmeCommand extends AbstractCommand {
public final static ExpmeCommand instance = new ExpmeCommand(
"转移假人的经验值",
"/fp expme [假人]",
"fakeplayer.exp"
);
public ExpmeCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
var target = getTarget(sender, args);
if (target == null) {
return false;
}
if (!(sender instanceof Player p)) {
sender.sendMessage(text("你不是玩家...", RED));
return true;
}
var exp = target.getTotalExperience();
target.setTotalExperience(0);
p.setTotalExperience(p.getTotalExperience() + exp);
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(" 转移 ", GRAY),
text(exp, DARK_GREEN),
text(" 点经验值给你", GRAY)
));
return true;
}
}

View File

@ -1,47 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.profile;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.DARK_GREEN;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class ExpCommand extends AbstractCommand {
public final static ExpCommand instance = new ExpCommand(
"查看经验值",
"/fp exp [假人]",
"fakeplayer.profile"
);
public ExpCommand(@NotNull String description, @NotNull String usage, @Nullable String permission) {
super(description, usage, permission);
}
@Override
protected boolean execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
var target = getTarget(sender, args);
if (target == null) {
return false;
}
var level = target.getLevel();
var total = target.getTotalExperience();
sender.sendMessage(textOfChildren(
text(target.getName(), GRAY),
text(" 当前 ", GRAY),
text(level, DARK_GREEN),
text(" 级, 共 ", GRAY),
text(total, DARK_GREEN),
text(" 点经验值", GRAY)
));
return true;
}
}

View File

@ -1,75 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.spawn;
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
import io.github.tanyaofei.plugin.toolkit.command.ExecutableCommand;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class CreateCommand extends ExecutableCommand {
public final static CreateCommand instance = new CreateCommand(
"创建假人",
"/fp create",
"fakeplayer.spawn"
);
private final FakeplayerManager manager = FakeplayerManager.instance;
public CreateCommand(@NotNull String description, @NotNull String usage, @Nullable String permission) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (sender instanceof Player p) {
manager.spawn(
p,
((Player) sender).getLocation()
);
} else {
if (args.length != 3) {
return false;
}
double x, y, z;
try {
x = Double.parseDouble(args[0]);
y = Double.parseDouble(args[1]);
z = Double.parseDouble(args[2]);
} catch (NumberFormatException e) {
return false;
}
var world = sender.getServer().getWorlds().get(0);
manager.spawn(sender, new Location(world, x, y, z));
}
sender.sendMessage(text("创建假人成功", GRAY));
return true;
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender commandSender,
@NotNull Command command,
@NotNull String s,
@NotNull String[] strings
) {
return Collections.emptyList();
}
}

View File

@ -1,80 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.spawn;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
public class KillCommand extends AbstractCommand {
public final static KillCommand instance = new KillCommand(
"移除假人",
"/fp kill [假人] [-a|-all]",
"fakeplayer.spawn"
);
private final FakeplayerManager manager = FakeplayerManager.instance;
public KillCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (hasFlag(args, "-a") || hasFlag(args, "--all")) {
int removed = sender.isOp()
? manager.removeAll()
: manager.removeAll(sender);
sender.sendMessage(text(String.format("已移除 %d 个假人", removed), GRAY));
return true;
}
var target = getTarget(sender, args);
if (target == null) {
return false;
}
manager.remove(target.getName());
sender.sendMessage(textOfChildren(
text("你移除了假人 ", GRAY),
text(target.getName(), WHITE)
));
return true;
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
var suggestion = super.onTabComplete(
sender,
command,
label,
args
);
if (args.length == 1 && suggestion != null) {
suggestion.add(0, "--all");
}
return suggestion;
}
}

View File

@ -1,107 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.spawn;
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
import io.github.tanyaofei.plugin.toolkit.command.ExecutableCommand;
import io.github.tanyaofei.plugin.toolkit.database.Page;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.textOfChildren;
public class ListCommand extends ExecutableCommand {
public final static ListCommand instance = new ListCommand(
"查看所有假人",
"/fp list [页码] [数量]",
"fakeplayer.spawn"
);
private final static FakeplayerManager manager = FakeplayerManager.instance;
public ListCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
int current = 1;
if (args.length >= 1) {
try {
current = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
return false;
}
}
if (current < 1) {
current = 1;
}
int size = 10;
if (args.length >= 2) {
try {
size = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
return false;
}
}
if (size < 1) {
size = 1;
}
var fakers = sender.isOp()
? manager.getAll()
: manager.getAll(sender);
var total = fakers.size();
var pages = total == 0 ? 1 : (int) Math.ceil((double) total / size);
if (current > pages) {
current = pages;
}
var page = new Page<>(
fakers.subList((current - 1) * size, Math.min(total, current * size)),
total,
pages,
current,
size
);
sender.sendMessage(page.toComponent(
"假人",
record -> textOfChildren(
text(record.getName()),
text(" - "),
text(Optional.ofNullable(manager.getCreator(record)).orElse("<不在线>"))
),
String.format("/fp list %d %d", current - 1, size),
String.format("/fp list %d %d", current + 1, size)
));
return true;
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
return Collections.emptyList();
}
}

View File

@ -1,33 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.tp;
import io.github.hello09x.fakeplayer.command.player.AbstractCommand;
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
import io.github.hello09x.fakeplayer.properties.FakeplayerProperties;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static org.bukkit.Sound.ENTITY_ENDERMAN_TELEPORT;
public abstract class AbstractTpCommand extends AbstractCommand {
protected final FakeplayerManager manager = FakeplayerManager.instance;
protected final FakeplayerProperties properties = FakeplayerProperties.instance;
public AbstractTpCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
protected void teleport(@NotNull Player from, @NotNull Player to) {
from.teleport(to.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
to.getLocation().getWorld().playSound(to.getLocation(), ENTITY_ENDERMAN_TELEPORT, 1.0F, 1.0F);
}
}

View File

@ -1,50 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.tp;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
public class TpHereCommand extends AbstractTpCommand {
public final static TpHereCommand instance = new TpHereCommand(
"传送假人到身边",
"/fp tphere [假人]",
"fakeplayer.tp"
);
public TpHereCommand(
@NotNull String description,
@NotNull String usage,
@Nullable String permission
) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (!(sender instanceof Player creator)) {
sender.sendMessage(text("你不是玩家...", RED));
return true;
}
var target = getTarget(creator, args);
if (target == null) {
return false;
}
super.teleport(target, creator);
return true;
}
}

View File

@ -1,54 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.tp;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
public class TpSwapCommand extends AbstractTpCommand {
public final static TpSwapCommand instance = new TpSwapCommand(
"与假人交换位置",
"/fp tps [假人]",
"fakeplayer.tp"
);
public TpSwapCommand(@NotNull String description, @NotNull String usage, @Nullable String permission) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (!(sender instanceof Player creator)) {
sender.sendMessage(text("你不是玩家...", RED));
return true;
}
var target = getTarget(sender, args);
if (target == null) {
return false;
}
var l1 = creator.getLocation();
var l2 = target.getLocation();
target.teleport(l1, PlayerTeleportEvent.TeleportCause.PLUGIN);
l1.getWorld().playSound(l1, Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1.0F, 1.0F);
creator.teleport(l2, PlayerTeleportEvent.TeleportCause.PLUGIN);
l2.getWorld().playSound(l2, Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1.0F, 1.0F);
return true;
}
}

View File

@ -1,50 +0,0 @@
package io.github.hello09x.fakeplayer.command.player.tp;
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
public class TpToCommand extends AbstractTpCommand {
private final FakeplayerManager manager = FakeplayerManager.instance;
public final static TpToCommand instance = new TpToCommand(
"传送到假人身边",
"/fp tp [假人]",
"fakeplayer.tp"
);
public TpToCommand(@NotNull String description, @NotNull String usage, @Nullable String permission) {
super(description, usage, permission);
}
@Override
protected boolean execute(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
if (!(sender instanceof Player creator)) {
sender.sendMessage(text("你不是玩家...", RED));
return true;
}
var target = getTarget(sender, args);
if (target == null) {
return false;
}
teleport(creator, target);
return true;
}
}

View File

@ -82,7 +82,8 @@ public class FakePlayer {
public void spawn(
boolean invulnerable,
boolean collidable,
boolean lookAtEntity
boolean lookAtEntity,
boolean pickupItems
) {
if (properties.isSimulateLogin()) {
preLogin:
@ -132,6 +133,7 @@ public class FakePlayer {
bukkitPlayer.setInvulnerable(invulnerable);
bukkitPlayer.setCollidable(collidable);
bukkitPlayer.setCanPickupItems(pickupItems);
new BukkitRunnable() {
@Override
public void run() {

View File

@ -248,6 +248,10 @@ public enum Action {
public void stop(@NotNull ActionPack ap) {}
public void inactiveTick(@NotNull ActionPack ap) {
this.stop(ap);
}
public static class ActionPack {
public final ServerPlayer player;

View File

@ -17,10 +17,12 @@ public class ActionManager {
public void tick() {
if (setting.wait > 0) {
setting.wait--;
inactiveTick();
return;
}
if (setting.times == 0) {
inactiveTick();
return;
}
@ -33,6 +35,10 @@ public class ActionManager {
}
}
public void inactiveTick() {
action.inactiveTick(this.actionPack);
}
public void stop() {
action.stop(this.actionPack);
}

View File

@ -37,4 +37,12 @@ public class ActionSetting {
return new ActionSetting(0, 1, 0);
}
public static ActionSetting interval(int interval) {
return new ActionSetting(-1, interval, 0);
}
public static ActionSetting continuous() {
return new ActionSetting(-1, 1, 0);
}
}

View File

@ -112,34 +112,35 @@ public class FakeplayerManager {
* @param creator 创建者
* @param spawnAt 生成地点
*/
public synchronized void spawn(
public synchronized @Nullable Player spawn(
@NotNull CommandSender creator,
@NotNull Location spawnAt
) {
var playerLimit = properties.getPlayerLimit();
if (!creator.isOp() && playerLimit != Integer.MAX_VALUE && getAll(creator).size() >= playerLimit) {
creator.sendMessage(text("你创建的假人数量已达到上限...", RED));
return;
return null;
}
var serverLimit = properties.getServerLimit();
if (!creator.isOp() && serverLimit != Integer.MAX_VALUE && getAll().size() >= serverLimit) {
creator.sendMessage(text("服务器假人数量已达到上限...", RED));
return;
return null;
}
if (!creator.isOp() && properties.isDetectIp() && countByAddress(AddressUtils.getAddress(creator)) >= 1) {
creator.sendMessage(text("你所在 IP 创建的假人数量已达到上限...", RED));
return;
return null;
}
var name = nameManager.take(creator);
boolean invulnerable = true, lookAtEntity = true, collidable = true;
boolean invulnerable = true, lookAtEntity = true, collidable = true, pickupItems = true;
if (creator instanceof Player p) {
var creatorId = p.getUniqueId();
invulnerable = userConfigRepository.selectOrDefault(creatorId, Configs.invulnerable);
lookAtEntity = userConfigRepository.selectOrDefault(creatorId, Configs.look_at_entity);
collidable = userConfigRepository.selectOrDefault(creatorId, Configs.collidable);
pickupItems = userConfigRepository.selectOrDefault(creatorId, Configs.pickup_items);
}
var serverPlayer = new FakePlayer(
@ -156,11 +157,12 @@ public class FakeplayerManager {
player.setMetadata(FakeplayerMetadata.NAME_SEQUENCE.key, new FixedMetadataValue(Main.getInstance(), name.sequence()));
player.playerListName(text(creator.getName() + "的假人").style(Style.style(GRAY, ITALIC)));
serverPlayer.spawn(invulnerable, collidable, lookAtEntity);
serverPlayer.spawn(invulnerable, collidable, lookAtEntity, pickupItems);
usedIdRepository.add(player.getUniqueId());
dispatchCommands(player, properties.getPreparingCommands());
performCommands(player);
// 先等待玩家 spawn 之后再 tp, 否则 tp 不生效
// 可能会被别的插件干预, 因此 tp 两次
@ -180,6 +182,8 @@ public class FakeplayerManager {
moveTo.getWorld().playSound(moveTo, Sound.ENTITY_ENDERMAN_TELEPORT, 1.0F, 1.0F);
}
}.runTaskLater(Main.getInstance(), 20);
return player;
}
public @Nullable Player get(@NotNull CommandSender creator, @NotNull String name) {
@ -356,6 +360,24 @@ public class FakeplayerManager {
return uuid;
}
public void performCommands(@NotNull Player player) {
if (!isFake(player)) {
return;
}
for (var cmd : properties.getSelfCommands()) {
cmd = cmd.trim();
if (cmd.startsWith("/")) {
cmd = cmd.substring(1);
}
if (cmd.isBlank()) {
continue;
}
player.performCommand(cmd);
}
}
public void dispatchCommands(@NotNull Player player, @NotNull List<String> commands) {
if (commands.isEmpty()) {
return;

View File

@ -67,6 +67,11 @@ public class FakeplayerProperties extends AbstractProperties<FakeplayerPropertie
*/
private List<String> preparingCommands;
/**
* 自执行命令
*/
private List<String> selfCommands;
/**
* 销毁命令
*/
@ -92,6 +97,7 @@ public class FakeplayerProperties extends AbstractProperties<FakeplayerPropertie
this.followQuiting = file.getBoolean("follow-quiting", true);
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.nameTemplate = file.getString("name-template", "");

View File

@ -42,6 +42,14 @@ public interface Configs {
Boolean::valueOf
);
Config<Boolean> pickup_items = build(
"pickup_items",
"是否可拾取物品",
true,
List.of("true", "false"),
Boolean::valueOf
);
private static <T> Config<T> build(
@NotNull String name,

View File

@ -1,6 +1,6 @@
# 配置文件版本
# 不要修改这个值
version: 7
version: 8
# 服务器最多存在多少个假人
# 默认: 1000
@ -40,9 +40,10 @@ detect-ip: true
kale-tps: 0
# 是否模拟登陆
# 真实的玩家登陆流程是 "预登陆" -> "登陆" -> "加入游戏", 而假人插件默认情况下跳过了这三个步骤直接生成实体, 如果设置为 true 将会模拟这三个步骤
# 因此有一些需要在 "登陆" 时生成玩家档案的插件发生异常比如 LuckPerms, 如果服务器没有出现严重的错误不需要理会这些异常, 只是这些插件无法对假人进行操作而已
# 开启也不一定能解决所有问题, 甚至可能衍生出更加奇怪的报错并且会让第三方插件生成大量的假人数据, 或者由于和一些 "新加入玩家限制行动之类的插件" 起冲突而导致假人飞走
# 真实的玩家登陆流程是 "预登陆" -> "登陆" -> "加入游戏", 而假人插件默认情况下跳过了前两个步骤直接加入游戏
# 有一些需要在 "登陆" 时生成玩家档案的插件发生异常比如 LuckPerms
# 如果服务器没有出现严重的错误不需要理会这些异常, 只是这些插件无法对假人进行操作而已
# 开启也不一定能解决所有问题, 也可能因为一些 "限制新加入玩家" 的插件而导致假人出现问题, 并且会创建更多的第三方插件数据
simulate-login: false
# 预准备命令
@ -68,3 +69,9 @@ destroy-commands:
- ''
- ''
# 自执行命令
# 假人在诞生时会以自己的身份按顺序执行命令
# 你可以在这里做一些 /register 之类的命令
self-commands:
- ''
- ''

View File

@ -4,13 +4,6 @@ main: io.github.hello09x.fakeplayer.Main
api-version: '1.20'
author: hello09x
commands:
fakeplayer:
description: '假人相关命令'
usage: §2/fp ? for more info
aliases:
- fp
permissions:
fakeplayer.all:
description: '假人所有基础权限'
@ -23,7 +16,7 @@ permissions:
default: op
fakeplayer.spawn:
description: '拥有 create, kick, list 命令权限'
description: '拥有 create, kill, list 命令权限'
default: op
fakeplayer.tp: