278 lines
12 KiB
Java
278 lines
12 KiB
Java
package carpet.patches;
|
|
|
|
import carpet.CarpetSettings;
|
|
import com.mojang.authlib.GameProfile;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.UUIDUtil;
|
|
import net.minecraft.network.DisconnectionDetails;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.contents.TranslatableContents;
|
|
import net.minecraft.network.protocol.PacketFlow;
|
|
import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
|
|
import net.minecraft.network.protocol.game.ServerboundClientCommandPacket;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.TickTask;
|
|
import net.minecraft.server.level.ClientInformation;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.server.network.CommonListenerCookie;
|
|
import net.minecraft.server.players.OldUsersConverter;
|
|
import net.minecraft.util.ProblemReporter;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EquipmentSlot;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.food.FoodData;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.component.ResolvableProfile;
|
|
import net.minecraft.world.level.GameType;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.portal.TeleportTransition;
|
|
import net.minecraft.world.level.storage.TagValueInput;
|
|
import net.minecraft.world.level.storage.ValueInput;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import carpet.fakes.ServerPlayerInterface;
|
|
import carpet.utils.Messenger;
|
|
|
|
import java.util.HashSet;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
@SuppressWarnings("EntityConstructor")
|
|
public class EntityPlayerMPFake extends ServerPlayer
|
|
{
|
|
private static final Set<String> spawning = new HashSet<>();
|
|
|
|
public Runnable fixStartingPosition = () -> {};
|
|
public boolean isAShadow;
|
|
|
|
// Returns true if it was successful, false if couldn't spawn due to the player not existing in Mojang servers
|
|
public static boolean createFake(String username, MinecraftServer server, Vec3 pos, double yaw, double pitch, ResourceKey<Level> dimensionId, GameType gamemode, boolean flying)
|
|
{
|
|
//prolly half of that crap is not necessary, but it works
|
|
ServerLevel worldIn = server.getLevel(dimensionId);
|
|
server.services().nameToIdCache().resolveOfflineUsers(false);
|
|
GameProfile gameprofile;
|
|
|
|
UUID uuid = OldUsersConverter.convertMobOwnerIfNecessary(server, username);
|
|
//NameAndId res = server.services().nameToIdCache().get(username).orElseThrow(); //findByName .orElse(null)
|
|
if (uuid == null && CarpetSettings.allowSpawningOfflinePlayers) {
|
|
server.services().nameToIdCache().resolveOfflineUsers(server.isDedicatedServer() && server.usesAuthentication());
|
|
uuid = UUIDUtil.createOfflinePlayerUUID(username);
|
|
}
|
|
if (uuid == null) {
|
|
return false; // no uuid, no player
|
|
}
|
|
gameprofile = new GameProfile(uuid, username);
|
|
|
|
|
|
//GameProfile finalGP = gameprofile;
|
|
|
|
// We need to mark this player as spawning so that we do not
|
|
// try to spawn another player with the name while the profile
|
|
// is being fetched - preventing multiple players spawning
|
|
String name = gameprofile.name();
|
|
spawning.add(name);
|
|
|
|
fetchGameProfile(server, gameprofile.id()).whenCompleteAsync((p, t) -> {
|
|
// Always remove the name, even if exception occurs
|
|
spawning.remove(name);
|
|
if (t != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GameProfile current;
|
|
if (p.name().isEmpty()) {
|
|
current = gameprofile;
|
|
}
|
|
else {
|
|
current = p;
|
|
}
|
|
|
|
EntityPlayerMPFake instance = new EntityPlayerMPFake(server, worldIn, current, ClientInformation.createDefault(), false);
|
|
instance.fixStartingPosition = () -> instance.snapTo(pos.x, pos.y, pos.z, (float) yaw, (float) pitch);
|
|
server.getPlayerList().placeNewPlayer(new FakeClientConnection(PacketFlow.SERVERBOUND), instance, new CommonListenerCookie(current, 0, instance.clientInformation(), false));
|
|
loadPlayerData(instance);
|
|
instance.stopRiding(); // otherwise the created fake player will be on the vehicle
|
|
instance.teleportTo(worldIn, pos.x, pos.y, pos.z, Set.of(), (float) yaw, (float) pitch, true);
|
|
instance.setHealth(20.0F);
|
|
instance.unsetRemoved();
|
|
instance.getAttribute(Attributes.STEP_HEIGHT).setBaseValue(0.6F);
|
|
instance.gameMode.changeGameModeForPlayer(gamemode);
|
|
server.getPlayerList().broadcastAll(new ClientboundRotateHeadPacket(instance, (byte) (instance.yHeadRot * 256 / 360)), dimensionId);//instance.dimension);
|
|
server.getPlayerList().broadcastAll(ClientboundEntityPositionSyncPacket.of(instance), dimensionId);//instance.dimension);
|
|
//instance.world.getChunkManager(). updatePosition(instance);
|
|
instance.entityData.set(DATA_PLAYER_MODE_CUSTOMISATION, (byte) 0x7f); // show all model layers (incl. capes)
|
|
instance.getAbilities().flying = flying;
|
|
}, server);
|
|
return true;
|
|
}
|
|
|
|
private static CompletableFuture<GameProfile> fetchGameProfile(MinecraftServer server, final UUID name) {
|
|
final ResolvableProfile resolvableProfile = ResolvableProfile.createUnresolved(name);
|
|
return resolvableProfile.resolveProfile(server.services().profileResolver());
|
|
}
|
|
|
|
private static void loadPlayerData(EntityPlayerMPFake player)
|
|
{
|
|
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), CarpetSettings.LOG))
|
|
{
|
|
Optional<ValueInput> optional = player.level().getServer().getPlayerList().loadPlayerData(player.nameAndId()).map((compoundTag) -> TagValueInput.create(scopedCollector, player.registryAccess(), compoundTag));
|
|
optional.ifPresent( valueInput -> {
|
|
player.load(valueInput);
|
|
player.loadAndSpawnEnderPearls(valueInput);
|
|
player.loadAndSpawnParentVehicle(valueInput);
|
|
});
|
|
}
|
|
}
|
|
|
|
public static EntityPlayerMPFake createShadow(MinecraftServer server, ServerPlayer player)
|
|
{
|
|
player.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"));
|
|
ServerLevel worldIn = player.level();//.getWorld(player.dimension);
|
|
GameProfile gameprofile = player.getGameProfile();
|
|
EntityPlayerMPFake playerShadow = new EntityPlayerMPFake(server, worldIn, gameprofile, player.clientInformation(), true);
|
|
playerShadow.setChatSession(player.getChatSession());
|
|
server.getPlayerList().placeNewPlayer(new FakeClientConnection(PacketFlow.SERVERBOUND), playerShadow, new CommonListenerCookie(gameprofile, 0, player.clientInformation(), true));
|
|
loadPlayerData(playerShadow);
|
|
|
|
playerShadow.setHealth(player.getHealth());
|
|
playerShadow.connection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot());
|
|
playerShadow.gameMode.changeGameModeForPlayer(player.gameMode.getGameModeForPlayer());
|
|
((ServerPlayerInterface) playerShadow).getActionPack().copyFrom(((ServerPlayerInterface) player).getActionPack());
|
|
// this might create problems if a player logs back in...
|
|
playerShadow.getAttribute(Attributes.STEP_HEIGHT).setBaseValue(0.6F);
|
|
playerShadow.entityData.set(DATA_PLAYER_MODE_CUSTOMISATION, player.getEntityData().get(DATA_PLAYER_MODE_CUSTOMISATION));
|
|
|
|
|
|
server.getPlayerList().broadcastAll(new ClientboundRotateHeadPacket(playerShadow, (byte) (player.yHeadRot * 256 / 360)), playerShadow.level().dimension());
|
|
server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, playerShadow));
|
|
//player.world.getChunkManager().updatePosition(playerShadow);
|
|
playerShadow.getAbilities().flying = player.getAbilities().flying;
|
|
return playerShadow;
|
|
}
|
|
|
|
public static EntityPlayerMPFake respawnFake(MinecraftServer server, ServerLevel level, GameProfile profile, ClientInformation cli)
|
|
{
|
|
return new EntityPlayerMPFake(server, level, profile, cli, false);
|
|
}
|
|
|
|
public static boolean isSpawningPlayer(String username)
|
|
{
|
|
return spawning.contains(username);
|
|
}
|
|
|
|
private EntityPlayerMPFake(MinecraftServer server, ServerLevel worldIn, GameProfile profile, ClientInformation cli, boolean shadow)
|
|
{
|
|
super(server, worldIn, profile, cli);
|
|
this.isAShadow = shadow;
|
|
}
|
|
|
|
@Override
|
|
public void onEquipItem(final EquipmentSlot slot, final ItemStack previous, final ItemStack stack)
|
|
{
|
|
if (!isUsingItem()) super.onEquipItem(slot, previous, stack);
|
|
}
|
|
|
|
@Override
|
|
public void kill(ServerLevel level)
|
|
{
|
|
kill(Messenger.s("Killed"));
|
|
}
|
|
|
|
public void kill(Component reason)
|
|
{
|
|
shakeOff();
|
|
|
|
if (reason.getContents() instanceof TranslatableContents text && text.getKey().equals("multiplayer.disconnect.duplicate_login")) {
|
|
this.connection.onDisconnect(new DisconnectionDetails(reason));
|
|
} else {
|
|
this.level().getServer().schedule(new TickTask(this.level().getServer().getTickCount(), () -> {
|
|
this.connection.onDisconnect(new DisconnectionDetails(reason));
|
|
}));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void tick()
|
|
{
|
|
if (this.level().getServer().getTickCount() % 10 == 0)
|
|
{
|
|
this.connection.resetPosition();
|
|
this.level().getChunkSource().move(this);
|
|
}
|
|
try
|
|
{
|
|
super.tick();
|
|
this.doTick();
|
|
}
|
|
catch (NullPointerException ignored)
|
|
{
|
|
// happens with that paper port thingy - not sure what that would fix, but hey
|
|
// the game not gonna crash violently.
|
|
}
|
|
|
|
|
|
}
|
|
|
|
private void shakeOff()
|
|
{
|
|
if (getVehicle() instanceof Player) stopRiding();
|
|
for (Entity passenger : getIndirectPassengers())
|
|
{
|
|
if (passenger instanceof Player) passenger.stopRiding();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void die(DamageSource cause)
|
|
{
|
|
shakeOff();
|
|
super.die(cause);
|
|
setHealth(20);
|
|
this.foodData = new FoodData();
|
|
kill(this.getCombatTracker().getDeathMessage());
|
|
}
|
|
|
|
@Override
|
|
public String getIpAddress()
|
|
{
|
|
return "127.0.0.1";
|
|
}
|
|
|
|
@Override
|
|
public boolean allowsListing() {
|
|
return CarpetSettings.allowListingFakePlayers;
|
|
}
|
|
|
|
@Override
|
|
protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) {
|
|
doCheckFallDamage(0.0, y, 0.0, onGround);
|
|
}
|
|
|
|
@Override
|
|
public ServerPlayer teleport(TeleportTransition serverLevel)
|
|
{
|
|
super.teleport(serverLevel);
|
|
if (wonGame) {
|
|
ServerboundClientCommandPacket p = new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN);
|
|
connection.handleClientCommand(p);
|
|
}
|
|
|
|
// If above branch was taken, *this* has been removed and replaced, the new instance has been set
|
|
// on 'our' connection (which is now theirs, but we still have a ref).
|
|
if (connection.player.isChangingDimension()) {
|
|
connection.player.hasChangedDimension();
|
|
}
|
|
return connection.player;
|
|
}
|
|
}
|