添加 look, jump, move 命令

This commit is contained in:
tanyaofei 2023-07-28 10:48:52 +08:00
parent 703b13515a
commit f9e19ae7f7
18 changed files with 413 additions and 185 deletions

View File

@ -28,6 +28,9 @@
+ /fp config get - 查看个性化配置
+ /fp drop - 丢弃手上物品
+ /fp dropinv - 丢弃背包物品
+ /fp look - 让假人看向指定位置
+ /fp move - 让家人移动
+ /fp jump - 让假人跳跃
+ /fp attack - 让假人点击鼠标左键 **(实验性)**
+ /fp use - 让假人点击鼠标右键 **(实验性)**
+ /fp cmd - 让假人执行他有权限执行的命令

View File

@ -21,7 +21,7 @@ public abstract class AbstractCommand {
protected final FakeplayerManager fakeplayerManager = FakeplayerManager.instance;
public static Argument<Player> targetArgument(@NotNull String nodeName) {
public static Argument<Player> target(@NotNull String nodeName) {
return new CustomArgument<>(new StringArgument(nodeName), info -> {
var sender = info.sender();
return sender.isOp()
@ -44,7 +44,7 @@ public abstract class AbstractCommand {
}));
}
public static Argument<List<Player>> multiTargetArgument(@NotNull String nodeName) {
public static Argument<List<Player>> targets(@NotNull String nodeName) {
return new CustomArgument<List<Player>, String>(new StringArgument(nodeName), info -> {
var sender = info.sender();
var arg = info.currentInput();

View File

@ -2,10 +2,19 @@ package io.github.hello09x.fakeplayer.command;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;
import dev.jorel.commandapi.executors.CommandExecutor;
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.hello09x.fakeplayer.util.MathUtils;
import io.github.hello09x.fakeplayer.util.Unwrapper;
import io.papermc.paper.entity.LookAnchor;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import static net.kyori.adventure.text.Component.text;
@ -19,9 +28,36 @@ public class ActionCommand extends AbstractCommand {
private final PlayerActionManager actionManager = PlayerActionManager.instance;
private static String toLocationString(@NotNull Location location) {
return StringUtils.joinWith(", ",
MathUtils.round(location.getX(), 0.5),
MathUtils.round(location.getY(), 0.5),
MathUtils.round(location.getZ(), 0.5));
}
public CommandExecutor action(@NotNull Action action, @NotNull ActionSetting setting) {
return (sender, args) -> action(sender, args, action, setting.clone());
}
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);
String desc;
if (setting.equals(ActionSetting.stop())) {
desc = " 已停止";
} else if (setting.equals(ActionSetting.once())) {
desc = "";
} else {
desc = " 开始";
}
sender.sendMessage(textOfChildren(
text(target.getName()),
text(desc, GRAY),
text(" "),
text(action.name, GRAY)
));
}
public void sneak(@NotNull CommandSender sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
@ -36,10 +72,77 @@ public class ActionCommand extends AbstractCommand {
sender.sendMessage(textOfChildren(
text(target.getName(), WHITE),
text("现在", GRAY),
text(" 现在", GRAY),
text(sneaking ? "潜行中" : "取消了潜行", GRAY)
));
}
public void lookAt(@NotNull CommandSender sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
var location = (Location) args.get("location");
target.lookAt(location, LookAnchor.EYES);
sender.sendMessage(textOfChildren(
text(target.getName(), WHITE),
text(" 正在看向 ", GRAY),
text(toLocationString(location), GRAY)
));
}
public CommandExecutor look(@NotNull Direction direction) {
return (sender, args) -> {
var target = getTarget(sender, args);
look(target, direction);
sender.sendMessage(textOfChildren(
text(target.getName(), WHITE),
text(" 看向 ", GRAY),
text(switch (direction) {
case DOWN -> "下方";
case UP -> "上方";
case NORTH -> "北边";
case SOUTH -> "南边";
case WEST -> "西边";
case EAST -> "东边";
}, GRAY)
));
};
}
private void look(
@NotNull Player target,
@NotNull Direction direction
) {
var player = Unwrapper.getServerPlayer(target);
switch (direction) {
case NORTH -> look(player, 180, 0);
case SOUTH -> look(player, 0, 0);
case EAST -> look(player, -90, 0);
case WEST -> look(player, 90, 0);
case UP -> look(player, player.getYRot(), -90);
case DOWN -> look(player, player.getYRot(), 90);
}
}
private void look(@NotNull ServerPlayer player, float yaw, float pitch) {
player.setYRot(yaw % 360);
player.setXRot(MathUtils.clamp(pitch, -90, 90));
}
public CommandExecutor move(float forward, float strafing) {
return (sender, args) -> {
var target = getTarget(sender, args);
var player = Unwrapper.getServerPlayer(target);
float vel = target.isSneaking() ? 0.3F : 1.0F;
if (forward != 0.0F) {
player.zza = vel * forward;
}
if (strafing != 0.0F) {
player.xxa = vel * strafing;
}
sender.sendMessage(textOfChildren(
text(target.getName()),
text(" 动了一下", GRAY)
));
};
}
}

View File

@ -9,12 +9,17 @@ import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
import static net.kyori.adventure.text.Component.textOfChildren;
import static net.kyori.adventure.text.format.NamedTextColor.*;
public class CmdCommand extends AbstractCommand {
public final static CmdCommand instance = new CmdCommand();
private static String toCommandString(@NotNull CommandResult command) {
return "/" + command.command().getName() + String.join(" ", command.args());
}
public void cmd(@NotNull CommandSender sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
var target = getTarget(sender, args);
var cmd = Objects.requireNonNull((CommandResult) args.get("command"));
@ -25,7 +30,20 @@ public class CmdCommand extends AbstractCommand {
return;
}
cmd.execute(target);
if (!cmd.execute(target)) {
sender.sendMessage(textOfChildren(
text(target.getName(), WHITE),
text(" 执行命令失败: ", GRAY),
text(toCommandString(cmd), RED),
text(" , 请检查命令是否正确以及假人是否有权限", GRAY)
));
}
sender.sendMessage(textOfChildren(
text(target.getName(), WHITE),
text(" 成功执行了命令: ", GRAY),
text(toCommandString(cmd), YELLOW)
));
}
}

View File

@ -8,12 +8,15 @@ 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 net.minecraft.core.Direction;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Arrays;
import static io.github.hello09x.fakeplayer.command.AbstractCommand.multiTargetArgument;
import static io.github.hello09x.fakeplayer.command.AbstractCommand.targetArgument;
import static io.github.hello09x.fakeplayer.command.AbstractCommand.target;
import static io.github.hello09x.fakeplayer.command.AbstractCommand.targets;
import static io.github.hello09x.fakeplayer.command.ConfigCommand.config;
import static io.github.hello09x.fakeplayer.command.ConfigCommand.configValue;
public class Commands {
@ -28,11 +31,11 @@ public class Commands {
private final static String PERMISSION_CMD = "fakeplayer.cmd";
public static void register() {
new CommandAPICommand("fakeplayer")
command("fakeplayer")
.withAliases("fp")
.withHelp(
"假人相关命令",
"fakeplayer 可以用来创建一个模拟玩家的假人, 能保持附近区块的刷新、触发怪物生成。同时还提供了一些操作命令让你控制假人的物品、动作等等。"
"假人",
"可以创建模拟玩家的假人, 能保持附近区块的刷新、触发怪物生成。同时还提供了一些操作命令让你控制假人的物品、动作等等。"
)
.withUsage(
"§6/fp spawn §7- §f创建假人",
@ -47,133 +50,179 @@ public class Commands {
"§6/fp health [假人] §7- §f查看生命值",
"§6/fp exp [假人] §7- §f查看经验值",
"§6/fp expme [假人] §7- §f转移经验值",
"§6/fp attack <once|continuous|interval|stop> [假人] §7- §攻击/破坏",
"§6/fp use <once|continuous|interval|stop> [假人] §7- §f使用/交互/放置",
"§6/fp jump <once|continuous|interval|stop> [假人] §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模拟鼠标右键",
"§6/fp cmd §7- §f让假人执行命令",
"§6/fp look <north|south|east|west|up|down|at> §7- §f看向指定位置",
"§6/fp move <forward|backward|left|right> §7- §f移动假人",
"§6/fp cmd §7- §f执行命令",
"§6/fp reload §7- §f重载配置文件"
)
.withSubcommands(
new CommandAPICommand("help")
command("help")
.withAliases("?")
.withOptionalArguments(new IntegerArgument("page", 1))
.executesPlayer(HelpCommand.instance::help),
new CommandAPICommand("spawn")
command("spawn")
.withPermission(PERMISSION_SPAWN)
.withOptionalArguments(new LocationArgument("location").withPermission(PERMISSION_SPAWN_LOCATION))
.withOptionalArguments(location("location").withPermission(PERMISSION_SPAWN_LOCATION))
.executes(SpawnCommand.instance::spawn),
new CommandAPICommand("kill")
command("kill")
.withPermission(PERMISSION_SPAWN)
.withOptionalArguments(multiTargetArgument("targets"))
.withOptionalArguments(targets("targets"))
.executes(SpawnCommand.instance::kill),
new CommandAPICommand("list")
command("list")
.withPermission(PERMISSION_SPAWN)
.withOptionalArguments(new IntegerArgument("page", 1), new IntegerArgument("size", 1))
.withOptionalArguments(integer("page", 1), integer("size", 1))
.executes(SpawnCommand.instance::list),
new CommandAPICommand("distance")
command("distance")
.withPermission(PERMISSION_SPAWN)
.withOptionalArguments(targetArgument("target"))
.withOptionalArguments(target("target"))
.executesPlayer(SpawnCommand.instance::distance),
new CommandAPICommand("exp")
command("exp")
.withPermission(PERMISSION_PROFILE)
.withOptionalArguments(targetArgument("target"))
.withOptionalArguments(target("target"))
.executes(ProfileCommand.instance::exp),
new CommandAPICommand("health")
command("health")
.withPermission(PERMISSION_PROFILE)
.withOptionalArguments(targetArgument("target"))
.withOptionalArguments(target("target"))
.executes(ProfileCommand.instance::health),
new CommandAPICommand("tp")
command("tp")
.withPermission(PERMISSION_TP)
.withOptionalArguments(targetArgument("target"))
.withOptionalArguments(target("target"))
.executesPlayer(TpCommand.instance::tp),
new CommandAPICommand("tphere")
command("tphere")
.withPermission(PERMISSION_TP)
.withOptionalArguments(targetArgument("target"))
.withOptionalArguments(target("target"))
.executesPlayer(TpCommand.instance::tphere),
new CommandAPICommand("tps")
command("tps")
.withPermission(PERMISSION_TP)
.withOptionalArguments(targetArgument("target"))
.withOptionalArguments(target("target"))
.executesPlayer(TpCommand.instance::tps),
new CommandAPICommand("config")
command("config")
.withSubcommands(
new CommandAPICommand("get")
.withArguments(ConfigCommand.configArgument("config"))
command("get")
.withArguments(config("config"))
.executesPlayer(ConfigCommand.instance::getConfig),
new CommandAPICommand("set")
command("set")
.withArguments(
ConfigCommand.configArgument("config"),
ConfigCommand.configValueArgument("config", "value"))
config("config"),
configValue("config", "value"))
.executesPlayer(ConfigCommand.instance::setConfig)
),
new CommandAPICommand("attack")
command("attack")
.withPermission(PERMISSION_EXPERIMENTAL_ACTION)
.withSubcommands(buildActionCommand(Action.ATTACK)),
new CommandAPICommand("use")
.withSubcommands(action(Action.ATTACK)),
command("use")
.withPermission(PERMISSION_EXPERIMENTAL_ACTION)
.withSubcommands(buildActionCommand(Action.USE)),
new CommandAPICommand("drop")
.withSubcommands(action(Action.USE)),
command("jump")
.withPermission(PERMISSION_ACTION)
.withSubcommands(action(Action.JUMP)),
command("drop")
.withPermission(PERMISSION_ACTION)
.withOptionalArguments(
targetArgument("target"),
new MultiLiteralArgument("all", List.of("-a", "--all")))
target("target"),
literals("all", "-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")
command("dropinv")
.withPermission(PERMISSION_ACTION)
.withOptionalArguments(targetArgument("target"))
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(sender, args, Action.DROP_INVENTORY, ActionSetting.once())),
new CommandAPICommand("sneak")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.action(Action.DROP_INVENTORY, ActionSetting.once())),
command("sneak")
.withPermission(PERMISSION_ACTION)
.withOptionalArguments(targetArgument("target"))
.withOptionalArguments(new MultiLiteralArgument("sneaking", List.of("true", "false")))
.withOptionalArguments(target("target"))
.withOptionalArguments(literals("sneaking", "true", "false"))
.executes(ActionCommand.instance::sneak),
command("look")
.withPermission(PERMISSION_ACTION)
.withSubcommands(
command("north")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.look(Direction.NORTH)),
command("south")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.look(Direction.SOUTH)),
command("west")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.look(Direction.WEST)),
command("east")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.look(Direction.EAST)),
command("up")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.look(Direction.UP)),
command("down")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.look(Direction.DOWN)),
command("at")
.withArguments(new LocationArgument("location"))
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance::lookAt)
),
command("move")
.withPermission(PERMISSION_ACTION)
.withSubcommands(
command("forward")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.move(1, 0)),
command("backward")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.move(-1, 0)),
command("left")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.move(0, 1)),
command("right")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.move(0, -1))
),
new CommandAPICommand("expme")
command("expme")
.withPermission(PERMISSION_EXP)
.withOptionalArguments(targetArgument("target"))
.withOptionalArguments(target("target"))
.executesPlayer(ExpCommand.instance::expme),
new CommandAPICommand("cmd")
command("cmd")
.withPermission(PERMISSION_CMD)
.withArguments(
targetArgument("target"),
target("target"),
new CommandArgument("command"))
.executes(CmdCommand.instance::cmd),
new CommandAPICommand("reload")
command("reload")
.withPermission(PERMISSION_ADMIN)
.executes(ReloadCommand.instance::reload)
).register();
}
private static CommandAPICommand[] buildActionCommand(@NotNull Action action) {
private static CommandAPICommand[] action(@NotNull Action action) {
return new CommandAPICommand[]{
new CommandAPICommand("once")
.withOptionalArguments(targetArgument("target"))
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(sender, args, action, ActionSetting.once())
command("once")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.action(action, ActionSetting.once())
),
new CommandAPICommand("continuous")
.withOptionalArguments(targetArgument("target"))
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(sender, args, action, ActionSetting.continuous())
command("continuous")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.action(action, ActionSetting.continuous())),
command("stop")
.withOptionalArguments(target("target"))
.executes(ActionCommand.instance.action(action, ActionSetting.stop())
),
new CommandAPICommand("stop")
.withOptionalArguments(targetArgument("target"))
.executes((CommandExecutor) (sender, args) -> ActionCommand.instance.action(sender, args, action, ActionSetting.stop())
),
new CommandAPICommand("interval")
command("interval")
.withOptionalArguments(
new IntegerArgument("interval", 1),
targetArgument("target"))
integer("interval", 1),
target("target"))
.executes((sender, args) -> {
int interval = (int) args.getOptional("interval").orElse(1);
ActionCommand.instance.action(sender, args, action, ActionSetting.interval(interval));
@ -181,5 +230,21 @@ public class Commands {
};
}
private static CommandAPICommand command(@NotNull String name) {
return new CommandAPICommand(name);
}
public static IntegerArgument integer(String name, int min) {
return new IntegerArgument(name, min);
}
public static LocationArgument location(String name) {
return new LocationArgument(name);
}
public static MultiLiteralArgument literals(String name, String... literals) {
return new MultiLiteralArgument(name, Arrays.asList(literals));
}
}

View File

@ -24,7 +24,7 @@ public class ConfigCommand extends AbstractCommand {
private final UserConfigRepository repository = UserConfigRepository.instance;
public static Argument<Config<Object>> configArgument(String nodeName) {
public static Argument<Config<Object>> config(String nodeName) {
return new CustomArgument<>(new StringArgument(nodeName), info -> {
var arg = info.currentInput();
try {
@ -35,7 +35,7 @@ public class ConfigCommand extends AbstractCommand {
}).replaceSuggestions(ArgumentSuggestions.strings(Arrays.stream(Configs.values()).map(Config::name).toList()));
}
public static Argument<Object> configValueArgument(String configNodeName, String nodeName) {
public static Argument<Object> configValue(String configNodeName, String nodeName) {
return new CustomArgument<>(new StringArgument(nodeName), info -> {
@SuppressWarnings("unchecked")
var config = Objects.requireNonNull((Config<Object>) info.previousArgs().get(configNodeName));

View File

@ -26,12 +26,11 @@ public class SpawnCommand extends AbstractCommand {
private static String toLocationString(@NotNull Location location) {
return location.getWorld().getName()
+ " 世界: "
+ ": "
+ StringUtils.joinWith(", ",
MathUtils.round(location.getX(), 0.5),
MathUtils.round(location.getY(), 0.5),
MathUtils.round(location.getZ(), 0.5));
}
public void spawn(@NotNull CommandSender sender, CommandArguments args) {
@ -131,9 +130,9 @@ public class SpawnCommand extends AbstractCommand {
var distance = location1.distance(location2);
sender.sendMessage(textOfChildren(
text("你与 "),
text("你与 ", GRAY),
text(target.getName()),
text(" 相距 "),
text(" 相距 ", GRAY),
text(MathUtils.round(distance, 0.5), WHITE)
));
}

View File

@ -130,6 +130,7 @@ public class FakePlayer {
bukkitPlayer.setInvulnerable(invulnerable);
bukkitPlayer.setCollidable(collidable);
bukkitPlayer.setCanPickupItems(pickupItems);
bukkitPlayer.getInventory().clear(); // 一些同步背包的插件可能在 JOIN 的时候恢复背包, 这个时候清空掉防止物品被复制了
new BukkitRunnable() {
@Override
public void run() {

View File

@ -1,7 +1,7 @@
package io.github.hello09x.fakeplayer.entity.action;
import io.github.hello09x.fakeplayer.util.Tracer;
import net.minecraft.core.BlockPos;
import lombok.AllArgsConstructor;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
@ -13,14 +13,15 @@ import org.jetbrains.annotations.NotNull;
import static net.minecraft.network.protocol.game.ServerboundPlayerActionPacket.Action.*;
@AllArgsConstructor
public enum Action {
USE {
USE("交互/使用/放置") {
@Override
@SuppressWarnings("resource")
public boolean tick(@NotNull ActionPack ap) {
if (ap.itemUseFreeze > 0) {
ap.itemUseFreeze--;
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
if (ap.use.freeze > 0) {
ap.use.freeze--;
return false;
}
@ -42,7 +43,7 @@ public enum Action {
var result = player.gameMode.useItemOn(player, world, player.getItemInHand(hand), hand, blockHit);
if (result.consumesAction()) {
player.swing(hand);
ap.itemUseFreeze = 3;
ap.use.freeze = 3;
return true;
}
}
@ -55,18 +56,18 @@ public enum Action {
boolean itemFrameEmpty = (entity instanceof ItemFrame) && ((ItemFrame) entity).getItem().isEmpty();
var pos = entityHit.getLocation().subtract(entity.getX(), entity.getY(), entity.getZ());
if (entity.interactAt(player, pos, hand).consumesAction()) {
ap.itemUseFreeze = 3;
ap.use.freeze = 3;
return true;
}
if (player.interactOn(entity, hand).consumesAction() && !(handWasEmpty && itemFrameEmpty)) {
ap.itemUseFreeze = 3;
ap.use.freeze = 3;
return true;
}
}
}
var handItem = player.getItemInHand(hand);
if (player.gameMode.useItem(player, player.level(), handItem, hand).consumesAction()) {
ap.itemUseFreeze = 3;
ap.use.freeze = 3;
return true;
}
}
@ -74,16 +75,16 @@ public enum Action {
}
@Override
public void stop(@NotNull ActionPack ap) {
ap.itemUseFreeze = 0;
public void stop(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
ap.use.freeze = 0;
ap.player.releaseUsingItem();
}
},
ATTACK {
ATTACK("攻击/破坏") {
@Override
@SuppressWarnings("resource")
public boolean tick(@NotNull ActionPack ap) {
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
var player = ap.player;
var hit = getTarget(player);
switch (hit.getType()) {
@ -96,8 +97,8 @@ public enum Action {
return true;
}
case BLOCK -> {
if (ap.blockHitFreeze > 0) {
ap.blockHitFreeze--;
if (ap.attack.freeze > 0) {
ap.attack.freeze--;
return false;
}
@ -109,8 +110,8 @@ public enum Action {
return false;
}
if (ap.curBlockPos != null && player.level().getBlockState(ap.curBlockPos).isAir()) {
ap.curBlockPos = null;
if (ap.attack.pos != null && player.level().getBlockState(ap.attack.pos).isAir()) {
ap.attack.pos = null;
return false;
}
@ -124,11 +125,11 @@ public enum Action {
player.level().getMaxBuildHeight(),
-1
);
ap.blockHitFreeze = 5;
} else if (ap.curBlockPos == null || !ap.curBlockPos.equals(pos)) {
if (ap.curBlockPos != null) {
ap.attack.freeze = 5;
} else if (ap.attack.pos == null || !ap.attack.pos.equals(pos)) {
if (ap.attack.pos != null) {
player.gameMode.handleBlockBreakAction(
ap.curBlockPos,
ap.attack.pos,
ABORT_DESTROY_BLOCK,
side,
player.level().getMaxBuildHeight(),
@ -144,20 +145,20 @@ public enum Action {
-1
);
if (!state.isAir() && ap.curBlockPgs == 0) {
if (!state.isAir() && ap.attack.progress == 0) {
state.attack(player.level(), pos, player);
}
if (!state.isAir() && state.getDestroyProgress(player, player.level(), pos) >= 1) {
ap.curBlockPos = null;
ap.attack.pos = null;
broken = true;
} else {
ap.curBlockPos = pos;
ap.curBlockPgs = 0;
ap.attack.pos = pos;
ap.attack.progress = 0;
}
} else {
ap.curBlockPgs += state.getDestroyProgress(player, player.level(), pos);
if (ap.curBlockPgs >= 1) {
ap.attack.progress += state.getDestroyProgress(player, player.level(), pos);
if (ap.attack.progress >= 1) {
player.gameMode.handleBlockBreakAction(
pos,
STOP_DESTROY_BLOCK,
@ -165,11 +166,11 @@ public enum Action {
player.level().getMaxBuildHeight(),
-1
);
ap.curBlockPos = null;
ap.blockHitFreeze = 5;
ap.attack.pos = null;
ap.attack.freeze = 5;
broken = true;
}
player.level().destroyBlockProgress(-1, pos, (int) (ap.curBlockPgs * 10));
player.level().destroyBlockProgress(-1, pos, (int) (ap.attack.progress * 10));
}
player.resetLastActionTime();
@ -181,30 +182,52 @@ public enum Action {
}
@Override
public void stop(@NotNull ActionPack ap) {
if (ap.curBlockPos == null) {
@SuppressWarnings("resource")
public void stop(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
if (ap.attack.pos == null) {
return;
}
var player = ap.player;
player.level().destroyBlockProgress(-1, ap.curBlockPos, -1);
player.level().destroyBlockProgress(-1, ap.attack.pos, -1);
player.gameMode.handleBlockBreakAction(
ap.curBlockPos,
ap.attack.pos,
ABORT_DESTROY_BLOCK,
Direction.DOWN,
player.level().getMaxBuildHeight(),
-1
);
ap.curBlockPos = null;
ap.blockHitFreeze = 0;
ap.curBlockPgs = 0;
ap.attack.pos = null;
ap.attack.freeze = 0;
ap.attack.progress = 0;
}
},
JUMP("") {
@Override
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
var player = ap.player;
if (setting.limit == 1) {
if (player.onGround()) {
player.jumpFromGround();
return true;
}
}
player.setJumping(true);
return true;
}
@Override
public void inactiveTick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
ap.player.setJumping(false);
}
},
DROP_ITEM {
DROP_ITEM("丢弃手上物品") {
@Override
public boolean tick(@NotNull ActionPack ap) {
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
var player = ap.player;
player.resetLastActionTime();
player.drop(false);
@ -212,9 +235,9 @@ public enum Action {
}
},
DROP_STACK {
DROP_STACK("丢弃手上整组物品") {
@Override
public boolean tick(@NotNull ActionPack ap) {
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
var player = ap.player;
player.resetLastActionTime();
player.drop(true);
@ -222,9 +245,9 @@ public enum Action {
}
},
DROP_INVENTORY {
DROP_INVENTORY("丢弃背包物品") {
@Override
public boolean tick(@NotNull ActionPack ap) {
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
var player = ap.player;
dropInventory(player);
return true;
@ -232,6 +255,8 @@ public enum Action {
};
public final String name;
static HitResult getTarget(ServerPlayer player) {
double reach = player.gameMode.isCreative() ? 5 : 4.5f;
return Tracer.rayTrace(player, 1, reach, false);
@ -244,31 +269,13 @@ public enum Action {
}
}
public abstract boolean tick(@NotNull ActionPack ap);
public void stop(@NotNull ActionPack ap) {}
public abstract boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting);
public void inactiveTick(@NotNull ActionPack ap) {
this.stop(ap);
}
public static class ActionPack {
public final ServerPlayer player;
// attack
public BlockPos curBlockPos;
public float curBlockPgs;
public int blockHitFreeze;
// use
public int itemUseFreeze;
public ActionPack(ServerPlayer player) {
this.player = player;
}
public void stop(@NotNull ActionPack ap, @NotNull ActionSetting setting) {}
public void inactiveTick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
this.stop(ap, setting);
}

View File

@ -5,13 +5,13 @@ import net.minecraft.server.level.ServerPlayer;
public class ActionManager {
public final Action action;
public final Action.ActionPack actionPack;
public final ActionPack actionPack;
public ActionSetting setting;
public ActionManager(ServerPlayer player, Action action, ActionSetting setting) {
this.action = action;
this.setting = setting;
this.actionPack = new Action.ActionPack(player);
this.actionPack = new ActionPack(player);
}
public void tick() {
@ -26,7 +26,7 @@ public class ActionManager {
return;
}
var done = action.tick(this.actionPack);
var done = action.tick(this.actionPack, this.setting);
if (done) {
if (setting.times > 0) {
setting.times--;
@ -36,11 +36,11 @@ public class ActionManager {
}
public void inactiveTick() {
action.inactiveTick(this.actionPack);
action.inactiveTick(this.actionPack, this.setting);
}
public void stop() {
action.stop(this.actionPack);
action.stop(this.actionPack, this.setting);
}

View File

@ -0,0 +1,28 @@
package io.github.hello09x.fakeplayer.entity.action;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
public class ActionPack {
public final ServerPlayer player;
public final AttackActionPack attack = new AttackActionPack();
public final UseActionPack use = new UseActionPack();
public ActionPack(ServerPlayer player) {
this.player = player;
}
public final static class AttackActionPack {
public BlockPos pos;
public float progress;
public int freeze;
}
public final static class UseActionPack {
public int freeze;
}
}

View File

@ -1,9 +1,17 @@
package io.github.hello09x.fakeplayer.entity.action;
public class ActionSetting {
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class ActionSetting implements Cloneable {
/**
* 次数
* 总次数
*/
public final int limit;
/**
* 剩余次数
*/
public int times;
@ -18,31 +26,38 @@ public class ActionSetting {
public int wait;
public ActionSetting(int times, int interval) {
this.times = times;
this.interval = interval;
this.wait = 0;
this(times, interval, 0);
}
public ActionSetting(int times, int interval, int wait) {
this.limit = times;
this.times = times;
this.interval = interval;
this.wait = wait;
}
public static ActionSetting once() {
return new ActionSetting(1, 1, 0);
return new ActionSetting(1, 1 );
}
public static ActionSetting stop() {
return new ActionSetting(0, 1, 0);
return new ActionSetting(0, 1);
}
public static ActionSetting interval(int interval) {
return new ActionSetting(-1, interval, 0);
return new ActionSetting(-1, interval);
}
public static ActionSetting continuous() {
return new ActionSetting(-1, 1, 0);
return new ActionSetting(-1, 1);
}
@Override
public ActionSetting clone() {
return new ActionSetting(
this.times,
this.interval,
this.wait
);
}
}

View File

@ -32,7 +32,7 @@ public class PlayerListeners implements Listener {
* 拒绝假人用过的 ID 上线
*/
@EventHandler(ignoreCancelled = true)
public void handleUsedIdLogin(@NotNull AsyncPlayerPreLoginEvent event) {
public void onLogin(@NotNull AsyncPlayerPreLoginEvent event) {
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
return;
}
@ -49,7 +49,7 @@ public class PlayerListeners implements Listener {
* 死亡退出游戏
*/
@EventHandler(ignoreCancelled = true)
public void handlePlayerDeath(@NotNull PlayerDeathEvent event) {
public void onDead(@NotNull PlayerDeathEvent event) {
var player = event.getPlayer();
if (!manager.isFake(player)) {
return;
@ -63,7 +63,7 @@ public class PlayerListeners implements Listener {
* 退出游戏掉落背包
*/
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void handlePlayerQuit(@NotNull PlayerQuitEvent event) {
public void onQuit(@NotNull PlayerQuitEvent event) {
var player = event.getPlayer();
if (!manager.isFake(player)) {
return;

View File

@ -122,7 +122,7 @@ public class FakeplayerManager {
usedIdRepository.add(bukkitPlayer.getUniqueId());
dispatchCommands(bukkitPlayer, properties.getPreparingCommands());
performCommands(bukkitPlayer);
performCommands(bukkitPlayer, properties.getSelfCommands());
bukkitPlayer.teleport(spawnAt); // 当前 tick 必须传到出生点否则无法触发区块刷新
spawnAt.getWorld().playSound(spawnAt, Sound.ENTITY_ENDERMAN_TELEPORT, 1.0F, 1.0F);
@ -239,10 +239,8 @@ public class FakeplayerManager {
Metadatas.NAME_SEQUENCE.get(fakePlayer).asInt()
);
Arrays.stream(Metadatas.values()).forEach(meta -> meta.remove(fakePlayer));
if (properties.isDropInventoryOnQuiting()) {
Action.dropInventory(Unwrapper.getServerPlayer(fakePlayer));
}
}
/**
* 获取创建者创建的所有假人
@ -298,12 +296,15 @@ public class FakeplayerManager {
return uuid;
}
public void performCommands(@NotNull Player player) {
public void performCommands(@NotNull Player player, @NotNull List<String> commands) {
if (commands.isEmpty()) {
return;
}
if (!isFake(player)) {
return;
}
for (var cmd : properties.getSelfCommands()) {
for (var cmd : commands) {
cmd = cmd.trim();
if (cmd.startsWith("/")) {
cmd = cmd.substring(1);
@ -327,7 +328,7 @@ public class FakeplayerManager {
var server = Bukkit.getServer();
var sender = Bukkit.getConsoleSender();
for (var cmd : properties.getPreparingCommands()) {
for (var cmd : commands) {
cmd = cmd.trim();
if (cmd.startsWith("/")) {
cmd = cmd.substring(1);

View File

@ -77,11 +77,6 @@ public class FakeplayerProperties extends AbstractProperties<FakeplayerPropertie
*/
private boolean simulateLogin;
/**
* 下线时是否丢弃背包
*/
private boolean dropInventoryOnQuiting;
public FakeplayerProperties(@NotNull JavaPlugin plugin, @NotNull String version) {
super(plugin, version);
}
@ -102,7 +97,6 @@ public class FakeplayerProperties extends AbstractProperties<FakeplayerPropertie
this.destroyCommands = file.getStringList("destroy-commands");
this.nameTemplate = file.getString("name-template", "");
this.simulateLogin = file.getBoolean("simulate-login", false);
this.dropInventoryOnQuiting = file.getBoolean("drop-inventory-on-quiting", true);
if (this.nameTemplate.startsWith("-")) {
log.warning("假人名称模版不能以 - 开头, 该配置不会生效: " + this.nameTemplate);

View File

@ -10,5 +10,9 @@ public class MathUtils {
return Math.floor(num / base) * base;
}
public static float clamp(float value, float min, float max) {
return value < min ? min : Math.min(value, max);
}
}

View File

@ -24,16 +24,6 @@ name-template: ''
# 如果玩家只是切换服务器, 那么不会触发跟随下线
follow-quiting: true
# 下线时是否丢弃背包
# 如果服务器有背包同步之类的插件,请验证是否会有复制 bug
# 这个 bug 原因是先保存了背包再丢弃背包的物品导致地上和背包各一份
# 验证方法如下:
# 1. 创建假人,并给他一点东西
# 2. kick 掉假人, 此时他会丢弃背包的物品
# 3. 重新创建假人, 看看他背包的物品是否多了一份
drop-inventory-on-quiting: true
# 是否检测 IP
# 如果启用, 则一个 IP 只能创建 `maximum` 个假人
# 能够避免玩家开小号疯狂创建假人
detect-ip: true
@ -75,7 +65,7 @@ destroy-commands:
# 自执行命令
# 假人在诞生时会以自己的身份按顺序执行命令
# 你可以在这里做一些 /register 之类的命令
# 你可以在这里做添加 /register 和 /login 命令来防止 `AuthMe` 等插件踢掉超时未登陆的玩家
self-commands:
- ''
- ''

View File

@ -32,7 +32,7 @@ permissions:
default: op
fakeplayer.action:
description: '拥有 drop, dropinv, sneak 命令权限'
description: '拥有 drop, dropinv, sneak, look, move, jump 命令权限'
default: op
fakeplayer.experimental.action: