Compare commits

...

11 Commits

Author SHA1 Message Date
tanyaofei
2e6a3a34d9 update README 2025-08-15 09:40:41 +08:00
tanyaofei
07340d3218 optimize code 2025-08-15 09:29:28 +08:00
tanyaofei
6a3d880f17 config: bump version to 19 2025-08-15 09:26:51 +08:00
tanyaofei
f261e16a23 config: 'name-prefix' and 'name-style' 2025-08-15 09:26:41 +08:00
tanyaofei
d4bc411536 bugfix: error spawning fake player 2025-08-15 09:26:14 +08:00
tanyaofei
07eaa9774a Bump version to 0.3.18 2025-07-25 11:36:10 +08:00
tanyaofei
62b6da6c81 Add supports for 1.21.8 2025-07-25 11:34:34 +08:00
tanyaofei
fe316696d4 fix plugin message 2025-07-22 09:26:35 +08:00
tanyaofei
61d4ba297d GitHub templates 2025-07-16 11:37:07 +08:00
tanyaofei
9035a895fe Bump version to 0.3.16 2025-07-16 11:10:54 +08:00
tanyaofei
a1252094d7 Add support for 1.21.6 2025-07-16 10:57:15 +08:00
35 changed files with 1806 additions and 52 deletions

View File

@ -1,26 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug / 描述你的 bug 情况**
A clear and concise description of what the bug is. / 简单描述这是一个什么 bug
**To Reproduce / 如何触发**
Steps to reproduce the behavior: / 触发的步骤
1. ...
2. ...
3. ...
4. ...
**Server information / 服务器信息**
- Software / 核心: [e.g. Purpur 1.20.1]
- Java: [e.g. 21]
- Version / 版本 [e.g. 0.3.1]
**Additional context / 更多信息**
Add any other context about the problem here. Such as log and plugin list / 添加更多有帮助的信息,如插件列表、日志

27
.github/ISSUE_TEMPLATE/bug-report_en.md vendored Normal file
View File

@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. ...
2. ...
3. ...
4. ...
**Server information**
- Software: [e.g. Purpur 1.20.1]
- Java: [e.g. 21]
- Fakeplayer Version: [e.g. 0.3.1]
**Additional context**
Add any other context about the problem here. Such as log, plugin list or config files of other plugins **(hide sensitive content)**.

30
.github/ISSUE_TEMPLATE/bug-report_zh.md vendored Normal file
View File

@ -0,0 +1,30 @@
---
name: Bug 修复
about: 告诉我们程序存在的问题以协助完善 Fakeplayer
title: ''
labels: ''
assignees: ''
---
**描述**
简单描述这是一个怎么样的 Bug, 导致了什么问题
**触发步骤**
1. ...
2. ...
3. ...
4. ...
**服务器信息**
- 核心: [e.g. Purpur 1.20.1]
- Java: [e.g. 21]
- Fakeplayer 版本: [e.g. 0.3.1]
**其他有帮助的信息**
提供一些有帮助的信息以协助我们定位问题,如插件清单、报错日志、或者插件的一些配置 **(请隐藏敏感信息)**

View File

@ -19,7 +19,7 @@ This is a server side plugin inspired by [Carpet-Mod](https://github.com/gnembon
## Requirements ## Requirements
+ [Paper](https://papermc.io) or [Purpur](http://purpurmc.org) software + [Paper](https://papermc.io) or [Purpur](http://purpurmc.org) software
+ [CommandAPI](https://commandapi.jorel.dev) Plugin (>=9.5.0, <= 9.7.0) + [CommandAPI](https://commandapi.jorel.dev) Plugin (Any version **except** `10.0.0`)
## Config file ## Config file
@ -47,7 +47,7 @@ This approach can let you preview new content when you are upgrading it.
| /fp invsee | Open an inventory of a fake player | fakeplayer.command.invsee | Right-clicking on fake players has the same effect | | /fp invsee | Open an inventory of a fake player | fakeplayer.command.invsee | Right-clicking on fake players has the same effect |
| /fp sleep | Sleep | fakeplayer.command.sleep | | | /fp sleep | Sleep | fakeplayer.command.sleep | |
| /fp wakeup | Wake up | fakeplayer.command.wakeup | | | /fp wakeup | Wake up | fakeplayer.command.wakeup | |
| /fp status | Show player status | fakeplayer.command.status | | | /fp status | Show status | fakeplayer.command.status | |
| /fp respawn | Respawn a dead fake player | fakeplayer.command.respawn | Available when server config does not kick on fake player death | | /fp respawn | Respawn a dead fake player | fakeplayer.command.respawn | Available when server config does not kick on fake player death |
| /fp tp | Teleport to a fake player | fakeplayer.command.tp | | | /fp tp | Teleport to a fake player | fakeplayer.command.tp | |
| /fp tphere | Teleport a fake player to you | fakeplayer.command.tphere | | | /fp tphere | Teleport a fake player to you | fakeplayer.command.tphere | |
@ -167,13 +167,13 @@ If your server does not restrict various player commands, you can use this direc
4. Edit your `config.yml`, set `i18n.locale` to the name suffix which you just created such as `en_us` 4. Edit your `config.yml`, set `i18n.locale` to the name suffix which you just created such as `en_us`
5. Type `/fp reload-translation` to reload translation file. If you change `i18n.local`, you should `/fp reload` first 5. Type `/fp reload-translation` to reload translation file. If you change `i18n.local`, you should `/fp reload` first
**Make sure the translation file is encoding with UTF-8** **Make sure the translation file is encoded with UTF-8**
# FAQs # FAQs
## xxx lost connection: PacketEvents 2.0 failed to inject ## xxx lost connection: PacketEvents 2.0 failed to inject
Some plugin change the `Connection` of the fake player, You can set `prevent-kicking` to `ALWAYS` to solve it. Some plugin changes the `Connection` of the fake player, You can set `prevent-kicking` to `ALWAYS` to solve it.
```yaml ```yaml
# config.yml # config.yml

View File

@ -6,12 +6,12 @@ import dev.jorel.commandapi.executors.CommandArguments;
import io.github.hello09x.devtools.core.utils.ExperienceUtils; import io.github.hello09x.devtools.core.utils.ExperienceUtils;
import io.github.hello09x.fakeplayer.core.command.Permission; import io.github.hello09x.fakeplayer.core.command.Permission;
import io.github.hello09x.fakeplayer.core.repository.model.Feature; import io.github.hello09x.fakeplayer.core.repository.model.Feature;
import io.github.hello09x.fakeplayer.core.util.Attributes;
import io.github.hello09x.fakeplayer.core.util.Mth; import io.github.hello09x.fakeplayer.core.util.Mth;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.Style;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance; import org.bukkit.attribute.AttributeInstance;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -87,7 +87,7 @@ public class StatusCommand extends AbstractCommand {
private @NotNull Component getHealthLine(@NotNull Player target) { private @NotNull Component getHealthLine(@NotNull Player target) {
var health = target.getHealth(); var health = target.getHealth();
double max = Optional.ofNullable(target.getAttribute(Attribute.GENERIC_MAX_HEALTH)) double max = Optional.ofNullable(target.getAttribute(Attributes.maxHealth()))
.map(AttributeInstance::getValue) .map(AttributeInstance::getValue)
.orElse(20D); .orElse(20D);

View File

@ -10,16 +10,15 @@ import io.github.hello09x.fakeplayer.core.Main;
import io.github.hello09x.fakeplayer.core.repository.model.Feature; import io.github.hello09x.fakeplayer.core.repository.model.Feature;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -52,6 +51,21 @@ public class FakeplayerConfig extends PluginConfig {
*/ */
private String nameTemplate; private String nameTemplate;
/**
* 假人名称前缀
*/
private String namePrefix;
/**
* 名称样式, 颜色
*/
private NamedTextColor nameStyleColor;
/**
* 名称样式, 格式
*/
private List<TextDecoration> nameStyleDecorations;
/** /**
* 创建者玩家下线时是否跟随下线 * 创建者玩家下线时是否跟随下线
*/ */
@ -187,6 +201,7 @@ public class FakeplayerConfig extends PluginConfig {
this.namePattern = getNamePattern(file); this.namePattern = getNamePattern(file);
this.preventKicking = this.getPreventKicking(file); this.preventKicking = this.getPreventKicking(file);
this.nameTemplate = getNameTemplate(file); this.nameTemplate = getNameTemplate(file);
this.namePrefix = file.getString("name-prefix", "");
this.lifespan = getLifespan(file); this.lifespan = getLifespan(file);
this.allowCommands = file.getStringList("allow-commands") this.allowCommands = file.getStringList("allow-commands")
.stream() .stream()
@ -199,6 +214,8 @@ public class FakeplayerConfig extends PluginConfig {
.collect(Collectors.toMap(Function.identity(), key -> file.getString("default-features." + key.name(), key.getDefaultOption()))); .collect(Collectors.toMap(Function.identity(), key -> file.getString("default-features." + key.name(), key.getDefaultOption())));
this.invseeImplement = ConfigUtils.getEnum(file, "invsee-implement", InvseeImplement.class, InvseeImplement.AUTO); this.invseeImplement = ConfigUtils.getEnum(file, "invsee-implement", InvseeImplement.class, InvseeImplement.AUTO);
this.debug = file.getBoolean("debug", false); this.debug = file.getBoolean("debug", false);
this.nameStyleColor = this.getNameStyleColor(file);
this.nameStyleDecorations = this.getNameStyleDecorations(file);
if (this.isConfigFileOutOfDate()) { if (this.isConfigFileOutOfDate()) {
Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> { Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> {
@ -262,4 +279,28 @@ public class FakeplayerConfig extends PluginConfig {
return ConfigUtils.getEnum(file, "prevent-kicking", PreventKicking.class, PreventKicking.ON_SPAWNING); return ConfigUtils.getEnum(file, "prevent-kicking", PreventKicking.class, PreventKicking.ON_SPAWNING);
} }
private @NotNull NamedTextColor getNameStyleColor(@NotNull FileConfiguration file) {
var styles = Objects.requireNonNullElse(file.getString("name-style"), "").split(",\\s*");
var color = NamedTextColor.WHITE;
for (var style : styles) {
var c = NamedTextColor.NAMES.value(style);
if (c != null) {
color = c;
}
}
return color;
}
private @NotNull List<TextDecoration> getNameStyleDecorations(@NotNull FileConfiguration file) {
var styles = Objects.requireNonNullElse(file.getString("name-style"), "").split(",\\s*");
var decorations = new ArrayList<TextDecoration>();
for (var style : styles) {
var decoration = TextDecoration.NAMES.value(style);
if (decoration != null) {
decorations.add(decoration);
}
}
return decorations;
}
} }

View File

@ -14,11 +14,12 @@ import io.github.hello09x.fakeplayer.core.manager.FakeplayerReplenishManager;
import io.github.hello09x.fakeplayer.core.manager.FakeplayerSkinManager; import io.github.hello09x.fakeplayer.core.manager.FakeplayerSkinManager;
import io.github.hello09x.fakeplayer.core.manager.action.ActionManager; import io.github.hello09x.fakeplayer.core.manager.action.ActionManager;
import io.github.hello09x.fakeplayer.core.manager.naming.SequenceName; import io.github.hello09x.fakeplayer.core.manager.naming.SequenceName;
import io.github.hello09x.fakeplayer.core.util.Attributes;
import io.github.hello09x.fakeplayer.core.util.InternalAddressGenerator; import io.github.hello09x.fakeplayer.core.util.InternalAddressGenerator;
import lombok.Getter; import lombok.Getter;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance; import org.bukkit.attribute.AttributeInstance;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -36,7 +37,6 @@ import java.util.concurrent.CompletableFuture;
import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.translatable; import static net.kyori.adventure.text.Component.translatable;
import static net.kyori.adventure.text.format.NamedTextColor.*; import static net.kyori.adventure.text.format.NamedTextColor.*;
import static net.kyori.adventure.text.format.TextDecoration.ITALIC;
public class Fakeplayer { public class Fakeplayer {
@ -165,7 +165,7 @@ public class Fakeplayer {
this.network = bridge.createNetwork(address); this.network = bridge.createNetwork(address);
this.network.placeNewPlayer(Bukkit.getServer(), this.player); this.network.placeNewPlayer(Bukkit.getServer(), this.player);
this.player.setHealth(Optional.ofNullable(this.player.getAttribute(Attribute.GENERIC_MAX_HEALTH)) this.player.setHealth(Optional.ofNullable(this.player.getAttribute(Attributes.maxHealth()))
.map(AttributeInstance::getValue) .map(AttributeInstance::getValue)
.orElse(20D)); // 恢复生命值 .orElse(20D)); // 恢复生命值
this.player.setFoodLevel(20); this.player.setFoodLevel(20);
@ -252,7 +252,7 @@ public class Fakeplayer {
} }
private void setupName() { private void setupName() {
var displayName = text(player.getName(), GRAY, ITALIC); var displayName = text(player.getName(), config.getNameStyleColor(), config.getNameStyleDecorations().toArray(new TextDecoration[0]));
player.playerListName(displayName); player.playerListName(displayName);
player.displayName(displayName); player.displayName(displayName);
player.customName(displayName); player.customName(displayName);

View File

@ -135,7 +135,7 @@ public class FakeplayerListener implements Listener {
// 有一些跨服同步插件会退出时同步生命值, 假人重新生成的时候同步为 0 // 有一些跨服同步插件会退出时同步生命值, 假人重新生成的时候同步为 0
// 因此在死亡时将生命值设置恢复满血先 // 因此在死亡时将生命值设置恢复满血先
Optional.ofNullable(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)) Optional.ofNullable(player.getAttribute(Attribute.MAX_HEALTH))
.map(AttributeInstance::getValue) .map(AttributeInstance::getValue)
.ifPresent(player::setHealth); .ifPresent(player::setHealth);
event.setCancelled(true); event.setCancelled(true);

View File

@ -9,6 +9,7 @@ import io.github.hello09x.fakeplayer.core.repository.FakeplayerProfileRepository
import io.github.hello09x.fakeplayer.core.repository.UsedIdRepository; import io.github.hello09x.fakeplayer.core.repository.UsedIdRepository;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
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;
@ -118,6 +119,9 @@ public class NameManager {
* @return 序列名 * @return 序列名
*/ */
public @NotNull SequenceName getSpecifiedName(@NotNull String name) { public @NotNull SequenceName getSpecifiedName(@NotNull String name) {
if (StringUtils.isNotBlank(config.getNamePrefix())) {
name = config.getNamePrefix().trim() + name;
}
if (name.startsWith("-")) { if (name.startsWith("-")) {
throw new IllegalCustomNameException(translatable( throw new IllegalCustomNameException(translatable(
"fakeplayer.spawn.error.name.start-with-illegal-character", "fakeplayer.spawn.error.name.start-with-illegal-character",
@ -184,6 +188,9 @@ public class NameManager {
source = creator.getName(); source = creator.getName();
} }
source = source.replace("%c", creator.getName()); source = source.replace("%c", creator.getName());
if (StringUtils.isNotBlank(config.getNamePrefix())) {
source = config.getNamePrefix().trim() + source;
}
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
var seq = nameSources.computeIfAbsent(source, ignored -> new NameSource(config.getPlayerLimit())).pop(); var seq = nameSources.computeIfAbsent(source, ignored -> new NameSource(config.getPlayerLimit())).pop();

View File

@ -0,0 +1,35 @@
package io.github.hello09x.fakeplayer.core.util;
import org.bukkit.attribute.Attribute;
import org.jetbrains.annotations.NotNull;
import java.util.NoSuchElementException;
/**
* @author tanyaofei
* @since 2025/7/16
**/
public abstract class Attributes {
/**
* <ul>
* <li><= 1.12.5 GENERIC_MAX_HEALTH</li>
* <li>>= 1.21.6 MAX_HEALTH</li>
* </ul>
*
* @return
*/
public static @NotNull Attribute maxHealth() {
try {
return (Attribute) Attribute.class.getField("GENERIC_MAX_HEALTH").get(Attribute.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
try {
return (Attribute) Attribute.class.getField("MAX_HEALTH").get(Attribute.class);
} catch (Exception e1) {
throw new NoSuchElementException("No attribute found for MAX_HEALTH or GENERIC_MAX_HEALTH");
}
}
}
}

View File

@ -1,10 +1,18 @@
# ========================================================================================================= # =========================================================================================================
# 【Important】【非常重要】 # 【Important】
# DO NOT EDIT config.temp.yml, copy it and rename to config.yml as your configuration file # DO NOT EDIT config.tmpl.yml !!!
# 不要直接修改 config.tmpl.yml 文件,将这份文件复制并重命名为 config.yml 来作为配置文件 # Copy and rename this file to config.yml as your configuration file.
# 【Important】【非常重要】 # Any changes to this file will be reverted.
# -------------------------------------------------------------------------------------------------------
# 【非常重要】
# 不要修改 config.tmpl.yml !!!
# 复制此文件并重命名为 config.yml 来作为你的配置文件
# 此文件的任何修改将会在服务器启动时被还原
# ========================================================================================================= # =========================================================================================================
version: 18
# DO NOT EDIT
# 不要修改
version: 19
# 多国语言配置 # 多国语言配置
# 可选项: en, zh, zh_cn, zh_tw, zh_hk # 可选项: en, zh, zh_cn, zh_tw, zh_hk
@ -60,10 +68,15 @@ lifespan: 0
# Tips: # Tips:
# 1. If this value contains characters other than alphabetic, numbers, and underscores, many vanilla commands will not be usable on them. # 1. If this value contains characters other than alphabetic, numbers, and underscores, many vanilla commands will not be usable on them.
# 2. Characters longer than 16 characters will be truncated # 2. Characters longer than 16 characters will be truncated
# 3. Can not start with '-' # 3. Cannot start with '-'
# 4. It's not recommended, players may give them RES privileges, or put some items into their inventory. # 4. It's not recommended, players may give them RES privileges, or put some items into their inventory.
name-template: '' name-template: ''
# 假人名称前缀
# 自动生成的名称或者玩家创建时指定名称都将会追加前缀
# Name prefix.
# Prefix will be added to the generated name or specified name
name-prefix: ''
# 假人自定义名称允许的字符 # 假人自定义名称允许的字符
# 格式: 正则表达式 # 格式: 正则表达式
@ -74,6 +87,14 @@ name-template: ''
# 3. 如果你改了正则表达式, 请确保它以 `^` 开头并且以 `$` 结尾 # 3. 如果你改了正则表达式, 请确保它以 `^` 开头并且以 `$` 结尾
name-pattern: '^[a-zA-Z0-9_]+$' name-pattern: '^[a-zA-Z0-9_]+$'
# 假人在 Tab 上的名称样式
# Name style on Tab listing
# Options:
# black, dark_blue, dark_green, dark_aqua, dark_red, dark_purple, gold, gray,
# dark_gray, blue, green, aqua, red, light_purple, yellow, white
# obfuscated, bold, strikethrough, underlined, italic
name-style: gray, italic
# 防止假人被其他插件踢掉, 这个选项用来兼容一些插件因为某些问题而踢掉假人 # 防止假人被其他插件踢掉, 这个选项用来兼容一些插件因为某些问题而踢掉假人
# 可选项: # 可选项:

View File

@ -96,6 +96,24 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-v1_21_6</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-v1_21_7</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-v1_21_8</artifactId>
<version>${revision}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -175,6 +193,21 @@
<include name="fakeplayer-${revision}.jar"/> <include name="fakeplayer-${revision}.jar"/>
</fileset> </fileset>
</copy> </copy>
<copy tofile="../server-1.21.6/plugins/fakeplayer.jar">
<fileset dir="${project.build.directory}">
<include name="fakeplayer-${revision}.jar"/>
</fileset>
</copy>
<copy tofile="../server-1.21.7/plugins/fakeplayer.jar">
<fileset dir="${project.build.directory}">
<include name="fakeplayer-${revision}.jar"/>
</fileset>
</copy>
<copy tofile="../server-1.21.8/plugins/fakeplayer.jar">
<fileset dir="${project.build.directory}">
<include name="fakeplayer-${revision}.jar"/>
</fileset>
</copy>
</target> </target>
</configuration> </configuration>
<goals> <goals>

View File

@ -9,3 +9,7 @@ io.github.hello09x.fakeplayer.v1_21_1.spi.NMSBridgeImpl
io.github.hello09x.fakeplayer.v1_21_3.spi.NMSBridgeImpl io.github.hello09x.fakeplayer.v1_21_3.spi.NMSBridgeImpl
io.github.hello09x.fakeplayer.v1_21_4.spi.NMSBridgeImpl io.github.hello09x.fakeplayer.v1_21_4.spi.NMSBridgeImpl
io.github.hello09x.fakeplayer.v1_21_5.spi.NMSBridgeImpl io.github.hello09x.fakeplayer.v1_21_5.spi.NMSBridgeImpl
io.github.hello09x.fakeplayer.v1_21_6.spi.NMSBridgeImpl
io.github.hello09x.fakeplayer.v1_21_7.spi.NMSBridgeImpl
io.github.hello09x.fakeplayer.v1_21_8.spi.NMSBridgeImpl

View File

@ -3,6 +3,7 @@ package io.github.hello09x.fakeplayer.v1_21_5.network;
import io.github.hello09x.fakeplayer.api.spi.NMSServerGamePacketListener; import io.github.hello09x.fakeplayer.api.spi.NMSServerGamePacketListener;
import io.github.hello09x.fakeplayer.core.Main; import io.github.hello09x.fakeplayer.core.Main;
import io.github.hello09x.fakeplayer.core.manager.FakeplayerManager; import io.github.hello09x.fakeplayer.core.manager.FakeplayerManager;
import lombok.Lombok;
import net.minecraft.network.Connection; import net.minecraft.network.Connection;
import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
@ -16,6 +17,7 @@ import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_21_R4.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_21_R4.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -66,7 +68,7 @@ public class FakeServerGamePacketListenerImpl extends ServerGamePacketListenerIm
return; return;
} }
if (!(payload instanceof DiscardedPayload p)) { if (!(payload instanceof DiscardedPayload discardedPayload)) {
return; return;
} }
@ -82,8 +84,20 @@ public class FakeServerGamePacketListenerImpl extends ServerGamePacketListenerIm
return; return;
} }
var message = p.data().array(); var message = getDiscardedPayloadData(discardedPayload);
recipient.sendPluginMessage(Main.getInstance(), BUNGEE_CORD_CHANNEL, message); recipient.sendPluginMessage(Main.getInstance(), BUNGEE_CORD_CHANNEL, message);
} }
private byte[] getDiscardedPayloadData(@NotNull DiscardedPayload payload) {
try {
return payload.data().array();
} catch (NoSuchMethodError e) {
try {
return (byte[]) payload.getClass().getMethod("data").invoke(payload); // 1.21.5 actual is `public final byte[] data() {}`
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
throw Lombok.sneakyThrow(e);
}
}
}
} }

100
fakeplayer-v1_21_6/pom.xml Normal file
View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-parent</artifactId>
<version>${revision}</version>
</parent>
<artifactId>fakeplayer-v1_21_6</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<nms.version>1.21.6-R0.1-SNAPSHOT</nms.version>
</properties>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${nms.version}</version>
<classifier>remapped-mojang</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
<version>2.0.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-obf</id>
<configuration>
<srgIn>org.spigotmc:minecraft-server:${nms.version}:txt:maps-mojang</srgIn>
<reverse>true</reverse>
<remappedDependencies>
org.spigotmc:spigot:${nms.version}:jar:remapped-mojang
</remappedDependencies>
<remappedArtifactAttached>true</remappedArtifactAttached>
<remappedClassifierName>remapped-obf</remappedClassifierName>
</configuration>
</execution>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-spigot</id>
<configuration>
<inputFile>
${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
</inputFile>
<srgIn>org.spigotmc:minecraft-server:${nms.version}:csrg:maps-spigot</srgIn>
<remappedDependencies>org.spigotmc:spigot:${nms.version}:jar:remapped-obf
</remappedDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,49 @@
package io.github.hello09x.fakeplayer.v1_21_6.action;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
public class AttackAction extends TraceAction {
private final ServerPlayer player;
public AttackAction(ServerPlayer player) {
super(player);
this.player = player;
}
@Override
public boolean tick() {
var hit = this.getTarget();
if (hit == null) {
return false;
}
if (hit.getType() != HitResult.Type.ENTITY) {
return false;
}
var entityHit = (EntityHitResult) hit;
player.attack(entityHit.getEntity());
player.swing(InteractionHand.MAIN_HAND);
player.resetAttackStrengthTicker();
player.resetLastActionTime();
return true;
}
@Override
public void inactiveTick() {
}
@Override
public void stop() {
}
}

View File

@ -0,0 +1,160 @@
package io.github.hello09x.fakeplayer.v1_21_6.action;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jetbrains.annotations.Nullable;
import static net.minecraft.network.protocol.game.ServerboundPlayerActionPacket.Action.*;
public class MineAction extends TraceAction {
private final Current current = new Current();
public MineAction(ServerPlayer player) {
super(player);
}
@Override
@SuppressWarnings("resource")
public boolean tick() {
var hit = this.getTarget();
if (hit == null) {
return false;
}
if (hit.getType() != HitResult.Type.BLOCK) {
return false;
}
if (current.freeze > 0) {
current.freeze--;
return false;
}
var blockHit = (BlockHitResult) hit;
var pos = blockHit.getBlockPos();
var side = blockHit.getDirection();
if (player.blockActionRestricted(player.level(), pos, player.gameMode.getGameModeForPlayer())) {
return false;
}
if (current.pos != null && player.level().getBlockState(current.pos).isAir()) {
current.pos = null;
return false;
}
var state = player.level().getBlockState(pos);
var broken = false;
if (player.gameMode.getGameModeForPlayer().isCreative()) {
player.gameMode.handleBlockBreakAction(
pos,
START_DESTROY_BLOCK,
side,
player.level().getMaxY(),
-1
);
current.freeze = 5;
broken = true;
} else if (current.pos == null || !current.pos.equals(pos)) {
if (current.pos != null) {
player.gameMode.handleBlockBreakAction(
current.pos,
ABORT_DESTROY_BLOCK,
side,
player.level().getMaxY(),
-1
);
}
player.gameMode.handleBlockBreakAction(
pos,
START_DESTROY_BLOCK,
side,
player.level().getMaxY(),
-1
);
if (!state.isAir() && current.progress == 0) {
state.attack(player.level(), pos, player);
}
if (!state.isAir() && state.getDestroyProgress(player, player.level(), pos) >= 1) {
current.pos = null;
broken = true;
} else {
current.pos = pos;
current.progress = 0;
}
} else {
current.progress += state.getDestroyProgress(player, player.level(), pos);
if (current.progress >= 1) {
player.gameMode.handleBlockBreakAction(
pos,
STOP_DESTROY_BLOCK,
side,
player.level().getMaxY(),
-1
);
current.pos = null;
current.freeze = 5;
broken = true;
}
player.level().destroyBlockProgress(-1, pos, (int) (current.progress * 10));
}
player.resetLastActionTime();
player.swing(InteractionHand.MAIN_HAND);
return broken;
}
@Override
public void inactiveTick() {
stop();
}
@Override
@SuppressWarnings("resource")
public void stop() {
if (current.pos == null) {
return;
}
player.level().destroyBlockProgress(-1, current.pos, -1);
player.gameMode.handleBlockBreakAction(
current.pos,
ABORT_DESTROY_BLOCK,
Direction.DOWN,
player.level().getMaxY(),
-1
);
current.pos = null;
current.freeze = 0;
current.progress = 0;
}
private static class Current {
/**
* 当前左键的目标位置
*/
@Nullable
public BlockPos pos;
/**
* 破坏方块的进度
*/
public float progress;
/**
* 冷却, 单位: tick
*/
public int freeze;
}
}

View File

@ -0,0 +1,24 @@
package io.github.hello09x.fakeplayer.v1_21_6.action;
import io.github.hello09x.fakeplayer.api.spi.Action;
import io.github.hello09x.fakeplayer.v1_21_6.action.util.Tracer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.phys.HitResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class TraceAction implements Action {
protected final ServerPlayer player;
protected TraceAction(@NotNull ServerPlayer player) {
this.player = player;
}
protected @Nullable HitResult getTarget() {
double reach = player.gameMode.isCreative() ? 5 : 4.5f;
return Tracer.rayTrace(player, 1, reach, false);
}
}

View File

@ -0,0 +1,97 @@
package io.github.hello09x.fakeplayer.v1_21_6.action;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import org.jetbrains.annotations.NotNull;
public class UseAction extends TraceAction {
private final Current current = new Current();
public UseAction(@NotNull ServerPlayer player) {
super(player);
}
@Override
@SuppressWarnings("resource")
public boolean tick() {
if (current.freeze > 0) {
current.freeze--;
return false;
}
if (player.isUsingItem()) {
return true;
}
var hit = this.getTarget();
if (hit == null) {
return false;
}
for (var hand : InteractionHand.values()) {
switch (hit.getType()) {
case BLOCK -> {
player.resetLastActionTime();
var world = player.level();
var blockHit = (BlockHitResult) hit;
var pos = blockHit.getBlockPos();
var side = blockHit.getDirection();
if (pos.getY() < player.level().getMaxY() - (side == Direction.UP ? 1 : 0) && world.mayInteract(player, pos)) {
var result = player.gameMode.useItemOn(player, world, player.getItemInHand(hand), hand, blockHit);
if (result.consumesAction()) {
player.swing(hand);
current.freeze = 3;
return true;
}
}
}
case ENTITY -> {
player.resetLastActionTime();
var entityHit = (EntityHitResult) hit;
var entity = entityHit.getEntity();
boolean handWasEmpty = player.getItemInHand(hand).isEmpty();
boolean itemFrameEmpty = (entity instanceof ItemFrame) && ((ItemFrame) entity).getItem().isEmpty();
var pos = entityHit.getLocation().subtract(entity.getX(), entity.getY(), entity.getZ());
if (entity.interactAt(player, pos, hand).consumesAction()) {
current.freeze = 3;
return true;
}
if (player.interactOn(entity, hand).consumesAction() && !(handWasEmpty && itemFrameEmpty)) {
current.freeze = 3;
return true;
}
}
}
var handItem = player.getItemInHand(hand);
if (player.gameMode.useItem(player, player.level(), handItem, hand).consumesAction()) {
player.resetLastActionTime();
current.freeze = 3;
return true;
}
}
return false;
}
@Override
public void inactiveTick() {
}
@Override
public void stop() {
current.freeze = 0;
player.releaseUsingItem();
}
private final static class Current {
/**
* 冷却, 单位: tick
*/
public int freeze;
}
}

View File

@ -0,0 +1,105 @@
package io.github.hello09x.fakeplayer.v1_21_6.action.util;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.phys.*;
import org.jetbrains.annotations.NotNull;
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,
float partialTicks,
double reach,
boolean fluids
) {
var blockHit = rayTraceBlocks(source, partialTicks, reach, fluids);
double maxSqDist = reach * reach;
if (blockHit != null) {
maxSqDist = blockHit.getLocation().distanceToSqr(source.getEyePosition(partialTicks));
}
EntityHitResult entityHit = rayTraceEntities(source, partialTicks, reach, maxSqDist);
return entityHit == null ? blockHit : entityHit;
}
@SuppressWarnings("resource")
public static @Nullable BlockHitResult rayTraceBlocks(
@NotNull Entity source,
float partialTicks,
double reach,
boolean fluids
) {
var pos = source.getEyePosition(partialTicks);
var rotation = source.getViewVector(partialTicks);
var reachEnd = pos.add(rotation.x * reach, rotation.y * reach, rotation.z * reach);
return source.level().clip(new ClipContext(pos, reachEnd, ClipContext.Block.OUTLINE, fluids ?
ClipContext.Fluid.ANY : ClipContext.Fluid.NONE, source));
}
public static @Nullable EntityHitResult rayTraceEntities(
@NotNull Entity source,
float partialTicks,
double reach,
double maxSqDist
) {
var pos = source.getEyePosition(partialTicks);
var reachVec = source.getViewVector(partialTicks).scale(reach);
var box = source.getBoundingBox().expandTowards(reachVec).inflate(1);
return rayTraceEntities(source,
pos,
pos.add(reachVec),
box,
e -> !e.isSpectator() && e.isPickable(),
maxSqDist);
}
public static @Nullable EntityHitResult rayTraceEntities(
@NotNull Entity source,
@NotNull Vec3 start,
@NotNull Vec3 end,
@NotNull AABB box,
@NotNull Predicate<Entity> predicate,
double maxSqDistance
) {
@SuppressWarnings("resource")
var world = source.level();
double targetDistance = maxSqDistance;
Entity target = null;
Vec3 targetHitPos = null;
for (Entity current : world.getEntities(source, box, predicate)) {
var currentBox = current.getBoundingBox().inflate(current.getPickRadius());
var currentHit = currentBox.clip(start, end);
if (currentBox.contains(start)) {
if (targetDistance >= 0) {
target = current;
targetHitPos = currentHit.orElse(start);
targetDistance = 0;
}
} else if (currentHit.isPresent()) {
var currentHitPos = currentHit.get();
var currentDistance = start.distanceToSqr(currentHitPos);
if (currentDistance < targetDistance || targetDistance == 0) {
if (current.getRootVehicle() == source.getRootVehicle()) {
if (targetDistance == 0) {
target = current;
targetHitPos = currentHitPos;
}
}
else
{
target = current;
targetHitPos = currentHitPos;
targetDistance = currentDistance;
}
}
}
}
return target == null ? null : new EntityHitResult(target, targetHitPos);
}
}

View File

@ -0,0 +1,48 @@
package io.github.hello09x.fakeplayer.v1_21_6.network;
import io.github.hello09x.fakeplayer.core.Main;
import io.github.hello09x.fakeplayer.core.manager.FakeplayerManager;
import io.github.hello09x.fakeplayer.core.network.FakeChannel;
import io.netty.channel.ChannelFutureListener;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.InetAddress;
import java.util.logging.Logger;
public class FakeConnection extends Connection {
private final static Logger log = Main.getInstance().getLogger();
private final FakeplayerManager manager = Main.getInjector().getInstance(FakeplayerManager.class);
public FakeConnection(@NotNull InetAddress address) {
super(PacketFlow.SERVERBOUND);
this.channel = new FakeChannel(null, address);
this.address = this.channel.remoteAddress();
Connection.configureSerialization(this.channel.pipeline(), PacketFlow.SERVERBOUND, false, null);
}
@Override
public boolean isConnected() {
return true;
}
@Override
public void send(Packet<?> packet, @Nullable ChannelFutureListener channelfuturelistener) {
}
@Override
public void send(Packet<?> packet, @Nullable ChannelFutureListener channelfuturelistener, boolean flag) {
}
@Override
public void send(Packet<?> packet) {
}
}

View File

@ -0,0 +1,64 @@
package io.github.hello09x.fakeplayer.v1_21_6.network;
import com.mojang.datafixers.DataFixer;
import net.minecraft.advancements.AdvancementHolder;
import net.minecraft.advancements.AdvancementProgress;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.ServerAdvancementManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.PlayerList;
import java.nio.file.Path;
public class FakePlayerAdvancements extends PlayerAdvancements {
public FakePlayerAdvancements(
DataFixer datafixer,
PlayerList playerlist,
ServerAdvancementManager manager,
Path path,
ServerPlayer player
) {
super(datafixer, playerlist, manager, path, player);
this.save();
}
@Override
public boolean award(AdvancementHolder advancementholder, String s) {
return false;
}
@Override
public void flushDirty(ServerPlayer entityplayer, boolean flag) {
}
@Override
public AdvancementProgress getOrStartProgress(AdvancementHolder advancement) {
return new AdvancementProgress();
}
@Override
public boolean revoke(AdvancementHolder advancement, String s) {
return false;
}
@Override
public void save() {
}
@Override
public void setPlayer(ServerPlayer player) {
}
@Override
public void setSelectedTab(AdvancementHolder advancement) {
}
@Override
public void stopListening() {
}
}

View File

@ -0,0 +1,115 @@
package io.github.hello09x.fakeplayer.v1_21_6.network;
import io.github.hello09x.fakeplayer.api.spi.NMSServerGamePacketListener;
import io.github.hello09x.fakeplayer.core.Main;
import io.github.hello09x.fakeplayer.core.manager.FakeplayerManager;
import lombok.Lombok;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.CommonListenerCookie;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
import java.util.logging.Logger;
public class FakeServerGamePacketListenerImpl extends ServerGamePacketListenerImpl implements NMSServerGamePacketListener {
private final FakeplayerManager manager = Main.getInjector().getInstance(FakeplayerManager.class);
private final static Logger log = Main.getInstance().getLogger();
public FakeServerGamePacketListenerImpl(
@NotNull MinecraftServer server,
@NotNull Connection connection,
@NotNull ServerPlayer player,
@NotNull CommonListenerCookie cookie
) {
super(server, connection, player, cookie);
Optional.ofNullable(Bukkit.getPlayer(player.getUUID()))
.ifPresent(p -> this.addChannel(p, BUNGEE_CORD_CORRECTED_CHANNEL));
}
private boolean addChannel(@NotNull Player player, @NotNull String channel) {
try {
var method = player.getClass().getMethod("addChannel", String.class);
var ret = method.invoke(player, channel);
if (ret instanceof Boolean success) {
return success;
}
return true;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
return false;
}
}
@Override
public void send(Packet<?> packet) {
if (packet instanceof ClientboundCustomPayloadPacket p) {
this.handleCustomPayloadPacket(p);
} else if (packet instanceof ClientboundSetEntityMotionPacket p) {
this.handleClientboundSetEntityMotionPacket(p);
}
}
/**
* 玩家被击退的动作由客户端完成, 假人没有客户端因此手动完成这个动作
*/
public void handleClientboundSetEntityMotionPacket(@NotNull ClientboundSetEntityMotionPacket packet) {
if (packet.getId() == this.player.getId() && this.player.hurtMarked) {
Bukkit.getScheduler().runTask(Main.getInstance(), () -> {
this.player.hurtMarked = true;
this.player.lerpMotion(packet.getXa(), packet.getYa(), packet.getZa());
});
}
}
private void handleCustomPayloadPacket(@NotNull ClientboundCustomPayloadPacket packet) {
var payload = packet.payload();
var resourceLocation = payload.type().id();
var channel = resourceLocation.getNamespace() + ":" + resourceLocation.getPath();
if (!channel.equals(BUNGEE_CORD_CORRECTED_CHANNEL)) {
return;
}
if (!(payload instanceof DiscardedPayload discardedPayload)) {
return;
}
var recipient = Bukkit
.getOnlinePlayers()
.stream()
.filter(manager::isNotFake)
.findAny()
.orElse(null);
if (recipient == null) {
log.warning("Failed to forward a plugin message cause non real players in the server");
return;
}
var message = getDiscardedPayloadData(discardedPayload);
recipient.sendPluginMessage(Main.getInstance(), BUNGEE_CORD_CHANNEL, message);
}
private byte[] getDiscardedPayloadData(@NotNull DiscardedPayload payload) {
try {
return payload.data().array();
} catch (NoSuchMethodError e) {
try {
return (byte[]) payload.getClass().getMethod("data").invoke(payload); // 1.21.5 actual is `public final byte[] data() {}`
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
throw Lombok.sneakyThrow(e);
}
}
}
}

View File

@ -0,0 +1,31 @@
package io.github.hello09x.fakeplayer.v1_21_6.spi;
import io.github.hello09x.fakeplayer.api.spi.ActionSetting;
import io.github.hello09x.fakeplayer.api.spi.ActionTicker;
import io.github.hello09x.fakeplayer.api.spi.ActionType;
import io.github.hello09x.fakeplayer.api.spi.NMSBridge;
import io.github.hello09x.fakeplayer.core.entity.action.BaseActionTicker;
import io.github.hello09x.fakeplayer.v1_21_6.action.AttackAction;
import io.github.hello09x.fakeplayer.v1_21_6.action.MineAction;
import io.github.hello09x.fakeplayer.v1_21_6.action.UseAction;
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class ActionTickerImpl extends BaseActionTicker implements ActionTicker {
public ActionTickerImpl(@NotNull NMSBridge nms, @NotNull Player player, @NotNull ActionType action, @NotNull ActionSetting setting) {
super(nms, player, action, setting);
if (this.action == null) {
this.action = switch (action) {
case ATTACK -> new AttackAction(((CraftPlayer) player).getHandle());
case MINE -> new MineAction(((CraftPlayer) player).getHandle());
case USE -> new UseAction(((CraftPlayer) player).getHandle());
case JUMP, LOOK_AT_NEAREST_ENTITY, DROP_INVENTORY, DROP_STACK, DROP_ITEM ->
throw new UnsupportedOperationException();
};
}
}
}

View File

@ -0,0 +1,54 @@
package io.github.hello09x.fakeplayer.v1_21_6.spi;
import io.github.hello09x.fakeplayer.api.spi.*;
import io.github.hello09x.fakeplayer.core.Main;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.net.InetAddress;
import java.util.Set;
public class NMSBridgeImpl implements NMSBridge {
private final static Set<String> SUPPORTS = Set.of("1.21.6");
@Override
public @NotNull NMSEntity fromEntity(@NotNull Entity entity) {
return new NMSEntityImpl(entity);
}
@Override
public @NotNull NMSServer fromServer(@NotNull Server server) {
return new NMSServerImpl(server);
}
@Override
public @NotNull NMSServerLevel fromWorld(@NotNull World world) {
return new NMSServerLevelImpl(world);
}
@Override
public @NotNull NMSServerPlayer fromPlayer(@NotNull Player player) {
return new NMSServerPlayerImpl(player);
}
@Override
public @NotNull NMSNetwork createNetwork(@NotNull InetAddress address) {
return new NMSNetworkImpl(address);
}
@Override
public boolean isSupported() {
return SUPPORTS.contains(Bukkit.getMinecraftVersion());
}
@Override
public @NotNull ActionTicker createAction(@NotNull Player player, @NotNull ActionType action, @NotNull ActionSetting setting) {
return new ActionTickerImpl(Main.getInjector().getInstance(NMSBridge.class), player, action, setting);
}
}

View File

@ -0,0 +1,19 @@
package io.github.hello09x.fakeplayer.v1_21_6.spi;
import io.github.hello09x.fakeplayer.api.spi.NMSEntity;
import lombok.Getter;
import net.minecraft.world.entity.Entity;
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity;
import org.jetbrains.annotations.NotNull;
public class NMSEntityImpl implements NMSEntity {
@Getter
private final Entity handle;
public NMSEntityImpl(@NotNull org.bukkit.entity.@NotNull Entity entity) {
this.handle = ((CraftEntity) entity).getHandle();
}
}

View File

@ -0,0 +1,64 @@
package io.github.hello09x.fakeplayer.v1_21_6.spi;
import io.github.hello09x.fakeplayer.api.spi.NMSNetwork;
import io.github.hello09x.fakeplayer.api.spi.NMSServerGamePacketListener;
import io.github.hello09x.fakeplayer.v1_21_6.network.FakeConnection;
import io.github.hello09x.fakeplayer.v1_21_6.network.FakeServerGamePacketListenerImpl;
import net.minecraft.server.network.CommonListenerCookie;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_21_R5.CraftServer;
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.net.InetAddress;
public class NMSNetworkImpl implements NMSNetwork {
@NotNull
private final FakeConnection connection;
private NMSServerGamePacketListener serverGamePacketListener;
public NMSNetworkImpl(
@NotNull InetAddress address
) {
this.connection = new FakeConnection(address);
}
@NotNull
@Override
public NMSServerGamePacketListener placeNewPlayer(
@NotNull Server server,
@NotNull Player player
) {
var handle = ((CraftPlayer) player).getHandle();
var cookie = CommonListenerCookie.createInitial(((CraftPlayer) player).getProfile(), false);
((CraftServer) server).getHandle().placeNewPlayer(
this.connection,
handle,
cookie
);
var listener = new FakeServerGamePacketListenerImpl(
((CraftServer) server).getServer(),
this.connection,
handle,
cookie
);
this.serverGamePacketListener = listener;
handle.connection = listener;
return listener;
}
@NotNull
@Override
public NMSServerGamePacketListener getServerGamePacketListener() throws IllegalStateException {
if (this.serverGamePacketListener == null) {
throw new IllegalStateException("not initialized");
}
return this.serverGamePacketListener;
}
}

View File

@ -0,0 +1,38 @@
package io.github.hello09x.fakeplayer.v1_21_6.spi;
import com.mojang.authlib.GameProfile;
import io.github.hello09x.devtools.core.utils.WorldUtils;
import io.github.hello09x.fakeplayer.api.spi.NMSServer;
import io.github.hello09x.fakeplayer.api.spi.NMSServerPlayer;
import lombok.Getter;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_21_R5.CraftServer;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class NMSServerImpl implements NMSServer {
@Getter
private final MinecraftServer handle;
public NMSServerImpl(@NotNull Server server) {
this.handle = ((CraftServer) server).getServer();
}
@Override
public @NotNull NMSServerPlayer newPlayer(@NotNull UUID uuid, @NotNull String name) {
var handle = new ServerPlayer(
new NMSServerImpl(Bukkit.getServer()).getHandle(),
new NMSServerLevelImpl(WorldUtils.getMainWorld()).getHandle(),
new GameProfile(uuid, name),
ClientInformation.createDefault()
);
return new NMSServerPlayerImpl(handle.getBukkitEntity());
}
}

View File

@ -0,0 +1,19 @@
package io.github.hello09x.fakeplayer.v1_21_6.spi;
import io.github.hello09x.fakeplayer.api.spi.NMSServerLevel;
import lombok.Getter;
import net.minecraft.server.level.ServerLevel;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R5.CraftWorld;
import org.jetbrains.annotations.NotNull;
public class NMSServerLevelImpl implements NMSServerLevel {
@Getter
private final ServerLevel handle;
public NMSServerLevelImpl(@NotNull World world) {
this.handle = ((CraftWorld) world).getHandle();
}
}

View File

@ -0,0 +1,256 @@
package io.github.hello09x.fakeplayer.v1_21_6.spi;
import io.github.hello09x.fakeplayer.api.spi.NMSServerPlayer;
import io.github.hello09x.fakeplayer.core.constant.ConstantPool;
import io.github.hello09x.fakeplayer.core.util.Reflections;
import io.github.hello09x.fakeplayer.v1_21_6.network.FakePlayerAdvancements;
import lombok.Getter;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.network.protocol.game.ServerboundClientCommandPacket;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ParticleStatus;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.player.ChatVisiblity;
import net.minecraft.world.level.storage.ValueInputContextHelper;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_21_R5.CraftServer;
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.stream.Stream;
public class NMSServerPlayerImpl implements NMSServerPlayer {
private final static Field ServerPlayer$advancements = Reflections.getFirstFieldByType(
ServerPlayer.class,
PlayerAdvancements.class,
false
);
@Getter
private final ServerPlayer handle;
@Getter
private final CraftPlayer player;
public NMSServerPlayerImpl(@NotNull Player player) {
this.player = ((CraftPlayer) player);
this.handle = ((CraftPlayer) player).getHandle();
}
@Override
public double getX() {
return handle.getX();
}
@Override
public double getY() {
return handle.getY();
}
@Override
public double getZ() {
return handle.getZ();
}
@Override
public void setXo(double xo) {
handle.xo = xo;
}
@Override
public void setYo(double yo) {
handle.yo = yo;
}
@Override
public void setZo(double zo) {
handle.zo = zo;
}
@Override
public void doTick() {
handle.doTick();
}
@Override
public void absMoveTo(double x, double y, double z, float yRot, float xRot) {
handle.absSnapTo(x, y, z, yRot, xRot);
}
@Override
public float getYRot() {
return handle.getYRot();
}
@Override
public void setYRot(float yRot) {
handle.setYRot(yRot);
}
@Override
public float getXRot() {
return handle.getXRot();
}
@Override
public void setXRot(float xRot) {
handle.setXRot(xRot);
}
@Override
public float getZza() {
return handle.zza;
}
@Override
public void setZza(float zza) {
handle.zza = zza;
}
@Override
public float getXxa() {
return handle.xxa;
}
@Override
public void setXxa(float xxa) {
handle.xxa = xxa;
}
@Override
public void setDeltaMovement(@NotNull Vector vector) {
handle.setDeltaMovement(new Vec3(
vector.getX(),
vector.getY(),
vector.getZ()
));
}
@Override
public boolean startRiding(@NotNull Entity entity, boolean force) {
return handle.startRiding(new NMSEntityImpl(entity).getHandle(), force);
}
@Override
public void stopRiding() {
handle.stopRiding();
}
@Override
public int getTickCount() {
return handle.tickCount;
}
@Override
public void drop(boolean allStack) {
handle.drop(allStack);
}
@Override
public void resetLastActionTime() {
handle.resetLastActionTime();
}
@Override
public boolean onGround() {
return handle.onGround();
}
@Override
public void jumpFromGround() {
handle.jumpFromGround();
}
@Override
public void setJumping(boolean jumping) {
handle.setJumping(jumping);
}
@Override
public boolean isUsingItem() {
return handle.isUsingItem();
}
@Override
public void disableAdvancements(@NotNull Plugin plugin) {
if (ServerPlayer$advancements == null) {
return;
}
var server = ((CraftServer) Bukkit.getServer()).getServer();
try {
ServerPlayer$advancements.set(
handle,
new FakePlayerAdvancements(
server.getFixerUpper(),
server.getPlayerList(),
server.getAdvancements(),
plugin.getDataFolder().getParentFile().toPath(),
handle
)
);
} catch (IllegalAccessException ignored) {
}
}
@Override
public void drop(int slot, boolean flag, boolean flag1) {
var inventory = handle.getInventory();
handle.drop(inventory.removeItem(slot, inventory.getItem(slot).getCount()), flag, flag1);
}
@Override
public void setPlayBefore() {
player.readExtraData(new ValueInputContextHelper(HolderLookup.Provider.create(Stream.empty()), null).empty());
}
@Override
public void setupClientOptions() {
var option = new ClientInformation(
"en_us",
Bukkit.getViewDistance(),
ChatVisiblity.SYSTEM,
false,
ConstantPool.MODEL_CUSTOMISATION,
HumanoidArm.RIGHT,
false,
true,
ParticleStatus.MINIMAL
);
handle.updateOptions(option);
}
@Override
public void respawn() {
if (!this.player.isDead()) {
return;
}
var packet = new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN);
handle.connection.handleClientCommand(packet);
}
@Override
public void swapItemWithOffhand() {
handle.connection.handlePlayerAction(new ServerboundPlayerActionPacket(
ServerboundPlayerActionPacket.Action.SWAP_ITEM_WITH_OFFHAND,
new BlockPos(0, 0, 0),
Direction.DOWN
));
}
}

106
fakeplayer-v1_21_7/pom.xml Normal file
View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-parent</artifactId>
<version>${revision}</version>
</parent>
<artifactId>fakeplayer-v1_21_7</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<nms.version>1.21.7-R0.1-SNAPSHOT</nms.version>
</properties>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-v1_21_6</artifactId>
<version>${revision}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${nms.version}</version>
<classifier>remapped-mojang</classifier>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
<version>2.0.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-obf</id>
<configuration>
<srgIn>org.spigotmc:minecraft-server:${nms.version}:txt:maps-mojang</srgIn>
<reverse>true</reverse>
<remappedDependencies>
org.spigotmc:spigot:${nms.version}:jar:remapped-mojang
</remappedDependencies>
<remappedArtifactAttached>true</remappedArtifactAttached>
<remappedClassifierName>remapped-obf</remappedClassifierName>
</configuration>
</execution>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-spigot</id>
<configuration>
<inputFile>
${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
</inputFile>
<srgIn>org.spigotmc:minecraft-server:${nms.version}:csrg:maps-spigot</srgIn>
<remappedDependencies>org.spigotmc:spigot:${nms.version}:jar:remapped-obf
</remappedDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,55 @@
package io.github.hello09x.fakeplayer.v1_21_7.spi;
import io.github.hello09x.fakeplayer.api.spi.*;
import io.github.hello09x.fakeplayer.core.Main;
import io.github.hello09x.fakeplayer.v1_21_6.spi.*;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.net.InetAddress;
import java.util.Set;
public class NMSBridgeImpl implements NMSBridge {
private final static Set<String> SUPPORTS = Set.of("1.21.7");
@Override
public @NotNull NMSEntity fromEntity(@NotNull Entity entity) {
return new NMSEntityImpl(entity);
}
@Override
public @NotNull NMSServer fromServer(@NotNull Server server) {
return new NMSServerImpl(server);
}
@Override
public @NotNull NMSServerLevel fromWorld(@NotNull World world) {
return new NMSServerLevelImpl(world);
}
@Override
public @NotNull NMSServerPlayer fromPlayer(@NotNull Player player) {
return new NMSServerPlayerImpl(player);
}
@Override
public @NotNull NMSNetwork createNetwork(@NotNull InetAddress address) {
return new NMSNetworkImpl(address);
}
@Override
public boolean isSupported() {
return SUPPORTS.contains(Bukkit.getMinecraftVersion());
}
@Override
public @NotNull ActionTicker createAction(@NotNull Player player, @NotNull ActionType action, @NotNull ActionSetting setting) {
return new ActionTickerImpl(Main.getInjector().getInstance(NMSBridge.class), player, action, setting);
}
}

106
fakeplayer-v1_21_8/pom.xml Normal file
View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-parent</artifactId>
<version>${revision}</version>
</parent>
<artifactId>fakeplayer-v1_21_8</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<nms.version>1.21.8-R0.1-SNAPSHOT</nms.version>
</properties>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.hello09x.fakeplayer</groupId>
<artifactId>fakeplayer-v1_21_6</artifactId>
<version>${revision}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${nms.version}</version>
<classifier>remapped-mojang</classifier>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
<version>2.0.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-obf</id>
<configuration>
<srgIn>org.spigotmc:minecraft-server:${nms.version}:txt:maps-mojang</srgIn>
<reverse>true</reverse>
<remappedDependencies>
org.spigotmc:spigot:${nms.version}:jar:remapped-mojang
</remappedDependencies>
<remappedArtifactAttached>true</remappedArtifactAttached>
<remappedClassifierName>remapped-obf</remappedClassifierName>
</configuration>
</execution>
<execution>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<id>remap-spigot</id>
<configuration>
<inputFile>
${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
</inputFile>
<srgIn>org.spigotmc:minecraft-server:${nms.version}:csrg:maps-spigot</srgIn>
<remappedDependencies>org.spigotmc:spigot:${nms.version}:jar:remapped-obf
</remappedDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,55 @@
package io.github.hello09x.fakeplayer.v1_21_8.spi;
import io.github.hello09x.fakeplayer.api.spi.*;
import io.github.hello09x.fakeplayer.core.Main;
import io.github.hello09x.fakeplayer.v1_21_6.spi.*;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.net.InetAddress;
import java.util.Set;
public class NMSBridgeImpl implements NMSBridge {
private final static Set<String> SUPPORTS = Set.of("1.21.8");
@Override
public @NotNull NMSEntity fromEntity(@NotNull Entity entity) {
return new NMSEntityImpl(entity);
}
@Override
public @NotNull NMSServer fromServer(@NotNull Server server) {
return new NMSServerImpl(server);
}
@Override
public @NotNull NMSServerLevel fromWorld(@NotNull World world) {
return new NMSServerLevelImpl(world);
}
@Override
public @NotNull NMSServerPlayer fromPlayer(@NotNull Player player) {
return new NMSServerPlayerImpl(player);
}
@Override
public @NotNull NMSNetwork createNetwork(@NotNull InetAddress address) {
return new NMSNetworkImpl(address);
}
@Override
public boolean isSupported() {
return SUPPORTS.contains(Bukkit.getMinecraftVersion());
}
@Override
public @NotNull ActionTicker createAction(@NotNull Player player, @NotNull ActionType action, @NotNull ActionSetting setting) {
return new ActionTickerImpl(Main.getInjector().getInstance(NMSBridge.class), player, action, setting);
}
}

View File

@ -25,14 +25,17 @@
<module>fakeplayer-v1_21_3</module> <module>fakeplayer-v1_21_3</module>
<module>fakeplayer-v1_21_4</module> <module>fakeplayer-v1_21_4</module>
<module>fakeplayer-v1_21_5</module> <module>fakeplayer-v1_21_5</module>
<module>fakeplayer-v1_21_6</module>
<module>fakeplayer-v1_21_7</module>
<module>fakeplayer-v1_21_8</module>
</modules> </modules>
<properties> <properties>
<java.version>21</java.version> <java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<revision>0.3.15</revision> <revision>0.3.18</revision>
<detools.version>0.1.5-SNAPSHOT</detools.version> <detools.version>0.1.6-SNAPSHOT</detools.version>
</properties> </properties>
<repositories> <repositories>
@ -63,7 +66,7 @@
<dependency> <dependency>
<groupId>io.papermc.paper</groupId> <groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId> <artifactId>paper-api</artifactId>
<version>1.20-R0.1-SNAPSHOT</version> <version>1.21.7-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>