diff --git a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerListener.java b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerListener.java
index aab3761..eaf9621 100644
--- a/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerListener.java
+++ b/fakeplayer-core/src/main/java/io/github/hello09x/fakeplayer/core/listener/FakeplayerListener.java
@@ -6,6 +6,7 @@ import io.github.hello09x.fakeplayer.core.Main;
import io.github.hello09x.fakeplayer.core.config.FakeplayerConfig;
import io.github.hello09x.fakeplayer.core.manager.FakeplayerManager;
import io.github.hello09x.fakeplayer.core.repository.UsedIdRepository;
+import io.github.hello09x.fakeplayer.core.util.InternalAddressGenerator;
import org.bukkit.Bukkit;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
@@ -41,7 +42,7 @@ public class FakeplayerListener implements Listener {
public void onLogin(@NotNull PlayerLoginEvent event) {
var player = event.getPlayer();
- if (event.getPlayer().hasMetadata("fakeplayer.dummy")) {
+ if (InternalAddressGenerator.canBeGenerated(event.getAddress())) {
return;
}
@@ -52,7 +53,7 @@ public class FakeplayerListener implements Listener {
newline(),
text("<<---- fakeplayer ---->>", GRAY)
));
- log.info("%s(%s) was refused to login cause his UUID was used by fake player".formatted(
+ log.info("%s(%s) was refused to login cause his UUID was used by [Fakeplayer]".formatted(
player.getName(),
player.getUniqueId()
));
diff --git a/fakeplayer-dist/pom.xml b/fakeplayer-dist/pom.xml
index 168165f..19bcd89 100644
--- a/fakeplayer-dist/pom.xml
+++ b/fakeplayer-dist/pom.xml
@@ -39,6 +39,11 @@
fakeplayer-v1_20_R2
+
+ io.github.hello09x.fakeplayer
+ fakeplayer-v1_20_R4
+
+
@@ -77,13 +82,13 @@
copy
package
-
+
-
+
run
diff --git a/fakeplayer-dist/src/main/resources/META-INF/services/io.github.hello09x.fakeplayer.api.spi.NMSBridge b/fakeplayer-dist/src/main/resources/META-INF/services/io.github.hello09x.fakeplayer.api.spi.NMSBridge
index 16915de..b295c80 100644
--- a/fakeplayer-dist/src/main/resources/META-INF/services/io.github.hello09x.fakeplayer.api.spi.NMSBridge
+++ b/fakeplayer-dist/src/main/resources/META-INF/services/io.github.hello09x.fakeplayer.api.spi.NMSBridge
@@ -1,2 +1,3 @@
io.github.hello09x.fakeplayer.v1_20_R1.spi.NMSBridgeImpl
io.github.hello09x.fakeplayer.v1_20_R2.spi.NMSBridgeImpl
+io.github.hello09x.fakeplayer.v1_20_R4.spi.NMSBridgeImpl
diff --git a/fakeplayer-v1_20_R4/pom.xml b/fakeplayer-v1_20_R4/pom.xml
new file mode 100644
index 0000000..9d9a26e
--- /dev/null
+++ b/fakeplayer-v1_20_R4/pom.xml
@@ -0,0 +1,98 @@
+
+
+ 4.0.0
+
+ io.github.hello09x.fakeplayer
+ fakeplayer-parent
+ 1.0.0
+
+
+ fakeplayer-v1_20_R4
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+ io.papermc.paper
+ paper-api
+ provided
+
+
+
+ io.github.hello09x.fakeplayer
+ fakeplayer-core
+ provided
+
+
+
+ io.github.hello09x.fakeplayer
+ fakeplayer-api
+ provided
+
+
+
+ org.spigotmc
+ spigot
+ 1.20.4-R0.1-SNAPSHOT
+ remapped-mojang
+ provided
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+
+
+
+
+ net.md-5
+ specialsource-maven-plugin
+ 1.2.4
+
+
+ package
+
+ remap
+
+ remap-obf
+
+ org.spigotmc:minecraft-server:1.20.4-R0.1-SNAPSHOT:txt:maps-mojang
+ true
+
+ org.spigotmc:spigot:1.20.4-R0.1-SNAPSHOT:jar:remapped-mojang
+
+ true
+ remapped-obf
+
+
+
+ package
+
+ remap
+
+ remap-spigot
+
+
+ ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
+
+ org.spigotmc:minecraft-server:1.20.4-R0.1-SNAPSHOT:csrg:maps-spigot
+ org.spigotmc:spigot:1.20.4-R0.1-SNAPSHOT:jar:remapped-obf
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/AttackAction.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/AttackAction.java
new file mode 100644
index 0000000..3b3d212
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/AttackAction.java
@@ -0,0 +1,49 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.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() {
+
+ }
+
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/MineAction.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/MineAction.java
new file mode 100644
index 0000000..4cc66df
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/MineAction.java
@@ -0,0 +1,160 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.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().getMaxBuildHeight(),
+ -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().getMaxBuildHeight(),
+ -1
+ );
+ }
+
+ player.gameMode.handleBlockBreakAction(
+ pos,
+ START_DESTROY_BLOCK,
+ side,
+ player.level().getMaxBuildHeight(),
+ -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().getMaxBuildHeight(),
+ -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().getMaxBuildHeight(),
+ -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;
+
+ }
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/TraceAction.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/TraceAction.java
new file mode 100644
index 0000000..02afe92
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/TraceAction.java
@@ -0,0 +1,24 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.action;
+
+import io.github.hello09x.fakeplayer.api.spi.Action;
+import io.github.hello09x.fakeplayer.v1_20_R4.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);
+ }
+
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/UseAction.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/UseAction.java
new file mode 100644
index 0000000..b11fec2
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/UseAction.java
@@ -0,0 +1,98 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.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.serverLevel();
+ var blockHit = (BlockHitResult) hit;
+ var pos = blockHit.getBlockPos();
+ var side = blockHit.getDirection();
+ if (pos.getY() < player.level().getMaxBuildHeight() - (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() {
+ this.stop();
+ }
+
+ @Override
+ public void stop() {
+ current.freeze = 0;
+ player.releaseUsingItem();
+ }
+
+ private final static class Current {
+
+ /**
+ * 冷却, 单位: tick
+ */
+ public int freeze;
+ }
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/util/Tracer.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/util/Tracer.java
new file mode 100644
index 0000000..782a52e
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/action/util/Tracer.java
@@ -0,0 +1,105 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.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 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);
+ }
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyChannel.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyChannel.java
new file mode 100644
index 0000000..8bee8a2
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyChannel.java
@@ -0,0 +1,99 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.network;
+
+import io.netty.channel.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+public class DummyChannel extends AbstractChannel {
+ private final static EventLoop EVENT_LOOP = new DefaultEventLoop();
+ private final ChannelConfig config = new DefaultChannelConfig(this);
+
+ private final InetAddress address;
+
+ public DummyChannel(@Nullable Channel parent, @NotNull InetAddress address) {
+ super(parent);
+ this.address = address;
+ }
+
+ @Override
+ public ChannelConfig config() {
+ config.setAutoRead(true);
+ return config;
+ }
+
+ @Override
+ protected void doBeginRead() throws Exception {
+ }
+
+ @Override
+ protected void doBind(SocketAddress arg0) throws Exception {
+ }
+
+ @Override
+ protected void doClose() throws Exception {
+ }
+
+ @Override
+ protected void doDisconnect() throws Exception {
+ }
+
+ @Override
+ protected void doWrite(ChannelOutboundBuffer in) throws Exception {
+ for (;;) {
+ Object msg = in.current();
+ if (msg == null) {
+ break;
+ }
+ in.remove();
+ }
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ protected boolean isCompatible(EventLoop arg0) {
+ return true;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return true;
+ }
+
+ @Override
+ protected SocketAddress localAddress0() {
+ return new InetSocketAddress(address, 25565);
+ }
+
+ @Override
+ public ChannelMetadata metadata() {
+ return new ChannelMetadata(true);
+ }
+
+ @Override
+ protected AbstractUnsafe newUnsafe() {
+ return new AbstractUnsafe() {
+ @Override
+ public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
+ safeSetSuccess(promise);
+ }
+ };
+ }
+
+ @Override
+ protected SocketAddress remoteAddress0() {
+ return new InetSocketAddress(address, 25565);
+ }
+
+ @Override
+ public EventLoop eventLoop() {
+ return EVENT_LOOP;
+ }
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyConnection.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyConnection.java
new file mode 100644
index 0000000..e8ab138
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyConnection.java
@@ -0,0 +1,38 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.network;
+
+import net.minecraft.network.Connection;
+import net.minecraft.network.ConnectionProtocol;
+import net.minecraft.network.PacketSendListener;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.PacketFlow;
+import org.jetbrains.annotations.NotNull;
+
+import java.net.InetAddress;
+
+public class DummyConnection extends Connection {
+ public DummyConnection(@NotNull InetAddress address) {
+ super(PacketFlow.SERVERBOUND);
+ this.channel = new DummyChannel(null, address);
+ this.address = this.channel.remoteAddress();
+ Connection.configureSerialization(this.channel.pipeline(), PacketFlow.SERVERBOUND, null);
+ }
+
+ @Override
+ public boolean isConnected() {
+ return true;
+ }
+
+ @Override
+ public void send(Packet> packet, PacketSendListener listener) {
+ }
+
+ @Override
+ public void send(Packet> packet) {
+ }
+
+ public void setProtocolAttr(@NotNull ConnectionProtocol protocol) {
+ this.channel.attr(Connection.ATTRIBUTE_SERVERBOUND_PROTOCOL).set(protocol.codec(PacketFlow.SERVERBOUND));
+ this.channel.attr(Connection.ATTRIBUTE_CLIENTBOUND_PROTOCOL).set(protocol.codec(PacketFlow.CLIENTBOUND));
+ }
+
+}
\ No newline at end of file
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyPlayerAdvancements.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyPlayerAdvancements.java
new file mode 100644
index 0000000..02b9b81
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyPlayerAdvancements.java
@@ -0,0 +1,63 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.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 DummyPlayerAdvancements extends PlayerAdvancements {
+
+ public DummyPlayerAdvancements(
+ 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 player) {
+ }
+
+ @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() {
+
+ }
+
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyServerGamePacketListenerImpl.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyServerGamePacketListenerImpl.java
new file mode 100644
index 0000000..a9445ba
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/network/DummyServerGamePacketListenerImpl.java
@@ -0,0 +1,69 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.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 io.netty.buffer.Unpooled;
+import net.minecraft.network.Connection;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
+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.craftbukkit.v1_20_R3.entity.CraftPlayer;
+import org.bukkit.plugin.messaging.StandardMessenger;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Optional;
+
+public class DummyServerGamePacketListenerImpl extends ServerGamePacketListenerImpl implements NMSServerGamePacketListener {
+
+ private final static FakeplayerManager manager = FakeplayerManager.instance;
+
+ public DummyServerGamePacketListenerImpl(
+ @NotNull MinecraftServer server,
+ @NotNull Connection connection,
+ @NotNull ServerPlayer player,
+ @NotNull CommonListenerCookie cookie
+ ) {
+ super(server, connection, player, cookie);
+ Optional.ofNullable(Bukkit.getPlayer(player.getUUID()))
+ .map(CraftPlayer.class::cast)
+ .ifPresent(p -> p.addChannel(StandardMessenger.validateAndCorrectChannel(BUNGEE_CORD_CHANNEL)));
+ }
+
+ @Override
+ public void send(Packet> packet) {
+ if (packet instanceof ClientboundCustomPayloadPacket p) {
+ this.handleCustomPayloadPacket(p.payload());
+ }
+ }
+
+ private void handleCustomPayloadPacket(@NotNull CustomPacketPayload payload) {
+ var channel = payload.id().getNamespace() + ":" + payload.id().getPath();
+ if (!channel.equals(BUNGEE_CORD_CHANNEL)) {
+ return;
+ }
+
+ var recipient = Bukkit
+ .getOnlinePlayers()
+ .stream()
+ .filter(manager::notContains)
+ .findAny()
+ .orElse(null);
+ if (recipient == null) {
+ return;
+ }
+
+ var buf = new FriendlyByteBuf(Unpooled.buffer(0, 1048576));
+ payload.write(buf);
+ var message = buf.array();
+
+ recipient.sendPluginMessage(Main.getInstance(), channel, message);
+ }
+
+}
\ No newline at end of file
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/ActionTickerImpl.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/ActionTickerImpl.java
new file mode 100644
index 0000000..17abf06
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/ActionTickerImpl.java
@@ -0,0 +1,29 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.spi;
+
+
+import io.github.hello09x.fakeplayer.api.spi.Action;
+import io.github.hello09x.fakeplayer.api.spi.ActionTicker;
+import io.github.hello09x.fakeplayer.core.entity.action.BaseActionTicker;
+import io.github.hello09x.fakeplayer.v1_20_R4.action.AttackAction;
+import io.github.hello09x.fakeplayer.v1_20_R4.action.MineAction;
+import io.github.hello09x.fakeplayer.v1_20_R4.action.UseAction;
+import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class ActionTickerImpl extends BaseActionTicker implements ActionTicker {
+
+ public ActionTickerImpl(@NotNull Player player, @NotNull Action.ActionType action, @NotNull Action.ActionSetting setting) {
+ super(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();
+ };
+ }
+ }
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSBridgeImpl.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSBridgeImpl.java
new file mode 100644
index 0000000..c84f4d4
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSBridgeImpl.java
@@ -0,0 +1,53 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.spi;
+
+import io.github.hello09x.fakeplayer.api.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 SUPPORTS = Set.of("1.20.3", "1.20.4");
+
+ @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 Action.ActionType action, @NotNull Action.ActionSetting setting) {
+ return new ActionTickerImpl(player, action, setting);
+ }
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSEntityImpl.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSEntityImpl.java
new file mode 100644
index 0000000..49687fb
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSEntityImpl.java
@@ -0,0 +1,19 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.spi;
+
+import io.github.hello09x.fakeplayer.api.spi.NMSEntity;
+import lombok.Getter;
+import net.minecraft.world.entity.Entity;
+import org.bukkit.craftbukkit.v1_20_R3.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();
+ }
+
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSNetworkImpl.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSNetworkImpl.java
new file mode 100644
index 0000000..43ced5f
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSNetworkImpl.java
@@ -0,0 +1,64 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.spi;
+
+import io.github.hello09x.fakeplayer.api.spi.NMSNetwork;
+import io.github.hello09x.fakeplayer.api.spi.NMSServerGamePacketListener;
+import io.github.hello09x.fakeplayer.v1_20_R4.network.DummyConnection;
+import io.github.hello09x.fakeplayer.v1_20_R4.network.DummyServerGamePacketListenerImpl;
+import net.minecraft.network.ConnectionProtocol;
+import net.minecraft.server.network.CommonListenerCookie;
+import org.bukkit.Server;
+import org.bukkit.craftbukkit.v1_20_R3.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.net.InetAddress;
+
+public class NMSNetworkImpl implements NMSNetwork {
+
+ @NotNull
+ private final DummyConnection connection;
+
+ private NMSServerGamePacketListener serverGamePacketListener;
+
+ public NMSNetworkImpl(
+ @NotNull InetAddress address
+ ) {
+ this.connection = new DummyConnection(address);
+ }
+
+ @NotNull
+ @Override
+ public NMSServerGamePacketListener placeNewPlayer(
+ @NotNull Server server,
+ @NotNull Player player
+ ) {
+ this.connection.setProtocolAttr(ConnectionProtocol.PLAY);
+ var handle = ((CraftPlayer) player).getHandle();
+ var cookie = CommonListenerCookie.createInitial(((CraftPlayer) player).getProfile());
+ var listener = new DummyServerGamePacketListenerImpl(
+ ((CraftServer) server).getServer(),
+ this.connection,
+ handle,
+ cookie
+ );
+ handle.connection = listener;
+ ((CraftServer) server).getHandle().placeNewPlayer(
+ this.connection,
+ handle,
+ cookie
+ );
+ this.serverGamePacketListener = listener;
+ return listener;
+ }
+
+ @NotNull
+ @Override
+ public NMSServerGamePacketListener getServerGamePacketListener() throws IllegalStateException {
+ if (this.serverGamePacketListener == null) {
+ throw new IllegalStateException("not initialized");
+ }
+ return this.serverGamePacketListener;
+ }
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerImpl.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerImpl.java
new file mode 100644
index 0000000..99c8d84
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerImpl.java
@@ -0,0 +1,38 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.spi;
+
+import com.mojang.authlib.GameProfile;
+import io.github.hello09x.bedrock.util.Worlds;
+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_20_R3.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(Worlds.getMainWorld()).getHandle(),
+ new GameProfile(uuid, name),
+ ClientInformation.createDefault()
+ );
+ return new NMSServerPlayerImpl(handle.getBukkitEntity());
+ }
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerLevelImpl.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerLevelImpl.java
new file mode 100644
index 0000000..93bb71d
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerLevelImpl.java
@@ -0,0 +1,19 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.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_20_R3.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();
+ }
+
+}
diff --git a/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerPlayerImpl.java b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerPlayerImpl.java
new file mode 100644
index 0000000..02c58ba
--- /dev/null
+++ b/fakeplayer-v1_20_R4/src/main/java/io/github/hello09x/fakeplayer/v1_20_R4/spi/NMSServerPlayerImpl.java
@@ -0,0 +1,232 @@
+package io.github.hello09x.fakeplayer.v1_20_R4.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_20_R4.network.DummyPlayerAdvancements;
+import lombok.Getter;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.nbt.CompoundTag;
+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.ServerPlayer;
+import net.minecraft.world.entity.HumanoidArm;
+import net.minecraft.world.entity.player.ChatVisiblity;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_20_R3.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Field;
+
+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.absMoveTo(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 void setZza(float zza) {
+ handle.zza = zza;
+ }
+
+ @Override
+ public void setXxa(float xxa) {
+ handle.xxa = xxa;
+ }
+
+ @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 DummyPlayerAdvancements(
+ 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 CompoundTag());
+ }
+
+ @Override
+ public void setupClientOptions() {
+ var option = new ClientInformation(
+ "en_us",
+ Bukkit.getViewDistance(),
+ ChatVisiblity.SYSTEM,
+ false,
+ ConstantPool.MODEL_CUSTOMISATION,
+ HumanoidArm.RIGHT,
+ false,
+ true
+ );
+
+ 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
+ ));
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index a11e106..bc4aa74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,15 +13,16 @@
fakeplayer-api
fakeplayer-core
+ fakeplayer-dist
fakeplayer-v1_20_R1
fakeplayer-v1_20_R2
- fakeplayer-dist
+ fakeplayer-v1_20_R4
17
UTF-8
- 0.2.8
+ 0.2.9
@@ -107,6 +108,12 @@
${project.version}
+
+ io.github.hello09x.fakeplayer
+ fakeplayer-v1_20_R4
+ ${project.version}
+
+
com.github.jikoo.OpenInv
openinvapi