mirror of
https://github.com/tanyaofei/minecraft-fakeplayer.git
synced 2025-09-14 11:16:46 +08:00
1. 修复可能由于背包同步插件导致的物品复制、召唤时生命值为 0 的 bug
2. 修复判断是否有更新的逻辑 3. 添加更多注释
This commit is contained in:
parent
9a2c7715d7
commit
3219a2634c
4
pom.xml
4
pom.xml
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>io.github.hello09x</groupId>
|
<groupId>io.github.hello09x</groupId>
|
||||||
<artifactId>fakeplayer</artifactId>
|
<artifactId>fakeplayer</artifactId>
|
||||||
<version>0.1.7</version>
|
<version>0.1.8</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>fakeplayer</name>
|
<name>fakeplayer</name>
|
||||||
@ -117,7 +117,7 @@
|
|||||||
<relocations>
|
<relocations>
|
||||||
<relocation>
|
<relocation>
|
||||||
<pattern>dev.jorel.commandapi</pattern>
|
<pattern>dev.jorel.commandapi</pattern>
|
||||||
<shadedPattern>io.github.hello09x.shaded.commandapi</shadedPattern>
|
<shadedPattern>io.github.hello09x.fakeplayer.shaded.commandapi</shadedPattern>
|
||||||
</relocation>
|
</relocation>
|
||||||
</relocations>
|
</relocations>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -69,18 +69,27 @@ public final class Main extends JavaPlugin {
|
|||||||
|
|
||||||
public void checkForUpdatesAsync() {
|
public void checkForUpdatesAsync() {
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
|
var meta = getPluginMeta();
|
||||||
var checker = new UpdateChecker("tanyaofei", "minecraft-fakeplayer");
|
var checker = new UpdateChecker("tanyaofei", "minecraft-fakeplayer");
|
||||||
try {
|
try {
|
||||||
var release = checker.getLastRelease();
|
var release = checker.getLastRelease();
|
||||||
if (!release.getTagName().equals(getPluginMeta().getVersion())) {
|
|
||||||
|
var current = meta.getVersion();
|
||||||
|
var other = release.getTagName();
|
||||||
|
if (other.charAt(0) == 'v') {
|
||||||
|
other = other.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UpdateChecker.isNew(current, other)) {
|
||||||
var log = getLogger();
|
var log = getLogger();
|
||||||
log.info("检测到新的版本: " + release.getTagName());
|
log.info("检测到新的版本: " + release.getTagName());
|
||||||
log.info("前往此处下载 https://github.com/tanyaofei/minecraft-fakeplayer");
|
log.info("前往此处下载 " + meta.getWebsite());
|
||||||
log.info("更新日志");
|
log.info("更新日志");
|
||||||
for (var line : release.getBody().split("\n")) {
|
for (var line : release.getBody().split("\n")) {
|
||||||
log.info("\t" + line);
|
log.info("\t" + line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
getLogger().warning("检测新版本发生异常: " + e.getMessage());
|
getLogger().warning("检测新版本发生异常: " + e.getMessage());
|
||||||
}
|
}
|
||||||
@ -90,7 +99,7 @@ public final class Main extends JavaPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
CommandAPI.onDisable();
|
CommandAPI.onDisable();
|
||||||
FakeplayerManager.instance.removeAll();
|
FakeplayerManager.instance.removeAll("plugin disabled");
|
||||||
UsedIdRepository.instance.saveAll();
|
UsedIdRepository.instance.saveAll();
|
||||||
FakeplayerManager.instance.onDisable();
|
FakeplayerManager.instance.onDisable();
|
||||||
WildFakeplayerManager.instance.onDisable();
|
WildFakeplayerManager.instance.onDisable();
|
||||||
|
@ -32,9 +32,9 @@ public class ActionCommand extends AbstractCommand {
|
|||||||
|
|
||||||
private static String toLocationString(@NotNull Location location) {
|
private static String toLocationString(@NotNull Location location) {
|
||||||
return StringUtils.joinWith(", ",
|
return StringUtils.joinWith(", ",
|
||||||
Mth.round(location.getX(), 0.5),
|
Mth.floor(location.getX(), 0.5),
|
||||||
Mth.round(location.getY(), 0.5),
|
Mth.floor(location.getY(), 0.5),
|
||||||
Mth.round(location.getZ(), 0.5));
|
Mth.floor(location.getZ(), 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandExecutor action(@NotNull Action action, @NotNull ActionSetting setting) {
|
public CommandExecutor action(@NotNull Action action, @NotNull ActionSetting setting) {
|
||||||
|
@ -35,29 +35,28 @@ public class CommandRegistry {
|
|||||||
"可以创建模拟玩家的假人, 能保持附近区块的刷新、触发怪物生成。同时还提供了一些操作命令让你控制假人的物品、动作等等。"
|
"可以创建模拟玩家的假人, 能保持附近区块的刷新、触发怪物生成。同时还提供了一些操作命令让你控制假人的物品、动作等等。"
|
||||||
)
|
)
|
||||||
.withUsage(
|
.withUsage(
|
||||||
"§6? [页码] §7- §f查看帮助",
|
usage("spawn [名称] [世界] [坐标]", "创建假人"),
|
||||||
"§6spawn [名称] [世界] [坐标] §7- §f创建假人",
|
usage("kill", "移除假人"),
|
||||||
"§6kill §7- §f移除假人",
|
usage("list [页码] [数量]", "查看所有假人"),
|
||||||
"§6list [页码] [数量] §7- §f查看所有假人",
|
usage("distance", "查看与假人的距离"),
|
||||||
"§6distance §7- §f查看与假人的距离",
|
usage("tp", "传送到假人身边"),
|
||||||
"§6tp §7- §f传送到假人身边",
|
usage("tphere", "将假人传送到身边"),
|
||||||
"§6tphere §7- §f将假人传送到身边",
|
usage("tps", "与假人交换位置"),
|
||||||
"§6tps §7- §f与假人交换位置",
|
usage("config get <配置项>", "查看配置项"),
|
||||||
"§6config get <配置项> §7- §f查看配置项",
|
usage("config set <配置项> <值>", "设置配置项"),
|
||||||
"§6config set <配置项> <配置值> §7- §f设置配置项",
|
usage("health", "查看生命值"),
|
||||||
"§6health §7- §f查看生命值",
|
usage("exp", "查看经验值"),
|
||||||
"§6exp §7- §f查看经验值",
|
usage("expme", "转移经验值"),
|
||||||
"§6expme §7- §f转移经验值",
|
usage("attack (once | continuous | interval | stop)", "攻击/破坏"),
|
||||||
"§6attack (once | continuous | interval | stop) §7- §f攻击/破坏",
|
usage("use (once | continuous | interval | stop)", "使用/交互/放置"),
|
||||||
"§6use (once | continuous | interval | stop) §7- §f使用/交互/放置",
|
usage("jump (once | continuous | interval | stop)", "跳"),
|
||||||
"§6jump (once | continuous | interval | stop) §7- §f跳跃",
|
usage("drop [-a|--all]", "丢弃手上物品"),
|
||||||
"§6drop [-a|--all] §7- §f丢弃手上物品",
|
usage("dropinv", "丢弃背包物品"),
|
||||||
"§6dropinv §7- §f丢弃背包物品",
|
usage("look (north | south | east | west | up | down | at | entity)", "看向指定位置"),
|
||||||
"§6look (north | south | east | west | up | down | at | entity) §7- §f看向指定位置",
|
usage("turn (left | right | back | to)", "转身"),
|
||||||
"§6turn (left | right | back | to) §7- §f转身到指定位置",
|
usage("move (forward | backward | left |right)", "移动"),
|
||||||
"§6move (forward | backward | left | right) §7- §f移动",
|
usage("cmd", "执行命令"),
|
||||||
"§6cmd <假人> <命令> §7- §f执行命令",
|
usage("reload", "重新加载配置文件")
|
||||||
"§6reload §7- §f重载配置文件"
|
|
||||||
)
|
)
|
||||||
.withSubcommands(
|
.withSubcommands(
|
||||||
command("help")
|
command("help")
|
||||||
|
@ -3,7 +3,7 @@ package io.github.hello09x.fakeplayer.command;
|
|||||||
|
|
||||||
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
||||||
import dev.jorel.commandapi.executors.CommandArguments;
|
import dev.jorel.commandapi.executors.CommandArguments;
|
||||||
import io.github.hello09x.fakeplayer.util.Experience;
|
import io.github.hello09x.bedrock.io.Experiences;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ public class ExpCommand extends AbstractCommand {
|
|||||||
|
|
||||||
public void expme(@NotNull Player sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
|
public void expme(@NotNull Player sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
|
||||||
var target = getTarget(sender, args);
|
var target = getTarget(sender, args);
|
||||||
var exp = Experience.getExp(target);
|
var exp = Experiences.getExp(target);
|
||||||
|
|
||||||
if (exp == 0) {
|
if (exp == 0) {
|
||||||
sender.sendMessage(textOfChildren(
|
sender.sendMessage(textOfChildren(
|
||||||
@ -27,9 +27,8 @@ public class ExpCommand extends AbstractCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Experience.clean(target);
|
Experiences.clean(target);
|
||||||
Experience.changeExp(sender, Experience.getExp(sender) + exp);
|
sender.giveExp(exp, false);
|
||||||
|
|
||||||
sender.sendMessage(textOfChildren(
|
sender.sendMessage(textOfChildren(
|
||||||
text(target.getName(), WHITE),
|
text(target.getName(), WHITE),
|
||||||
text(" 转移 ", GRAY),
|
text(" 转移 ", GRAY),
|
||||||
|
@ -2,7 +2,7 @@ package io.github.hello09x.fakeplayer.command;
|
|||||||
|
|
||||||
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
||||||
import dev.jorel.commandapi.executors.CommandArguments;
|
import dev.jorel.commandapi.executors.CommandArguments;
|
||||||
import io.github.hello09x.fakeplayer.util.Experience;
|
import io.github.hello09x.bedrock.io.Experiences;
|
||||||
import io.github.hello09x.fakeplayer.util.Mth;
|
import io.github.hello09x.fakeplayer.util.Mth;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.bukkit.attribute.AttributeInstance;
|
import org.bukkit.attribute.AttributeInstance;
|
||||||
@ -24,7 +24,7 @@ public class ProfileCommand extends AbstractCommand {
|
|||||||
var target = getTarget(sender, args);
|
var target = getTarget(sender, args);
|
||||||
|
|
||||||
var level = target.getLevel();
|
var level = target.getLevel();
|
||||||
var total = Experience.getExp(target);
|
var total = Experiences.getExp(target);
|
||||||
sender.sendMessage(textOfChildren(
|
sender.sendMessage(textOfChildren(
|
||||||
text(target.getName(), WHITE),
|
text(target.getName(), WHITE),
|
||||||
text(" 当前 ", GRAY),
|
text(" 当前 ", GRAY),
|
||||||
@ -60,7 +60,7 @@ public class ProfileCommand extends AbstractCommand {
|
|||||||
sender.sendMessage(textOfChildren(
|
sender.sendMessage(textOfChildren(
|
||||||
text(target.getName(), WHITE),
|
text(target.getName(), WHITE),
|
||||||
text(" 当前生命值: ", GRAY),
|
text(" 当前生命值: ", GRAY),
|
||||||
text(Mth.round(health, 0.5), color),
|
text(Mth.floor(health, 0.5), color),
|
||||||
text("/", color),
|
text("/", color),
|
||||||
text(max, color)
|
text(max, color)
|
||||||
));
|
));
|
||||||
|
@ -15,7 +15,7 @@ public class ReloadCommand extends AbstractCommand {
|
|||||||
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
||||||
|
|
||||||
public void reload(@NotNull CommandSender sender, @NotNull CommandArguments args) {
|
public void reload(@NotNull CommandSender sender, @NotNull CommandArguments args) {
|
||||||
config.reload();
|
config.reload(true);
|
||||||
sender.sendMessage(text("重载配置文件完成", GRAY));
|
sender.sendMessage(text("重载配置文件完成", GRAY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ package io.github.hello09x.fakeplayer.command;
|
|||||||
|
|
||||||
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
||||||
import dev.jorel.commandapi.executors.CommandArguments;
|
import dev.jorel.commandapi.executors.CommandArguments;
|
||||||
|
import io.github.hello09x.bedrock.page.Page;
|
||||||
import io.github.hello09x.fakeplayer.command.Permission.Keepalive;
|
import io.github.hello09x.fakeplayer.command.Permission.Keepalive;
|
||||||
import io.github.hello09x.fakeplayer.util.Mth;
|
import io.github.hello09x.fakeplayer.util.Mth;
|
||||||
import io.github.tanyaofei.plugin.toolkit.database.Page;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
@ -20,6 +20,7 @@ import java.util.*;
|
|||||||
import static net.kyori.adventure.text.Component.*;
|
import static net.kyori.adventure.text.Component.*;
|
||||||
import static net.kyori.adventure.text.event.ClickEvent.runCommand;
|
import static net.kyori.adventure.text.event.ClickEvent.runCommand;
|
||||||
import static net.kyori.adventure.text.format.NamedTextColor.*;
|
import static net.kyori.adventure.text.format.NamedTextColor.*;
|
||||||
|
import static net.kyori.adventure.text.format.TextDecoration.BOLD;
|
||||||
|
|
||||||
public class SpawnCommand extends AbstractCommand {
|
public class SpawnCommand extends AbstractCommand {
|
||||||
|
|
||||||
@ -29,9 +30,9 @@ public class SpawnCommand extends AbstractCommand {
|
|||||||
return location.getWorld().getName()
|
return location.getWorld().getName()
|
||||||
+ ": "
|
+ ": "
|
||||||
+ StringUtils.joinWith(", ",
|
+ StringUtils.joinWith(", ",
|
||||||
Mth.round(location.getX(), 0.5),
|
Mth.floor(location.getX(), 0.5),
|
||||||
Mth.round(location.getY(), 0.5),
|
Mth.floor(location.getY(), 0.5),
|
||||||
Mth.round(location.getZ(), 0.5));
|
Mth.floor(location.getZ(), 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void spawn(@NotNull CommandSender sender, @NotNull CommandArguments args) {
|
public void spawn(@NotNull CommandSender sender, @NotNull CommandArguments args) {
|
||||||
@ -65,7 +66,7 @@ public class SpawnCommand extends AbstractCommand {
|
|||||||
text("你创建了假人 ", GRAY),
|
text("你创建了假人 ", GRAY),
|
||||||
text(player.getName()),
|
text(player.getName()),
|
||||||
text(", 位于 ", GRAY),
|
text(", 位于 ", GRAY),
|
||||||
text(toLocationString(player.getLocation())),
|
text(toLocationString(spawnpoint)),
|
||||||
Keepalive.isPermanent(keepalive)
|
Keepalive.isPermanent(keepalive)
|
||||||
? empty()
|
? empty()
|
||||||
: textOfChildren(
|
: textOfChildren(
|
||||||
@ -98,7 +99,7 @@ public class SpawnCommand extends AbstractCommand {
|
|||||||
|
|
||||||
var names = new StringJoiner(", ");
|
var names = new StringJoiner(", ");
|
||||||
for (var target : targets) {
|
for (var target : targets) {
|
||||||
if (fakeplayerManager.remove(target.getName())) {
|
if (fakeplayerManager.remove(target.getName(), "command kill")) {
|
||||||
names.add(target.getName());
|
names.add(target.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,19 +117,11 @@ public class SpawnCommand extends AbstractCommand {
|
|||||||
? fakeplayerManager.getAll()
|
? fakeplayerManager.getAll()
|
||||||
: fakeplayerManager.getAll(sender);
|
: fakeplayerManager.getAll(sender);
|
||||||
|
|
||||||
var total = fakers.size();
|
var p = Page.of(fakers, page, 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
|
|
||||||
);
|
|
||||||
|
|
||||||
var canTp = sender instanceof Player && sender.hasPermission(Permission.tp);
|
var canTp = sender instanceof Player && sender.hasPermission(Permission.tp);
|
||||||
sender.sendMessage(p.toComponent(
|
sender.sendMessage(p.asComponent(
|
||||||
"假人",
|
text("假人", AQUA, BOLD),
|
||||||
fakeplayer -> textOfChildren(
|
fakeplayer -> textOfChildren(
|
||||||
text(fakeplayer.getName() + " (" + fakeplayerManager.getCreator(fakeplayer) + ")", GOLD),
|
text(fakeplayer.getName() + " (" + fakeplayerManager.getCreator(fakeplayer) + ")", GOLD),
|
||||||
text(" - ", GRAY),
|
text(" - ", GRAY),
|
||||||
@ -136,8 +129,7 @@ public class SpawnCommand extends AbstractCommand {
|
|||||||
canTp ? text(" [<--传送]", AQUA).clickEvent(runCommand("/fp tp " + fakeplayer.getName())) : empty(),
|
canTp ? text(" [<--传送]", AQUA).clickEvent(runCommand("/fp tp " + fakeplayer.getName())) : empty(),
|
||||||
text(" [<--移除]", RED).clickEvent(runCommand("/fp kill " + fakeplayer.getName()))
|
text(" [<--移除]", RED).clickEvent(runCommand("/fp kill " + fakeplayer.getName()))
|
||||||
),
|
),
|
||||||
String.format("/fp list %d %d", page - 1, size),
|
i -> "/fp list " + i + " " + size
|
||||||
String.format("/fp list %d %d", page + 1, size)
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +150,7 @@ public class SpawnCommand extends AbstractCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var euclidean = Mth.round(from.distance(to), 0.5);
|
var euclidean = Mth.floor(from.distance(to), 0.5);
|
||||||
var x = Math.abs(from.getBlockX() - to.getBlockX());
|
var x = Math.abs(from.getBlockX() - to.getBlockX());
|
||||||
var y = Math.abs(from.getBlockY() - to.getBlockY());
|
var y = Math.abs(from.getBlockY() - to.getBlockY());
|
||||||
var z = Math.abs(from.getBlockZ() - to.getBlockZ());
|
var z = Math.abs(from.getBlockZ() - to.getBlockZ());
|
||||||
|
@ -21,6 +21,7 @@ import java.util.regex.PatternSyntaxException;
|
|||||||
public class FakeplayerConfig extends Config<FakeplayerConfig> {
|
public class FakeplayerConfig extends Config<FakeplayerConfig> {
|
||||||
|
|
||||||
public final static FakeplayerConfig instance;
|
public final static FakeplayerConfig instance;
|
||||||
|
|
||||||
private final static Logger log;
|
private final static Logger log;
|
||||||
|
|
||||||
private final static String defaultNameChars = "^[a-zA-Z0-9_]+$";
|
private final static String defaultNameChars = "^[a-zA-Z0-9_]+$";
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.hello09x.fakeplayer.entity;
|
package io.github.hello09x.fakeplayer.entity;
|
||||||
|
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import io.github.hello09x.bedrock.task.Tasks;
|
||||||
import io.github.hello09x.fakeplayer.Main;
|
import io.github.hello09x.fakeplayer.Main;
|
||||||
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
||||||
import io.github.hello09x.fakeplayer.manager.action.Action;
|
import io.github.hello09x.fakeplayer.manager.action.Action;
|
||||||
@ -10,9 +11,8 @@ import io.github.hello09x.fakeplayer.manager.naming.SequenceName;
|
|||||||
import io.github.hello09x.fakeplayer.network.EmptyConnection;
|
import io.github.hello09x.fakeplayer.network.EmptyConnection;
|
||||||
import io.github.hello09x.fakeplayer.network.EmptyLoginPacketListener;
|
import io.github.hello09x.fakeplayer.network.EmptyLoginPacketListener;
|
||||||
import io.github.hello09x.fakeplayer.network.EmptyServerGamePacketListener;
|
import io.github.hello09x.fakeplayer.network.EmptyServerGamePacketListener;
|
||||||
import io.github.hello09x.fakeplayer.util.BK;
|
|
||||||
import io.github.hello09x.fakeplayer.util.Tasker;
|
|
||||||
import io.github.hello09x.fakeplayer.util.Teleportor;
|
import io.github.hello09x.fakeplayer.util.Teleportor;
|
||||||
|
import io.github.hello09x.fakeplayer.util.Worlds;
|
||||||
import io.github.hello09x.fakeplayer.util.nms.NMS;
|
import io.github.hello09x.fakeplayer.util.nms.NMS;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.minecraft.network.protocol.PacketFlow;
|
import net.minecraft.network.protocol.PacketFlow;
|
||||||
@ -23,7 +23,6 @@ import org.bukkit.Location;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||||
import org.bukkit.event.player.PlayerLoginEvent;
|
import org.bukkit.event.player.PlayerLoginEvent;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -47,28 +46,37 @@ public class FakePlayer {
|
|||||||
|
|
||||||
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
private final MinecraftServer server;
|
private final MinecraftServer server;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
private final String creator;
|
private final String creator;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
private final ServerPlayer handle;
|
private final ServerPlayer handle;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
private final Player bukkitPlayer;
|
private final Player bukkitPlayer;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
private final String creatorIp;
|
private final String creatorIp;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
private final SequenceName sequenceName;
|
private final SequenceName sequenceName;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
private final FakeplayerTicker ticker;
|
private final FakeplayerTicker ticker;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Getter
|
@Getter
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
private final UUID uuid;
|
private final UUID uuid;
|
||||||
|
|
||||||
public FakePlayer(
|
public FakePlayer(
|
||||||
@ -94,7 +102,7 @@ public class FakePlayer {
|
|||||||
|
|
||||||
bukkitPlayer.setPersistent(false);
|
bukkitPlayer.setPersistent(false);
|
||||||
bukkitPlayer.setSleepingIgnored(true);
|
bukkitPlayer.setSleepingIgnored(true);
|
||||||
nms.setPlayBefore(bukkitPlayer);
|
nms.setPlayBefore(bukkitPlayer); // 可避免一些插件的第一次入服欢迎信息
|
||||||
nms.unpersistAdvancements(bukkitPlayer);
|
nms.unpersistAdvancements(bukkitPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,28 +111,22 @@ public class FakePlayer {
|
|||||||
*/
|
*/
|
||||||
public void spawn(@NotNull SpawnOption option) {
|
public void spawn(@NotNull SpawnOption option) {
|
||||||
if (config.isSimulateLogin()) {
|
if (config.isSimulateLogin()) {
|
||||||
new BukkitRunnable() {
|
Tasks.runAsync(Main.getInstance(), () -> {
|
||||||
@Override
|
Bukkit.getPluginManager().callEvent(new AsyncPlayerPreLoginEvent(
|
||||||
public void run() {
|
handle.getGameProfile().getName(),
|
||||||
var preLoginEvent = new AsyncPlayerPreLoginEvent(
|
fakeAddress,
|
||||||
handle.getGameProfile().getName(),
|
fakeAddress,
|
||||||
fakeAddress,
|
handle.getUUID(),
|
||||||
fakeAddress,
|
bukkitPlayer.getPlayerProfile(),
|
||||||
handle.getUUID(),
|
fakeAddress.getHostName()
|
||||||
bukkitPlayer.getPlayerProfile(),
|
|
||||||
fakeAddress.getHostName()
|
|
||||||
);
|
|
||||||
Bukkit.getPluginManager().callEvent(preLoginEvent);
|
|
||||||
}
|
|
||||||
}.runTaskAsynchronously(Main.getInstance());
|
|
||||||
|
|
||||||
{
|
|
||||||
Bukkit.getPluginManager().callEvent(new PlayerLoginEvent(
|
|
||||||
bukkitPlayer,
|
|
||||||
fakeAddress.getHostName(),
|
|
||||||
fakeAddress
|
|
||||||
));
|
));
|
||||||
}
|
});
|
||||||
|
|
||||||
|
Bukkit.getPluginManager().callEvent(new PlayerLoginEvent(
|
||||||
|
bukkitPlayer,
|
||||||
|
fakeAddress.getHostName(),
|
||||||
|
fakeAddress
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -144,6 +146,13 @@ public class FakePlayer {
|
|||||||
connection.setListener(listener);
|
connection.setListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.isDropInventoryOnQuiting()) {
|
||||||
|
// 跨服背包同步插件可能导致假人既丢弃了一份到地上,在重新生成的时候又回来了
|
||||||
|
// 因此在生成的时候清空一次背包
|
||||||
|
// 但无法解决登陆后延迟同步背包的情况
|
||||||
|
bukkitPlayer.getInventory().clear();
|
||||||
|
}
|
||||||
|
|
||||||
bukkitPlayer.setInvulnerable(option.invulnerable());
|
bukkitPlayer.setInvulnerable(option.invulnerable());
|
||||||
bukkitPlayer.setCollidable(option.collidable());
|
bukkitPlayer.setCollidable(option.collidable());
|
||||||
bukkitPlayer.setCanPickupItems(option.pickupItems());
|
bukkitPlayer.setCanPickupItems(option.pickupItems());
|
||||||
@ -152,7 +161,7 @@ public class FakePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var spawnAt = option.spawnAt().clone();
|
var spawnAt = option.spawnAt().clone();
|
||||||
if (BK.isOverworld(spawnAt.getWorld())) {
|
if (Worlds.isOverworld(spawnAt.getWorld())) {
|
||||||
// 创建在主世界时需要跨越一次世界才能拥有刷怪能力
|
// 创建在主世界时需要跨越一次世界才能拥有刷怪能力
|
||||||
teleportToSpawnpointAfterChangingDimension(spawnAt);
|
teleportToSpawnpointAfterChangingDimension(spawnAt);
|
||||||
} else {
|
} else {
|
||||||
@ -168,7 +177,7 @@ public class FakePlayer {
|
|||||||
* @param spawnpoint 最终目的地, 即出生点
|
* @param spawnpoint 最终目的地, 即出生点
|
||||||
*/
|
*/
|
||||||
private void teleportToSpawnpointAfterChangingDimension(@NotNull Location spawnpoint) {
|
private void teleportToSpawnpointAfterChangingDimension(@NotNull Location spawnpoint) {
|
||||||
var world = BK.getNonOverworld();
|
var world = Worlds.getNonOverworld();
|
||||||
if (world == null || !bukkitPlayer.teleport(world.getSpawnLocation())) {
|
if (world == null || !bukkitPlayer.teleport(world.getSpawnLocation())) {
|
||||||
Optional.ofNullable(Bukkit.getPlayerExact(creator))
|
Optional.ofNullable(Bukkit.getPlayerExact(creator))
|
||||||
.ifPresentOrElse(
|
.ifPresentOrElse(
|
||||||
@ -181,7 +190,7 @@ public class FakePlayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tasker.nextTick(() -> teleportToSpawnpoint(spawnpoint));
|
Tasks.runNextTick(Main.getInstance(), () -> teleportToSpawnpoint(spawnpoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void teleportToSpawnpoint(@NotNull Location spawnpoint) {
|
private void teleportToSpawnpoint(@NotNull Location spawnpoint) {
|
||||||
@ -208,4 +217,8 @@ public class FakePlayer {
|
|||||||
return Bukkit.getPlayerExact(this.creator);
|
return Bukkit.getPlayerExact(this.creator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTickCount() {
|
||||||
|
return this.handle.tickCount;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package io.github.hello09x.fakeplayer.entity;
|
package io.github.hello09x.fakeplayer.entity;
|
||||||
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
@ -17,9 +17,17 @@ import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
|
|||||||
|
|
||||||
public class FakeplayerTicker extends BukkitRunnable {
|
public class FakeplayerTicker extends BukkitRunnable {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
private final FakePlayer player;
|
private final FakePlayer player;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除时间
|
||||||
|
* <p>如果不需要定时移除则为 0</p>
|
||||||
|
*/
|
||||||
private final long removeAt;
|
private final long removeAt;
|
||||||
|
|
||||||
|
private final FakeplayerManager manager = FakeplayerManager.instance;
|
||||||
|
|
||||||
public FakeplayerTicker(
|
public FakeplayerTicker(
|
||||||
@NotNull FakePlayer player,
|
@NotNull FakePlayer player,
|
||||||
@Nullable LocalDateTime removeAt
|
@Nullable LocalDateTime removeAt
|
||||||
@ -30,15 +38,14 @@ public class FakeplayerTicker extends BukkitRunnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!getBukkitPlayer().isOnline()) {
|
if (!player.isOnline()) {
|
||||||
cancel();
|
cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = getServerPlayer();
|
if (removeAt != 0 && player.getTickCount() % 20 == 0 && System.currentTimeMillis() > removeAt) {
|
||||||
if (removeAt != 0 && player.tickCount % 20 == 0 && System.currentTimeMillis() > removeAt) {
|
manager.remove(player.getName(), "存活时间到期");
|
||||||
getBukkitPlayer().kick(text("[fakeplayer] 存活时间到期"));
|
Optional.ofNullable(player.getCreatorPlayer())
|
||||||
Optional.ofNullable(this.player.getCreatorPlayer())
|
|
||||||
.ifPresent(p -> p.sendMessage(textOfChildren(
|
.ifPresent(p -> p.sendMessage(textOfChildren(
|
||||||
text("假人 ", GRAY),
|
text("假人 ", GRAY),
|
||||||
text(this.player.getName()),
|
text(this.player.getName()),
|
||||||
@ -48,22 +55,26 @@ public class FakeplayerTicker extends BukkitRunnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.tickCount == 0) {
|
var handle = player.getHandle();
|
||||||
var x = player.getX();
|
|
||||||
var y = player.getY();
|
if (handle.tickCount == 0) {
|
||||||
var z = player.getZ();
|
// region 处理第一次生成时被别的插件干预然后随机传送
|
||||||
|
var x = handle.getX();
|
||||||
|
var y = handle.getY();
|
||||||
|
var z = handle.getZ();
|
||||||
|
|
||||||
// 将本 tick 的移动取消
|
// 将本 tick 的移动取消
|
||||||
player.xo = x;
|
handle.xo = x;
|
||||||
player.yo = y;
|
handle.yo = y;
|
||||||
player.zo = z;
|
handle.zo = z;
|
||||||
player.doTick();
|
handle.doTick();
|
||||||
|
|
||||||
// clearFog 插件会在第一次传送的时候改变了玩家的位置, 因此必须进行一次传送
|
// clearFog 插件会在第一次传送的时候改变了玩家的位置, 因此必须进行一次传送
|
||||||
getBukkitPlayer().teleport(new Location(getBukkitPlayer().getWorld(), x, y, z, player.getYRot(), player.getXRot()));
|
getBukkitPlayer().teleport(new Location(getBukkitPlayer().getWorld(), x, y, z, handle.getYRot(), handle.getXRot()));
|
||||||
player.absMoveTo(x, y, z, player.getYRot(), player.getXRot());
|
handle.absMoveTo(x, y, z, handle.getYRot(), handle.getXRot());
|
||||||
|
// endregion
|
||||||
} else {
|
} else {
|
||||||
player.doTick();
|
handle.doTick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +82,4 @@ public class FakeplayerTicker extends BukkitRunnable {
|
|||||||
return this.player.getBukkitPlayer();
|
return this.player.getBukkitPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NotNull ServerPlayer getServerPlayer() {
|
|
||||||
return this.player.getHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import io.github.hello09x.fakeplayer.Main;
|
|||||||
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
||||||
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
|
import io.github.hello09x.fakeplayer.manager.FakeplayerManager;
|
||||||
import io.github.hello09x.fakeplayer.repository.UsedIdRepository;
|
import io.github.hello09x.fakeplayer.repository.UsedIdRepository;
|
||||||
|
import org.bukkit.attribute.Attribute;
|
||||||
|
import org.bukkit.attribute.AttributeInstance;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@ -13,6 +15,7 @@ import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
|||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static net.kyori.adventure.text.Component.text;
|
import static net.kyori.adventure.text.Component.text;
|
||||||
@ -49,9 +52,20 @@ public class PlayerListeners implements Listener {
|
|||||||
/**
|
/**
|
||||||
* 死亡退出游戏
|
* 死亡退出游戏
|
||||||
*/
|
*/
|
||||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||||
public void onDead(@NotNull PlayerDeathEvent event) {
|
public void onDead(@NotNull PlayerDeathEvent event) {
|
||||||
manager.remove(event.getPlayer().getName());
|
var player = event.getPlayer();
|
||||||
|
if (!manager.isFake(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有一些跨服同步插件会退出时同步生命值, 假人重新生成的时候同步为 0
|
||||||
|
// 因此在死亡时将生命值设置恢复满血先
|
||||||
|
Optional.ofNullable(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
||||||
|
.map(AttributeInstance::getValue)
|
||||||
|
.ifPresent(player::setHealth);
|
||||||
|
event.setCancelled(true);
|
||||||
|
manager.remove(event.getPlayer().getName(), "dead");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,6 +3,7 @@ package io.github.hello09x.fakeplayer.manager;
|
|||||||
import io.github.hello09x.fakeplayer.entity.FakePlayer;
|
import io.github.hello09x.fakeplayer.entity.FakePlayer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -16,6 +17,11 @@ public class FakeplayerList {
|
|||||||
|
|
||||||
private final Map<String, List<FakePlayer>> playersByCreator = new HashMap<>();
|
private final Map<String, List<FakePlayer>> playersByCreator = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个假人到假人清单
|
||||||
|
*
|
||||||
|
* @param player 假人
|
||||||
|
*/
|
||||||
public void add(@NotNull FakePlayer player) {
|
public void add(@NotNull FakePlayer player) {
|
||||||
this.playersByName.put(player.getName(), player);
|
this.playersByName.put(player.getName(), player);
|
||||||
this.playersByUUID.put(player.getUUID(), player);
|
this.playersByUUID.put(player.getUUID(), player);
|
||||||
@ -23,41 +29,80 @@ public class FakeplayerList {
|
|||||||
this.playersByCreator.get(player.getCreator()).add(player);
|
this.playersByCreator.get(player.getCreator()).add(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过假人的名称获取假人
|
||||||
|
*
|
||||||
|
* @param name 名称
|
||||||
|
* @return 假人
|
||||||
|
*/
|
||||||
public @Nullable FakePlayer getByName(@NotNull String name) {
|
public @Nullable FakePlayer getByName(@NotNull String name) {
|
||||||
return Optional.ofNullable(this.playersByName.get(name)).map(this::checkOnline).orElse(null);
|
return Optional.ofNullable(this.playersByName.get(name)).map(this::checkOnline).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 UUID 获取假人
|
||||||
|
*
|
||||||
|
* @param uuid UUID
|
||||||
|
* @return 假人
|
||||||
|
*/
|
||||||
public @Nullable FakePlayer getByUUID(@NotNull UUID uuid) {
|
public @Nullable FakePlayer getByUUID(@NotNull UUID uuid) {
|
||||||
return Optional.ofNullable(this.playersByUUID.get(uuid)).map(this::checkOnline).orElse(null);
|
return Optional.ofNullable(this.playersByUUID.get(uuid)).map(this::checkOnline).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull List<FakePlayer> getByCreator(@NotNull String creator) {
|
/**
|
||||||
|
* 获取创建者创建的所有假人
|
||||||
|
*
|
||||||
|
* @param creator 创建者
|
||||||
|
* @return 假人
|
||||||
|
*/
|
||||||
|
public @NotNull @Unmodifiable List<FakePlayer> getByCreator(@NotNull String creator) {
|
||||||
return Optional.ofNullable(this.playersByCreator.get(creator)).map(Collections::unmodifiableList).orElse(Collections.emptyList());
|
return Optional.ofNullable(this.playersByCreator.get(creator)).map(Collections::unmodifiableList).orElse(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除一个假人
|
||||||
|
*
|
||||||
|
* @param player 假人
|
||||||
|
*/
|
||||||
public void remove(@NotNull FakePlayer player) {
|
public void remove(@NotNull FakePlayer player) {
|
||||||
this.playersByName.remove(player.getName());
|
this.playersByName.remove(player.getName());
|
||||||
this.playersByUUID.remove(player.getUUID());
|
this.playersByUUID.remove(player.getUUID());
|
||||||
Optional.ofNullable(this.playersByCreator.get(player.getCreator())).map(players -> players.remove(player));
|
Optional.ofNullable(this.playersByCreator.get(player.getCreator())).map(players -> players.remove(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable FakePlayer removeByUUID(@NotNull UUID uniqueId) {
|
/**
|
||||||
var player = getByUUID(uniqueId);
|
* 通过 UUID 移除假人
|
||||||
|
*
|
||||||
|
* @param uuid UUID
|
||||||
|
* @return 被移除的假人
|
||||||
|
*/
|
||||||
|
public @Nullable FakePlayer removeByUUID(@NotNull UUID uuid) {
|
||||||
|
var player = getByUUID(uuid);
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
remove(player);
|
this.remove(player);
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FakePlayer> getAll() {
|
/**
|
||||||
|
* 获取所有假人
|
||||||
|
*
|
||||||
|
* @return 假人
|
||||||
|
*/
|
||||||
|
public @NotNull @Unmodifiable List<FakePlayer> getAll() {
|
||||||
return List.copyOf(this.playersByUUID.values());
|
return List.copyOf(this.playersByUUID.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测假人是否在线, 如果不在线了则移除并返回 {@code null}
|
||||||
|
*
|
||||||
|
* @param player 假人
|
||||||
|
* @return 假人
|
||||||
|
*/
|
||||||
private @Nullable FakePlayer checkOnline(@NotNull FakePlayer player) {
|
private @Nullable FakePlayer checkOnline(@NotNull FakePlayer player) {
|
||||||
if (!player.isOnline()) {
|
if (!player.isOnline()) {
|
||||||
this.playersByName.remove(player.getName());
|
this.remove(player);
|
||||||
this.playersByUUID.remove(player.getUUID());
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.hello09x.fakeplayer.manager;
|
package io.github.hello09x.fakeplayer.manager;
|
||||||
|
|
||||||
|
import io.github.hello09x.bedrock.task.Tasks;
|
||||||
import io.github.hello09x.fakeplayer.Main;
|
import io.github.hello09x.fakeplayer.Main;
|
||||||
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
||||||
import io.github.hello09x.fakeplayer.entity.FakePlayer;
|
import io.github.hello09x.fakeplayer.entity.FakePlayer;
|
||||||
@ -13,12 +14,10 @@ import io.github.hello09x.fakeplayer.repository.UserConfigRepository;
|
|||||||
import io.github.hello09x.fakeplayer.repository.model.Configs;
|
import io.github.hello09x.fakeplayer.repository.model.Configs;
|
||||||
import io.github.hello09x.fakeplayer.util.AddressUtils;
|
import io.github.hello09x.fakeplayer.util.AddressUtils;
|
||||||
import io.github.hello09x.fakeplayer.util.Commands;
|
import io.github.hello09x.fakeplayer.util.Commands;
|
||||||
import io.github.hello09x.fakeplayer.util.Tasker;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -57,14 +56,11 @@ public class FakeplayerManager {
|
|||||||
private FakeplayerManager() {
|
private FakeplayerManager() {
|
||||||
timer.scheduleAtFixedRate(() -> {
|
timer.scheduleAtFixedRate(() -> {
|
||||||
if (Bukkit.getServer().getTPS()[1] < config.getKaleTps()) {
|
if (Bukkit.getServer().getTPS()[1] < config.getKaleTps()) {
|
||||||
new BukkitRunnable() {
|
Tasks.runNextTick(Main.getInstance(), () -> {
|
||||||
@Override
|
if (removeAll("low tps") > 0) {
|
||||||
public void run() {
|
Bukkit.broadcast(text("[服务器过于卡顿, 已移除所有假人]", RED, ITALIC));
|
||||||
if (removeAll() > 0) {
|
|
||||||
Bukkit.broadcast(text("[服务器过于卡顿, 已移除所有假人]", RED, ITALIC));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.runTask(Main.getInstance());
|
});
|
||||||
}
|
}
|
||||||
}, 0, 60, TimeUnit.SECONDS
|
}, 0, 60, TimeUnit.SECONDS
|
||||||
);
|
);
|
||||||
@ -103,19 +99,19 @@ public class FakeplayerManager {
|
|||||||
try {
|
try {
|
||||||
sn = name.isBlank() ? nameManager.register(creator) : nameManager.custom(creator, name);
|
sn = name.isBlank() ? nameManager.register(creator) : nameManager.custom(creator, name);
|
||||||
} catch (IllegalCustomNameException e) {
|
} catch (IllegalCustomNameException e) {
|
||||||
creator.sendMessage(e.getMsg());
|
creator.sendMessage(e.getText());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = new FakePlayer(
|
var fake = new FakePlayer(
|
||||||
creator.getName(),
|
creator.getName(),
|
||||||
AddressUtils.getAddress(creator),
|
AddressUtils.getAddress(creator),
|
||||||
sn,
|
sn,
|
||||||
removeAt
|
removeAt
|
||||||
);
|
);
|
||||||
|
|
||||||
var bukkitPlayer = player.getBukkitPlayer();
|
var player = fake.getBukkitPlayer();
|
||||||
bukkitPlayer.playerListName(text(bukkitPlayer.getName(), GRAY, ITALIC));
|
player.playerListName(text(player.getName(), GRAY, ITALIC));
|
||||||
|
|
||||||
boolean invulnerable = true, lookAtEntity = true, collidable = true, pickupItems = true;
|
boolean invulnerable = true, lookAtEntity = true, collidable = true, pickupItems = true;
|
||||||
if (creator instanceof Player p) {
|
if (creator instanceof Player p) {
|
||||||
@ -125,7 +121,7 @@ public class FakeplayerManager {
|
|||||||
collidable = userConfigRepository.selectOrDefault(creatorId, Configs.collidable);
|
collidable = userConfigRepository.selectOrDefault(creatorId, Configs.collidable);
|
||||||
pickupItems = userConfigRepository.selectOrDefault(creatorId, Configs.pickup_items);
|
pickupItems = userConfigRepository.selectOrDefault(creatorId, Configs.pickup_items);
|
||||||
}
|
}
|
||||||
player.spawn(new SpawnOption(
|
fake.spawn(new SpawnOption(
|
||||||
spawnAt,
|
spawnAt,
|
||||||
invulnerable,
|
invulnerable,
|
||||||
collidable,
|
collidable,
|
||||||
@ -133,15 +129,15 @@ public class FakeplayerManager {
|
|||||||
pickupItems
|
pickupItems
|
||||||
));
|
));
|
||||||
|
|
||||||
playerList.add(player);
|
playerList.add(fake);
|
||||||
usedIdRepository.add(bukkitPlayer.getUniqueId());
|
usedIdRepository.add(player.getUniqueId());
|
||||||
|
|
||||||
Tasker.later(() -> {
|
Tasks.runLater(Main.getInstance(), 20, () -> {
|
||||||
dispatchCommands(bukkitPlayer, config.getPreparingCommands());
|
dispatchCommands(player, config.getPreparingCommands());
|
||||||
performCommands(bukkitPlayer, config.getSelfCommands());
|
performCommands(player, config.getSelfCommands());
|
||||||
}, 20);
|
});
|
||||||
|
|
||||||
return bukkitPlayer;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,22 +168,6 @@ public class FakeplayerManager {
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据名称删除假人
|
|
||||||
*
|
|
||||||
* @param name 名称
|
|
||||||
* @return 名称对应的玩家不在线或者不是假人
|
|
||||||
*/
|
|
||||||
public boolean remove(@NotNull String name) {
|
|
||||||
var player = get(name);
|
|
||||||
if (player == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.kick(text("[fakeplayer] removed"));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取一个假人的创建者, 如果这个玩家不是假人, 则为 {@code null}
|
* 获取一个假人的创建者, 如果这个玩家不是假人, 则为 {@code null}
|
||||||
*
|
*
|
||||||
@ -201,14 +181,32 @@ public class FakeplayerManager {
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据名称删除假人
|
||||||
|
*
|
||||||
|
* @param name 名称
|
||||||
|
* @return 名称对应的玩家不在线或者不是假人
|
||||||
|
*/
|
||||||
|
public boolean remove(@NotNull String name, @Nullable String reason) {
|
||||||
|
var player = this.get(name);
|
||||||
|
if (player == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.kick(text("[fakeplayer] " + (reason == null ? "removed" : reason)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除所有假人
|
* 移除所有假人
|
||||||
*
|
*
|
||||||
* @return 移除的假人数量
|
* @return 移除的假人数量
|
||||||
*/
|
*/
|
||||||
public int removeAll() {
|
public int removeAll(@Nullable String reason) {
|
||||||
var fakers = getAll();
|
var fakers = getAll();
|
||||||
fakers.forEach(Player::kick);
|
for (var f : fakers) {
|
||||||
|
f.kick(text("[fakeplayer] " + (reason == null ? "removed" : reason)));
|
||||||
|
}
|
||||||
return fakers.size();
|
return fakers.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ public class WildFakeplayerManager implements PluginMessageListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
for (var target : targets) {
|
for (var target : targets) {
|
||||||
manager.remove(target.getName());
|
manager.remove(target.getName(), "creator offline");
|
||||||
}
|
}
|
||||||
log.info(String.format("玩家 %s 已不在线, 移除他创建的 %d 个假人", entry.getKey(), entry.getValue().size()));
|
log.info(String.format("玩家 %s 已不在线, 移除他创建的 %d 个假人", entry.getKey(), entry.getValue().size()));
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import net.minecraft.world.phys.BlockHitResult;
|
|||||||
import net.minecraft.world.phys.EntityHitResult;
|
import net.minecraft.world.phys.EntityHitResult;
|
||||||
import net.minecraft.world.phys.HitResult;
|
import net.minecraft.world.phys.HitResult;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Damageable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -234,6 +235,7 @@ public enum Action {
|
|||||||
},
|
},
|
||||||
|
|
||||||
LOOK_AT_NEAREST_ENTITY("目视实体") {
|
LOOK_AT_NEAREST_ENTITY("目视实体") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
|
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
|
||||||
@ -243,14 +245,21 @@ public enum Action {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entities = bukkitPlayer.getNearbyEntities(4.5, 4.5, 4.5);
|
var target = bukkitPlayer
|
||||||
if (entities.isEmpty()) {
|
.getNearbyEntities(4.5, 4.5, 4.5)
|
||||||
|
.stream()
|
||||||
|
.filter(e -> e instanceof Damageable)
|
||||||
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bukkitPlayer.lookAt(entities.get(0), LookAnchor.EYES, LookAnchor.EYES);
|
bukkitPlayer.lookAt(target, LookAnchor.EYES, LookAnchor.EYES);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
DROP_ITEM("丢弃手上物品") {
|
DROP_ITEM("丢弃手上物品") {
|
||||||
@ -283,6 +292,7 @@ public enum Action {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
static @Nullable HitResult getTarget(@NotNull ServerPlayer player) {
|
static @Nullable HitResult getTarget(@NotNull ServerPlayer player) {
|
||||||
@ -300,11 +310,12 @@ public enum Action {
|
|||||||
|
|
||||||
public abstract boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting);
|
public abstract boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting);
|
||||||
|
|
||||||
public void stop(@NotNull ActionPack ap, @NotNull ActionSetting setting) {}
|
|
||||||
|
|
||||||
public void inactiveTick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
|
public void inactiveTick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
|
||||||
this.stop(ap, setting);
|
this.stop(ap, setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stop(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ public class ActionManager {
|
|||||||
|
|
||||||
public final static ActionManager instance = new ActionManager();
|
public final static ActionManager instance = new ActionManager();
|
||||||
|
|
||||||
private final ConcurrentMap<UUID, Map<Action, ActionTicker>> MANAGERS = new ConcurrentHashMap<>();
|
private final ConcurrentMap<UUID, Map<Action, ActionTicker>> managers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public ActionManager() {
|
public ActionManager() {
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
@ -33,7 +33,7 @@ public class ActionManager {
|
|||||||
@NotNull Action action,
|
@NotNull Action action,
|
||||||
@NotNull ActionSetting setting
|
@NotNull ActionSetting setting
|
||||||
) {
|
) {
|
||||||
var managers = MANAGERS.computeIfAbsent(player.getUniqueId(), key -> new HashMap<>());
|
var managers = this.managers.computeIfAbsent(player.getUniqueId(), key -> new HashMap<>());
|
||||||
var ticker = managers.computeIfAbsent(action, key -> new ActionTicker(
|
var ticker = managers.computeIfAbsent(action, key -> new ActionTicker(
|
||||||
Main.getNms().getServerPlayer(player),
|
Main.getNms().getServerPlayer(player),
|
||||||
action,
|
action,
|
||||||
@ -43,11 +43,13 @@ public class ActionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void tick() {
|
public void tick() {
|
||||||
var itr = MANAGERS.entrySet().iterator();
|
var itr = managers.entrySet().iterator();
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
var entry = itr.next();
|
var entry = itr.next();
|
||||||
var player = Bukkit.getPlayer(entry.getKey());
|
var player = Bukkit.getPlayer(entry.getKey());
|
||||||
|
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
|
// 假人下线
|
||||||
itr.remove();
|
itr.remove();
|
||||||
for (var ticker : entry.getValue().values()) {
|
for (var ticker : entry.getValue().values()) {
|
||||||
ticker.stop();
|
ticker.stop();
|
||||||
@ -55,6 +57,7 @@ public class ActionManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do tick
|
||||||
for (var ticker : entry.getValue().values()) {
|
for (var ticker : entry.getValue().values()) {
|
||||||
ticker.tick();
|
ticker.tick();
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,57 @@ package io.github.hello09x.fakeplayer.manager.action;
|
|||||||
|
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class ActionPack {
|
public class ActionPack {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 假人玩家
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
public final ServerPlayer player;
|
public final ServerPlayer player;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 左键数据
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
public final AttackActionPack attack = new AttackActionPack();
|
public final AttackActionPack attack = new AttackActionPack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 右键相关数据
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
public final UseActionPack use = new UseActionPack();
|
public final UseActionPack use = new UseActionPack();
|
||||||
|
|
||||||
public ActionPack(ServerPlayer player) {
|
public ActionPack(@NotNull ServerPlayer player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final static class AttackActionPack {
|
public final static class AttackActionPack {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前左键的目标位置
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
public BlockPos pos;
|
public BlockPos pos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 破坏方块的进度
|
||||||
|
*/
|
||||||
public float progress;
|
public float progress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冷却, 单位: tick
|
||||||
|
*/
|
||||||
public int freeze;
|
public int freeze;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final static class UseActionPack {
|
public final static class UseActionPack {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冷却, 单位: tick
|
||||||
|
*/
|
||||||
public int freeze;
|
public int freeze;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,14 +2,29 @@ package io.github.hello09x.fakeplayer.manager.action;
|
|||||||
|
|
||||||
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class ActionTicker {
|
public class ActionTicker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为类型
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
public final Action action;
|
public final Action action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为数据
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
public final ActionPack actionPack;
|
public final ActionPack actionPack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为设置
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
public ActionSetting setting;
|
public ActionSetting setting;
|
||||||
|
|
||||||
public ActionTicker(ServerPlayer player, Action action, ActionSetting setting) {
|
public ActionTicker(@NotNull ServerPlayer player, @NotNull Action action, @NotNull ActionSetting setting) {
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.setting = setting;
|
this.setting = setting;
|
||||||
this.actionPack = new ActionPack(player);
|
this.actionPack = new ActionPack(player);
|
||||||
|
@ -4,11 +4,14 @@ import io.github.hello09x.fakeplayer.Main;
|
|||||||
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
||||||
import io.github.hello09x.fakeplayer.manager.naming.exception.IllegalCustomNameException;
|
import io.github.hello09x.fakeplayer.manager.naming.exception.IllegalCustomNameException;
|
||||||
import io.github.hello09x.fakeplayer.repository.UsedIdRepository;
|
import io.github.hello09x.fakeplayer.repository.UsedIdRepository;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -33,6 +36,36 @@ public class NameManager {
|
|||||||
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
||||||
private final Map<String, NameSource> nameSources = new HashMap<>();
|
private final Map<String, NameSource> nameSources = new HashMap<>();
|
||||||
|
|
||||||
|
private final String serverId;
|
||||||
|
|
||||||
|
public NameManager() {
|
||||||
|
var file = new File(Main.getInstance().getDataFolder(), "serverid");
|
||||||
|
serverId = Optional.ofNullable(readServerId(file)).orElseGet(() -> {
|
||||||
|
var uuid = UUID.randomUUID().toString();
|
||||||
|
try (var out = new FileWriter(file, StandardCharsets.UTF_8)) {
|
||||||
|
IOUtils.write(uuid, out);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
return uuid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable String readServerId(@NotNull File file) {
|
||||||
|
if (!file.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String serverId;
|
||||||
|
try(var in = new FileReader(file, StandardCharsets.UTF_8)) {
|
||||||
|
serverId = IOUtils.toString(in);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverId.isBlank() ? null : serverId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过名称生成 UUID
|
* 通过名称生成 UUID
|
||||||
*
|
*
|
||||||
@ -40,7 +73,8 @@ public class NameManager {
|
|||||||
* @return UUID
|
* @return UUID
|
||||||
*/
|
*/
|
||||||
private @NotNull UUID uuidFromName(@NotNull String name) {
|
private @NotNull UUID uuidFromName(@NotNull String name) {
|
||||||
var uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
|
var base = serverId + ":" + name;
|
||||||
|
var uuid = UUID.nameUUIDFromBytes(base.getBytes(StandardCharsets.UTF_8));
|
||||||
if (!usedIdRepository.exists(uuid) && Bukkit.getOfflinePlayer(uuid).hasPlayedBefore()) {
|
if (!usedIdRepository.exists(uuid) && Bukkit.getOfflinePlayer(uuid).hasPlayedBefore()) {
|
||||||
uuid = UUID.randomUUID();
|
uuid = UUID.randomUUID();
|
||||||
log.warning(String.format("Could not generate a UUID bound with name '%s' which is never played at this server, using random UUID as fallback: %s", name, uuid));
|
log.warning(String.format("Could not generate a UUID bound with name '%s' which is never played at this server, using random UUID as fallback: %s", name, uuid));
|
||||||
@ -134,17 +168,17 @@ public class NameManager {
|
|||||||
* @param group 分组
|
* @param group 分组
|
||||||
* @param sequence 序列
|
* @param sequence 序列
|
||||||
*/
|
*/
|
||||||
public void unregister(@NotNull String group, @NotNull Integer sequence) {
|
public void unregister(@NotNull String group, int sequence) {
|
||||||
Optional.ofNullable(nameSources.get(group)).ifPresent(ns -> ns.push(sequence));
|
Optional.ofNullable(nameSources.get(group)).ifPresent(ns -> ns.push(sequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 归还序列名
|
* 归还序列名
|
||||||
*
|
*
|
||||||
* @param sequenceName 序列名
|
* @param sn 序列名
|
||||||
*/
|
*/
|
||||||
public void unregister(@NotNull SequenceName sequenceName) {
|
public void unregister(@NotNull SequenceName sn) {
|
||||||
this.unregister(sequenceName.group(), sequenceName.sequence());
|
this.unregister(sn.group(), sn.sequence());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,8 +4,14 @@ import java.util.LinkedList;
|
|||||||
|
|
||||||
public class NameSource {
|
public class NameSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接下来可以使用的名称序号
|
||||||
|
*/
|
||||||
private final LinkedList<Integer> names;
|
private final LinkedList<Integer> names;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容量
|
||||||
|
*/
|
||||||
private volatile int capacity;
|
private volatile int capacity;
|
||||||
|
|
||||||
public NameSource(int initializeCapacity) {
|
public NameSource(int initializeCapacity) {
|
||||||
@ -20,6 +26,11 @@ public class NameSource {
|
|||||||
this(0);
|
this(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个可使用的名称序号
|
||||||
|
*
|
||||||
|
* @return 名称序号
|
||||||
|
*/
|
||||||
public synchronized int pop() {
|
public synchronized int pop() {
|
||||||
if (names.isEmpty()) {
|
if (names.isEmpty()) {
|
||||||
var newCapacity = capacity * 2;
|
var newCapacity = capacity * 2;
|
||||||
@ -31,6 +42,11 @@ public class NameSource {
|
|||||||
return names.pop();
|
return names.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归还一个名称序号
|
||||||
|
*
|
||||||
|
* @param i 名称序号
|
||||||
|
*/
|
||||||
public synchronized void push(int i) {
|
public synchronized void push(int i) {
|
||||||
if (i >= capacity) {
|
if (i >= capacity) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,11 +1,29 @@
|
|||||||
package io.github.hello09x.fakeplayer.manager.naming;
|
package io.github.hello09x.fakeplayer.manager.naming;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列名
|
||||||
|
*
|
||||||
|
* @param group 分组
|
||||||
|
* @param sequence 序列
|
||||||
|
* @param uuid UUID
|
||||||
|
* @param name 名称
|
||||||
|
*/
|
||||||
public record SequenceName(
|
public record SequenceName(
|
||||||
|
|
||||||
|
@NotNull
|
||||||
String group,
|
String group,
|
||||||
Integer sequence,
|
|
||||||
|
int sequence,
|
||||||
|
|
||||||
|
@NotNull
|
||||||
UUID uuid,
|
UUID uuid,
|
||||||
|
|
||||||
|
@NotNull
|
||||||
String name
|
String name
|
||||||
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,11 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
public class IllegalCustomNameException extends IllegalArgumentException {
|
public class IllegalCustomNameException extends IllegalArgumentException {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Component msg;
|
private final Component text;
|
||||||
|
|
||||||
public IllegalCustomNameException(@NotNull TextComponent message) {
|
public IllegalCustomNameException(@NotNull TextComponent message) {
|
||||||
super(message.content());
|
super(message.content());
|
||||||
this.msg = message;
|
this.text = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,9 @@ public class UsedIdRepository {
|
|||||||
return UUIDS.contains(uuid);
|
return UUIDS.contains(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从文件里读取使用过的 UUIDs
|
||||||
|
*/
|
||||||
public void load() {
|
public void load() {
|
||||||
var file = new File(Main.getInstance().getDataFolder(), "used-uuids.txt");
|
var file = new File(Main.getInstance().getDataFolder(), "used-uuids.txt");
|
||||||
if (!file.exists() || !file.isFile()) {
|
if (!file.exists() || !file.isFile()) {
|
||||||
@ -60,6 +63,9 @@ public class UsedIdRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将使用过的 UUIDs 写入文件
|
||||||
|
*/
|
||||||
public void saveAll() {
|
public void saveAll() {
|
||||||
var folder = Main.getInstance().getDataFolder();
|
var folder = Main.getInstance().getDataFolder();
|
||||||
if (!folder.exists() && !folder.mkdirs()) {
|
if (!folder.exists() && !folder.mkdirs()) {
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
package io.github.hello09x.fakeplayer.util;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public class Experience {
|
|
||||||
|
|
||||||
private Experience() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate a player's total experience based on level and progress to next.
|
|
||||||
*
|
|
||||||
* @param player the Player
|
|
||||||
* @return the amount of experience the Player has
|
|
||||||
* @see <a href=http://minecraft.gamepedia.com/Experience#Leveling_up>Experience#Leveling_up</a>
|
|
||||||
*/
|
|
||||||
public static int getExp(@NotNull Player player) {
|
|
||||||
return getExpFromLevel(player.getLevel())
|
|
||||||
+ Math.round(getExpToNext(player.getLevel()) * player.getExp());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate total experience based on level.
|
|
||||||
*
|
|
||||||
* @param level the level
|
|
||||||
* @return the total experience calculated
|
|
||||||
* @see <a href=http://minecraft.gamepedia.com/Experience#Leveling_up>Experience#Leveling_up</a>
|
|
||||||
*/
|
|
||||||
public static int getExpFromLevel(int level) {
|
|
||||||
if (level > 30) {
|
|
||||||
return (int) (4.5 * level * level - 162.5 * level + 2220);
|
|
||||||
}
|
|
||||||
if (level > 15) {
|
|
||||||
return (int) (2.5 * level * level - 40.5 * level + 360);
|
|
||||||
}
|
|
||||||
return level * level + 6 * level;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate level (including progress to next level) based on total experience.
|
|
||||||
*
|
|
||||||
* @param exp the total experience
|
|
||||||
* @return the level calculated
|
|
||||||
*/
|
|
||||||
public static double getLevelFromExp(long exp) {
|
|
||||||
int level = getIntLevelFromExp(exp);
|
|
||||||
|
|
||||||
// Get remaining exp progressing towards next level. Cast to float for next bit of math.
|
|
||||||
float remainder = exp - (float) getExpFromLevel(level);
|
|
||||||
|
|
||||||
// Get level progress with float precision.
|
|
||||||
float progress = remainder / getExpToNext(level);
|
|
||||||
|
|
||||||
// Slap both numbers together and call it a day. While it shouldn't be possible for progress
|
|
||||||
// to be an invalid value (value < 0 || 1 <= value)
|
|
||||||
return ((double) level) + progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate level based on total experience.
|
|
||||||
*
|
|
||||||
* @param exp the total experience
|
|
||||||
* @return the level calculated
|
|
||||||
*/
|
|
||||||
public static int getIntLevelFromExp(long exp) {
|
|
||||||
if (exp > 1395) {
|
|
||||||
return (int) ((Math.sqrt(72 * exp - 54215D) + 325) / 18);
|
|
||||||
}
|
|
||||||
if (exp > 315) {
|
|
||||||
return (int) (Math.sqrt(40 * exp - 7839D) / 10 + 8.1);
|
|
||||||
}
|
|
||||||
if (exp > 0) {
|
|
||||||
return (int) (Math.sqrt(exp + 9D) - 3);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the total amount of experience required to progress to the next level.
|
|
||||||
*
|
|
||||||
* @param level the current level
|
|
||||||
* @see <a href=http://minecraft.gamepedia.com/Experience#Leveling_up>Experience#Leveling_up</a>
|
|
||||||
*/
|
|
||||||
private static int getExpToNext(int level) {
|
|
||||||
if (level >= 30) {
|
|
||||||
// Simplified formula. Internal: 112 + (level - 30) * 9
|
|
||||||
return level * 9 - 158;
|
|
||||||
}
|
|
||||||
if (level >= 15) {
|
|
||||||
// Simplified formula. Internal: 37 + (level - 15) * 5
|
|
||||||
return level * 5 - 38;
|
|
||||||
}
|
|
||||||
// Internal: 7 + level * 2
|
|
||||||
return level * 2 + 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change a Player's experience.
|
|
||||||
*
|
|
||||||
* <p>This method is preferred over {@link Player#giveExp(int)}.
|
|
||||||
* <br>In older versions the method does not take differences in exp per level into account.
|
|
||||||
* This leads to overlevelling when granting players large amounts of experience.
|
|
||||||
* <br>In modern versions, while differing amounts of experience per level are accounted for, the
|
|
||||||
* approach used is loop-heavy and requires an excessive number of calculations, which makes it
|
|
||||||
* quite slow.
|
|
||||||
*
|
|
||||||
* @param player the Player affected
|
|
||||||
* @param exp the amount of experience to add or remove
|
|
||||||
*/
|
|
||||||
public static void changeExp(@NotNull Player player, int exp) {
|
|
||||||
exp += getExp(player);
|
|
||||||
|
|
||||||
if (exp < 0) {
|
|
||||||
exp = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
double levelAndExp = getLevelFromExp(exp);
|
|
||||||
int level = (int) levelAndExp;
|
|
||||||
player.setLevel(level);
|
|
||||||
player.setExp((float) (levelAndExp - level));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clean(@NotNull Player player) {
|
|
||||||
player.setLevel(0);
|
|
||||||
player.setExp(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -2,13 +2,32 @@ package io.github.hello09x.fakeplayer.util;
|
|||||||
|
|
||||||
public class Mth {
|
public class Mth {
|
||||||
|
|
||||||
public static double round(double num, double base) {
|
/**
|
||||||
|
* 将 num 以 base 向下取整
|
||||||
|
* <ul>
|
||||||
|
* <li>3.0, 0.5 -> 3.0</li>
|
||||||
|
* <li>3.1, 0.5 -> 3.0</li>
|
||||||
|
* <li>3.6, 0.5 -> 3.5</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param num 数
|
||||||
|
* @param base 基数
|
||||||
|
* @return 取整后的数
|
||||||
|
*/
|
||||||
|
public static double floor(double num, double base) {
|
||||||
if (num % base == 0) {
|
if (num % base == 0) {
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
return Math.floor(num / base) * base;
|
return Math.floor(num / base) * base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将一个数约束在范围以内
|
||||||
|
* @param value 数
|
||||||
|
* @param min 最小值
|
||||||
|
* @param max 最大值
|
||||||
|
* @return 约束后的数
|
||||||
|
*/
|
||||||
public static float clamp(float value, float min, float max) {
|
public static float clamp(float value, float min, float max) {
|
||||||
return value < min ? min : Math.min(value, max);
|
return value < min ? min : Math.min(value, max);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.hello09x.fakeplayer.util;
|
package io.github.hello09x.fakeplayer.util;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@ -7,7 +8,11 @@ import java.lang.reflect.Modifier;
|
|||||||
|
|
||||||
public class Reflections {
|
public class Reflections {
|
||||||
|
|
||||||
public static @Nullable Field getFirstFieldByType(Class<?> clazz, Class<?> fieldType, boolean includeStatic) {
|
public static @Nullable Field getFirstFieldByType(
|
||||||
|
@NotNull Class<?> clazz,
|
||||||
|
@NotNull Class<?> fieldType,
|
||||||
|
boolean includeStatic
|
||||||
|
) {
|
||||||
for (var field : clazz.getDeclaredFields()) {
|
for (var field : clazz.getDeclaredFields()) {
|
||||||
if (includeStatic ^ Modifier.isStatic(field.getModifiers())) {
|
if (includeStatic ^ Modifier.isStatic(field.getModifiers())) {
|
||||||
continue;
|
continue;
|
||||||
@ -20,7 +25,11 @@ public class Reflections {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @Nullable Field getFirstFieldByAssignFromType(Class<?> clazz, Class<?> fieldType, boolean includeStatic) {
|
public static @Nullable Field getFirstFieldByAssignFromType(
|
||||||
|
@NotNull Class<?> clazz,
|
||||||
|
@NotNull Class<?> fieldType,
|
||||||
|
boolean includeStatic
|
||||||
|
) {
|
||||||
for (var field : clazz.getDeclaredFields()) {
|
for (var field : clazz.getDeclaredFields()) {
|
||||||
if (includeStatic ^ Modifier.isStatic(field.getModifiers())) {
|
if (includeStatic ^ Modifier.isStatic(field.getModifiers())) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
package io.github.hello09x.fakeplayer.util;
|
|
||||||
|
|
||||||
import io.github.hello09x.fakeplayer.Main;
|
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public class Tasker {
|
|
||||||
|
|
||||||
public static @NotNull BukkitTask nextTick(@NotNull Runnable runnable) {
|
|
||||||
return new BukkitRunnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
}.runTask(Main.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull BukkitTask later(@NotNull Runnable runnable, long delay) {
|
|
||||||
return new BukkitRunnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
}.runTaskLater(Main.getInstance(), delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -8,6 +8,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copy from fabric carpet mod
|
||||||
|
*/
|
||||||
public class Tracer {
|
public class Tracer {
|
||||||
public static @Nullable HitResult rayTrace(
|
public static @Nullable HitResult rayTrace(
|
||||||
@NotNull Entity source,
|
@NotNull Entity source,
|
||||||
|
@ -5,7 +5,7 @@ import org.bukkit.World;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class BK {
|
public class Worlds {
|
||||||
|
|
||||||
private final static String WORLD_OVERWORLD = "world";
|
private final static String WORLD_OVERWORLD = "world";
|
||||||
|
|
@ -23,6 +23,35 @@ public class UpdateChecker {
|
|||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isNew(@NotNull String current, @NotNull String other) {
|
||||||
|
var split1 = current.split("\\.");
|
||||||
|
var split2 = other.split("\\.");
|
||||||
|
|
||||||
|
if (split2.length > split1.length) {
|
||||||
|
// 如果 other 的版本号位数更多, 则认为是新版本号
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split2.length < split1.length) {
|
||||||
|
// 如果 other 的版本号位数更少, 则认为是旧版本
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// split2.length == split1.length
|
||||||
|
var length = split1.length;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
var v1 = Integer.parseInt(split1[i]);
|
||||||
|
var v2 = Integer.parseInt(split2[i]);
|
||||||
|
if (v1 == v2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v1 < v2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public @NotNull Release getLastRelease() throws IOException, InterruptedException {
|
public @NotNull Release getLastRelease() throws IOException, InterruptedException {
|
||||||
var url = String.format("https://api.github.com/repos/%s/%s/releases/latest", this.author, this.repository);
|
var url = String.format("https://api.github.com/repos/%s/%s/releases/latest", this.author, this.repository);
|
||||||
var client = HttpClient.newHttpClient();
|
var client = HttpClient.newHttpClient();
|
||||||
@ -31,7 +60,12 @@ public class UpdateChecker {
|
|||||||
.GET()
|
.GET()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
var body = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)).body();
|
var response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new IllegalStateException("Not 200 response: " + response.statusCode() + ": " + response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = response.body();
|
||||||
return gson.fromJson(body, Release.class);
|
return gson.fromJson(body, Release.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +37,16 @@ name-template: ''
|
|||||||
name-pattern: '^[a-zA-Z0-9_]+$'
|
name-pattern: '^[a-zA-Z0-9_]+$'
|
||||||
|
|
||||||
# 跟随下线
|
# 跟随下线
|
||||||
# 假人创建者玩家下线时是否自动下线
|
# 假人创建者下线时是否也跟着下线
|
||||||
# 如果玩家只是切换服务器, 那么不会触发跟随下线
|
# 如果玩家只是切换服务器, 那么不会触发跟随下线
|
||||||
follow-quiting: true
|
follow-quiting: true
|
||||||
|
|
||||||
# 退出时是否丢弃背包物品
|
# 退出时是否丢弃背包物品
|
||||||
# 有跨服背包同步的谨慎开启, 需验证是否会导致物品复制
|
# 有跨服背包同步的谨慎开启, 需验证是否会导致物品复制
|
||||||
|
# 验证过程:
|
||||||
|
# 1. 创建假人
|
||||||
|
# 2. 通过 /kill 之类的命令杀死这个假人,假人死前会将背包物品丢弃出来
|
||||||
|
# 3. 重新召唤这个假人, 查看他的背包数据有没有被复制了一份
|
||||||
drop-inventory-on-quiting: true
|
drop-inventory-on-quiting: true
|
||||||
|
|
||||||
# 如果启用, 则一个 IP 只能创建 `maximum` 个假人
|
# 如果启用, 则一个 IP 只能创建 `maximum` 个假人
|
||||||
@ -59,7 +63,7 @@ kale-tps: 0
|
|||||||
# 有一些需要在 "登陆" 时生成玩家档案的插件发生异常比如 LuckPerms
|
# 有一些需要在 "登陆" 时生成玩家档案的插件发生异常比如 LuckPerms
|
||||||
# 如果服务器没有出现严重的错误不需要理会这些异常, 只是这些插件无法对假人进行操作而已
|
# 如果服务器没有出现严重的错误不需要理会这些异常, 只是这些插件无法对假人进行操作而已
|
||||||
# 开启也不一定能解决所有问题, 也可能因为一些 "限制新加入玩家" 的插件而导致假人出现问题, 并且会创建更多的第三方插件数据
|
# 开启也不一定能解决所有问题, 也可能因为一些 "限制新加入玩家" 的插件而导致假人出现问题, 并且会创建更多的第三方插件数据
|
||||||
simulate-login: false
|
simulate-login: true
|
||||||
|
|
||||||
# 预准备命令
|
# 预准备命令
|
||||||
# 假人诞生时会以控制台的身份按顺序执行以下命令
|
# 假人诞生时会以控制台的身份按顺序执行以下命令
|
||||||
|
@ -3,6 +3,7 @@ version: '${project.version}'
|
|||||||
main: io.github.hello09x.fakeplayer.Main
|
main: io.github.hello09x.fakeplayer.Main
|
||||||
api-version: '1.20'
|
api-version: '1.20'
|
||||||
author: hello09x
|
author: hello09x
|
||||||
|
website: 'https://github.com/tanyaofei/minecraft-fakeplayer'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
fakeplayer.all:
|
fakeplayer.all:
|
||||||
|
Loading…
Reference in New Issue
Block a user