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>
|
||||
<artifactId>fakeplayer</artifactId>
|
||||
<version>0.1.7</version>
|
||||
<version>0.1.8</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>fakeplayer</name>
|
||||
@ -117,7 +117,7 @@
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>dev.jorel.commandapi</pattern>
|
||||
<shadedPattern>io.github.hello09x.shaded.commandapi</shadedPattern>
|
||||
<shadedPattern>io.github.hello09x.fakeplayer.shaded.commandapi</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
|
@ -69,18 +69,27 @@ public final class Main extends JavaPlugin {
|
||||
|
||||
public void checkForUpdatesAsync() {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
var meta = getPluginMeta();
|
||||
var checker = new UpdateChecker("tanyaofei", "minecraft-fakeplayer");
|
||||
try {
|
||||
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();
|
||||
log.info("检测到新的版本: " + release.getTagName());
|
||||
log.info("前往此处下载 https://github.com/tanyaofei/minecraft-fakeplayer");
|
||||
log.info("前往此处下载 " + meta.getWebsite());
|
||||
log.info("更新日志");
|
||||
for (var line : release.getBody().split("\n")) {
|
||||
log.info("\t" + line);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
getLogger().warning("检测新版本发生异常: " + e.getMessage());
|
||||
}
|
||||
@ -90,7 +99,7 @@ public final class Main extends JavaPlugin {
|
||||
@Override
|
||||
public void onDisable() {
|
||||
CommandAPI.onDisable();
|
||||
FakeplayerManager.instance.removeAll();
|
||||
FakeplayerManager.instance.removeAll("plugin disabled");
|
||||
UsedIdRepository.instance.saveAll();
|
||||
FakeplayerManager.instance.onDisable();
|
||||
WildFakeplayerManager.instance.onDisable();
|
||||
|
@ -32,9 +32,9 @@ public class ActionCommand extends AbstractCommand {
|
||||
|
||||
private static String toLocationString(@NotNull Location location) {
|
||||
return StringUtils.joinWith(", ",
|
||||
Mth.round(location.getX(), 0.5),
|
||||
Mth.round(location.getY(), 0.5),
|
||||
Mth.round(location.getZ(), 0.5));
|
||||
Mth.floor(location.getX(), 0.5),
|
||||
Mth.floor(location.getY(), 0.5),
|
||||
Mth.floor(location.getZ(), 0.5));
|
||||
}
|
||||
|
||||
public CommandExecutor action(@NotNull Action action, @NotNull ActionSetting setting) {
|
||||
|
@ -35,29 +35,28 @@ public class CommandRegistry {
|
||||
"可以创建模拟玩家的假人, 能保持附近区块的刷新、触发怪物生成。同时还提供了一些操作命令让你控制假人的物品、动作等等。"
|
||||
)
|
||||
.withUsage(
|
||||
"§6? [页码] §7- §f查看帮助",
|
||||
"§6spawn [名称] [世界] [坐标] §7- §f创建假人",
|
||||
"§6kill §7- §f移除假人",
|
||||
"§6list [页码] [数量] §7- §f查看所有假人",
|
||||
"§6distance §7- §f查看与假人的距离",
|
||||
"§6tp §7- §f传送到假人身边",
|
||||
"§6tphere §7- §f将假人传送到身边",
|
||||
"§6tps §7- §f与假人交换位置",
|
||||
"§6config get <配置项> §7- §f查看配置项",
|
||||
"§6config set <配置项> <配置值> §7- §f设置配置项",
|
||||
"§6health §7- §f查看生命值",
|
||||
"§6exp §7- §f查看经验值",
|
||||
"§6expme §7- §f转移经验值",
|
||||
"§6attack (once | continuous | interval | stop) §7- §f攻击/破坏",
|
||||
"§6use (once | continuous | interval | stop) §7- §f使用/交互/放置",
|
||||
"§6jump (once | continuous | interval | stop) §7- §f跳跃",
|
||||
"§6drop [-a|--all] §7- §f丢弃手上物品",
|
||||
"§6dropinv §7- §f丢弃背包物品",
|
||||
"§6look (north | south | east | west | up | down | at | entity) §7- §f看向指定位置",
|
||||
"§6turn (left | right | back | to) §7- §f转身到指定位置",
|
||||
"§6move (forward | backward | left | right) §7- §f移动",
|
||||
"§6cmd <假人> <命令> §7- §f执行命令",
|
||||
"§6reload §7- §f重载配置文件"
|
||||
usage("spawn [名称] [世界] [坐标]", "创建假人"),
|
||||
usage("kill", "移除假人"),
|
||||
usage("list [页码] [数量]", "查看所有假人"),
|
||||
usage("distance", "查看与假人的距离"),
|
||||
usage("tp", "传送到假人身边"),
|
||||
usage("tphere", "将假人传送到身边"),
|
||||
usage("tps", "与假人交换位置"),
|
||||
usage("config get <配置项>", "查看配置项"),
|
||||
usage("config set <配置项> <值>", "设置配置项"),
|
||||
usage("health", "查看生命值"),
|
||||
usage("exp", "查看经验值"),
|
||||
usage("expme", "转移经验值"),
|
||||
usage("attack (once | continuous | interval | stop)", "攻击/破坏"),
|
||||
usage("use (once | continuous | interval | stop)", "使用/交互/放置"),
|
||||
usage("jump (once | continuous | interval | stop)", "跳"),
|
||||
usage("drop [-a|--all]", "丢弃手上物品"),
|
||||
usage("dropinv", "丢弃背包物品"),
|
||||
usage("look (north | south | east | west | up | down | at | entity)", "看向指定位置"),
|
||||
usage("turn (left | right | back | to)", "转身"),
|
||||
usage("move (forward | backward | left |right)", "移动"),
|
||||
usage("cmd", "执行命令"),
|
||||
usage("reload", "重新加载配置文件")
|
||||
)
|
||||
.withSubcommands(
|
||||
command("help")
|
||||
|
@ -3,7 +3,7 @@ package io.github.hello09x.fakeplayer.command;
|
||||
|
||||
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
||||
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.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -17,7 +17,7 @@ public class ExpCommand extends AbstractCommand {
|
||||
|
||||
public void expme(@NotNull Player sender, @NotNull CommandArguments args) throws WrapperCommandSyntaxException {
|
||||
var target = getTarget(sender, args);
|
||||
var exp = Experience.getExp(target);
|
||||
var exp = Experiences.getExp(target);
|
||||
|
||||
if (exp == 0) {
|
||||
sender.sendMessage(textOfChildren(
|
||||
@ -27,9 +27,8 @@ public class ExpCommand extends AbstractCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
Experience.clean(target);
|
||||
Experience.changeExp(sender, Experience.getExp(sender) + exp);
|
||||
|
||||
Experiences.clean(target);
|
||||
sender.giveExp(exp, false);
|
||||
sender.sendMessage(textOfChildren(
|
||||
text(target.getName(), WHITE),
|
||||
text(" 转移 ", GRAY),
|
||||
|
@ -2,7 +2,7 @@ package io.github.hello09x.fakeplayer.command;
|
||||
|
||||
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
||||
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 net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
@ -24,7 +24,7 @@ public class ProfileCommand extends AbstractCommand {
|
||||
var target = getTarget(sender, args);
|
||||
|
||||
var level = target.getLevel();
|
||||
var total = Experience.getExp(target);
|
||||
var total = Experiences.getExp(target);
|
||||
sender.sendMessage(textOfChildren(
|
||||
text(target.getName(), WHITE),
|
||||
text(" 当前 ", GRAY),
|
||||
@ -60,7 +60,7 @@ public class ProfileCommand extends AbstractCommand {
|
||||
sender.sendMessage(textOfChildren(
|
||||
text(target.getName(), WHITE),
|
||||
text(" 当前生命值: ", GRAY),
|
||||
text(Mth.round(health, 0.5), color),
|
||||
text(Mth.floor(health, 0.5), color),
|
||||
text("/", color),
|
||||
text(max, color)
|
||||
));
|
||||
|
@ -15,7 +15,7 @@ public class ReloadCommand extends AbstractCommand {
|
||||
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
||||
|
||||
public void reload(@NotNull CommandSender sender, @NotNull CommandArguments args) {
|
||||
config.reload();
|
||||
config.reload(true);
|
||||
sender.sendMessage(text("重载配置文件完成", GRAY));
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ package io.github.hello09x.fakeplayer.command;
|
||||
|
||||
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
|
||||
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.util.Mth;
|
||||
import io.github.tanyaofei.plugin.toolkit.database.Page;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
@ -20,6 +20,7 @@ import java.util.*;
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
import static net.kyori.adventure.text.event.ClickEvent.runCommand;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.*;
|
||||
import static net.kyori.adventure.text.format.TextDecoration.BOLD;
|
||||
|
||||
public class SpawnCommand extends AbstractCommand {
|
||||
|
||||
@ -29,9 +30,9 @@ public class SpawnCommand extends AbstractCommand {
|
||||
return location.getWorld().getName()
|
||||
+ ": "
|
||||
+ StringUtils.joinWith(", ",
|
||||
Mth.round(location.getX(), 0.5),
|
||||
Mth.round(location.getY(), 0.5),
|
||||
Mth.round(location.getZ(), 0.5));
|
||||
Mth.floor(location.getX(), 0.5),
|
||||
Mth.floor(location.getY(), 0.5),
|
||||
Mth.floor(location.getZ(), 0.5));
|
||||
}
|
||||
|
||||
public void spawn(@NotNull CommandSender sender, @NotNull CommandArguments args) {
|
||||
@ -65,7 +66,7 @@ public class SpawnCommand extends AbstractCommand {
|
||||
text("你创建了假人 ", GRAY),
|
||||
text(player.getName()),
|
||||
text(", 位于 ", GRAY),
|
||||
text(toLocationString(player.getLocation())),
|
||||
text(toLocationString(spawnpoint)),
|
||||
Keepalive.isPermanent(keepalive)
|
||||
? empty()
|
||||
: textOfChildren(
|
||||
@ -98,7 +99,7 @@ public class SpawnCommand extends AbstractCommand {
|
||||
|
||||
var names = new StringJoiner(", ");
|
||||
for (var target : targets) {
|
||||
if (fakeplayerManager.remove(target.getName())) {
|
||||
if (fakeplayerManager.remove(target.getName(), "command kill")) {
|
||||
names.add(target.getName());
|
||||
}
|
||||
}
|
||||
@ -116,19 +117,11 @@ public class SpawnCommand extends AbstractCommand {
|
||||
? fakeplayerManager.getAll()
|
||||
: fakeplayerManager.getAll(sender);
|
||||
|
||||
var total = fakers.size();
|
||||
var pages = total == 0 ? 1 : (int) Math.ceil((double) total / size);
|
||||
var p = new Page<>(
|
||||
fakers.subList((page - 1) * size, Math.min(total, page * size)),
|
||||
total,
|
||||
pages,
|
||||
page,
|
||||
size
|
||||
);
|
||||
var p = Page.of(fakers, page, size);
|
||||
|
||||
var canTp = sender instanceof Player && sender.hasPermission(Permission.tp);
|
||||
sender.sendMessage(p.toComponent(
|
||||
"假人",
|
||||
sender.sendMessage(p.asComponent(
|
||||
text("假人", AQUA, BOLD),
|
||||
fakeplayer -> textOfChildren(
|
||||
text(fakeplayer.getName() + " (" + fakeplayerManager.getCreator(fakeplayer) + ")", GOLD),
|
||||
text(" - ", GRAY),
|
||||
@ -136,8 +129,7 @@ public class SpawnCommand extends AbstractCommand {
|
||||
canTp ? text(" [<--传送]", AQUA).clickEvent(runCommand("/fp tp " + fakeplayer.getName())) : empty(),
|
||||
text(" [<--移除]", RED).clickEvent(runCommand("/fp kill " + fakeplayer.getName()))
|
||||
),
|
||||
String.format("/fp list %d %d", page - 1, size),
|
||||
String.format("/fp list %d %d", page + 1, size)
|
||||
i -> "/fp list " + i + " " + size
|
||||
));
|
||||
}
|
||||
|
||||
@ -158,7 +150,7 @@ public class SpawnCommand extends AbstractCommand {
|
||||
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 y = Math.abs(from.getBlockY() - to.getBlockY());
|
||||
var z = Math.abs(from.getBlockZ() - to.getBlockZ());
|
||||
|
@ -21,6 +21,7 @@ import java.util.regex.PatternSyntaxException;
|
||||
public class FakeplayerConfig extends Config<FakeplayerConfig> {
|
||||
|
||||
public final static FakeplayerConfig instance;
|
||||
|
||||
private final static Logger log;
|
||||
|
||||
private final static String defaultNameChars = "^[a-zA-Z0-9_]+$";
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.hello09x.fakeplayer.entity;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.github.hello09x.bedrock.task.Tasks;
|
||||
import io.github.hello09x.fakeplayer.Main;
|
||||
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
||||
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.EmptyLoginPacketListener;
|
||||
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.Worlds;
|
||||
import io.github.hello09x.fakeplayer.util.nms.NMS;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
@ -23,7 +23,6 @@ import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -47,28 +46,37 @@ public class FakePlayer {
|
||||
|
||||
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
||||
|
||||
@NotNull
|
||||
private final MinecraftServer server;
|
||||
|
||||
@NotNull
|
||||
@Getter
|
||||
private final String creator;
|
||||
|
||||
@NotNull
|
||||
@Getter
|
||||
private final ServerPlayer handle;
|
||||
|
||||
@NotNull
|
||||
@Getter
|
||||
private final Player bukkitPlayer;
|
||||
|
||||
@NotNull
|
||||
@Getter
|
||||
private final String creatorIp;
|
||||
|
||||
@NotNull
|
||||
@Getter
|
||||
private final SequenceName sequenceName;
|
||||
|
||||
@NotNull
|
||||
private final FakeplayerTicker ticker;
|
||||
|
||||
@NotNull
|
||||
@Getter
|
||||
private final String name;
|
||||
|
||||
@NotNull
|
||||
private final UUID uuid;
|
||||
|
||||
public FakePlayer(
|
||||
@ -94,7 +102,7 @@ public class FakePlayer {
|
||||
|
||||
bukkitPlayer.setPersistent(false);
|
||||
bukkitPlayer.setSleepingIgnored(true);
|
||||
nms.setPlayBefore(bukkitPlayer);
|
||||
nms.setPlayBefore(bukkitPlayer); // 可避免一些插件的第一次入服欢迎信息
|
||||
nms.unpersistAdvancements(bukkitPlayer);
|
||||
}
|
||||
|
||||
@ -103,28 +111,22 @@ public class FakePlayer {
|
||||
*/
|
||||
public void spawn(@NotNull SpawnOption option) {
|
||||
if (config.isSimulateLogin()) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
var preLoginEvent = new AsyncPlayerPreLoginEvent(
|
||||
handle.getGameProfile().getName(),
|
||||
fakeAddress,
|
||||
fakeAddress,
|
||||
handle.getUUID(),
|
||||
bukkitPlayer.getPlayerProfile(),
|
||||
fakeAddress.getHostName()
|
||||
);
|
||||
Bukkit.getPluginManager().callEvent(preLoginEvent);
|
||||
}
|
||||
}.runTaskAsynchronously(Main.getInstance());
|
||||
|
||||
{
|
||||
Bukkit.getPluginManager().callEvent(new PlayerLoginEvent(
|
||||
bukkitPlayer,
|
||||
fakeAddress.getHostName(),
|
||||
fakeAddress
|
||||
Tasks.runAsync(Main.getInstance(), () -> {
|
||||
Bukkit.getPluginManager().callEvent(new AsyncPlayerPreLoginEvent(
|
||||
handle.getGameProfile().getName(),
|
||||
fakeAddress,
|
||||
fakeAddress,
|
||||
handle.getUUID(),
|
||||
bukkitPlayer.getPlayerProfile(),
|
||||
fakeAddress.getHostName()
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
Bukkit.getPluginManager().callEvent(new PlayerLoginEvent(
|
||||
bukkitPlayer,
|
||||
fakeAddress.getHostName(),
|
||||
fakeAddress
|
||||
));
|
||||
}
|
||||
|
||||
{
|
||||
@ -144,6 +146,13 @@ public class FakePlayer {
|
||||
connection.setListener(listener);
|
||||
}
|
||||
|
||||
if (config.isDropInventoryOnQuiting()) {
|
||||
// 跨服背包同步插件可能导致假人既丢弃了一份到地上,在重新生成的时候又回来了
|
||||
// 因此在生成的时候清空一次背包
|
||||
// 但无法解决登陆后延迟同步背包的情况
|
||||
bukkitPlayer.getInventory().clear();
|
||||
}
|
||||
|
||||
bukkitPlayer.setInvulnerable(option.invulnerable());
|
||||
bukkitPlayer.setCollidable(option.collidable());
|
||||
bukkitPlayer.setCanPickupItems(option.pickupItems());
|
||||
@ -152,7 +161,7 @@ public class FakePlayer {
|
||||
}
|
||||
|
||||
var spawnAt = option.spawnAt().clone();
|
||||
if (BK.isOverworld(spawnAt.getWorld())) {
|
||||
if (Worlds.isOverworld(spawnAt.getWorld())) {
|
||||
// 创建在主世界时需要跨越一次世界才能拥有刷怪能力
|
||||
teleportToSpawnpointAfterChangingDimension(spawnAt);
|
||||
} else {
|
||||
@ -168,7 +177,7 @@ public class FakePlayer {
|
||||
* @param spawnpoint 最终目的地, 即出生点
|
||||
*/
|
||||
private void teleportToSpawnpointAfterChangingDimension(@NotNull Location spawnpoint) {
|
||||
var world = BK.getNonOverworld();
|
||||
var world = Worlds.getNonOverworld();
|
||||
if (world == null || !bukkitPlayer.teleport(world.getSpawnLocation())) {
|
||||
Optional.ofNullable(Bukkit.getPlayerExact(creator))
|
||||
.ifPresentOrElse(
|
||||
@ -181,7 +190,7 @@ public class FakePlayer {
|
||||
return;
|
||||
}
|
||||
|
||||
Tasker.nextTick(() -> teleportToSpawnpoint(spawnpoint));
|
||||
Tasks.runNextTick(Main.getInstance(), () -> teleportToSpawnpoint(spawnpoint));
|
||||
}
|
||||
|
||||
private void teleportToSpawnpoint(@NotNull Location spawnpoint) {
|
||||
@ -208,4 +217,8 @@ public class FakePlayer {
|
||||
return Bukkit.getPlayerExact(this.creator);
|
||||
}
|
||||
|
||||
public int getTickCount() {
|
||||
return this.handle.tickCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
@ -17,9 +17,17 @@ import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
|
||||
|
||||
public class FakeplayerTicker extends BukkitRunnable {
|
||||
|
||||
@NotNull
|
||||
private final FakePlayer player;
|
||||
|
||||
/**
|
||||
* 移除时间
|
||||
* <p>如果不需要定时移除则为 0</p>
|
||||
*/
|
||||
private final long removeAt;
|
||||
|
||||
private final FakeplayerManager manager = FakeplayerManager.instance;
|
||||
|
||||
public FakeplayerTicker(
|
||||
@NotNull FakePlayer player,
|
||||
@Nullable LocalDateTime removeAt
|
||||
@ -30,15 +38,14 @@ public class FakeplayerTicker extends BukkitRunnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!getBukkitPlayer().isOnline()) {
|
||||
if (!player.isOnline()) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
var player = getServerPlayer();
|
||||
if (removeAt != 0 && player.tickCount % 20 == 0 && System.currentTimeMillis() > removeAt) {
|
||||
getBukkitPlayer().kick(text("[fakeplayer] 存活时间到期"));
|
||||
Optional.ofNullable(this.player.getCreatorPlayer())
|
||||
if (removeAt != 0 && player.getTickCount() % 20 == 0 && System.currentTimeMillis() > removeAt) {
|
||||
manager.remove(player.getName(), "存活时间到期");
|
||||
Optional.ofNullable(player.getCreatorPlayer())
|
||||
.ifPresent(p -> p.sendMessage(textOfChildren(
|
||||
text("假人 ", GRAY),
|
||||
text(this.player.getName()),
|
||||
@ -48,22 +55,26 @@ public class FakeplayerTicker extends BukkitRunnable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.tickCount == 0) {
|
||||
var x = player.getX();
|
||||
var y = player.getY();
|
||||
var z = player.getZ();
|
||||
var handle = player.getHandle();
|
||||
|
||||
if (handle.tickCount == 0) {
|
||||
// region 处理第一次生成时被别的插件干预然后随机传送
|
||||
var x = handle.getX();
|
||||
var y = handle.getY();
|
||||
var z = handle.getZ();
|
||||
|
||||
// 将本 tick 的移动取消
|
||||
player.xo = x;
|
||||
player.yo = y;
|
||||
player.zo = z;
|
||||
player.doTick();
|
||||
handle.xo = x;
|
||||
handle.yo = y;
|
||||
handle.zo = z;
|
||||
handle.doTick();
|
||||
|
||||
// clearFog 插件会在第一次传送的时候改变了玩家的位置, 因此必须进行一次传送
|
||||
getBukkitPlayer().teleport(new Location(getBukkitPlayer().getWorld(), x, y, z, player.getYRot(), player.getXRot()));
|
||||
player.absMoveTo(x, y, z, player.getYRot(), player.getXRot());
|
||||
getBukkitPlayer().teleport(new Location(getBukkitPlayer().getWorld(), x, y, z, handle.getYRot(), handle.getXRot()));
|
||||
handle.absMoveTo(x, y, z, handle.getYRot(), handle.getXRot());
|
||||
// endregion
|
||||
} else {
|
||||
player.doTick();
|
||||
handle.doTick();
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,8 +82,4 @@ public class FakeplayerTicker extends BukkitRunnable {
|
||||
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.manager.FakeplayerManager;
|
||||
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.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
@ -13,6 +15,7 @@ import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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) {
|
||||
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 org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ -16,6 +17,11 @@ public class FakeplayerList {
|
||||
|
||||
private final Map<String, List<FakePlayer>> playersByCreator = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 添加一个假人到假人清单
|
||||
*
|
||||
* @param player 假人
|
||||
*/
|
||||
public void add(@NotNull FakePlayer player) {
|
||||
this.playersByName.put(player.getName(), player);
|
||||
this.playersByUUID.put(player.getUUID(), player);
|
||||
@ -23,41 +29,80 @@ public class FakeplayerList {
|
||||
this.playersByCreator.get(player.getCreator()).add(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过假人的名称获取假人
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 假人
|
||||
*/
|
||||
public @Nullable FakePlayer getByName(@NotNull String name) {
|
||||
return Optional.ofNullable(this.playersByName.get(name)).map(this::checkOnline).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 UUID 获取假人
|
||||
*
|
||||
* @param uuid UUID
|
||||
* @return 假人
|
||||
*/
|
||||
public @Nullable FakePlayer getByUUID(@NotNull UUID uuid) {
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个假人
|
||||
*
|
||||
* @param player 假人
|
||||
*/
|
||||
public void remove(@NotNull FakePlayer player) {
|
||||
this.playersByName.remove(player.getName());
|
||||
this.playersByUUID.remove(player.getUUID());
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
remove(player);
|
||||
this.remove(player);
|
||||
return player;
|
||||
}
|
||||
|
||||
public List<FakePlayer> getAll() {
|
||||
/**
|
||||
* 获取所有假人
|
||||
*
|
||||
* @return 假人
|
||||
*/
|
||||
public @NotNull @Unmodifiable List<FakePlayer> getAll() {
|
||||
return List.copyOf(this.playersByUUID.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测假人是否在线, 如果不在线了则移除并返回 {@code null}
|
||||
*
|
||||
* @param player 假人
|
||||
* @return 假人
|
||||
*/
|
||||
private @Nullable FakePlayer checkOnline(@NotNull FakePlayer player) {
|
||||
if (!player.isOnline()) {
|
||||
this.playersByName.remove(player.getName());
|
||||
this.playersByUUID.remove(player.getUUID());
|
||||
this.remove(player);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.hello09x.fakeplayer.manager;
|
||||
|
||||
import io.github.hello09x.bedrock.task.Tasks;
|
||||
import io.github.hello09x.fakeplayer.Main;
|
||||
import io.github.hello09x.fakeplayer.config.FakeplayerConfig;
|
||||
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.util.AddressUtils;
|
||||
import io.github.hello09x.fakeplayer.util.Commands;
|
||||
import io.github.hello09x.fakeplayer.util.Tasker;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -57,14 +56,11 @@ public class FakeplayerManager {
|
||||
private FakeplayerManager() {
|
||||
timer.scheduleAtFixedRate(() -> {
|
||||
if (Bukkit.getServer().getTPS()[1] < config.getKaleTps()) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (removeAll() > 0) {
|
||||
Bukkit.broadcast(text("[服务器过于卡顿, 已移除所有假人]", RED, ITALIC));
|
||||
}
|
||||
Tasks.runNextTick(Main.getInstance(), () -> {
|
||||
if (removeAll("low tps") > 0) {
|
||||
Bukkit.broadcast(text("[服务器过于卡顿, 已移除所有假人]", RED, ITALIC));
|
||||
}
|
||||
}.runTask(Main.getInstance());
|
||||
});
|
||||
}
|
||||
}, 0, 60, TimeUnit.SECONDS
|
||||
);
|
||||
@ -103,19 +99,19 @@ public class FakeplayerManager {
|
||||
try {
|
||||
sn = name.isBlank() ? nameManager.register(creator) : nameManager.custom(creator, name);
|
||||
} catch (IllegalCustomNameException e) {
|
||||
creator.sendMessage(e.getMsg());
|
||||
creator.sendMessage(e.getText());
|
||||
return null;
|
||||
}
|
||||
|
||||
var player = new FakePlayer(
|
||||
var fake = new FakePlayer(
|
||||
creator.getName(),
|
||||
AddressUtils.getAddress(creator),
|
||||
sn,
|
||||
removeAt
|
||||
);
|
||||
|
||||
var bukkitPlayer = player.getBukkitPlayer();
|
||||
bukkitPlayer.playerListName(text(bukkitPlayer.getName(), GRAY, ITALIC));
|
||||
var player = fake.getBukkitPlayer();
|
||||
player.playerListName(text(player.getName(), GRAY, ITALIC));
|
||||
|
||||
boolean invulnerable = true, lookAtEntity = true, collidable = true, pickupItems = true;
|
||||
if (creator instanceof Player p) {
|
||||
@ -125,7 +121,7 @@ public class FakeplayerManager {
|
||||
collidable = userConfigRepository.selectOrDefault(creatorId, Configs.collidable);
|
||||
pickupItems = userConfigRepository.selectOrDefault(creatorId, Configs.pickup_items);
|
||||
}
|
||||
player.spawn(new SpawnOption(
|
||||
fake.spawn(new SpawnOption(
|
||||
spawnAt,
|
||||
invulnerable,
|
||||
collidable,
|
||||
@ -133,15 +129,15 @@ public class FakeplayerManager {
|
||||
pickupItems
|
||||
));
|
||||
|
||||
playerList.add(player);
|
||||
usedIdRepository.add(bukkitPlayer.getUniqueId());
|
||||
playerList.add(fake);
|
||||
usedIdRepository.add(player.getUniqueId());
|
||||
|
||||
Tasker.later(() -> {
|
||||
dispatchCommands(bukkitPlayer, config.getPreparingCommands());
|
||||
performCommands(bukkitPlayer, config.getSelfCommands());
|
||||
}, 20);
|
||||
Tasks.runLater(Main.getInstance(), 20, () -> {
|
||||
dispatchCommands(player, config.getPreparingCommands());
|
||||
performCommands(player, config.getSelfCommands());
|
||||
});
|
||||
|
||||
return bukkitPlayer;
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,22 +168,6 @@ public class FakeplayerManager {
|
||||
.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}
|
||||
*
|
||||
@ -201,14 +181,32 @@ public class FakeplayerManager {
|
||||
.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 移除的假人数量
|
||||
*/
|
||||
public int removeAll() {
|
||||
public int removeAll(@Nullable String reason) {
|
||||
var fakers = getAll();
|
||||
fakers.forEach(Player::kick);
|
||||
for (var f : fakers) {
|
||||
f.kick(text("[fakeplayer] " + (reason == null ? "removed" : reason)));
|
||||
}
|
||||
return fakers.size();
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ public class WildFakeplayerManager implements PluginMessageListener {
|
||||
@Override
|
||||
public void run() {
|
||||
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()));
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Damageable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -234,6 +235,7 @@ public enum Action {
|
||||
},
|
||||
|
||||
LOOK_AT_NEAREST_ENTITY("目视实体") {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public boolean tick(@NotNull ActionPack ap, @NotNull ActionSetting setting) {
|
||||
@ -243,14 +245,21 @@ public enum Action {
|
||||
return true;
|
||||
}
|
||||
|
||||
var entities = bukkitPlayer.getNearbyEntities(4.5, 4.5, 4.5);
|
||||
if (entities.isEmpty()) {
|
||||
var target = bukkitPlayer
|
||||
.getNearbyEntities(4.5, 4.5, 4.5)
|
||||
.stream()
|
||||
.filter(e -> e instanceof Damageable)
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bukkitPlayer.lookAt(entities.get(0), LookAnchor.EYES, LookAnchor.EYES);
|
||||
bukkitPlayer.lookAt(target, LookAnchor.EYES, LookAnchor.EYES);
|
||||
return true;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
DROP_ITEM("丢弃手上物品") {
|
||||
@ -283,6 +292,7 @@ public enum Action {
|
||||
};
|
||||
|
||||
|
||||
@NotNull
|
||||
public final String name;
|
||||
|
||||
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 void stop(@NotNull ActionPack ap, @NotNull ActionSetting setting) {}
|
||||
|
||||
public void inactiveTick(@NotNull ActionPack ap, @NotNull ActionSetting 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();
|
||||
|
||||
private final ConcurrentMap<UUID, Map<Action, ActionTicker>> MANAGERS = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<UUID, Map<Action, ActionTicker>> managers = new ConcurrentHashMap<>();
|
||||
|
||||
public ActionManager() {
|
||||
new BukkitRunnable() {
|
||||
@ -33,7 +33,7 @@ public class ActionManager {
|
||||
@NotNull Action action,
|
||||
@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(
|
||||
Main.getNms().getServerPlayer(player),
|
||||
action,
|
||||
@ -43,11 +43,13 @@ public class ActionManager {
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
var itr = MANAGERS.entrySet().iterator();
|
||||
var itr = managers.entrySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
var entry = itr.next();
|
||||
var player = Bukkit.getPlayer(entry.getKey());
|
||||
|
||||
if (player == null) {
|
||||
// 假人下线
|
||||
itr.remove();
|
||||
for (var ticker : entry.getValue().values()) {
|
||||
ticker.stop();
|
||||
@ -55,6 +57,7 @@ public class ActionManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
// do tick
|
||||
for (var ticker : entry.getValue().values()) {
|
||||
ticker.tick();
|
||||
}
|
||||
|
@ -2,26 +2,57 @@ package io.github.hello09x.fakeplayer.manager.action;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ActionPack {
|
||||
|
||||
/**
|
||||
* 假人玩家
|
||||
*/
|
||||
@NotNull
|
||||
public final ServerPlayer player;
|
||||
|
||||
/**
|
||||
* 左键数据
|
||||
*/
|
||||
@NotNull
|
||||
public final AttackActionPack attack = new AttackActionPack();
|
||||
|
||||
/**
|
||||
* 右键相关数据
|
||||
*/
|
||||
@NotNull
|
||||
public final UseActionPack use = new UseActionPack();
|
||||
|
||||
public ActionPack(ServerPlayer player) {
|
||||
public ActionPack(@NotNull ServerPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public final static class AttackActionPack {
|
||||
|
||||
/**
|
||||
* 当前左键的目标位置
|
||||
*/
|
||||
@Nullable
|
||||
public BlockPos pos;
|
||||
|
||||
/**
|
||||
* 破坏方块的进度
|
||||
*/
|
||||
public float progress;
|
||||
|
||||
/**
|
||||
* 冷却, 单位: tick
|
||||
*/
|
||||
public int freeze;
|
||||
}
|
||||
|
||||
public final static class UseActionPack {
|
||||
|
||||
/**
|
||||
* 冷却, 单位: tick
|
||||
*/
|
||||
public int freeze;
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,29 @@ package io.github.hello09x.fakeplayer.manager.action;
|
||||
|
||||
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ActionTicker {
|
||||
|
||||
/**
|
||||
* 行为类型
|
||||
*/
|
||||
@NotNull
|
||||
public final Action action;
|
||||
|
||||
/**
|
||||
* 行为数据
|
||||
*/
|
||||
@NotNull
|
||||
public final ActionPack actionPack;
|
||||
|
||||
/**
|
||||
* 行为设置
|
||||
*/
|
||||
@NotNull
|
||||
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.setting = setting;
|
||||
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.manager.naming.exception.IllegalCustomNameException;
|
||||
import io.github.hello09x.fakeplayer.repository.UsedIdRepository;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -33,6 +36,36 @@ public class NameManager {
|
||||
private final FakeplayerConfig config = FakeplayerConfig.instance;
|
||||
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
|
||||
*
|
||||
@ -40,7 +73,8 @@ public class NameManager {
|
||||
* @return UUID
|
||||
*/
|
||||
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()) {
|
||||
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));
|
||||
@ -134,17 +168,17 @@ public class NameManager {
|
||||
* @param group 分组
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 归还序列名
|
||||
*
|
||||
* @param sequenceName 序列名
|
||||
* @param sn 序列名
|
||||
*/
|
||||
public void unregister(@NotNull SequenceName sequenceName) {
|
||||
this.unregister(sequenceName.group(), sequenceName.sequence());
|
||||
public void unregister(@NotNull SequenceName sn) {
|
||||
this.unregister(sn.group(), sn.sequence());
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,8 +4,14 @@ import java.util.LinkedList;
|
||||
|
||||
public class NameSource {
|
||||
|
||||
/**
|
||||
* 接下来可以使用的名称序号
|
||||
*/
|
||||
private final LinkedList<Integer> names;
|
||||
|
||||
/**
|
||||
* 容量
|
||||
*/
|
||||
private volatile int capacity;
|
||||
|
||||
public NameSource(int initializeCapacity) {
|
||||
@ -20,6 +26,11 @@ public class NameSource {
|
||||
this(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个可使用的名称序号
|
||||
*
|
||||
* @return 名称序号
|
||||
*/
|
||||
public synchronized int pop() {
|
||||
if (names.isEmpty()) {
|
||||
var newCapacity = capacity * 2;
|
||||
@ -31,6 +42,11 @@ public class NameSource {
|
||||
return names.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 归还一个名称序号
|
||||
*
|
||||
* @param i 名称序号
|
||||
*/
|
||||
public synchronized void push(int i) {
|
||||
if (i >= capacity) {
|
||||
return;
|
||||
|
@ -1,11 +1,29 @@
|
||||
package io.github.hello09x.fakeplayer.manager.naming;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 序列名
|
||||
*
|
||||
* @param group 分组
|
||||
* @param sequence 序列
|
||||
* @param uuid UUID
|
||||
* @param name 名称
|
||||
*/
|
||||
public record SequenceName(
|
||||
|
||||
@NotNull
|
||||
String group,
|
||||
Integer sequence,
|
||||
|
||||
int sequence,
|
||||
|
||||
@NotNull
|
||||
UUID uuid,
|
||||
|
||||
@NotNull
|
||||
String name
|
||||
|
||||
) {
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class IllegalCustomNameException extends IllegalArgumentException {
|
||||
|
||||
@Getter
|
||||
private final Component msg;
|
||||
private final Component text;
|
||||
|
||||
public IllegalCustomNameException(@NotNull TextComponent message) {
|
||||
super(message.content());
|
||||
this.msg = message;
|
||||
this.text = message;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,6 +38,9 @@ public class UsedIdRepository {
|
||||
return UUIDS.contains(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件里读取使用过的 UUIDs
|
||||
*/
|
||||
public void load() {
|
||||
var file = new File(Main.getInstance().getDataFolder(), "used-uuids.txt");
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
@ -60,6 +63,9 @@ public class UsedIdRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将使用过的 UUIDs 写入文件
|
||||
*/
|
||||
public void saveAll() {
|
||||
var folder = Main.getInstance().getDataFolder();
|
||||
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 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) {
|
||||
return num;
|
||||
}
|
||||
return Math.floor(num / base) * base;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个数约束在范围以内
|
||||
* @param value 数
|
||||
* @param min 最小值
|
||||
* @param max 最大值
|
||||
* @return 约束后的数
|
||||
*/
|
||||
public static float clamp(float value, float min, float max) {
|
||||
return value < min ? min : Math.min(value, max);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.hello09x.fakeplayer.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -7,7 +8,11 @@ import java.lang.reflect.Modifier;
|
||||
|
||||
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()) {
|
||||
if (includeStatic ^ Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
@ -20,7 +25,11 @@ public class Reflections {
|
||||
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()) {
|
||||
if (includeStatic ^ Modifier.isStatic(field.getModifiers())) {
|
||||
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;
|
||||
|
||||
/**
|
||||
* copy from fabric carpet mod
|
||||
*/
|
||||
public class Tracer {
|
||||
public static @Nullable HitResult rayTrace(
|
||||
@NotNull Entity source,
|
||||
|
@ -5,7 +5,7 @@ import org.bukkit.World;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BK {
|
||||
public class Worlds {
|
||||
|
||||
private final static String WORLD_OVERWORLD = "world";
|
||||
|
@ -23,6 +23,35 @@ public class UpdateChecker {
|
||||
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 {
|
||||
var url = String.format("https://api.github.com/repos/%s/%s/releases/latest", this.author, this.repository);
|
||||
var client = HttpClient.newHttpClient();
|
||||
@ -31,7 +60,12 @@ public class UpdateChecker {
|
||||
.GET()
|
||||
.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);
|
||||
}
|
||||
|
||||
|
@ -37,12 +37,16 @@ name-template: ''
|
||||
name-pattern: '^[a-zA-Z0-9_]+$'
|
||||
|
||||
# 跟随下线
|
||||
# 假人创建者玩家下线时是否自动下线
|
||||
# 假人创建者下线时是否也跟着下线
|
||||
# 如果玩家只是切换服务器, 那么不会触发跟随下线
|
||||
follow-quiting: true
|
||||
|
||||
# 退出时是否丢弃背包物品
|
||||
# 有跨服背包同步的谨慎开启, 需验证是否会导致物品复制
|
||||
# 验证过程:
|
||||
# 1. 创建假人
|
||||
# 2. 通过 /kill 之类的命令杀死这个假人,假人死前会将背包物品丢弃出来
|
||||
# 3. 重新召唤这个假人, 查看他的背包数据有没有被复制了一份
|
||||
drop-inventory-on-quiting: true
|
||||
|
||||
# 如果启用, 则一个 IP 只能创建 `maximum` 个假人
|
||||
@ -59,7 +63,7 @@ kale-tps: 0
|
||||
# 有一些需要在 "登陆" 时生成玩家档案的插件发生异常比如 LuckPerms
|
||||
# 如果服务器没有出现严重的错误不需要理会这些异常, 只是这些插件无法对假人进行操作而已
|
||||
# 开启也不一定能解决所有问题, 也可能因为一些 "限制新加入玩家" 的插件而导致假人出现问题, 并且会创建更多的第三方插件数据
|
||||
simulate-login: false
|
||||
simulate-login: true
|
||||
|
||||
# 预准备命令
|
||||
# 假人诞生时会以控制台的身份按顺序执行以下命令
|
||||
|
@ -3,6 +3,7 @@ version: '${project.version}'
|
||||
main: io.github.hello09x.fakeplayer.Main
|
||||
api-version: '1.20'
|
||||
author: hello09x
|
||||
website: 'https://github.com/tanyaofei/minecraft-fakeplayer'
|
||||
|
||||
permissions:
|
||||
fakeplayer.all:
|
||||
|
Loading…
Reference in New Issue
Block a user