Remove FakePlayer (#2006)

* feat: remove FakePlayer

* fix: oops, demo server exists
This commit is contained in:
Matt Worzala 2024-02-27 08:37:42 -05:00 committed by GitHub
parent 009ba773ed
commit 2947279898
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1 additions and 489 deletions

View File

@ -14,7 +14,6 @@ import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.ItemEntity;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.damage.Damage;
import net.minestom.server.entity.fakeplayer.FakePlayer;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.entity.EntityAttackEvent;
@ -41,7 +40,6 @@ import net.minestom.server.world.DimensionType;
import java.time.Duration;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReference;
@ -84,10 +82,6 @@ public class PlayerInit {
itemEntity.setInstance(player.getInstance(), playerPos.withY(y -> y + 1.5));
Vec velocity = playerPos.direction().mul(6);
itemEntity.setVelocity(velocity);
FakePlayer.initPlayer(UUID.randomUUID(), "fake123", fp -> {
System.out.println("fp = " + fp);
});
})
.addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername()))
.addListener(AsyncPlayerConfigurationEvent.class, event -> {

View File

@ -33,7 +33,6 @@ import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.effects.Effects;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.entity.fakeplayer.FakePlayer;
import net.minestom.server.entity.metadata.LivingEntityMeta;
import net.minestom.server.entity.metadata.PlayerMeta;
import net.minestom.server.entity.vehicle.PlayerVehicleInformation;
@ -112,8 +111,7 @@ import java.util.function.Consumer;
import java.util.function.UnaryOperator;
/**
* Those are the major actors of the server,
* they are not necessary backed by a {@link PlayerSocketConnection} as shown by {@link FakePlayer}.
* Those are the major actors of the server
* <p>
* You can easily create your own implementation of this and use it with {@link ConnectionManager#setPlayerProvider(PlayerProvider)}.
*/

View File

@ -1,170 +0,0 @@
package net.minestom.server.entity.fakeplayer;
import com.extollit.gaming.ai.path.HydrazinePathFinder;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.NavigableEntity;
import net.minestom.server.entity.pathfinding.Navigator;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.packet.client.login.ClientLoginAcknowledgedPacket;
import net.minestom.server.network.player.FakePlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.time.TimeUnit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* A fake player will behave exactly the same way as would do a {@link Player} backed by a socket connection
* (events, velocity, gravity, player list, etc...) with the exception that you need to control it server-side
* using a {@link FakePlayerController} (see {@link #getController()}).
* <p>
* You can create one using {@link #initPlayer(UUID, String, Consumer)}. Be aware that this really behave exactly like a player
* and this is a feature not a bug, you will need to check at some place if the player is a fake one or not (instanceof) if you want to change it.
*/
public class FakePlayer extends Player implements NavigableEntity {
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
private static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager();
private final FakePlayerOption option;
private final FakePlayerController fakePlayerController;
private final Navigator navigator = new Navigator(this);
private EventListener<PlayerSpawnEvent> spawnListener;
/**
* Initializes a new {@link FakePlayer} with the given {@code uuid}, {@code username} and {@code option}'s.
*
* @param uuid The unique identifier for the fake player.
* @param username The username for the fake player.
* @param option Any option for the fake player.
*/
protected FakePlayer(@NotNull UUID uuid, @NotNull String username,
@NotNull FakePlayerOption option,
@Nullable Consumer<FakePlayer> spawnCallback) {
super(uuid, username, new FakePlayerConnection());
this.option = option;
this.fakePlayerController = new FakePlayerController(this);
if (spawnCallback != null) {
spawnListener = EventListener.builder(PlayerSpawnEvent.class)
.expireWhen(ignored -> this.isRemoved())
.handler(event -> {
if (event.getPlayer().equals(this))
if (event.isFirstSpawn()) {
spawnCallback.accept(this);
MinecraftServer.getGlobalEventHandler().removeListener(spawnListener);
}
}).build();
MinecraftServer.getGlobalEventHandler().addListener(spawnListener);
}
playerConnection.setConnectionState(ConnectionState.LOGIN);
CONNECTION_MANAGER.transitionLoginToConfig(this).thenRun(() -> {
// Need to immediately reply with login acknowledged for the player to enter config.
PACKET_LISTENER_MANAGER.processClientPacket(new ClientLoginAcknowledgedPacket(), getPlayerConnection());
});
}
/**
* Initializes a new {@link FakePlayer}.
*
* @param uuid the FakePlayer uuid
* @param username the FakePlayer username
* @param spawnCallback the optional callback called when the fake player first spawn
*/
public static void initPlayer(@NotNull UUID uuid, @NotNull String username,
@NotNull FakePlayerOption option, @Nullable Consumer<FakePlayer> spawnCallback) {
new FakePlayer(uuid, username, option, spawnCallback);
}
/**
* Initializes a new {@link FakePlayer} without adding it in cache.
* <p>
* If you want the fake player to be obtainable with the {@link net.minestom.server.network.ConnectionManager}
* you need to specify it in a {@link FakePlayerOption} and use {@link #initPlayer(UUID, String, FakePlayerOption, Consumer)}.
*
* @param uuid the FakePlayer uuid
* @param username the FakePlayer username
* @param spawnCallback the optional callback called when the fake player first spawn
*/
public static void initPlayer(@NotNull UUID uuid, @NotNull String username, @Nullable Consumer<FakePlayer> spawnCallback) {
initPlayer(uuid, username, new FakePlayerOption(), spawnCallback);
}
/**
* Gets the fake player option container.
*
* @return the fake player option
*/
@NotNull
public FakePlayerOption getOption() {
return option;
}
/**
* Retrieves the controller for the fake player.
*
* @return The fake player's controller.
*/
@NotNull
public FakePlayerController getController() {
return fakePlayerController;
}
@Override
public void update(long time) {
super.update(time);
// Path finding
this.navigator.tick();
}
@Override
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
this.navigator.setPathFinder(new HydrazinePathFinder(navigator.getPathingEntity(), instance.getInstanceSpace()));
return super.setInstance(instance, spawnPosition);
}
@Override
public void updateNewViewer(@NotNull Player player) {
player.sendPacket(getAddPlayerToList());
handleTabList(player.getPlayerConnection());
super.updateNewViewer(player);
}
/**
* {@inheritDoc}
*/
@Override
protected void showPlayer(@NotNull PlayerConnection connection) {
super.showPlayer(connection);
handleTabList(connection);
}
@NotNull
@Override
public Navigator getNavigator() {
return navigator;
}
private void handleTabList(PlayerConnection connection) {
if (!option.isInTabList()) {
// Remove from tab-list
MinecraftServer.getSchedulerManager().buildTask(() -> connection.sendPacket(getRemovePlayerToList())).delay(20, TimeUnit.SERVER_TICK).schedule();
}
}
}

View File

@ -1,215 +0,0 @@
package net.minestom.server.entity.fakeplayer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.inventory.AbstractInventory;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.client.common.ClientKeepAlivePacket;
import net.minestom.server.network.packet.client.common.ClientPluginMessagePacket;
import net.minestom.server.network.packet.client.configuration.ClientFinishConfigurationPacket;
import net.minestom.server.network.packet.client.play.*;
import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.common.KeepAlivePacket;
import net.minestom.server.network.packet.server.configuration.FinishConfigurationPacket;
import net.minestom.server.network.packet.server.play.PlayerPositionAndLookPacket;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* This class acts as a client controller for {@link FakePlayer}.
* <p>
* The main use is to simulate the receiving of {@link ClientPacket}
*/
public class FakePlayerController {
private final FakePlayer fakePlayer;
/**
* Initializes a new {@link FakePlayerController} with the given {@link FakePlayer}.
*
* @param fakePlayer The fake player that should used the controller.
*/
public FakePlayerController(@NotNull FakePlayer fakePlayer) {
this.fakePlayer = fakePlayer;
}
/**
* Simulates a click in a window.
*
* @param playerInventory {@code true} if the window a {@link PlayerInventory}, otherwise {@code false}.
* @param slot The slot where the fake player should click on.
* @param button The mouse button that the fake player should used.
* @param clickType The click type
*/
public void clickWindow(boolean playerInventory, short slot, byte button, ClientClickWindowPacket.ClickType clickType) {
Inventory inventory = playerInventory ? null : fakePlayer.getOpenInventory();
AbstractInventory abstractInventory = inventory == null ? fakePlayer.getInventory() : inventory;
playerInventory = abstractInventory instanceof PlayerInventory;
slot = playerInventory ? (short) PlayerInventoryUtils.convertToPacketSlot(slot) : slot;
ItemStack itemStack = abstractInventory.getItemStack(slot);
addToQueue(new ClientClickWindowPacket(playerInventory ? 0 : inventory.getWindowId(), 0,
slot, button, clickType,
List.of(), itemStack));
}
/**
* Closes the current opened inventory if there is any.
*/
public void closeWindow() {
Inventory openInventory = fakePlayer.getOpenInventory();
addToQueue(new ClientCloseWindowPacket(openInventory == null ? 0 : openInventory.getWindowId()));
}
/**
* Sends a plugin message to the player.
*
* @param channel The channel of the message.
* @param message The message data.
*/
public void sendPluginMessage(String channel, byte[] message) {
addToQueue(new ClientPluginMessagePacket(channel, message));
}
/**
* Sends a plugin message to the player.
*
* @param channel The channel of the message.
* @param message The message data.
*/
public void sendPluginMessage(String channel, String message) {
sendPluginMessage(channel, message.getBytes());
}
/**
* Attacks the given {@code entity}.
*
* @param entity The entity that is to be attacked.
*/
public void attackEntity(Entity entity) {
addToQueue(new ClientInteractEntityPacket(entity.getEntityId(), new ClientInteractEntityPacket.Attack(), fakePlayer.isSneaking()));
}
/**
* Respawns the player.
*
* @see Player#respawn()
*/
public void respawn() {
// Sending the respawn packet for some reason
// Is related to FakePlayer#showPlayer and the tablist option (probably because of the scheduler)
/*ClientStatusPacket statusPacket = new ClientStatusPacket();
statusPacket.action = ClientStatusPacket.Action.PERFORM_RESPAWN;
addToQueue(statusPacket);*/
fakePlayer.respawn();
}
/**
* Changes the current held slot for the player.
*
* @param slot The slot that the player has to held.
* @throws IllegalArgumentException If {@code slot} is not between {@code 0} and {@code 8}.
*/
public void setHeldItem(short slot) {
Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "Slot has to be between 0 and 8!");
addToQueue(new ClientHeldItemChangePacket(slot));
}
/**
* Sends an animation packet that animates the specified arm.
*
* @param hand The hand of the arm to be animated.
*/
public void sendArmAnimation(Player.Hand hand) {
addToQueue(new ClientAnimationPacket(hand));
}
/**
* Uses the item in the given {@code hand}.
*
* @param hand The hand in which an ite mshould be.
*/
public void useItem(Player.Hand hand) {
addToQueue(new ClientUseItemPacket(hand, 0));
}
/**
* Rotates the fake player.
*
* @param yaw The new yaw for the fake player.
* @param pitch The new pitch for the fake player.
*/
public void rotate(float yaw, float pitch) {
addToQueue(new ClientPlayerRotationPacket(yaw, pitch, fakePlayer.isOnGround()));
}
/**
* Starts the digging process of the fake player.
*
* @param blockPosition The position of the block to be excavated.
* @param blockFace From where the block is struck.
*/
public void startDigging(Point blockPosition, BlockFace blockFace) {
addToQueue(new ClientPlayerDiggingPacket(ClientPlayerDiggingPacket.Status.STARTED_DIGGING, blockPosition, blockFace, 0));
}
/**
* Stops the digging process of the fake player.
*
* @param blockPosition The position of the block to be excavated.
* @param blockFace From where the block is struck.
*/
public void stopDigging(Point blockPosition, BlockFace blockFace) {
addToQueue(new ClientPlayerDiggingPacket(ClientPlayerDiggingPacket.Status.CANCELLED_DIGGING, blockPosition, blockFace, 0));
}
/**
* Finishes the digging process of the fake player.
*
* @param blockPosition The position of the block to be excavated.
* @param blockFace From where the block is struck.
*/
public void finishDigging(Point blockPosition, BlockFace blockFace) {
addToQueue(new ClientPlayerDiggingPacket(ClientPlayerDiggingPacket.Status.FINISHED_DIGGING, blockPosition, blockFace, 0));
}
/**
* Makes the player receives a packet
* WARNING: pretty much unsafe, used internally to redirect packets here,
* you should instead use {@link PlayerConnection#sendPacket(SendablePacket)}
*
* @param serverPacket the packet to consume
*/
public void consumePacket(ServerPacket serverPacket) {
if (serverPacket instanceof PlayerPositionAndLookPacket playerPositionAndLookPacket) {
addToQueue(new ClientTeleportConfirmPacket(playerPositionAndLookPacket.teleportId()));
} else if (serverPacket instanceof KeepAlivePacket keepAlivePacket) {
addToQueue(new ClientKeepAlivePacket(keepAlivePacket.id()));
} else if (serverPacket instanceof FinishConfigurationPacket) {
addToQueue(new ClientFinishConfigurationPacket());
}
}
/**
* All packets in the queue are executed in the {@link Player#update(long)} method. It is used internally to add all
* received packet from the client. Could be used to "simulate" a received packet, but to use at your own risk!
*
* @param clientPlayPacket The packet to add in the queue.
*/
private void addToQueue(ClientPacket clientPlayPacket) {
this.fakePlayer.addPacketToQueue(clientPlayPacket);
}
}

View File

@ -1,56 +0,0 @@
package net.minestom.server.entity.fakeplayer;
import net.minestom.server.network.ConnectionManager;
/**
* Represents any options for a {@link FakePlayer}.
*/
public class FakePlayerOption {
private boolean registered = false;
private boolean inTabList = false;
/**
* Gets if the player is registered internally as a Player.
*
* @return true if the player is registered in {@link ConnectionManager}, false otherwise
*/
public boolean isRegistered() {
return registered;
}
/**
* Sets the FakePlayer as registered or not.
* <p>
* WARNING: this can't be changed halfway.
*
* @param registered should the fake player be registered internally
* @return this instance, allowing for chained method calls
*/
public FakePlayerOption setRegistered(boolean registered) {
this.registered = registered;
return this;
}
/**
* Gets if the player is visible in the tab-list or not.
*
* @return true if the player is in the tab-list, false otherwise
*/
public boolean isInTabList() {
return inTabList;
}
/**
* Sets the player in the tab-list or not.
* <p>
* WARNING: this can't be changed halfway.
*
* @param inTabList should the player be in the tab-list
* @return this instance, allowing for chained method calls
*/
public FakePlayerOption setInTabList(boolean inTabList) {
this.inTabList = inTabList;
return this;
}
}

View File

@ -1,39 +0,0 @@
package net.minestom.server.network.player;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.fakeplayer.FakePlayer;
import net.minestom.server.entity.fakeplayer.FakePlayerController;
import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class FakePlayerConnection extends PlayerConnection {
@Override
public void sendPacket(@NotNull SendablePacket packet) {
FakePlayerController controller = getFakePlayer().getController();
final ServerPacket serverPacket = SendablePacket.extractServerPacket(getConnectionState(), packet);
controller.consumePacket(serverPacket);
}
@NotNull
@Override
public SocketAddress getRemoteAddress() {
return new InetSocketAddress(0);
}
public FakePlayer getFakePlayer() {
return (FakePlayer) getPlayer();
}
@Override
public void setPlayer(Player player) {
Check.argCondition(!(player instanceof FakePlayer), "FakePlayerController needs a FakePlayer object");
super.setPlayer(player);
}
}