2020-04-24 03:25:58 +02:00
package net.minestom.server.entity ;
2019-08-03 15:25:24 +02:00
2024-01-06 08:43:43 +01:00
import it.unimi.dsi.fastutil.longs.LongArrayPriorityQueue ;
import it.unimi.dsi.fastutil.longs.LongPriorityQueue ;
2021-03-01 16:25:23 +01:00
import net.kyori.adventure.audience.MessageType ;
2021-03-01 18:25:55 +01:00
import net.kyori.adventure.bossbar.BossBar ;
2021-03-12 16:36:22 +01:00
import net.kyori.adventure.identity.Identified ;
2021-03-01 16:25:23 +01:00
import net.kyori.adventure.identity.Identity ;
import net.kyori.adventure.inventory.Book ;
2021-06-10 14:53:34 +02:00
import net.kyori.adventure.pointer.Pointers ;
2024-01-06 09:20:32 +01:00
import net.kyori.adventure.resource.ResourcePackCallback ;
import net.kyori.adventure.resource.ResourcePackRequest ;
import net.kyori.adventure.resource.ResourcePackStatus ;
2021-03-27 14:59:08 +01:00
import net.kyori.adventure.sound.Sound ;
2021-03-02 16:56:20 +01:00
import net.kyori.adventure.sound.SoundStop ;
2021-03-01 16:25:23 +01:00
import net.kyori.adventure.text.Component ;
2021-03-11 18:07:04 +01:00
import net.kyori.adventure.text.event.HoverEvent ;
import net.kyori.adventure.text.event.HoverEvent.ShowEntity ;
import net.kyori.adventure.text.event.HoverEventSource ;
2021-10-15 15:54:11 +02:00
import net.kyori.adventure.text.format.NamedTextColor ;
2021-10-31 19:29:41 +01:00
import net.kyori.adventure.title.TitlePart ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.MinecraftServer ;
2023-09-08 14:18:48 +02:00
import net.minestom.server.ServerFlag ;
2020-10-25 12:28:06 +01:00
import net.minestom.server.advancements.AdvancementTab ;
2021-03-05 20:59:28 +01:00
import net.minestom.server.adventure.AdventurePacketConvertor ;
2021-03-03 17:32:51 +01:00
import net.minestom.server.adventure.Localizable ;
2021-03-26 17:00:07 +01:00
import net.minestom.server.adventure.audience.Audiences ;
2021-03-11 16:56:12 +01:00
import net.minestom.server.attribute.Attribute ;
2023-12-01 16:33:38 +01:00
import net.minestom.server.collision.BoundingBox ;
2020-06-22 01:25:50 +02:00
import net.minestom.server.command.CommandSender ;
2021-07-08 18:56:40 +02:00
import net.minestom.server.coordinate.Point ;
import net.minestom.server.coordinate.Pos ;
import net.minestom.server.coordinate.Vec ;
2020-04-28 16:08:21 +02:00
import net.minestom.server.effects.Effects ;
2020-04-27 20:33:08 +02:00
import net.minestom.server.entity.damage.DamageType ;
2024-01-29 21:59:44 +01:00
import net.minestom.server.entity.metadata.LivingEntityMeta ;
2021-06-14 21:49:16 +02:00
import net.minestom.server.entity.metadata.PlayerMeta ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.entity.vehicle.PlayerVehicleInformation ;
2021-06-04 03:48:51 +02:00
import net.minestom.server.event.EventDispatcher ;
2020-05-12 17:12:11 +02:00
import net.minestom.server.event.inventory.InventoryOpenEvent ;
2020-05-07 15:46:21 +02:00
import net.minestom.server.event.item.ItemDropEvent ;
2020-05-12 14:12:17 +02:00
import net.minestom.server.event.item.ItemUpdateStateEvent ;
2020-05-07 15:46:21 +02:00
import net.minestom.server.event.item.PickupExperienceEvent ;
2020-12-13 23:01:01 +01:00
import net.minestom.server.event.player.* ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.instance.Chunk ;
2021-11-01 18:04:00 +01:00
import net.minestom.server.instance.EntityTracker ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.instance.Instance ;
2023-12-01 16:33:38 +01:00
import net.minestom.server.instance.block.Block ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.inventory.Inventory ;
import net.minestom.server.inventory.PlayerInventory ;
import net.minestom.server.item.ItemStack ;
2020-05-12 14:12:17 +02:00
import net.minestom.server.item.Material ;
2021-04-11 19:13:50 +02:00
import net.minestom.server.item.metadata.WrittenBookMeta ;
2022-04-15 12:25:58 +02:00
import net.minestom.server.listener.manager.PacketListenerManager ;
2021-05-31 18:53:57 +02:00
import net.minestom.server.message.ChatMessageType ;
import net.minestom.server.message.ChatPosition ;
2021-05-05 19:21:38 +02:00
import net.minestom.server.message.Messenger ;
2020-10-11 15:27:23 +02:00
import net.minestom.server.network.ConnectionManager ;
2021-01-06 19:02:35 +01:00
import net.minestom.server.network.ConnectionState ;
2020-10-11 15:27:23 +02:00
import net.minestom.server.network.PlayerProvider ;
2021-11-30 17:49:41 +01:00
import net.minestom.server.network.packet.client.ClientPacket ;
2021-11-17 06:31:24 +01:00
import net.minestom.server.network.packet.server.SendablePacket ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.network.packet.server.ServerPacket ;
2024-01-06 09:20:32 +01:00
import net.minestom.server.network.packet.server.common.* ;
2021-01-06 19:02:35 +01:00
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket ;
2020-12-13 23:01:01 +01:00
import net.minestom.server.network.packet.server.play.* ;
2024-04-10 14:31:47 +02:00
import net.minestom.server.network.packet.server.play.data.WorldPos ;
2022-10-27 14:33:48 +02:00
import net.minestom.server.network.player.GameProfile ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.network.player.PlayerConnection ;
2021-08-08 19:11:47 +02:00
import net.minestom.server.network.player.PlayerSocketConnection ;
2020-05-25 02:37:57 +02:00
import net.minestom.server.recipe.Recipe ;
import net.minestom.server.recipe.RecipeManager ;
2020-08-09 17:10:58 +02:00
import net.minestom.server.scoreboard.BelowNameTag ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.scoreboard.Team ;
2022-03-03 07:44:57 +01:00
import net.minestom.server.snapshot.EntitySnapshot ;
import net.minestom.server.snapshot.PlayerSnapshot ;
2022-05-04 13:25:24 +02:00
import net.minestom.server.snapshot.SnapshotImpl ;
2022-03-03 07:44:57 +01:00
import net.minestom.server.snapshot.SnapshotUpdater ;
2021-07-28 14:29:28 +02:00
import net.minestom.server.statistic.PlayerStatistic ;
2022-03-16 06:54:30 +01:00
import net.minestom.server.timer.Scheduler ;
2021-07-25 06:30:49 +02:00
import net.minestom.server.utils.MathUtils ;
import net.minestom.server.utils.PacketUtils ;
2024-01-06 09:33:36 +01:00
import net.minestom.server.utils.PropertyUtils ;
2021-07-11 03:35:17 +02:00
import net.minestom.server.utils.async.AsyncUtils ;
2022-06-04 22:04:57 +02:00
import net.minestom.server.utils.chunk.ChunkUpdateLimitChecker ;
2020-05-25 13:46:48 +02:00
import net.minestom.server.utils.chunk.ChunkUtils ;
2021-11-01 18:04:00 +01:00
import net.minestom.server.utils.function.IntegerBiConsumer ;
2021-04-12 11:49:04 +02:00
import net.minestom.server.utils.identity.NamedAndIdentified ;
2020-10-31 00:23:52 +01:00
import net.minestom.server.utils.instance.InstanceUtils ;
2021-04-10 18:55:26 +02:00
import net.minestom.server.utils.inventory.PlayerInventoryUtils ;
2021-03-31 19:17:37 +02:00
import net.minestom.server.utils.time.Cooldown ;
2020-11-14 09:02:29 +01:00
import net.minestom.server.utils.time.TimeUnit ;
2020-05-23 04:20:01 +02:00
import net.minestom.server.utils.validate.Check ;
2020-07-13 14:12:21 +02:00
import net.minestom.server.world.DimensionType ;
2021-11-20 11:56:35 +01:00
import org.jctools.queues.MessagePassingQueue ;
import org.jctools.queues.MpscUnboundedXaddArrayQueue ;
2021-05-01 00:05:49 +02:00
import org.jetbrains.annotations.ApiStatus ;
2020-12-02 21:28:36 +01:00
import org.jetbrains.annotations.NotNull ;
import org.jetbrains.annotations.Nullable ;
2024-01-06 08:43:43 +01:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2020-12-02 21:28:36 +01:00
2020-12-28 10:40:50 +01:00
import java.nio.charset.StandardCharsets ;
2021-06-30 01:31:09 +02:00
import java.time.Duration ;
2020-12-13 23:01:01 +01:00
import java.util.* ;
2021-07-11 02:59:24 +02:00
import java.util.concurrent.CompletableFuture ;
2022-03-16 06:54:30 +01:00
import java.util.concurrent.CountDownLatch ;
2020-12-13 23:01:01 +01:00
import java.util.concurrent.atomic.AtomicInteger ;
2024-01-06 08:43:43 +01:00
import java.util.concurrent.locks.ReentrantLock ;
2021-11-01 18:04:00 +01:00
import java.util.function.Consumer ;
2021-03-11 18:07:04 +01:00
import java.util.function.UnaryOperator ;
2020-12-13 23:01:01 +01:00
2020-10-11 15:27:23 +02:00
/ * *
2024-02-27 14:37:42 +01:00
* Those are the major actors of the server
2020-12-13 23:01:01 +01:00
* < p >
* You can easily create your own implementation of this and use it with { @link ConnectionManager # setPlayerProvider ( PlayerProvider ) } .
2020-10-11 15:27:23 +02:00
* /
2021-04-12 11:49:04 +02:00
public class Player extends LivingEntity implements CommandSender , Localizable , HoverEventSource < ShowEntity > , Identified , NamedAndIdentified {
2024-01-06 08:43:43 +01:00
private static final Logger logger = LoggerFactory . getLogger ( Player . class ) ;
2021-10-15 15:54:11 +02:00
private static final Component REMOVE_MESSAGE = Component . text ( " You have been removed from the server without reason. " , NamedTextColor . RED ) ;
2024-01-06 09:33:36 +01:00
private static final float MIN_CHUNKS_PER_TICK = PropertyUtils . getFloat ( " minestom.chunk-queue.min-per-tick " , 0 . 01f ) ;
private static final float MAX_CHUNKS_PER_TICK = PropertyUtils . getFloat ( " minestom.chunk-queue.max-per-tick " , 64 . 0f ) ;
private static final float CHUNKS_PER_TICK_MULTIPLIER = PropertyUtils . getFloat ( " minestom.chunk-queue.multiplier " , 1f ) ;
2024-01-06 08:43:43 +01:00
2023-12-01 16:33:38 +01:00
public static final boolean EXPERIMENT_PERFORM_POSE_UPDATES = Boolean . getBoolean ( " minestom.experiment.pose-updates " ) ;
2024-03-28 03:08:36 +01:00
// Magic values: https://wiki.vg/Entity_statuses#Player
private static final int STATUS_ENABLE_REDUCED_DEBUG_INFO = 22 ;
private static final int STATUS_DISABLE_REDUCED_DEBUG_INFO = 23 ;
private static final int STATUS_PERMISSION_LEVEL_OFFSET = 24 ;
2023-12-01 16:33:38 +01:00
2020-12-13 23:01:01 +01:00
private long lastKeepAlive ;
private boolean answerKeepAlive ;
private String username ;
2021-04-12 16:08:27 +02:00
private Component usernameComponent ;
2020-12-13 23:01:01 +01:00
protected final PlayerConnection playerConnection ;
private int latency ;
2021-03-03 20:27:33 +01:00
private Component displayName ;
2020-12-13 23:01:01 +01:00
private PlayerSkin skin ;
2023-11-28 17:48:00 +01:00
private Instance pendingInstance = null ;
2020-12-13 23:01:01 +01:00
private DimensionType dimensionType ;
private GameMode gameMode ;
2024-04-10 14:31:47 +02:00
private WorldPos deathLocation ;
2024-01-06 08:43:43 +01:00
2022-06-04 22:04:57 +02:00
/ * *
* Keeps track of what chunks are sent to the client , this defines the center of the loaded area
2024-03-28 03:08:36 +01:00
* in the range of { @link ServerFlag # CHUNK_VIEW_DISTANCE }
2022-06-04 22:04:57 +02:00
* /
private Vec chunksLoadedByClient = Vec . ZERO ;
2024-01-06 08:43:43 +01:00
private final ReentrantLock chunkQueueLock = new ReentrantLock ( ) ;
private final LongPriorityQueue chunkQueue = new LongArrayPriorityQueue ( this : : compareChunkDistance ) ;
private float targetChunksPerTick = 9f ; // Always send 9 chunks immediately
private float pendingChunkCount = 0f ; // Number of chunks to send on the current tick (ie 0.5 means we cannot send a chunk yet, 1.5 would send a single chunk with a 0.5 remainder)
private int maxChunkBatchLead = 1 ; // Maximum number of batches to send before waiting for a reply
private int chunkBatchLead = 0 ; // Number of batches sent without a reply
2021-11-01 18:04:00 +01:00
final IntegerBiConsumer chunkAdder = ( chunkX , chunkZ ) - > {
// Load new chunks
2024-01-16 15:07:45 +01:00
this . instance . loadOptionalChunk ( chunkX , chunkZ ) . thenAccept ( this : : sendChunk ) ;
2021-11-01 18:04:00 +01:00
} ;
final IntegerBiConsumer chunkRemover = ( chunkX , chunkZ ) - > {
// Unload old chunks
2021-11-19 06:06:32 +01:00
sendPacket ( new UnloadChunkPacket ( chunkX , chunkZ ) ) ;
2022-02-13 12:34:27 +01:00
EventDispatcher . call ( new PlayerChunkUnloadEvent ( this , chunkX , chunkZ ) ) ;
2021-11-01 18:04:00 +01:00
} ;
2021-03-08 16:49:16 +01:00
2020-12-13 23:01:01 +01:00
private final AtomicInteger teleportId = new AtomicInteger ( ) ;
2021-03-08 16:49:16 +01:00
private int receivedTeleportId ;
2020-12-13 23:01:01 +01:00
2024-01-16 15:47:38 +01:00
private final MessagePassingQueue < ClientPacket > packets = new MpscUnboundedXaddArrayQueue < > ( 32 ) ;
2020-12-13 23:01:01 +01:00
private final boolean levelFlat ;
private final PlayerSettings settings ;
private float exp ;
private int level ;
2023-06-15 04:11:18 +02:00
private int portalCooldown = 0 ;
2020-12-13 23:01:01 +01:00
2020-12-29 16:42:07 +01:00
protected PlayerInventory inventory ;
2020-12-13 23:01:01 +01:00
private Inventory openInventory ;
// Used internally to allow the closing of inventory within the inventory listener
private boolean didCloseInventory ;
private byte heldSlot ;
2021-07-06 20:44:24 +02:00
private Pos respawnPoint ;
2020-12-13 23:01:01 +01:00
private int food ;
private float foodSaturation ;
private long startEatingTime ;
private long defaultEatingTime = 1000L ;
private long eatingTime ;
2021-04-13 22:59:40 +02:00
private Hand eatingHand ;
2020-12-13 23:01:01 +01:00
// Game state (https://wiki.vg/Protocol#Change_Game_State)
private boolean enableRespawnScreen ;
2022-06-04 22:04:57 +02:00
private final ChunkUpdateLimitChecker chunkUpdateLimitChecker = new ChunkUpdateLimitChecker ( 6 ) ;
2020-12-13 23:01:01 +01:00
// Experience orb pickup
2021-06-30 01:31:09 +02:00
protected Cooldown experiencePickupCooldown = new Cooldown ( Duration . of ( 10 , TimeUnit . SERVER_TICK ) ) ;
2020-12-13 23:01:01 +01:00
private BelowNameTag belowNameTag ;
2021-08-26 17:10:48 +02:00
private int permissionLevel ;
2020-12-13 23:01:01 +01:00
private boolean reducedDebugScreenInformation ;
2024-01-26 08:10:00 +01:00
private boolean hardcore ;
2020-12-13 23:01:01 +01:00
// Abilities
private boolean flying ;
private boolean allowFlying ;
private boolean instantBreak ;
private float flyingSpeed = 0 . 05f ;
2020-12-15 13:41:42 +01:00
private float fieldViewModifier = 0 . 1f ;
2020-05-25 02:37:57 +02:00
2020-12-13 23:01:01 +01:00
// Statistics
private final Map < PlayerStatistic , Integer > statisticValueMap = new Hashtable < > ( ) ;
2020-05-29 02:11:41 +02:00
2020-12-13 23:01:01 +01:00
// Vehicle
private final PlayerVehicleInformation vehicleInformation = new PlayerVehicleInformation ( ) ;
2020-05-26 16:14:52 +02:00
2021-03-15 14:53:06 +01:00
// Adventure
private Identity identity ;
2021-06-15 15:06:56 +02:00
private final Pointers pointers ;
2021-03-15 14:53:06 +01:00
2024-01-06 09:20:32 +01:00
// Resource packs
private final Map < UUID , ResourcePackCallback > resourcePackCallbacks = new HashMap < > ( ) ;
// The future is non-null when a resource pack is in-flight, and completed when all statuses have been received.
private CompletableFuture < Void > resourcePackFuture = null ;
2020-12-16 00:13:40 +01:00
public Player ( @NotNull UUID uuid , @NotNull String username , @NotNull PlayerConnection playerConnection ) {
2021-02-05 14:32:28 +01:00
super ( EntityType . PLAYER , uuid ) ;
2020-12-13 23:01:01 +01:00
this . username = username ;
2021-04-22 18:21:34 +02:00
this . usernameComponent = Component . text ( username ) ;
2020-12-13 23:01:01 +01:00
this . playerConnection = playerConnection ;
2020-05-25 02:37:57 +02:00
2021-07-06 20:44:24 +02:00
setRespawnPoint ( Pos . ZERO ) ;
2020-12-13 23:01:01 +01:00
this . settings = new PlayerSettings ( ) ;
this . inventory = new PlayerInventory ( this ) ;
setCanPickupItem ( true ) ; // By default
// Allow the server to send the next keep alive packet
refreshAnswerKeepAlive ( true ) ;
this . gameMode = GameMode . SURVIVAL ;
2021-01-07 04:21:34 +01:00
this . dimensionType = DimensionType . OVERWORLD ; // Default dimension
2020-12-13 23:01:01 +01:00
this . levelFlat = true ;
2021-03-09 20:51:11 +01:00
getAttribute ( Attribute . MOVEMENT_SPEED ) . setBaseValue ( 0 . 1f ) ;
2020-12-13 23:01:01 +01:00
// FakePlayer init its connection there
playerConnectionInit ( ) ;
2021-03-15 14:53:06 +01:00
this . identity = Identity . identity ( uuid ) ;
2021-06-15 15:06:56 +02:00
this . pointers = Pointers . builder ( )
. withDynamic ( Identity . UUID , this : : getUuid )
. withDynamic ( Identity . NAME , this : : getUsername )
. withDynamic ( Identity . DISPLAY_NAME , this : : getDisplayName )
. build ( ) ;
2023-09-30 19:17:44 +02:00
// When in configuration state no metadata updates can be sent.
metadata . setNotifyAboutChanges ( false ) ;
2020-12-13 23:01:01 +01:00
}
2023-11-28 17:48:00 +01:00
@ApiStatus.Internal
2024-01-26 08:10:00 +01:00
public void setPendingOptions ( @NotNull Instance pendingInstance , boolean hardcore ) {
2023-11-28 17:48:00 +01:00
// I(mattw) am not a big fan of this function, but somehow we need to store
// the instance and i didn't like a record in ConnectionManager either.
this . pendingInstance = pendingInstance ;
2024-01-26 08:10:00 +01:00
this . hardcore = hardcore ;
2023-11-28 17:48:00 +01:00
}
2020-12-13 23:01:01 +01:00
/ * *
* Used when the player is created .
* Init the player and spawn him .
* < p >
* WARNING : executed in the main update thread
2021-08-08 19:11:47 +02:00
* UNSAFE : Only meant to be used when a socket player connects through the server .
2020-12-13 23:01:01 +01:00
* /
2023-11-25 20:43:28 +01:00
@ApiStatus.Internal
2023-11-28 17:48:00 +01:00
public CompletableFuture < Void > UNSAFE_init ( ) {
final Instance spawnInstance = this . pendingInstance ;
this . pendingInstance = null ;
2023-11-27 22:55:28 +01:00
this . removed = false ;
2021-01-07 04:21:34 +01:00
this . dimensionType = spawnInstance . getDimensionType ( ) ;
2023-10-07 13:39:24 +02:00
final JoinGamePacket joinGamePacket = new JoinGamePacket (
2024-01-26 08:10:00 +01:00
getEntityId ( ) , this . hardcore , List . of ( ) , 0 ,
2024-03-28 03:08:36 +01:00
ServerFlag . CHUNK_VIEW_DISTANCE , ServerFlag . CHUNK_VIEW_DISTANCE ,
2023-10-07 15:00:15 +02:00
false , true , false , dimensionType . toString ( ) , spawnInstance . getDimensionName ( ) ,
2023-10-07 13:39:24 +02:00
0 , gameMode , null , false , levelFlat , deathLocation , portalCooldown ) ;
2022-05-10 10:07:55 +02:00
sendPacket ( joinGamePacket ) ;
2020-12-13 23:01:01 +01:00
2021-07-22 13:01:00 +02:00
// Difficulty
2022-05-10 10:07:55 +02:00
sendPacket ( new ServerDifficultyPacket ( MinecraftServer . getDifficulty ( ) , true ) ) ;
2020-12-13 23:01:01 +01:00
2022-05-10 10:07:55 +02:00
sendPacket ( new SpawnPositionPacket ( respawnPoint , 0 ) ) ;
2020-12-13 23:01:01 +01:00
2024-01-07 21:14:38 +01:00
// Reenable metadata notifications as we leave the configuration state
metadata . setNotifyAboutChanges ( true ) ;
2024-01-03 18:41:25 +01:00
sendPacket ( getMetadataPacket ( ) ) ;
2020-12-13 23:01:01 +01:00
// Add player to list with spawning skin
2022-10-27 14:33:48 +02:00
PlayerSkin profileSkin = null ;
if ( playerConnection instanceof PlayerSocketConnection socketConnection ) {
final GameProfile gameProfile = socketConnection . gameProfile ( ) ;
if ( gameProfile ! = null ) {
for ( GameProfile . Property property : gameProfile . properties ( ) ) {
if ( property . name ( ) . equals ( " textures " ) ) {
profileSkin = new PlayerSkin ( property . value ( ) , property . signature ( ) ) ;
break ;
}
}
}
}
PlayerSkinInitEvent skinInitEvent = new PlayerSkinInitEvent ( this , profileSkin ) ;
2021-06-04 03:48:51 +02:00
EventDispatcher . call ( skinInitEvent ) ;
2020-12-13 23:01:01 +01:00
this . skin = skinInitEvent . getSkin ( ) ;
2021-02-06 03:51:17 +01:00
// FIXME: when using Geyser, this line remove the skin of the client
2023-11-28 17:48:00 +01:00
PacketUtils . broadcastPlayPacket ( getAddPlayerToList ( ) ) ;
2023-12-01 16:33:38 +01:00
2023-11-28 17:48:00 +01:00
var connectionManager = MinecraftServer . getConnectionManager ( ) ;
2023-12-24 19:12:16 +01:00
for ( var player : connectionManager . getOnlinePlayers ( ) ) {
2023-08-05 16:30:53 +02:00
if ( player ! = this ) {
sendPacket ( player . getAddPlayerToList ( ) ) ;
if ( player . displayName ! = null ) {
sendPacket ( new PlayerInfoUpdatePacket ( PlayerInfoUpdatePacket . Action . UPDATE_DISPLAY_NAME , player . infoEntry ( ) ) ) ;
}
}
2021-08-11 14:18:04 +02:00
}
2022-04-13 17:57:15 +02:00
2022-03-26 16:16:35 +01:00
//Teams
for ( Team team : MinecraftServer . getTeamManager ( ) . getTeams ( ) ) {
sendPacket ( team . createTeamsCreationPacket ( ) ) ;
}
2020-12-13 23:01:01 +01:00
2021-08-12 03:53:49 +02:00
// Commands
refreshCommands ( ) ;
2020-12-13 23:01:01 +01:00
// Recipes start
{
RecipeManager recipeManager = MinecraftServer . getRecipeManager ( ) ;
2022-05-10 10:07:55 +02:00
sendPacket ( recipeManager . getDeclareRecipesPacket ( ) ) ;
2020-05-25 02:37:57 +02:00
2020-12-13 23:01:01 +01:00
List < String > recipesIdentifier = new ArrayList < > ( ) ;
for ( Recipe recipe : recipeManager . getRecipes ( ) ) {
if ( ! recipe . shouldShow ( this ) )
continue ;
recipesIdentifier . add ( recipe . getRecipeId ( ) ) ;
}
if ( ! recipesIdentifier . isEmpty ( ) ) {
2021-11-30 17:49:41 +01:00
UnlockRecipesPacket unlockRecipesPacket = new UnlockRecipesPacket ( 0 ,
false , false ,
false , false ,
false , false ,
false , false ,
recipesIdentifier , recipesIdentifier ) ;
2022-05-10 10:07:55 +02:00
sendPacket ( unlockRecipesPacket ) ;
2020-12-13 23:01:01 +01:00
}
2020-05-25 02:37:57 +02:00
}
2020-12-13 23:01:01 +01:00
// Recipes end
2020-05-25 03:17:24 +02:00
2021-07-22 13:01:00 +02:00
// Some client updates
2022-05-10 10:07:55 +02:00
sendPacket ( getPropertiesPacket ( ) ) ; // Send default properties
2024-03-28 03:08:36 +01:00
triggerStatus ( ( byte ) ( STATUS_PERMISSION_LEVEL_OFFSET + permissionLevel ) ) ; // Set permission level
2020-12-13 23:01:01 +01:00
refreshHealth ( ) ; // Heal and send health packet
refreshAbilities ( ) ; // Send abilities packet
2021-08-15 00:52:07 +02:00
2022-03-14 19:01:48 +01:00
return setInstance ( spawnInstance ) ;
2020-12-13 23:01:01 +01:00
}
2023-11-25 20:43:28 +01:00
/ * *
* Moves the player immediately to the configuration state . The player is automatically moved
* to configuration upon finishing login , this method can be used to move them back to configuration
* after entering the play state .
*
* < p > This will result in them being removed from the current instance , player list , etc . < / p >
* /
public void startConfigurationPhase ( ) {
2024-01-16 15:47:38 +01:00
Check . stateCondition ( playerConnection . getConnectionState ( ) ! = ConnectionState . PLAY ,
2023-11-28 17:48:00 +01:00
" Player must be in the play state for reconfiguration. " ) ;
2023-11-25 20:43:28 +01:00
2023-11-28 17:48:00 +01:00
// Remove the player, then send them back to configuration
remove ( false ) ;
2023-11-25 20:43:28 +01:00
2023-11-28 17:48:00 +01:00
var connectionManager = MinecraftServer . getConnectionManager ( ) ;
connectionManager . transitionPlayToConfig ( this ) ;
2023-11-25 20:43:28 +01:00
}
2020-12-13 23:01:01 +01:00
/ * *
* Used to initialize the player connection
* /
protected void playerConnectionInit ( ) {
2022-01-12 09:50:09 +01:00
PlayerConnection connection = playerConnection ;
if ( connection ! = null ) connection . setPlayer ( this ) ;
2020-12-13 23:01:01 +01:00
}
@Override
public void update ( long time ) {
// Process received packets
2022-01-28 05:32:35 +01:00
interpretPacketQueue ( ) ;
2023-11-28 17:48:00 +01:00
// It is possible to be removed during packet processing, if thats the case exit immediately.
2023-11-27 22:55:28 +01:00
if ( isRemoved ( ) ) return ;
2019-08-18 20:38:09 +02:00
2024-01-06 08:43:43 +01:00
// Send any available queued chunks
sendPendingChunks ( ) ;
2020-12-13 23:01:01 +01:00
super . update ( time ) ; // Super update (item pickup/fire management)
// Experience orb pickup
2021-03-31 19:17:37 +02:00
if ( experiencePickupCooldown . isReady ( time ) ) {
experiencePickupCooldown . refreshLastUpdate ( time ) ;
2022-07-08 17:37:24 +02:00
final Point loweredPosition = position . sub ( 0 , . 5 , 0 ) ;
2022-03-09 19:08:42 +01:00
this . instance . getEntityTracker ( ) . nearbyEntities ( position , expandedBoundingBox . width ( ) ,
2021-11-01 18:04:00 +01:00
EntityTracker . Target . EXPERIENCE_ORBS , experienceOrb - > {
2022-07-06 01:28:15 +02:00
if ( expandedBoundingBox . intersectEntity ( loweredPosition , experienceOrb ) ) {
2021-11-01 18:04:00 +01:00
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent ( this , experienceOrb ) ;
EventDispatcher . callCancellable ( pickupExperienceEvent , ( ) - > {
short experienceCount = pickupExperienceEvent . getExperienceCount ( ) ; // TODO give to player
experienceOrb . remove ( ) ;
} ) ;
}
} ) ;
2019-08-31 07:54:53 +02:00
}
2020-12-13 23:01:01 +01:00
// Eating animation
if ( isEating ( ) ) {
if ( time - startEatingTime > = eatingTime ) {
triggerStatus ( ( byte ) 9 ) ; // Mark item use as finished
2021-05-23 16:46:29 +02:00
ItemUpdateStateEvent itemUpdateStateEvent = callItemUpdateStateEvent ( eatingHand ) ;
2020-12-13 23:01:01 +01:00
Check . notNull ( itemUpdateStateEvent , " #callItemUpdateStateEvent returned null. " ) ;
// Refresh hand
final boolean isOffHand = itemUpdateStateEvent . getHand ( ) = = Player . Hand . OFF ;
refreshActiveHand ( false , isOffHand , false ) ;
final ItemStack foodItem = itemUpdateStateEvent . getItemStack ( ) ;
2022-04-13 17:57:15 +02:00
final boolean isFood = foodItem . material ( ) . isFood ( ) ;
2020-12-13 23:01:01 +01:00
if ( isFood ) {
2021-04-13 22:59:40 +02:00
PlayerEatEvent playerEatEvent = new PlayerEatEvent ( this , foodItem , eatingHand ) ;
2021-06-04 03:48:51 +02:00
EventDispatcher . call ( playerEatEvent ) ;
2020-12-13 23:01:01 +01:00
}
2021-04-13 22:59:40 +02:00
refreshEating ( null ) ;
2020-12-13 23:01:01 +01:00
}
2020-05-12 14:12:17 +02:00
}
2020-12-13 23:01:01 +01:00
2023-12-01 16:33:38 +01:00
if ( EXPERIMENT_PERFORM_POSE_UPDATES ) updatePose ( ) ;
2020-12-13 23:01:01 +01:00
// Tick event
2022-02-13 12:34:27 +01:00
EventDispatcher . call ( new PlayerTickEvent ( this ) ) ;
2019-08-24 20:34:01 +02:00
}
2020-12-13 23:01:01 +01:00
@Override
public void kill ( ) {
if ( ! isDead ( ) ) {
2021-03-03 20:27:33 +01:00
Component deathText ;
Component chatMessage ;
2020-12-22 05:26:37 +01:00
// get death screen text to the killed player
2020-12-13 23:01:01 +01:00
{
2023-11-02 03:35:18 +01:00
if ( lastDamage ! = null ) {
deathText = lastDamage . buildDeathScreenText ( this ) ;
2020-12-13 23:01:01 +01:00
} else { // may happen if killed by the server without applying damage
2021-03-03 20:27:33 +01:00
deathText = Component . text ( " Killed by poor programming. " ) ;
2020-12-13 23:01:01 +01:00
}
}
2020-12-22 05:26:37 +01:00
// get death message to chat
2020-12-13 23:01:01 +01:00
{
2023-11-02 03:35:18 +01:00
if ( lastDamage ! = null ) {
chatMessage = lastDamage . buildDeathMessage ( this ) ;
2020-12-13 23:01:01 +01:00
} else { // may happen if killed by the server without applying damage
2021-03-03 20:27:33 +01:00
chatMessage = Component . text ( getUsername ( ) + " was killed by poor programming. " ) ;
2020-12-13 23:01:01 +01:00
}
2020-12-22 05:26:37 +01:00
}
2020-12-13 23:01:01 +01:00
2020-12-22 05:26:37 +01:00
// Call player death event
PlayerDeathEvent playerDeathEvent = new PlayerDeathEvent ( this , deathText , chatMessage ) ;
2021-06-04 03:48:51 +02:00
EventDispatcher . call ( playerDeathEvent ) ;
2020-12-22 05:26:37 +01:00
deathText = playerDeathEvent . getDeathText ( ) ;
chatMessage = playerDeathEvent . getChatMessage ( ) ;
// #buildDeathScreenText can return null, check here
if ( deathText ! = null ) {
2023-06-15 04:11:18 +02:00
sendPacket ( new DeathCombatEventPacket ( getEntityId ( ) , deathText ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-12-22 05:26:37 +01:00
// #buildDeathMessage can return null, check here
if ( chatMessage ! = null ) {
2021-03-26 20:28:07 +01:00
Audiences . players ( ) . sendMessage ( chatMessage ) ;
2020-12-22 05:26:37 +01:00
}
2023-03-30 03:03:36 +02:00
// Set death location
if ( getInstance ( ) ! = null )
setDeathLocation ( getInstance ( ) . getDimensionType ( ) , getPosition ( ) ) ;
2020-04-27 21:12:42 +02:00
}
2020-12-13 23:01:01 +01:00
super . kill ( ) ;
2019-08-20 22:40:57 +02:00
}
2020-12-13 23:01:01 +01:00
/ * *
* Respawns the player by sending a { @link RespawnPacket } to the player and teleporting him
2021-01-22 21:28:33 +01:00
* to { @link # getRespawnPoint ( ) } . It also resets fire and health .
2020-12-13 23:01:01 +01:00
* /
public void respawn ( ) {
if ( ! isDead ( ) )
return ;
setFireForDuration ( 0 ) ;
setOnFire ( false ) ;
refreshHealth ( ) ;
2023-03-22 04:22:24 +01:00
2023-07-04 23:44:30 +02:00
sendPacket ( new RespawnPacket ( getDimensionType ( ) . toString ( ) , instance . getDimensionName ( ) ,
2023-11-02 03:51:37 +01:00
0 , gameMode , gameMode , false , levelFlat , deathLocation , portalCooldown , RespawnPacket . COPY_ALL ) ) ;
2024-02-10 23:41:57 +01:00
refreshClientStateAfterRespawn ( ) ;
2022-01-29 14:56:45 +01:00
2020-12-13 23:01:01 +01:00
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent ( this ) ;
2021-06-04 03:48:51 +02:00
EventDispatcher . call ( respawnEvent ) ;
2020-12-13 23:01:01 +01:00
refreshIsDead ( false ) ;
2022-05-03 22:29:38 +02:00
updatePose ( ) ;
2020-12-13 23:01:01 +01:00
2022-10-25 03:04:41 +02:00
Pos respawnPosition = respawnEvent . getRespawnPosition ( ) ;
// The client unloads chunks when respawning, so resend all chunks next to spawn
2024-01-06 08:43:43 +01:00
ChunkUtils . forChunksInRange ( respawnPosition , settings . getEffectiveViewDistance ( ) , chunkAdder ) ;
2022-10-25 03:04:41 +02:00
chunksLoadedByClient = new Vec ( respawnPosition . chunkX ( ) , respawnPosition . chunkZ ( ) ) ;
2022-11-26 21:23:39 +01:00
// Client also needs all entities resent to them, since those are unloaded as well
2024-03-28 03:08:36 +01:00
this . instance . getEntityTracker ( ) . nearbyEntitiesByChunkRange ( respawnPosition , settings . getEffectiveViewDistance ( ) ,
2022-11-26 21:23:39 +01:00
EntityTracker . Target . ENTITIES , entity - > {
// Skip refreshing self with a new viewer
2023-12-06 07:55:02 +01:00
if ( ! entity . getUuid ( ) . equals ( uuid ) & & entity . isViewer ( this ) ) {
2022-11-26 21:23:39 +01:00
entity . updateNewViewer ( this ) ;
}
} ) ;
2022-10-25 03:04:41 +02:00
teleport ( respawnPosition ) . thenRun ( this : : refreshAfterTeleport ) ;
2019-08-27 20:49:11 +02:00
}
2021-08-15 00:52:07 +02:00
2022-01-29 14:56:45 +01:00
/ * *
* Sends necessary packets to synchronize player data after a { @link RespawnPacket }
* /
private void refreshClientStateAfterRespawn ( ) {
2024-01-26 08:07:39 +01:00
sendPacket ( new ChangeGameStatePacket ( ChangeGameStatePacket . Reason . LEVEL_CHUNKS_LOAD_START , 0 ) ) ;
2023-07-05 00:02:13 +02:00
sendPacket ( new ServerDifficultyPacket ( MinecraftServer . getDifficulty ( ) , false ) ) ;
2022-05-10 10:07:55 +02:00
sendPacket ( new UpdateHealthPacket ( this . getHealth ( ) , food , foodSaturation ) ) ;
sendPacket ( new SetExperiencePacket ( exp , level , 0 ) ) ;
2024-03-28 03:08:36 +01:00
triggerStatus ( ( byte ) ( STATUS_PERMISSION_LEVEL_OFFSET + permissionLevel ) ) ; // Set permission level
2022-01-29 14:56:45 +01:00
refreshAbilities ( ) ;
}
2021-08-12 03:53:49 +02:00
/ * *
* Refreshes the command list for this player . This checks the
* { @link net . minestom . server . command . builder . condition . CommandCondition } s
* again , and any changes will be visible to the player .
* /
public void refreshCommands ( ) {
2022-05-10 10:07:55 +02:00
sendPacket ( MinecraftServer . getCommandManager ( ) . createDeclareCommandsPacket ( this ) ) ;
2021-08-12 03:53:49 +02:00
}
2019-08-27 20:49:11 +02:00
2020-12-13 23:01:01 +01:00
@Override
public boolean isOnGround ( ) {
return onGround ;
}
@Override
2023-11-25 20:43:28 +01:00
public void remove ( boolean permanent ) {
2021-08-05 15:10:15 +02:00
if ( isRemoved ( ) ) return ;
2023-11-25 20:43:28 +01:00
if ( permanent ) {
this . packets . clear ( ) ;
EventDispatcher . call ( new PlayerDisconnectEvent ( this ) ) ;
}
super . remove ( permanent ) ;
2021-10-15 15:54:11 +02:00
final Inventory currentInventory = getOpenInventory ( ) ;
if ( currentInventory ! = null ) currentInventory . removeViewer ( this ) ;
2021-03-26 18:06:10 +01:00
MinecraftServer . getBossBarManager ( ) . removeAllBossBars ( this ) ;
2020-12-13 23:01:01 +01:00
// Advancement tabs cache
{
Set < AdvancementTab > advancementTabs = AdvancementTab . getTabs ( this ) ;
if ( advancementTabs ! = null ) {
for ( AdvancementTab advancementTab : advancementTabs ) {
advancementTab . removeViewer ( this ) ;
}
2020-10-31 00:23:52 +01:00
}
2020-12-13 23:01:01 +01:00
}
2021-11-01 18:04:00 +01:00
final Pos position = this . position ;
final int chunkX = position . chunkX ( ) ;
final int chunkZ = position . chunkZ ( ) ;
2020-12-13 23:01:01 +01:00
// Clear all viewable chunks
2024-03-28 03:08:36 +01:00
ChunkUtils . forChunksInRange ( chunkX , chunkZ , settings . getEffectiveViewDistance ( ) , chunkRemover ) ;
2021-08-11 14:18:04 +02:00
// Remove from the tab-list
2023-11-28 17:48:00 +01:00
PacketUtils . broadcastPlayPacket ( getRemovePlayerToList ( ) ) ;
2021-10-15 15:54:11 +02:00
// Prevent the player from being stuck in loading screen, or just unable to interact with the server
// This should be considered as a bug, since the player will ultimately time out anyway.
2023-11-25 20:43:28 +01:00
if ( permanent & & playerConnection . isOnline ( ) ) kick ( REMOVE_MESSAGE ) ;
2020-12-13 23:01:01 +01:00
}
@Override
2021-11-01 18:04:00 +01:00
public void updateOldViewer ( @NotNull Player player ) {
super . updateOldViewer ( player ) ;
2020-12-13 23:01:01 +01:00
// Team
2021-02-25 08:37:02 +01:00
if ( this . getTeam ( ) ! = null & & this . getTeam ( ) . getMembers ( ) . size ( ) = = 1 ) { // If team only contains "this" player
2021-11-01 18:04:00 +01:00
player . sendPacket ( this . getTeam ( ) . createTeamDestructionPacket ( ) ) ;
2021-02-25 08:37:02 +01:00
}
2020-12-13 23:01:01 +01:00
}
2021-03-25 18:07:05 +01:00
@Override
2021-11-17 06:31:24 +01:00
public void sendPacketToViewersAndSelf ( @NotNull SendablePacket packet ) {
2022-05-10 10:07:55 +02:00
sendPacket ( packet ) ;
2021-03-25 18:07:05 +01:00
super . sendPacketToViewersAndSelf ( packet ) ;
}
2020-12-13 23:01:01 +01:00
/ * *
* Changes the player instance and load surrounding chunks if needed .
* < p >
* Be aware that because chunk operations are expensive ,
* it is possible for this method to be non - blocking when retrieving chunks is required .
2021-07-11 03:35:17 +02:00
*
* @param instance the new player instance
2021-01-06 19:06:37 +01:00
* @param spawnPosition the new position of the player
2021-07-28 16:36:21 +02:00
* @return a future called once the player instance changed
2020-12-13 23:01:01 +01:00
* /
2021-02-25 11:56:10 +01:00
@Override
2021-07-11 02:59:24 +02:00
public CompletableFuture < Void > setInstance ( @NotNull Instance instance , @NotNull Pos spawnPosition ) {
2021-08-15 00:52:07 +02:00
final Instance currentInstance = this . instance ;
Check . argCondition ( currentInstance = = instance , " Instance should be different than the current one " ) ;
2021-11-01 18:04:00 +01:00
if ( InstanceUtils . areLinked ( currentInstance , instance ) & & spawnPosition . sameChunk ( this . position ) ) {
2020-12-13 23:01:01 +01:00
// The player already has the good version of all the chunks.
// We just need to refresh his entity viewing list and add him to the instance
2021-12-27 17:58:53 +01:00
spawnPlayer ( instance , spawnPosition , false , false , false ) ;
2021-11-01 18:04:00 +01:00
return AsyncUtils . VOID_FUTURE ;
2020-12-13 23:01:01 +01:00
}
2021-11-01 18:04:00 +01:00
// Must update the player chunks
2022-11-26 11:10:44 +01:00
chunkUpdateLimitChecker . clearHistory ( ) ;
2023-07-05 03:21:59 +02:00
final boolean dimensionChange = currentInstance ! = null & & ! Objects . equals ( currentInstance . getDimensionName ( ) , instance . getDimensionName ( ) ) ;
2021-11-01 18:04:00 +01:00
final Consumer < Instance > runnable = ( i ) - > spawnPlayer ( i , spawnPosition ,
currentInstance = = null , dimensionChange , true ) ;
2022-03-16 06:54:30 +01:00
// Ensure that surrounding chunks are loaded
2021-11-01 18:04:00 +01:00
List < CompletableFuture < Chunk > > futures = new ArrayList < > ( ) ;
2024-03-28 03:08:36 +01:00
ChunkUtils . forChunksInRange ( spawnPosition , settings . getEffectiveViewDistance ( ) , ( chunkX , chunkZ ) - > {
2022-03-16 06:54:30 +01:00
final CompletableFuture < Chunk > future = instance . loadOptionalChunk ( chunkX , chunkZ ) ;
if ( ! future . isDone ( ) ) futures . add ( future ) ;
} ) ;
if ( futures . isEmpty ( ) ) {
// All chunks are already loaded
runnable . accept ( instance ) ;
return AsyncUtils . VOID_FUTURE ;
}
2022-01-20 04:57:41 +01:00
2022-03-16 06:54:30 +01:00
// One or more chunks need to be loaded
final Thread runThread = Thread . currentThread ( ) ;
CountDownLatch latch = new CountDownLatch ( 1 ) ;
2022-03-16 06:58:01 +01:00
Scheduler scheduler = MinecraftServer . getSchedulerManager ( ) ;
2022-01-20 04:57:41 +01:00
CompletableFuture < Void > future = new CompletableFuture < > ( ) {
@Override
public Void join ( ) {
// Prevent deadlock
2022-03-16 06:54:30 +01:00
if ( runThread = = Thread . currentThread ( ) ) {
try {
latch . await ( ) ;
} catch ( InterruptedException e ) {
throw new RuntimeException ( e ) ;
}
scheduler . process ( ) ;
assert isDone ( ) ;
}
return super . join ( ) ;
2022-01-20 04:57:41 +01:00
}
} ;
CompletableFuture . allOf ( futures . toArray ( CompletableFuture [ ] : : new ) )
. thenRun ( ( ) - > {
2022-03-16 06:54:30 +01:00
scheduler . scheduleNextProcess ( ( ) - > {
2021-11-01 18:04:00 +01:00
runnable . accept ( instance ) ;
2022-01-20 04:57:41 +01:00
future . complete ( null ) ;
2022-03-16 06:54:30 +01:00
} ) ;
latch . countDown ( ) ;
2021-11-01 18:04:00 +01:00
} ) ;
2022-01-20 04:57:41 +01:00
return future ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Changes the player instance without changing its position ( defaulted to { @link # getRespawnPoint ( ) }
2021-01-06 19:06:37 +01:00
* if the player is not in any instance ) .
2020-12-13 23:01:01 +01:00
*
* @param instance the new player instance
2021-07-11 13:45:28 +02:00
* @return a { @link CompletableFuture } called once the entity ' s instance has been set ,
* this is due to chunks needing to load for players
2021-07-06 20:44:24 +02:00
* @see # setInstance ( Instance , Pos )
2020-12-13 23:01:01 +01:00
* /
@Override
2021-07-11 13:45:28 +02:00
public CompletableFuture < Void > setInstance ( @NotNull Instance instance ) {
return setInstance ( instance , this . instance ! = null ? getPosition ( ) : getRespawnPoint ( ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-11-25 12:12:58 +01:00
2020-12-13 23:01:01 +01:00
/ * *
* Used to spawn the player once the client has all the required chunks .
* < p >
* Does add the player to { @code instance } , remove all viewable entities and call { @link PlayerSpawnEvent } .
* < p >
2021-07-06 20:44:24 +02:00
* UNSAFE : only called with { @link # setInstance ( Instance , Pos ) } .
2020-12-13 23:01:01 +01:00
*
2021-12-27 17:58:53 +01:00
* @param spawnPosition the position to teleport the player
* @param firstSpawn true if this is the player first spawn
* @param updateChunks true if chunks should be refreshed , false if the new instance shares the same
* chunks
2020-12-13 23:01:01 +01:00
* /
2021-07-06 20:44:24 +02:00
private void spawnPlayer ( @NotNull Instance instance , @NotNull Pos spawnPosition ,
2021-05-10 00:51:35 +02:00
boolean firstSpawn , boolean dimensionChange , boolean updateChunks ) {
2023-07-05 00:02:13 +02:00
if ( ! firstSpawn & & ! dimensionChange ) {
2021-05-10 00:51:35 +02:00
// Player instance changed, clear current viewable collections
2021-11-01 18:04:00 +01:00
if ( updateChunks )
2024-03-28 03:08:36 +01:00
ChunkUtils . forChunksInRange ( spawnPosition , settings . getEffectiveViewDistance ( ) , chunkRemover ) ;
2021-08-04 16:58:33 +02:00
}
2023-07-04 23:44:30 +02:00
if ( dimensionChange ) sendDimension ( instance . getDimensionType ( ) , instance . getDimensionName ( ) ) ;
2021-11-01 18:04:00 +01:00
2021-09-13 09:05:16 +02:00
super . setInstance ( instance , spawnPosition ) ;
2021-05-10 00:51:35 +02:00
if ( updateChunks ) {
2022-06-04 22:04:57 +02:00
final int chunkX = spawnPosition . chunkX ( ) ;
final int chunkZ = spawnPosition . chunkZ ( ) ;
chunksLoadedByClient = new Vec ( chunkX , chunkZ ) ;
2022-11-26 22:08:08 +01:00
chunkUpdateLimitChecker . addToHistory ( getChunk ( ) ) ;
2022-06-04 22:04:57 +02:00
sendPacket ( new UpdateViewPositionPacket ( chunkX , chunkZ ) ) ;
2023-06-17 16:43:03 +02:00
2024-01-06 08:43:43 +01:00
// Load the nearby chunks and queue them to be sent to them
2024-03-28 03:08:36 +01:00
ChunkUtils . forChunksInRange ( spawnPosition , settings . getEffectiveViewDistance ( ) , chunkAdder ) ;
2021-03-07 23:18:32 +01:00
}
2024-03-28 02:45:55 +01:00
synchronizePositionAfterTeleport ( spawnPosition , 0 ) ; // So the player doesn't get stuck
2023-07-04 23:38:03 +02:00
if ( dimensionChange ) {
2023-07-04 23:57:40 +02:00
sendPacket ( new SpawnPositionPacket ( spawnPosition , 0 ) ) ;
2023-07-04 23:38:03 +02:00
instance . getWorldBorder ( ) . init ( this ) ;
sendPacket ( new TimeUpdatePacket ( instance . getWorldAge ( ) , instance . getTime ( ) ) ) ;
}
2021-09-13 09:05:16 +02:00
2021-03-08 17:12:21 +01:00
if ( dimensionChange | | firstSpawn ) {
2021-03-07 23:18:32 +01:00
this . inventory . update ( ) ;
2023-07-05 00:02:13 +02:00
sendPacket ( new HeldItemChangePacket ( heldSlot ) ) ;
2023-12-19 07:18:01 +01:00
// Tell the client to leave the loading terrain screen
sendPacket ( new ChangeGameStatePacket ( ChangeGameStatePacket . Reason . LEVEL_CHUNKS_LOAD_START , 0 ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-11-14 21:45:30 +01:00
2021-11-01 18:04:00 +01:00
EventDispatcher . call ( new PlayerSpawnEvent ( this , instance , firstSpawn ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-10-31 00:23:52 +01:00
2024-01-06 08:43:43 +01:00
@ApiStatus.Internal
public void onChunkBatchReceived ( float newTargetChunksPerTick ) {
// logger.debug("chunk batch received player={} chunks/tick={} lead={}", username, newTargetChunksPerTick, chunkBatchLead);
chunkBatchLead - = 1 ;
2024-01-06 09:33:36 +01:00
targetChunksPerTick = Float . isNaN ( newTargetChunksPerTick ) ? MIN_CHUNKS_PER_TICK : MathUtils . clamp (
newTargetChunksPerTick * CHUNKS_PER_TICK_MULTIPLIER , MIN_CHUNKS_PER_TICK , MAX_CHUNKS_PER_TICK ) ;
2024-01-06 08:43:43 +01:00
// Beyond the first batch we can preemptively send up to 10 (matching mojang server)
if ( maxChunkBatchLead = = 1 ) maxChunkBatchLead = 10 ;
}
/ * *
* Queues the given chunk to be sent to the player .
2024-01-27 16:04:52 +01:00
*
2024-01-06 08:43:43 +01:00
* @param chunk The chunk to send
* /
public void sendChunk ( @NotNull Chunk chunk ) {
if ( ! chunk . isLoaded ( ) ) return ;
chunkQueueLock . lock ( ) ;
try {
chunkQueue . enqueue ( ChunkUtils . getChunkIndex ( chunk . getChunkX ( ) , chunk . getChunkZ ( ) ) ) ;
} finally {
chunkQueueLock . unlock ( ) ;
}
}
private void sendPendingChunks ( ) {
// If we have nothing to send or have sent the max # of batches without reply, do nothing
if ( chunkQueue . isEmpty ( ) | | chunkBatchLead > = maxChunkBatchLead ) return ;
// Increment the pending chunk count by the target chunks per tick
pendingChunkCount = Math . min ( pendingChunkCount + targetChunksPerTick , MAX_CHUNKS_PER_TICK ) ;
if ( pendingChunkCount < 1 ) return ; // Cant send anything
chunkQueueLock . lock ( ) ;
try {
int batchSize = 0 ;
sendPacket ( new ChunkBatchStartPacket ( ) ) ;
while ( ! chunkQueue . isEmpty ( ) & & pendingChunkCount > = 1f ) {
long chunkIndex = chunkQueue . dequeueLong ( ) ;
int chunkX = ChunkUtils . getChunkCoordX ( chunkIndex ) , chunkZ = ChunkUtils . getChunkCoordZ ( chunkIndex ) ;
var chunk = instance . getChunk ( chunkX , chunkZ ) ;
if ( chunk = = null | | ! chunk . isLoaded ( ) ) continue ;
sendPacket ( chunk . getFullDataPacket ( ) ) ;
EventDispatcher . call ( new PlayerChunkLoadEvent ( this , chunkX , chunkZ ) ) ;
pendingChunkCount - = 1f ;
batchSize + = 1 ;
}
sendPacket ( new ChunkBatchFinishedPacket ( batchSize ) ) ;
chunkBatchLead + = 1 ;
// logger.debug("chunk batch sent player={} chunks={} lead={}", username, batchSize, chunkBatchLead);
} finally {
chunkQueueLock . unlock ( ) ;
}
}
2023-12-01 16:33:38 +01:00
@Override
protected void updatePose ( ) {
Pose oldPose = getPose ( ) ;
Pose newPose ;
// Figure out their expected state
2024-01-29 21:59:44 +01:00
var meta = getEntityMeta ( ) ;
2023-12-01 16:33:38 +01:00
if ( meta . isFlyingWithElytra ( ) ) {
newPose = Pose . FALL_FLYING ;
} else if ( false ) { // When should they be sleeping? We don't have any in-bed state...
newPose = Pose . SLEEPING ;
} else if ( meta . isSwimming ( ) ) {
newPose = Pose . SWIMMING ;
2024-01-29 21:59:44 +01:00
} else if ( meta instanceof LivingEntityMeta livingMeta & & livingMeta . isInRiptideSpinAttack ( ) ) {
2023-12-01 16:33:38 +01:00
newPose = Pose . SPIN_ATTACK ;
} else if ( isSneaking ( ) & & ! isFlying ( ) ) {
newPose = Pose . SNEAKING ;
} else {
newPose = Pose . STANDING ;
}
// Try to put them in their expected state, or the closest if they don't fit.
if ( canFitWithBoundingBox ( newPose ) ) {
// Use expected state
} else if ( canFitWithBoundingBox ( Pose . SNEAKING ) ) {
newPose = Pose . SNEAKING ;
} else if ( canFitWithBoundingBox ( Pose . SWIMMING ) ) {
newPose = Pose . SWIMMING ;
} else {
// If they can't fit anywhere, just use standing
newPose = Pose . STANDING ;
}
if ( newPose ! = oldPose ) setPose ( newPose ) ;
}
/ * *
* Returns true if the player can fit at the current position with the given { @link net . minestom . server . entity . Entity . Pose } , false otherwise .
2024-01-06 08:43:43 +01:00
*
2023-12-01 16:33:38 +01:00
* @param pose The pose to check
* /
private boolean canFitWithBoundingBox ( @NotNull Pose pose ) {
BoundingBox bb = pose = = Pose . STANDING ? boundingBox : BoundingBox . fromPose ( pose ) ;
if ( bb = = null ) return false ;
var position = getPosition ( ) ;
var iter = bb . getBlocks ( getPosition ( ) ) ;
while ( iter . hasNext ( ) ) {
var pos = iter . next ( ) ;
2023-12-08 22:39:47 +01:00
var block = instance . getBlock ( pos , Block . Getter . Condition . TYPE ) ;
// For now just ignore scaffolding. It seems to have a dynamic bounding box, or is just parsed
// incorrectly in MinestomDataGenerator.
if ( block . id ( ) = = Block . SCAFFOLDING . id ( ) ) continue ;
var hit = block . registry ( ) . collisionShape ( )
2023-12-01 20:59:50 +01:00
. intersectBox ( position . sub ( pos . blockX ( ) , pos . blockY ( ) , pos . blockZ ( ) ) , bb ) ;
2023-12-01 16:33:38 +01:00
if ( hit ) return false ;
}
return true ;
}
2020-12-13 23:01:01 +01:00
/ * *
* Sends a plugin message to the player .
*
* @param channel the message channel
* @param data the message data
* /
2021-07-22 13:01:00 +02:00
public void sendPluginMessage ( @NotNull String channel , byte @NotNull [ ] data ) {
2022-05-10 10:07:55 +02:00
sendPacket ( new PluginMessagePacket ( channel , data ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-08-15 13:38:57 +02:00
2020-12-13 23:01:01 +01:00
/ * *
* Sends a plugin message to the player .
2020-12-28 10:40:50 +01:00
* < p >
* Message encoded to UTF - 8 .
2020-12-13 23:01:01 +01:00
*
* @param channel the message channel
* @param message the message
* /
public void sendPluginMessage ( @NotNull String channel , @NotNull String message ) {
2021-07-22 13:01:00 +02:00
sendPluginMessage ( channel , message . getBytes ( StandardCharsets . UTF_8 ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-08-15 13:38:57 +02:00
2024-03-28 03:08:36 +01:00
/ * *
* Deprecated , as the Adventure library has deprecated this method in the Audience class
* @param source the identity of the source of the message
* @param message a message
* @param type the type
* /
2021-03-01 16:25:23 +01:00
@Override
2024-03-28 03:08:36 +01:00
@Deprecated
2021-03-02 18:47:56 +01:00
public void sendMessage ( @NotNull Identity source , @NotNull Component message , @NotNull MessageType type ) {
2021-05-05 19:21:38 +02:00
Messenger . sendMessage ( this , message , ChatPosition . fromMessageType ( type ) , source . uuid ( ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-06-23 22:46:22 +02:00
2021-03-02 16:56:20 +01:00
@Override
2021-03-27 14:59:08 +01:00
public void playSound ( @NotNull Sound sound ) {
2021-07-06 20:44:24 +02:00
this . playSound ( sound , this . position . x ( ) , this . position . y ( ) , this . position . z ( ) ) ;
2021-03-02 16:56:20 +01:00
}
2023-01-09 01:38:30 +01:00
public void playSound ( @NotNull Sound sound , @NotNull Point point ) {
sendPacket ( AdventurePacketConvertor . createSoundPacket ( sound , point . x ( ) , point . y ( ) , point . z ( ) ) ) ;
}
2021-03-02 16:56:20 +01:00
@Override
2021-03-27 14:59:08 +01:00
public void playSound ( @NotNull Sound sound , double x , double y , double z ) {
2022-05-10 10:07:55 +02:00
sendPacket ( AdventurePacketConvertor . createSoundPacket ( sound , x , y , z ) ) ;
2021-03-02 16:56:20 +01:00
}
2021-06-11 16:51:45 +02:00
@Override
public void playSound ( @NotNull Sound sound , Sound . @NotNull Emitter emitter ) {
final ServerPacket packet ;
if ( emitter = = Sound . Emitter . self ( ) ) {
packet = AdventurePacketConvertor . createSoundPacket ( sound , this ) ;
} else {
packet = AdventurePacketConvertor . createSoundPacket ( sound , emitter ) ;
}
2022-05-10 10:07:55 +02:00
sendPacket ( packet ) ;
2021-06-11 16:51:45 +02:00
}
2021-03-02 16:56:20 +01:00
@Override
2021-03-02 18:47:56 +01:00
public void stopSound ( @NotNull SoundStop stop ) {
2022-05-10 10:07:55 +02:00
sendPacket ( AdventurePacketConvertor . createSoundStopPacket ( stop ) ) ;
2021-03-02 16:56:20 +01:00
}
2020-04-28 16:08:21 +02:00
/ * *
2020-12-13 23:01:01 +01:00
* Plays a given effect at the given position for this player .
2020-04-28 17:58:34 +02:00
*
2020-12-13 23:01:01 +01:00
* @param effect the effect to play
* @param x x position of the effect
* @param y y position of the effect
* @param z z position of the effect
* @param data data for the effect
* @param disableRelativeVolume disable volume scaling based on distance
* /
public void playEffect ( @NotNull Effects effect , int x , int y , int z , int data , boolean disableRelativeVolume ) {
2022-05-10 10:07:55 +02:00
sendPacket ( new EffectPacket ( effect . getId ( ) , new Vec ( x , y , z ) , data , disableRelativeVolume ) ) ;
2020-12-13 23:01:01 +01:00
}
2021-03-01 16:25:23 +01:00
@Override
2021-03-02 18:47:56 +01:00
public void sendPlayerListHeaderAndFooter ( @NotNull Component header , @NotNull Component footer ) {
2022-05-10 10:07:55 +02:00
sendPacket ( new PlayerListHeaderAndFooterPacket ( header , footer ) ) ;
2021-03-01 16:25:23 +01:00
}
@Override
2021-10-31 19:29:41 +01:00
public < T > void sendTitlePart ( @NotNull TitlePart < T > part , @NotNull T value ) {
2022-05-10 10:07:55 +02:00
sendPacket ( AdventurePacketConvertor . createTitlePartPacket ( part , value ) ) ;
2021-03-01 16:25:23 +01:00
}
@Override
2021-03-02 18:47:56 +01:00
public void sendActionBar ( @NotNull Component message ) {
2022-05-10 10:07:55 +02:00
sendPacket ( new ActionBarPacket ( message ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-05-04 18:32:14 +02:00
2021-03-01 18:27:10 +01:00
@Override
2020-12-13 23:01:01 +01:00
public void resetTitle ( ) {
2022-05-10 10:07:55 +02:00
sendPacket ( new ClearTitlesPacket ( true ) ) ;
2021-03-01 16:25:23 +01:00
}
@Override
public void clearTitle ( ) {
2022-05-10 10:07:55 +02:00
sendPacket ( new ClearTitlesPacket ( false ) ) ;
2020-12-13 23:01:01 +01:00
}
2020-10-17 14:46:14 +02:00
2021-03-01 18:25:55 +01:00
@Override
2021-03-02 18:47:56 +01:00
public void showBossBar ( @NotNull BossBar bar ) {
2021-03-01 18:25:55 +01:00
MinecraftServer . getBossBarManager ( ) . addBossBar ( this , bar ) ;
}
@Override
2021-03-02 18:47:56 +01:00
public void hideBossBar ( @NotNull BossBar bar ) {
2021-03-01 18:25:55 +01:00
MinecraftServer . getBossBarManager ( ) . removeBossBar ( this , bar ) ;
}
2021-03-01 16:25:23 +01:00
@Override
2021-03-02 18:47:56 +01:00
public void openBook ( @NotNull Book book ) {
2024-04-15 02:34:26 +02:00
// Close the open inventory if there is one because the book will replace it.
if ( getOpenInventory ( ) ! = null ) {
closeInventory ( ) ;
}
2021-07-22 09:54:34 +02:00
final ItemStack writtenBook = ItemStack . builder ( Material . WRITTEN_BOOK )
2022-04-13 17:57:15 +02:00
. meta ( WrittenBookMeta . class , builder - > builder . resolved ( false )
. generation ( WrittenBookMeta . WrittenBookGeneration . ORIGINAL )
. author ( book . author ( ) )
. title ( book . title ( ) )
. pages ( book . pages ( ) ) )
2021-04-10 18:55:26 +02:00
. build ( ) ;
2021-03-02 16:56:20 +01:00
// Set book in offhand
2022-05-10 10:07:55 +02:00
sendPacket ( new SetSlotPacket ( ( byte ) 0 , 0 , ( short ) PlayerInventoryUtils . OFFHAND_SLOT , writtenBook ) ) ;
2021-03-02 16:56:20 +01:00
// Open the book
2022-05-10 10:07:55 +02:00
sendPacket ( new OpenBookPacket ( Hand . OFF ) ) ;
2021-03-02 16:56:20 +01:00
// Restore the item in offhand
2022-05-10 10:07:55 +02:00
sendPacket ( new SetSlotPacket ( ( byte ) 0 , 0 , ( short ) PlayerInventoryUtils . OFFHAND_SLOT , getItemInOffHand ( ) ) ) ;
2021-03-01 16:25:23 +01:00
}
2020-12-13 23:01:01 +01:00
@Override
public boolean isImmune ( @NotNull DamageType type ) {
if ( ! getGameMode ( ) . canTakeDamage ( ) ) {
2023-11-02 03:35:18 +01:00
return type ! = DamageType . OUT_OF_WORLD ;
2020-04-27 20:33:08 +02:00
}
2020-12-13 23:01:01 +01:00
return super . isImmune ( type ) ;
}
@Override
public void setHealth ( float health ) {
super . setHealth ( health ) ;
2022-05-10 10:07:55 +02:00
sendPacket ( new UpdateHealthPacket ( health , food , foodSaturation ) ) ;
2020-12-13 23:01:01 +01:00
}
2024-01-29 20:53:15 +01:00
/ * *
* Gets the entity meta for the player .
*
* < p > Note that this method will throw an exception if the player ' s entity type has
* been changed with { @link # switchEntityType ( EntityType ) } . It is wise to check
* { @link # getEntityType ( ) } first . < / p >
* /
public @NotNull PlayerMeta getPlayerMeta ( ) {
2021-06-14 21:49:16 +02:00
return ( PlayerMeta ) super . getEntityMeta ( ) ;
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets the player additional hearts .
*
2024-01-29 20:53:15 +01:00
* < p > Note that this function is uncallable if the player has their entity type switched
* with { @link # switchEntityType ( EntityType ) } . < / p >
*
2020-12-13 23:01:01 +01:00
* @return the player additional hearts
* /
public float getAdditionalHearts ( ) {
2024-01-29 20:53:15 +01:00
return getPlayerMeta ( ) . getAdditionalHearts ( ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
2021-01-30 04:44:44 +01:00
* Changes the amount of additional hearts shown .
2020-12-13 23:01:01 +01:00
*
2024-01-29 20:53:15 +01:00
* < p > Note that this function is uncallable if the player has their entity type switched
* with { @link # switchEntityType ( EntityType ) } . < / p >
*
2020-12-13 23:01:01 +01:00
* @param additionalHearts the count of additional hearts
* /
public void setAdditionalHearts ( float additionalHearts ) {
2024-01-29 20:53:15 +01:00
getPlayerMeta ( ) . setAdditionalHearts ( additionalHearts ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Gets the player food .
*
* @return the player food
* /
public int getFood ( ) {
return food ;
}
/ * *
* Sets and refresh client food bar .
*
* @param food the new food value
* @throws IllegalArgumentException if { @code food } is not between 0 and 20
* /
public void setFood ( int food ) {
2021-03-21 19:47:22 +01:00
Check . argCondition ( ! MathUtils . isBetween ( food , 0 , 20 ) ,
" Food has to be between 0 and 20 " ) ;
2020-12-13 23:01:01 +01:00
this . food = food ;
2022-05-10 10:07:55 +02:00
sendPacket ( new UpdateHealthPacket ( getHealth ( ) , food , foodSaturation ) ) ;
2020-12-13 23:01:01 +01:00
}
public float getFoodSaturation ( ) {
return foodSaturation ;
}
/ * *
* Sets and refresh client food saturation .
*
* @param foodSaturation the food saturation
2021-03-21 19:47:22 +01:00
* @throws IllegalArgumentException if { @code foodSaturation } is not between 0 and 20
2020-12-13 23:01:01 +01:00
* /
public void setFoodSaturation ( float foodSaturation ) {
2021-03-21 19:47:22 +01:00
Check . argCondition ( ! MathUtils . isBetween ( foodSaturation , 0 , 20 ) ,
" Food saturation has to be between 0 and 20 " ) ;
2020-12-13 23:01:01 +01:00
this . foodSaturation = foodSaturation ;
2022-05-10 10:07:55 +02:00
sendPacket ( new UpdateHealthPacket ( getHealth ( ) , food , foodSaturation ) ) ;
2019-08-25 20:03:43 +02:00
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets if the player is eating .
*
* @return true if the player is eating , false otherwise
* /
public boolean isEating ( ) {
2021-04-13 22:59:40 +02:00
return eatingHand ! = null ;
2020-12-13 23:01:01 +01:00
}
2021-05-23 16:46:29 +02:00
/ * *
* Gets the hand which the player is eating from .
*
* @return the eating hand , null if none
* /
2021-05-23 20:19:46 +02:00
public @Nullable Hand getEatingHand ( ) {
2021-05-23 16:46:29 +02:00
return eatingHand ;
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets the player default eating time .
*
* @return the player default eating time
* /
public long getDefaultEatingTime ( ) {
return defaultEatingTime ;
}
/ * *
* Used to change the default eating time animation .
*
* @param defaultEatingTime the default eating time in milliseconds
* /
public void setDefaultEatingTime ( long defaultEatingTime ) {
this . defaultEatingTime = defaultEatingTime ;
}
2022-05-03 22:29:38 +02:00
@Override
public double getEyeHeight ( ) {
return switch ( getPose ( ) ) {
case SLEEPING - > 0 . 2 ;
case FALL_FLYING , SWIMMING , SPIN_ATTACK - > 0 . 4 ;
case SNEAKING - > 1 . 27 ;
default - > 1 . 62 ;
} ;
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets the player display name in the tab - list .
*
* @return the player display name , null means that { @link # getUsername ( ) } is displayed
* /
2021-07-27 06:55:08 +02:00
public @Nullable Component getDisplayName ( ) {
2020-12-13 23:01:01 +01:00
return displayName ;
}
2021-03-03 20:27:33 +01:00
/ * *
* Changes the player display name in the tab - list .
* < p >
* Sets to null to show the player username .
*
* @param displayName the display name , null to display the username
* /
public void setDisplayName ( @Nullable Component displayName ) {
2020-12-13 23:01:01 +01:00
this . displayName = displayName ;
2023-11-28 17:48:00 +01:00
PacketUtils . broadcastPlayPacket ( new PlayerInfoUpdatePacket ( PlayerInfoUpdatePacket . Action . UPDATE_DISPLAY_NAME , infoEntry ( ) ) ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Gets the player skin .
*
* @return the player skin object ,
* null means that the player has his { @link # getUuid ( ) } default skin
* /
2021-07-27 06:55:08 +02:00
public @Nullable PlayerSkin getSkin ( ) {
2020-12-13 23:01:01 +01:00
return skin ;
}
/ * *
* Changes the player skin .
* < p >
* This does remove the player for all viewers to spawn it again with the correct new skin .
*
* @param skin the player skin , null to reset it to his { @link # getUuid ( ) } default skin
* @see PlayerSkinInitEvent if you want to apply the skin at connection
* /
public synchronized void setSkin ( @Nullable PlayerSkin skin ) {
this . skin = skin ;
if ( instance = = null )
return ;
2021-07-14 16:26:32 +02:00
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket ( getEntityId ( ) ) ;
2020-12-13 23:01:01 +01:00
2023-05-21 16:51:13 +02:00
final PlayerInfoRemovePacket removePlayerPacket = getRemovePlayerToList ( ) ;
final PlayerInfoUpdatePacket addPlayerPacket = getAddPlayerToList ( ) ;
2020-12-13 23:01:01 +01:00
2023-07-04 23:44:30 +02:00
RespawnPacket respawnPacket = new RespawnPacket ( getDimensionType ( ) . toString ( ) , instance . getDimensionName ( ) ,
2023-11-02 03:51:37 +01:00
0 , gameMode , gameMode , false , levelFlat , deathLocation , portalCooldown , RespawnPacket . COPY_ALL ) ;
2020-12-13 23:01:01 +01:00
2022-05-10 10:07:55 +02:00
sendPacket ( removePlayerPacket ) ;
sendPacket ( destroyEntitiesPacket ) ;
sendPacket ( addPlayerPacket ) ;
sendPacket ( respawnPacket ) ;
2022-01-29 14:56:45 +01:00
refreshClientStateAfterRespawn ( ) ;
2020-12-13 23:01:01 +01:00
{
// Remove player
2023-11-28 17:48:00 +01:00
PacketUtils . broadcastPlayPacket ( removePlayerPacket ) ;
2021-07-14 16:26:32 +02:00
sendPacketToViewers ( destroyEntitiesPacket ) ;
2020-12-13 23:01:01 +01:00
// Show player again
2023-11-28 17:48:00 +01:00
PacketUtils . broadcastPlayPacket ( addPlayerPacket ) ;
2020-12-13 23:01:01 +01:00
getViewers ( ) . forEach ( player - > showPlayer ( player . getPlayerConnection ( ) ) ) ;
}
getInventory ( ) . update ( ) ;
teleport ( getPosition ( ) ) ;
}
2023-03-30 03:03:36 +02:00
public void setDeathLocation ( @NotNull DimensionType type , @NotNull Pos position ) {
2024-04-10 14:31:47 +02:00
this . deathLocation = new WorldPos ( type . getName ( ) . asString ( ) , position ) ;
2023-03-30 03:03:36 +02:00
}
2024-04-10 14:31:47 +02:00
public @Nullable WorldPos getDeathLocation ( ) {
2023-03-30 03:03:36 +02:00
return this . deathLocation ;
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets if the player has the respawn screen enabled or disabled .
*
* @return true if the player has the respawn screen , false if he didn ' t
* /
public boolean isEnableRespawnScreen ( ) {
return enableRespawnScreen ;
}
/ * *
* Enables or disable the respawn screen .
*
* @param enableRespawnScreen true to enable the respawn screen , false to disable it
* /
public void setEnableRespawnScreen ( boolean enableRespawnScreen ) {
this . enableRespawnScreen = enableRespawnScreen ;
2021-12-17 20:38:04 +01:00
sendPacket ( new ChangeGameStatePacket ( ChangeGameStatePacket . Reason . ENABLE_RESPAWN_SCREEN , enableRespawnScreen ? 0 : 1 ) ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
2021-04-12 16:08:27 +02:00
* Gets the player ' s name as a component . This will either return the display name
* ( if set ) or a component holding the username .
2020-12-13 23:01:01 +01:00
*
2021-04-12 16:08:27 +02:00
* @return the name
2020-12-13 23:01:01 +01:00
* /
2021-04-12 11:49:04 +02:00
@Override
2021-04-12 16:08:27 +02:00
public @NotNull Component getName ( ) {
2021-12-17 20:53:00 +01:00
return Objects . requireNonNullElse ( displayName , usernameComponent ) ;
2021-04-12 11:49:04 +02:00
}
/ * *
* Gets the player ' s username .
*
* @return the player ' s username
* /
public @NotNull String getUsername ( ) {
2020-12-13 23:01:01 +01:00
return username ;
}
/ * *
2020-12-27 22:16:19 +01:00
* Changes the internal player name , used for the { @link AsyncPlayerPreLoginEvent }
2020-12-13 23:01:01 +01:00
* mostly unsafe outside of it .
*
* @param username the new player name
* /
2021-01-06 19:02:35 +01:00
public void setUsernameField ( @NotNull String username ) {
2020-12-13 23:01:01 +01:00
this . username = username ;
2021-04-22 18:21:34 +02:00
this . usernameComponent = Component . text ( username ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Calls an { @link ItemDropEvent } with a specified item .
* < p >
* Returns false if { @code item } is air .
*
* @param item the item to drop
* @return true if player can drop the item ( event not cancelled ) , false otherwise
* /
public boolean dropItem ( @NotNull ItemStack item ) {
2021-12-17 20:53:00 +01:00
if ( item . isAir ( ) ) return false ;
2020-12-13 23:01:01 +01:00
ItemDropEvent itemDropEvent = new ItemDropEvent ( this , item ) ;
2021-06-04 03:48:51 +02:00
EventDispatcher . call ( itemDropEvent ) ;
2020-12-13 23:01:01 +01:00
return ! itemDropEvent . isCancelled ( ) ;
}
2024-01-06 09:20:32 +01:00
@Override
public void sendResourcePacks ( @NotNull ResourcePackRequest request ) {
if ( request . replace ( ) ) clearResourcePacks ( ) ;
for ( var pack : request . packs ( ) ) {
sendPacket ( new ResourcePackPushPacket ( pack , request . required ( ) , request . prompt ( ) ) ) ;
resourcePackCallbacks . put ( pack . id ( ) , request . callback ( ) ) ;
if ( resourcePackFuture = = null ) {
resourcePackFuture = new CompletableFuture < > ( ) ;
}
}
}
@Override
public void removeResourcePacks ( @NotNull UUID id , @NotNull UUID @NotNull . . . others ) {
sendPacket ( new ResourcePackPopPacket ( id ) ) ;
for ( var other : others ) {
sendPacket ( new ResourcePackPopPacket ( other ) ) ;
}
}
@Override
public void clearResourcePacks ( ) {
sendPacket ( new ResourcePackPopPacket ( ( UUID ) null ) ) ;
}
2020-12-13 23:01:01 +01:00
/ * *
2024-01-06 09:20:32 +01:00
* If there are resource packs in - flight , a future is returned which will be completed when
* all resource packs have been responded to by the client . Otherwise null is returned .
2020-12-13 23:01:01 +01:00
* /
2024-01-06 09:20:32 +01:00
@ApiStatus.Internal
public @Nullable CompletableFuture < Void > getResourcePackFuture ( ) {
return resourcePackFuture ;
}
@ApiStatus.Internal
public void onResourcePackStatus ( @NotNull UUID id , @NotNull ResourcePackStatus status ) {
var callback = resourcePackCallbacks . get ( id ) ;
if ( callback = = null ) return ;
callback . packEventReceived ( id , status , this ) ;
if ( ! status . intermediate ( ) ) {
// Remove the callback and finish the future if relevant
resourcePackCallbacks . remove ( id ) ;
if ( resourcePackCallbacks . isEmpty ( ) & & resourcePackFuture ! = null ) {
resourcePackFuture . complete ( null ) ;
resourcePackFuture = null ;
}
}
2020-12-13 23:01:01 +01:00
}
/ * *
* Rotates the player to face { @code targetPosition } .
*
* @param facePoint the point from where the player should aim
* @param targetPosition the target position to face
* /
2021-07-25 06:30:49 +02:00
public void facePosition ( @NotNull FacePoint facePoint , @NotNull Point targetPosition ) {
2020-12-13 23:01:01 +01:00
facePosition ( facePoint , targetPosition , null , null ) ;
}
/ * *
* Rotates the player to face { @code entity } .
*
* @param facePoint the point from where the player should aim
* @param entity the entity to face
* @param targetPoint the point to aim at { @code entity } position
* /
public void facePosition ( @NotNull FacePoint facePoint , Entity entity , FacePoint targetPoint ) {
facePosition ( facePoint , entity . getPosition ( ) , entity , targetPoint ) ;
}
2021-07-06 20:44:24 +02:00
private void facePosition ( @NotNull FacePoint facePoint , @NotNull Point targetPosition ,
2020-12-13 23:01:01 +01:00
@Nullable Entity entity , @Nullable FacePoint targetPoint ) {
2021-11-30 17:49:41 +01:00
final int entityId = entity ! = null ? entity . getEntityId ( ) : 0 ;
2022-05-10 10:07:55 +02:00
sendPacket ( new FacePlayerPacket (
2021-11-30 17:49:41 +01:00
facePoint = = FacePoint . EYE ?
FacePlayerPacket . FacePosition . EYES : FacePlayerPacket . FacePosition . FEET , targetPosition ,
entityId ,
targetPoint = = FacePoint . EYE ?
FacePlayerPacket . FacePosition . EYES : FacePlayerPacket . FacePosition . FEET ) ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Sets the camera at { @code entity } eyes .
*
* @param entity the entity to spectate
* /
public void spectate ( @NotNull Entity entity ) {
2022-05-10 10:07:55 +02:00
sendPacket ( new CameraPacket ( entity ) ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Resets the camera at the player .
* /
public void stopSpectating ( ) {
spectate ( this ) ;
}
/ * *
* Used to retrieve the default spawn point .
* < p >
2021-07-06 20:44:24 +02:00
* Can be altered by the { @link PlayerRespawnEvent # setRespawnPosition ( Pos ) } .
2020-12-13 23:01:01 +01:00
*
* @return a copy of the default respawn point
* /
2021-07-06 20:44:24 +02:00
public @NotNull Pos getRespawnPoint ( ) {
return respawnPoint ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Changes the default spawn point .
*
* @param respawnPoint the player respawn point
* /
2021-07-06 20:44:24 +02:00
public void setRespawnPoint ( @NotNull Pos respawnPoint ) {
2020-12-13 23:01:01 +01:00
this . respawnPoint = respawnPoint ;
}
/ * *
* Called after the player teleportation to refresh his position
* and send data to his new viewers .
* /
protected void refreshAfterTeleport ( ) {
2021-07-27 11:56:20 +02:00
sendPacketsToViewers ( getEntityType ( ) . registry ( ) . spawnType ( ) . getSpawnPacket ( this ) ) ;
2020-12-13 23:01:01 +01:00
// Update for viewers
sendPacketToViewersAndSelf ( getVelocityPacket ( ) ) ;
sendPacketToViewersAndSelf ( getMetadataPacket ( ) ) ;
2021-03-12 01:38:52 +01:00
sendPacketToViewersAndSelf ( getPropertiesPacket ( ) ) ;
sendPacketToViewersAndSelf ( getEquipmentsPacket ( ) ) ;
2020-12-13 23:01:01 +01:00
2021-05-05 05:43:41 +02:00
getInventory ( ) . update ( ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Sets the player food and health values to their maximum .
* /
protected void refreshHealth ( ) {
this . food = 20 ;
this . foodSaturation = 5 ;
// refresh health and send health packet
heal ( ) ;
}
/ * *
* Gets the percentage displayed in the experience bar .
*
* @return the exp percentage 0 - 1
* /
public float getExp ( ) {
return exp ;
}
/ * *
* Used to change the percentage experience bar .
* This cannot change the displayed level , see { @link # setLevel ( int ) } .
*
* @param exp a percentage between 0 and 1
* @throws IllegalArgumentException if { @code exp } is not between 0 and 1
* /
public void setExp ( float exp ) {
Check . argCondition ( ! MathUtils . isBetween ( exp , 0 , 1 ) , " Exp should be between 0 and 1 " ) ;
this . exp = exp ;
2022-05-10 10:07:55 +02:00
sendPacket ( new SetExperiencePacket ( exp , level , 0 ) ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Gets the level of the player displayed in the experience bar .
*
* @return the player level
* /
public int getLevel ( ) {
return level ;
}
/ * *
* Used to change the level of the player
* This cannot change the displayed percentage bar see { @link # setExp ( float ) }
*
* @param level the new level of the player
* /
public void setLevel ( int level ) {
this . level = level ;
2022-05-10 10:07:55 +02:00
sendPacket ( new SetExperiencePacket ( exp , level , 0 ) ) ;
2020-12-13 23:01:01 +01:00
}
2023-06-15 04:11:18 +02:00
public int getPortalCooldown ( ) {
return portalCooldown ;
}
public void setPortalCooldown ( int portalCooldown ) {
this . portalCooldown = portalCooldown ;
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets the player connection .
* < p >
* Used to send packets and get stuff related to the connection .
*
* @return the player connection
* /
2021-09-10 05:41:54 +02:00
public @NotNull PlayerConnection getPlayerConnection ( ) {
2020-12-13 23:01:01 +01:00
return playerConnection ;
}
2021-09-10 05:41:54 +02:00
/ * *
2021-11-17 06:31:24 +01:00
* Shortcut for { @link PlayerConnection # sendPacket ( SendablePacket ) } .
2021-09-10 05:41:54 +02:00
*
* @param packet the packet to send
* /
@ApiStatus.Experimental
2021-11-17 06:31:24 +01:00
public void sendPacket ( @NotNull SendablePacket packet ) {
2021-09-10 05:41:54 +02:00
this . playerConnection . sendPacket ( packet ) ;
}
2021-10-13 06:33:44 +02:00
@ApiStatus.Experimental
2021-11-17 06:31:24 +01:00
public void sendPackets ( @NotNull SendablePacket . . . packets ) {
this . playerConnection . sendPackets ( packets ) ;
}
@ApiStatus.Experimental
public void sendPackets ( @NotNull Collection < SendablePacket > packets ) {
this . playerConnection . sendPackets ( packets ) ;
2021-10-13 06:33:44 +02:00
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets if the player is online or not .
*
* @return true if the player is online , false otherwise
* /
public boolean isOnline ( ) {
return playerConnection . isOnline ( ) ;
}
/ * *
* Gets the player settings .
*
* @return the player settings
* /
2021-09-10 05:50:08 +02:00
public @NotNull PlayerSettings getSettings ( ) {
2020-12-13 23:01:01 +01:00
return settings ;
}
/ * *
* Gets the player dimension .
*
* @return the player current dimension
* /
public DimensionType getDimensionType ( ) {
return dimensionType ;
}
2021-09-10 05:50:08 +02:00
public @NotNull PlayerInventory getInventory ( ) {
2020-12-13 23:01:01 +01:00
return inventory ;
}
/ * *
* Used to get the player latency ,
* computed by seeing how long it takes the client to answer the { @link KeepAlivePacket } packet .
*
* @return the player latency
* /
public int getLatency ( ) {
return latency ;
}
/ * *
* Gets the player { @link GameMode } .
*
* @return the player current gamemode
* /
public GameMode getGameMode ( ) {
return gameMode ;
}
/ * *
2022-01-29 14:56:45 +01:00
* Changes the player { @link GameMode }
2020-12-13 23:01:01 +01:00
*
* @param gameMode the new player GameMode
2023-08-05 18:53:57 +02:00
* @return true if the gamemode was changed successfully , false otherwise ( cancelled by event )
2020-12-13 23:01:01 +01:00
* /
2023-08-05 18:53:57 +02:00
public boolean setGameMode ( @NotNull GameMode gameMode ) {
PlayerGameModeChangeEvent playerGameModeChangeEvent = new PlayerGameModeChangeEvent ( this , gameMode ) ;
EventDispatcher . call ( playerGameModeChangeEvent ) ;
if ( playerGameModeChangeEvent . isCancelled ( ) ) {
// Abort
return false ;
}
gameMode = playerGameModeChangeEvent . getNewGameMode ( ) ;
2020-12-13 23:01:01 +01:00
this . gameMode = gameMode ;
2020-12-18 00:57:23 +01:00
// Condition to prevent sending the packets before spawning the player
if ( isActive ( ) ) {
2022-03-04 07:07:53 +01:00
sendPacket ( new ChangeGameStatePacket ( ChangeGameStatePacket . Reason . CHANGE_GAMEMODE , gameMode . id ( ) ) ) ;
2023-11-28 17:48:00 +01:00
PacketUtils . broadcastPlayPacket ( new PlayerInfoUpdatePacket ( PlayerInfoUpdatePacket . Action . UPDATE_GAME_MODE , infoEntry ( ) ) ) ;
2020-12-18 00:57:23 +01:00
}
2022-01-29 14:56:45 +01:00
// The client updates their abilities based on the GameMode as follows
switch ( gameMode ) {
case CREATIVE - > {
this . allowFlying = true ;
this . instantBreak = true ;
this . invulnerable = true ;
}
case SPECTATOR - > {
this . allowFlying = true ;
this . instantBreak = false ;
this . invulnerable = true ;
2024-02-28 18:54:12 +01:00
if ( isActive ( ) ) {
refreshFlying ( true ) ;
} else {
this . flying = true ;
}
2022-01-29 14:56:45 +01:00
}
default - > {
this . allowFlying = false ;
this . instantBreak = false ;
this . invulnerable = false ;
2024-02-28 18:54:12 +01:00
if ( isActive ( ) ) {
refreshFlying ( false ) ;
} else {
this . flying = false ;
}
2022-01-29 14:56:45 +01:00
}
}
2022-02-22 21:02:26 +01:00
// Make sure that the player is in the PLAY state and synchronize their flight speed.
if ( isActive ( ) ) {
refreshAbilities ( ) ;
}
2023-08-05 18:53:57 +02:00
return true ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Gets if this player is in creative . Used for code readability .
*
* @return true if the player is in creative mode
* /
public boolean isCreative ( ) {
return gameMode = = GameMode . CREATIVE ;
}
/ * *
* Changes the dimension of the player .
* Mostly unsafe since it requires sending chunks after .
*
* @param dimensionType the new player dimension
* /
2023-07-04 23:44:30 +02:00
protected void sendDimension ( @NotNull DimensionType dimensionType , @NotNull String dimensionName ) {
2023-07-05 03:21:59 +02:00
Check . argCondition ( instance . getDimensionName ( ) . equals ( dimensionName ) ,
2021-03-21 19:47:22 +01:00
" The dimension needs to be different than the current one! " ) ;
2020-12-13 23:01:01 +01:00
this . dimensionType = dimensionType ;
2023-07-04 23:44:30 +02:00
sendPacket ( new RespawnPacket ( dimensionType . toString ( ) , dimensionName ,
2023-11-18 16:11:28 +01:00
0 , gameMode , gameMode , false , levelFlat ,
2023-11-02 03:51:37 +01:00
deathLocation , portalCooldown , RespawnPacket . COPY_ALL ) ) ;
2022-01-29 14:56:45 +01:00
refreshClientStateAfterRespawn ( ) ;
2020-12-13 23:01:01 +01:00
}
2021-03-01 16:25:23 +01:00
/ * *
* Kicks the player with a reason .
*
* @param component the reason
* /
public void kick ( @NotNull Component component ) {
2021-01-06 19:02:35 +01:00
// Packet type depends on the current player connection state
final ServerPacket disconnectPacket ;
2024-01-16 15:47:38 +01:00
if ( playerConnection . getConnectionState ( ) = = ConnectionState . LOGIN ) {
2021-03-12 16:33:19 +01:00
disconnectPacket = new LoginDisconnectPacket ( component ) ;
2021-01-06 19:02:35 +01:00
} else {
2021-03-12 16:33:19 +01:00
disconnectPacket = new DisconnectPacket ( component ) ;
2021-01-06 19:02:35 +01:00
}
2022-05-10 10:07:55 +02:00
sendPacket ( disconnectPacket ) ;
2022-03-20 02:29:00 +01:00
playerConnection . disconnect ( ) ;
2020-12-13 23:01:01 +01:00
}
2021-07-27 06:55:08 +02:00
/ * *
* Kicks the player with a reason .
*
* @param message the kick reason
* /
public void kick ( @NotNull String message ) {
this . kick ( Component . text ( message ) ) ;
}
2020-12-13 23:01:01 +01:00
/ * *
* 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 0 and 8
* /
public void setHeldItemSlot ( byte slot ) {
Check . argCondition ( ! MathUtils . isBetween ( slot , 0 , 8 ) , " Slot has to be between 0 and 8 " ) ;
refreshHeldSlot ( slot ) ;
2022-05-10 10:07:55 +02:00
sendPacket ( new HeldItemChangePacket ( slot ) ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Gets the player held slot ( 0 - 8 ) .
*
* @return the current held slot for the player
* /
public byte getHeldSlot ( ) {
return heldSlot ;
}
/ * *
* Changes the tag below the name .
*
* @param belowNameTag The new below name tag
* /
public void setBelowNameTag ( BelowNameTag belowNameTag ) {
if ( this . belowNameTag = = belowNameTag ) return ;
if ( this . belowNameTag ! = null ) {
this . belowNameTag . removeViewer ( this ) ;
}
this . belowNameTag = belowNameTag ;
}
/ * *
* Gets the player open inventory .
*
* @return the currently open inventory , null if there is not ( player inventory is not detected )
* /
2021-09-10 05:50:08 +02:00
public @Nullable Inventory getOpenInventory ( ) {
2020-12-13 23:01:01 +01:00
return openInventory ;
}
/ * *
* Opens the specified Inventory , close the previous inventory if existing .
*
* @param inventory the inventory to open
* @return true if the inventory has been opened / sent to the player , false otherwise ( cancelled by event )
* /
public boolean openInventory ( @NotNull Inventory inventory ) {
2020-12-16 03:21:59 +01:00
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent ( inventory , this ) ;
2020-12-13 23:01:01 +01:00
2021-06-04 03:48:51 +02:00
EventDispatcher . callCancellable ( inventoryOpenEvent , ( ) - > {
2021-04-24 19:14:19 +02:00
Inventory openInventory = getOpenInventory ( ) ;
if ( openInventory ! = null ) {
openInventory . removeViewer ( this ) ;
2020-12-13 23:01:01 +01:00
}
Inventory newInventory = inventoryOpenEvent . getInventory ( ) ;
if ( newInventory = = null ) {
// just close the inventory
return ;
}
2022-05-10 10:07:55 +02:00
sendPacket ( new OpenWindowPacket ( newInventory . getWindowId ( ) ,
2021-11-30 17:49:41 +01:00
newInventory . getInventoryType ( ) . getWindowType ( ) , newInventory . getTitle ( ) ) ) ;
2020-12-13 23:01:01 +01:00
newInventory . addViewer ( this ) ;
this . openInventory = newInventory ;
} ) ;
return ! inventoryOpenEvent . isCancelled ( ) ;
}
/ * *
* Closes the current inventory if there is any .
* It closes the player inventory ( when opened ) if { @link # getOpenInventory ( ) } returns null .
* /
public void closeInventory ( ) {
2024-01-27 16:04:52 +01:00
closeInventory ( false ) ;
}
@ApiStatus.Internal
public void closeInventory ( boolean fromClient ) {
2020-12-13 23:01:01 +01:00
Inventory openInventory = getOpenInventory ( ) ;
// Drop cursor item when closing inventory
ItemStack cursorItem ;
if ( openInventory = = null ) {
cursorItem = getInventory ( ) . getCursorItem ( ) ;
2021-04-02 18:13:02 +02:00
getInventory ( ) . setCursorItem ( ItemStack . AIR ) ;
2020-12-13 23:01:01 +01:00
} else {
cursorItem = openInventory . getCursorItem ( this ) ;
2021-04-02 18:13:02 +02:00
openInventory . setCursorItem ( this , ItemStack . AIR ) ;
2020-12-13 23:01:01 +01:00
}
if ( ! cursorItem . isAir ( ) ) {
// Add item to inventory if he hasn't been able to drop it
if ( ! dropItem ( cursorItem ) ) {
getInventory ( ) . addItemStack ( cursorItem ) ;
}
}
2022-07-08 17:37:56 +02:00
if ( openInventory = = getOpenInventory ( ) ) {
CloseWindowPacket closeWindowPacket ;
if ( openInventory = = null ) {
closeWindowPacket = new CloseWindowPacket ( ( byte ) 0 ) ;
} else {
closeWindowPacket = new CloseWindowPacket ( openInventory . getWindowId ( ) ) ;
openInventory . removeViewer ( this ) ; // Clear cache
this . openInventory = null ;
}
2024-01-27 16:04:52 +01:00
if ( ! fromClient ) sendPacket ( closeWindowPacket ) ;
2022-07-08 17:37:56 +02:00
inventory . update ( ) ;
this . didCloseInventory = true ;
2020-12-13 23:01:01 +01:00
}
}
/ * *
* Used internally to prevent an inventory click to be processed
* when the inventory listeners closed the inventory .
* < p >
* Should only be used within an inventory listener ( event or condition ) .
*
* @return true if the inventory has been closed , false otherwise
* /
public boolean didCloseInventory ( ) {
return didCloseInventory ;
}
/ * *
* Used internally to reset the didCloseInventory field .
* < p >
* Shouldn ' t be used externally without proper understanding of its consequence .
*
* @param didCloseInventory the new didCloseInventory field
* /
2024-03-28 03:08:36 +01:00
@ApiStatus.Internal
2020-12-13 23:01:01 +01:00
public void UNSAFE_changeDidCloseInventory ( boolean didCloseInventory ) {
this . didCloseInventory = didCloseInventory ;
}
2021-08-28 11:28:14 +02:00
public int getNextTeleportId ( ) {
return teleportId . incrementAndGet ( ) ;
}
2021-03-08 16:49:16 +01:00
public int getLastSentTeleportId ( ) {
return teleportId . get ( ) ;
}
public int getLastReceivedTeleportId ( ) {
return receivedTeleportId ;
}
public void refreshReceivedTeleportId ( int receivedTeleportId ) {
this . receivedTeleportId = receivedTeleportId ;
}
2020-12-13 23:01:01 +01:00
/ * *
2024-03-31 05:54:27 +02:00
* Used to synchronize player position with viewers on spawn or after { @link Entity # teleport ( Pos , long [ ] , int ) }
2024-03-28 02:45:55 +01:00
* in cases where a { @link PlayerPositionAndLookPacket } is required
*
* @param position the position used by { @link PlayerPositionAndLookPacket }
* this may not be the same as the { @link Entity # position }
* @param relativeFlags byte flags used by { @link PlayerPositionAndLookPacket }
2020-12-13 23:01:01 +01:00
* /
2021-05-01 00:05:49 +02:00
@ApiStatus.Internal
2024-03-28 02:45:55 +01:00
void synchronizePositionAfterTeleport ( @NotNull Pos position , int relativeFlags ) {
sendPacket ( new PlayerPositionAndLookPacket ( position , ( byte ) relativeFlags , getNextTeleportId ( ) ) ) ;
super . synchronizePosition ( ) ;
2020-12-13 23:01:01 +01:00
}
2024-03-31 05:54:27 +02:00
/ * *
* Forces the player ' s client to look towards the target yaw / pitch
*
* @param yaw the new yaw
* @param pitch the new pitch
* /
@Override
public void setView ( float yaw , float pitch ) {
teleport ( new Pos ( 0 , 0 , 0 , yaw , pitch ) , null , RelativeFlags . COORD ) . join ( ) ;
}
/ * *
* Forces the player ' s client to look towards the specified point
* < p >
* Note : the player ' s position is not updated on the server until
* the client receives this packet
*
* @param point the point to look at
* /
@Override
public void lookAt ( @NotNull Point point ) {
// Let the player's client provide updated position values
sendPacket ( new FacePlayerPacket ( FacePlayerPacket . FacePosition . EYES , point , 0 , null ) ) ;
}
/ * *
* Forces the player ' s client to look towards the specified entity
* < p >
* Note : the player ' s position is not updated on the server until
* the client receives this packet
*
* @param entity the entity to look at
* /
@Override
public void lookAt ( @NotNull Entity entity ) {
// Let the player's client provide updated position values
sendPacket ( new FacePlayerPacket ( FacePlayerPacket . FacePosition . EYES , entity . getPosition ( ) , entity . getEntityId ( ) , FacePlayerPacket . FacePosition . EYES ) ) ;
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets the player permission level .
*
* @return the player permission level
* /
public int getPermissionLevel ( ) {
return permissionLevel ;
}
/ * *
* Changes the player permission level .
*
* @param permissionLevel the new player permission level
* @throws IllegalArgumentException if { @code permissionLevel } is not between 0 and 4
* /
public void setPermissionLevel ( int permissionLevel ) {
Check . argCondition ( ! MathUtils . isBetween ( permissionLevel , 0 , 4 ) , " permissionLevel has to be between 0 and 4 " ) ;
2021-08-26 17:10:48 +02:00
this . permissionLevel = permissionLevel ;
2020-12-13 23:01:01 +01:00
2022-01-29 14:56:45 +01:00
// Condition to prevent sending the packets before spawning the player
if ( isActive ( ) ) {
2024-03-28 03:08:36 +01:00
final byte permissionLevelStatus = ( byte ) ( STATUS_PERMISSION_LEVEL_OFFSET + permissionLevel ) ;
2022-01-29 14:56:45 +01:00
triggerStatus ( permissionLevelStatus ) ;
}
2020-12-13 23:01:01 +01:00
}
/ * *
* Sets or remove the reduced debug screen .
*
* @param reduced should the player has the reduced debug screen
* /
public void setReducedDebugScreenInformation ( boolean reduced ) {
this . reducedDebugScreenInformation = reduced ;
2024-03-28 03:08:36 +01:00
final byte debugScreenStatus = ( byte ) ( reduced ? STATUS_ENABLE_REDUCED_DEBUG_INFO : STATUS_DISABLE_REDUCED_DEBUG_INFO ) ;
2020-12-13 23:01:01 +01:00
triggerStatus ( debugScreenStatus ) ;
}
/ * *
* Gets if the player has the reduced debug screen .
*
* @return true if the player has the reduced debug screen , false otherwise
* /
public boolean hasReducedDebugScreenInformation ( ) {
return reducedDebugScreenInformation ;
}
/ * *
* The invulnerable field appear in the { @link PlayerAbilitiesPacket } packet .
*
* @return true if the player is invulnerable , false otherwise
* /
public boolean isInvulnerable ( ) {
return super . isInvulnerable ( ) ;
}
/ * *
* This do update the { @code invulnerable } field in the packet { @link PlayerAbilitiesPacket }
* and prevent the player from receiving damage .
*
* @param invulnerable should the player be invulnerable
* /
public void setInvulnerable ( boolean invulnerable ) {
super . setInvulnerable ( invulnerable ) ;
refreshAbilities ( ) ;
}
2021-09-14 11:37:57 +02:00
@Override
public void setSneaking ( boolean sneaking ) {
2021-09-20 19:34:43 +02:00
if ( isFlying ( ) ) { //If we are flying, don't set the players pose to sneaking as this can clip them through blocks
2021-09-14 11:37:57 +02:00
this . entityMeta . setSneaking ( sneaking ) ;
} else {
super . setSneaking ( sneaking ) ;
}
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets if the player is currently flying .
*
* @return true if the player if flying , false otherwise
* /
public boolean isFlying ( ) {
return flying ;
}
/ * *
* Sets the player flying .
*
* @param flying should the player fly
* /
public void setFlying ( boolean flying ) {
2021-09-14 11:50:19 +02:00
refreshFlying ( flying ) ;
2020-12-13 23:01:01 +01:00
refreshAbilities ( ) ;
}
/ * *
* Updates the internal flying field .
* < p >
* Mostly unsafe since there is nothing to backup the value , used internally for creative players .
*
* @param flying the new flying field
* @see # setFlying ( boolean ) instead
* /
public void refreshFlying ( boolean flying ) {
2021-09-14 11:50:19 +02:00
//When the player starts or stops flying, their pose needs to change
2021-09-20 19:34:43 +02:00
if ( this . flying ! = flying ) {
2021-09-14 11:50:19 +02:00
Pose pose = getPose ( ) ;
2021-09-20 19:34:43 +02:00
if ( this . isSneaking ( ) & & pose = = Pose . STANDING ) {
2021-09-14 11:50:19 +02:00
setPose ( Pose . SNEAKING ) ;
2021-09-20 19:34:43 +02:00
} else if ( pose = = Pose . SNEAKING ) {
2021-09-14 11:50:19 +02:00
setPose ( Pose . STANDING ) ;
}
}
2020-12-13 23:01:01 +01:00
this . flying = flying ;
}
/ * *
* Gets if the player is allowed to fly .
*
* @return true if the player if allowed to fly , false otherwise
* /
public boolean isAllowFlying ( ) {
return allowFlying ;
}
/ * *
* Allows or forbid the player to fly .
*
* @param allowFlying should the player be allowed to fly
* /
public void setAllowFlying ( boolean allowFlying ) {
this . allowFlying = allowFlying ;
refreshAbilities ( ) ;
}
public boolean isInstantBreak ( ) {
return instantBreak ;
}
/ * *
* Changes the player ability " Creative Mode " .
*
* @param instantBreak true to allow instant break
2021-06-17 15:50:28 +02:00
* @see < a href = " https://wiki.vg/Protocol#Player_Abilities_.28clientbound.29 " > player abilities < / a >
2020-12-13 23:01:01 +01:00
* /
public void setInstantBreak ( boolean instantBreak ) {
this . instantBreak = instantBreak ;
refreshAbilities ( ) ;
}
/ * *
* Gets the player flying speed .
*
* @return the flying speed of the player
* /
public float getFlyingSpeed ( ) {
return flyingSpeed ;
}
/ * *
* Updates the internal field and send a { @link PlayerAbilitiesPacket } with the new flying speed .
*
* @param flyingSpeed the new flying speed of the player
* /
public void setFlyingSpeed ( float flyingSpeed ) {
this . flyingSpeed = flyingSpeed ;
refreshAbilities ( ) ;
}
2020-12-15 13:41:42 +01:00
public float getFieldViewModifier ( ) {
return fieldViewModifier ;
2020-12-13 23:01:01 +01:00
}
2020-12-15 13:41:42 +01:00
public void setFieldViewModifier ( float fieldViewModifier ) {
this . fieldViewModifier = fieldViewModifier ;
2020-12-13 23:01:01 +01:00
refreshAbilities ( ) ;
}
/ * *
* This is the map used to send the statistic packet .
* It is possible to add / remove / change statistic value directly into it .
*
* @return the modifiable statistic map
* /
2021-07-27 07:44:06 +02:00
public @NotNull Map < PlayerStatistic , Integer > getStatisticValueMap ( ) {
2020-12-13 23:01:01 +01:00
return statisticValueMap ;
}
/ * *
* Gets the player vehicle information .
*
* @return the player vehicle information
* /
2021-07-27 07:44:06 +02:00
public @NotNull PlayerVehicleInformation getVehicleInformation ( ) {
2020-12-13 23:01:01 +01:00
return vehicleInformation ;
}
/ * *
2020-12-15 13:41:42 +01:00
* Sends to the player a { @link PlayerAbilitiesPacket } with all the updated fields .
2020-12-13 23:01:01 +01:00
* /
protected void refreshAbilities ( ) {
2021-07-27 07:44:06 +02:00
byte flags = 0 ;
if ( invulnerable )
2021-07-27 12:08:13 +02:00
flags | = PlayerAbilitiesPacket . FLAG_INVULNERABLE ;
2021-07-27 07:44:06 +02:00
if ( flying )
2021-07-27 12:08:13 +02:00
flags | = PlayerAbilitiesPacket . FLAG_FLYING ;
2021-07-27 07:44:06 +02:00
if ( allowFlying )
2021-07-27 12:08:13 +02:00
flags | = PlayerAbilitiesPacket . FLAG_ALLOW_FLYING ;
2021-07-27 07:44:06 +02:00
if ( instantBreak )
2021-07-27 12:08:13 +02:00
flags | = PlayerAbilitiesPacket . FLAG_INSTANT_BREAK ;
2022-05-10 10:07:55 +02:00
sendPacket ( new PlayerAbilitiesPacket ( flags , flyingSpeed , fieldViewModifier ) ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* All packets in the queue are executed in the { @link # 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 packet the packet to add in the queue
* /
2024-01-16 15:47:38 +01:00
public void addPacketToQueue ( @NotNull ClientPacket packet ) {
this . packets . offer ( packet ) ;
2020-12-13 23:01:01 +01:00
}
2022-01-28 05:32:35 +01:00
@ApiStatus.Internal
@ApiStatus.Experimental
public void interpretPacketQueue ( ) {
2023-09-08 14:18:48 +02:00
if ( this . packets . size ( ) > = ServerFlag . PLAYER_PACKET_QUEUE_SIZE ) {
2022-04-15 12:25:58 +02:00
kick ( Component . text ( " Too Many Packets " , NamedTextColor . RED ) ) ;
return ;
}
final PacketListenerManager manager = MinecraftServer . getPacketListenerManager ( ) ;
2022-01-28 05:32:35 +01:00
// This method is NOT thread-safe
2024-02-09 12:09:08 +01:00
this . packets . drain ( packet - > manager . processClientPacket ( packet , playerConnection ) , ServerFlag . PLAYER_PACKET_PER_TICK ) ;
2022-01-28 05:32:35 +01:00
}
2020-12-13 23:01:01 +01:00
/ * *
* Changes the storage player latency and update its tab value .
*
* @param latency the new player latency
* /
public void refreshLatency ( int latency ) {
this . latency = latency ;
2024-01-16 15:47:38 +01:00
if ( getPlayerConnection ( ) . getConnectionState ( ) = = ConnectionState . PLAY ) {
2023-11-28 17:48:00 +01:00
PacketUtils . broadcastPlayPacket ( new PlayerInfoUpdatePacket ( PlayerInfoUpdatePacket . Action . UPDATE_LATENCY , infoEntry ( ) ) ) ;
2023-09-30 19:17:44 +02:00
}
2020-12-13 23:01:01 +01:00
}
public void refreshOnGround ( boolean onGround ) {
this . onGround = onGround ;
2021-05-31 18:53:57 +02:00
if ( this . onGround & & this . isFlyingWithElytra ( ) ) {
2021-05-14 19:00:07 +02:00
this . setFlyingWithElytra ( false ) ;
2021-06-04 03:48:51 +02:00
EventDispatcher . call ( new PlayerStopFlyingWithElytraEvent ( this ) ) ;
2021-05-14 19:00:07 +02:00
}
2020-12-13 23:01:01 +01:00
}
/ * *
* Used to change internally the last sent last keep alive id .
* < p >
* Warning : could lead to have the player kicked because of a wrong keep alive packet .
*
* @param lastKeepAlive the new lastKeepAlive id
* /
public void refreshKeepAlive ( long lastKeepAlive ) {
this . lastKeepAlive = lastKeepAlive ;
this . answerKeepAlive = false ;
}
public boolean didAnswerKeepAlive ( ) {
return answerKeepAlive ;
}
public void refreshAnswerKeepAlive ( boolean answerKeepAlive ) {
this . answerKeepAlive = answerKeepAlive ;
}
/ * *
* Changes the held item for the player viewers
* Also cancel eating if { @link # isEating ( ) } was true .
* < p >
* Warning : the player will not be noticed by this chance , only his viewers ,
* see instead : { @link # setHeldItemSlot ( byte ) } .
*
* @param slot the new held slot
* /
public void refreshHeldSlot ( byte slot ) {
this . heldSlot = slot ;
2021-05-11 14:10:45 +02:00
syncEquipment ( EquipmentSlot . MAIN_HAND ) ;
2021-04-13 22:59:40 +02:00
refreshEating ( null ) ;
2020-12-13 23:01:01 +01:00
}
2021-04-13 22:59:40 +02:00
public void refreshEating ( @Nullable Hand eatingHand , long eatingTime ) {
this . eatingHand = eatingHand ;
if ( eatingHand ! = null ) {
2020-12-13 23:01:01 +01:00
this . startEatingTime = System . currentTimeMillis ( ) ;
this . eatingTime = eatingTime ;
} else {
this . startEatingTime = 0 ;
}
}
2021-04-13 22:59:40 +02:00
public void refreshEating ( @Nullable Hand eatingHand ) {
refreshEating ( eatingHand , defaultEatingTime ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Used to call { @link ItemUpdateStateEvent } with the proper item
* It does check which hand to get the item to update .
*
* @param allowFood true if food should be updated , false otherwise
* @return the called { @link ItemUpdateStateEvent } ,
* null if there is no item to update the state
2021-05-23 20:36:51 +02:00
* @deprecated Use { @link # callItemUpdateStateEvent ( Hand ) } instead
2020-12-13 23:01:01 +01:00
* /
2021-05-23 20:36:51 +02:00
@Deprecated
2021-04-13 22:59:40 +02:00
public @Nullable ItemUpdateStateEvent callItemUpdateStateEvent ( boolean allowFood , @Nullable Hand hand ) {
if ( hand = = null )
2020-12-13 23:01:01 +01:00
return null ;
2021-04-13 22:59:40 +02:00
final ItemStack updatedItem = getItemInHand ( hand ) ;
2022-04-13 17:57:15 +02:00
final boolean isFood = updatedItem . material ( ) . isFood ( ) ;
2020-12-13 23:01:01 +01:00
if ( isFood & & ! allowFood )
return null ;
ItemUpdateStateEvent itemUpdateStateEvent = new ItemUpdateStateEvent ( this , hand , updatedItem ) ;
2021-06-04 03:48:51 +02:00
EventDispatcher . call ( itemUpdateStateEvent ) ;
2020-12-13 23:01:01 +01:00
return itemUpdateStateEvent ;
}
2021-05-23 20:19:46 +02:00
/ * *
* Used to call { @link ItemUpdateStateEvent } with the proper item
* It does check which hand to get the item to update . Allows food .
*
* @return the called { @link ItemUpdateStateEvent } ,
* null if there is no item to update the state
* /
public @Nullable ItemUpdateStateEvent callItemUpdateStateEvent ( @Nullable Hand hand ) {
return callItemUpdateStateEvent ( true , hand ) ;
}
2020-12-13 23:01:01 +01:00
public void refreshVehicleSteer ( float sideways , float forward , boolean jump , boolean unmount ) {
this . vehicleInformation . refresh ( sideways , forward , jump , unmount ) ;
}
/ * *
* Gets the last sent keep alive id .
*
* @return the last keep alive id sent to the player
* /
public long getLastKeepAlive ( ) {
return lastKeepAlive ;
}
2021-03-11 18:07:04 +01:00
@Override
public @NotNull HoverEvent < ShowEntity > asHoverEvent ( @NotNull UnaryOperator < ShowEntity > op ) {
2024-01-06 09:20:32 +01:00
return HoverEvent . showEntity ( ShowEntity . showEntity ( EntityType . PLAYER , this . uuid , this . displayName ) ) ;
2021-03-11 18:07:04 +01:00
}
2020-12-13 23:01:01 +01:00
/ * *
* Gets the packet to add the player from the tab - list .
*
2023-05-21 16:51:13 +02:00
* @return a { @link PlayerInfoUpdatePacket } to add the player
2020-12-13 23:01:01 +01:00
* /
2023-05-21 16:51:13 +02:00
protected @NotNull PlayerInfoUpdatePacket getAddPlayerToList ( ) {
return new PlayerInfoUpdatePacket ( EnumSet . of ( PlayerInfoUpdatePacket . Action . ADD_PLAYER , PlayerInfoUpdatePacket . Action . UPDATE_LISTED ) ,
List . of ( infoEntry ( ) ) ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Gets the packet to remove the player from the tab - list .
*
2023-05-21 16:51:13 +02:00
* @return a { @link PlayerInfoRemovePacket } to remove the player
2020-12-13 23:01:01 +01:00
* /
2023-05-21 16:51:13 +02:00
protected @NotNull PlayerInfoRemovePacket getRemovePlayerToList ( ) {
return new PlayerInfoRemovePacket ( getUuid ( ) ) ;
}
private PlayerInfoUpdatePacket . Entry infoEntry ( ) {
final PlayerSkin skin = this . skin ;
List < PlayerInfoUpdatePacket . Property > prop = skin ! = null ?
List . of ( new PlayerInfoUpdatePacket . Property ( " textures " , skin . textures ( ) , skin . signature ( ) ) ) :
List . of ( ) ;
return new PlayerInfoUpdatePacket . Entry ( getUuid ( ) , getUsername ( ) , prop ,
true , getLatency ( ) , getGameMode ( ) , displayName , null ) ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Sends all the related packet to have the player sent to another with related data
* ( create player , spawn position , velocity , metadata , equipments , passengers , team ) .
* < p >
* WARNING : this alone does not sync the player , please use { @link # addViewer ( Player ) } .
*
* @param connection the connection to show the player to
* /
protected void showPlayer ( @NotNull PlayerConnection connection ) {
2021-07-27 11:56:20 +02:00
connection . sendPacket ( getEntityType ( ) . registry ( ) . spawnType ( ) . getSpawnPacket ( this ) ) ;
2020-12-13 23:01:01 +01:00
connection . sendPacket ( getVelocityPacket ( ) ) ;
connection . sendPacket ( getMetadataPacket ( ) ) ;
2021-03-12 01:38:52 +01:00
connection . sendPacket ( getEquipmentsPacket ( ) ) ;
2020-12-13 23:01:01 +01:00
if ( hasPassenger ( ) ) {
connection . sendPacket ( getPassengersPacket ( ) ) ;
}
2021-07-23 08:15:25 +02:00
connection . sendPacket ( new EntityHeadLookPacket ( getEntityId ( ) , position . yaw ( ) ) ) ;
2020-12-13 23:01:01 +01:00
}
@Override
2021-09-10 05:50:08 +02:00
public @NotNull ItemStack getItemInMainHand ( ) {
2020-12-13 23:01:01 +01:00
return inventory . getItemInMainHand ( ) ;
}
@Override
public void setItemInMainHand ( @NotNull ItemStack itemStack ) {
inventory . setItemInMainHand ( itemStack ) ;
}
@Override
2021-09-10 05:50:08 +02:00
public @NotNull ItemStack getItemInOffHand ( ) {
2020-12-13 23:01:01 +01:00
return inventory . getItemInOffHand ( ) ;
}
@Override
public void setItemInOffHand ( @NotNull ItemStack itemStack ) {
inventory . setItemInOffHand ( itemStack ) ;
}
@Override
2021-09-10 05:50:08 +02:00
public @NotNull ItemStack getHelmet ( ) {
2020-12-13 23:01:01 +01:00
return inventory . getHelmet ( ) ;
}
@Override
public void setHelmet ( @NotNull ItemStack itemStack ) {
inventory . setHelmet ( itemStack ) ;
}
@Override
2021-09-10 05:50:08 +02:00
public @NotNull ItemStack getChestplate ( ) {
2020-12-13 23:01:01 +01:00
return inventory . getChestplate ( ) ;
}
@Override
public void setChestplate ( @NotNull ItemStack itemStack ) {
inventory . setChestplate ( itemStack ) ;
}
@Override
2021-09-10 05:50:08 +02:00
public @NotNull ItemStack getLeggings ( ) {
2020-12-13 23:01:01 +01:00
return inventory . getLeggings ( ) ;
}
@Override
public void setLeggings ( @NotNull ItemStack itemStack ) {
inventory . setLeggings ( itemStack ) ;
}
@Override
2021-09-10 05:50:08 +02:00
public @NotNull ItemStack getBoots ( ) {
2020-12-13 23:01:01 +01:00
return inventory . getBoots ( ) ;
}
@Override
public void setBoots ( @NotNull ItemStack itemStack ) {
inventory . setBoots ( itemStack ) ;
}
2021-03-03 17:32:51 +01:00
@Override
public Locale getLocale ( ) {
2021-09-08 00:54:00 +02:00
final String locale = settings . locale ;
if ( locale = = null ) return null ;
return Locale . forLanguageTag ( locale . replace ( " _ " , " - " ) ) ;
2021-03-03 17:32:51 +01:00
}
2022-03-03 07:44:57 +01:00
@Override
public @NotNull PlayerSnapshot updateSnapshot ( @NotNull SnapshotUpdater updater ) {
final EntitySnapshot snapshot = super . updateSnapshot ( updater ) ;
2022-05-04 13:25:24 +02:00
return new SnapshotImpl . Player ( snapshot , username , gameMode ) ;
2022-03-03 07:44:57 +01:00
}
2021-03-03 17:32:51 +01:00
/ * *
* Sets the player ' s locale . This will only set the locale of the player as it
* is stored in the server . This will also be reset if the settings are refreshed .
*
* @param locale the new locale
* /
@Override
public void setLocale ( @Nullable Locale locale ) {
settings . locale = locale = = null ? null : locale . toLanguageTag ( ) ;
}
2021-03-12 16:36:22 +01:00
@Override
2021-03-12 18:26:57 +01:00
public @NotNull Identity identity ( ) {
2021-03-15 14:53:06 +01:00
return this . identity ;
}
@Override
2021-06-10 14:53:34 +02:00
public @NotNull Pointers pointers ( ) {
return this . pointers ;
}
@Override
2021-03-15 14:53:06 +01:00
public void setUuid ( @NotNull UUID uuid ) {
super . setUuid ( uuid ) ;
// update identity
this . identity = Identity . identity ( uuid ) ;
2021-03-12 16:36:22 +01:00
}
2021-04-04 15:10:06 +02:00
@Override
public boolean isPlayer ( ) {
return true ;
}
@Override
public Player asPlayer ( ) {
return this ;
}
2022-06-04 22:04:57 +02:00
protected void sendChunkUpdates ( Chunk newChunk ) {
if ( chunkUpdateLimitChecker . addToHistory ( newChunk ) ) {
final int newX = newChunk . getChunkX ( ) ;
final int newZ = newChunk . getChunkZ ( ) ;
final Vec old = chunksLoadedByClient ;
sendPacket ( new UpdateViewPositionPacket ( newX , newZ ) ) ;
ChunkUtils . forDifferingChunksInRange ( newX , newZ , ( int ) old . x ( ) , ( int ) old . z ( ) ,
2024-03-28 03:08:36 +01:00
settings . getEffectiveViewDistance ( ) , chunkAdder , chunkRemover ) ;
2022-06-04 22:04:57 +02:00
this . chunksLoadedByClient = new Vec ( newX , newZ ) ;
}
}
2024-03-28 02:45:55 +01:00
/ * *
* @see # teleport ( Pos , long [ ] , int )
* /
2022-11-26 11:10:44 +01:00
@Override
2024-03-28 02:45:55 +01:00
public @NotNull CompletableFuture < Void > teleport ( @NotNull Pos position , long @Nullable [ ] chunks , int flags ) {
2022-11-26 11:10:44 +01:00
chunkUpdateLimitChecker . clearHistory ( ) ;
2024-03-28 02:45:55 +01:00
return super . teleport ( position , chunks , flags ) ;
2022-11-26 11:10:44 +01:00
}
2020-12-13 23:01:01 +01:00
/ * *
* Represents the main or off hand of the player .
* /
public enum Hand {
MAIN ,
OFF
}
public enum FacePoint {
FEET ,
EYE
}
// Settings enum
/ * *
* Represents where is located the main hand of the player ( can be changed in Minecraft option ) .
* /
public enum MainHand {
LEFT ,
RIGHT
}
public class PlayerSettings {
private String locale ;
private byte viewDistance ;
2021-05-05 19:21:38 +02:00
private ChatMessageType chatMessageType ;
2020-12-13 23:01:01 +01:00
private boolean chatColors ;
private byte displayedSkinParts ;
private MainHand mainHand ;
2022-01-29 22:07:50 +01:00
private boolean enableTextFiltering ;
private boolean allowServerListings ;
2020-12-13 23:01:01 +01:00
2021-05-07 02:24:28 +02:00
public PlayerSettings ( ) {
2024-03-28 03:08:36 +01:00
viewDistance = ( byte ) ServerFlag . CHUNK_VIEW_DISTANCE ;
2021-05-07 02:24:28 +02:00
}
2020-12-13 23:01:01 +01:00
/ * *
* The player game language .
*
* @return the player locale
* /
public String getLocale ( ) {
return locale ;
}
/ * *
* Gets the player view distance .
*
* @return the player view distance
* /
public byte getViewDistance ( ) {
return viewDistance ;
}
2024-01-06 08:43:43 +01:00
public int getEffectiveViewDistance ( ) {
2024-03-28 03:08:36 +01:00
return Math . min ( getViewDistance ( ) , ServerFlag . CHUNK_VIEW_DISTANCE ) ;
2024-01-06 08:43:43 +01:00
}
2021-05-05 19:21:38 +02:00
/ * *
* Gets the messages this player wants to receive .
*
* @return the messages
* /
2021-05-06 17:12:46 +02:00
public @Nullable ChatMessageType getChatMessageType ( ) {
2021-05-05 19:21:38 +02:00
return chatMessageType ;
2020-12-13 23:01:01 +01:00
}
/ * *
* Gets if the player has chat colors enabled .
*
* @return true if chat colors are enabled , false otherwise
* /
public boolean hasChatColors ( ) {
return chatColors ;
}
public byte getDisplayedSkinParts ( ) {
return displayedSkinParts ;
}
/ * *
* Gets the player main hand .
*
* @return the player main hand
* /
public MainHand getMainHand ( ) {
return mainHand ;
}
2022-04-13 17:57:15 +02:00
public boolean enableTextFiltering ( ) {
return enableTextFiltering ;
}
2022-01-29 22:07:50 +01:00
2022-04-13 17:57:15 +02:00
public boolean allowServerListings ( ) {
return allowServerListings ;
}
2022-01-29 22:07:50 +01:00
2020-12-13 23:01:01 +01:00
/ * *
* Changes the player settings internally .
* < p >
* WARNING : the player will not be noticed by this change , probably unsafe .
*
* @param locale the player locale
* @param viewDistance the player view distance
2021-05-31 18:53:57 +02:00
* @param chatMessageType the chat messages the player wishes to receive
2021-05-05 19:21:38 +02:00
* @param chatColors if chat colors should be displayed
2020-12-13 23:01:01 +01:00
* @param displayedSkinParts the player displayed skin parts
* @param mainHand the player main hand
* /
2021-05-05 19:21:38 +02:00
public void refresh ( String locale , byte viewDistance , ChatMessageType chatMessageType , boolean chatColors ,
2022-01-29 22:07:50 +01:00
byte displayedSkinParts , MainHand mainHand , boolean enableTextFiltering , boolean allowServerListings ) {
2020-12-13 23:01:01 +01:00
this . locale = locale ;
2022-11-26 21:23:39 +01:00
// Clamp viewDistance to valid bounds
this . viewDistance = ( byte ) MathUtils . clamp ( viewDistance , 2 , 32 ) ;
2021-05-05 19:21:38 +02:00
this . chatMessageType = chatMessageType ;
2020-12-13 23:01:01 +01:00
this . chatColors = chatColors ;
this . displayedSkinParts = displayedSkinParts ;
this . mainHand = mainHand ;
2022-01-29 22:07:50 +01:00
this . enableTextFiltering = enableTextFiltering ;
this . allowServerListings = allowServerListings ;
2021-01-30 04:44:44 +01:00
2024-01-16 15:47:38 +01:00
boolean isInPlayState = getPlayerConnection ( ) . getConnectionState ( ) = = ConnectionState . PLAY ;
2024-03-28 03:08:36 +01:00
PlayerMeta playerMeta = getPlayerMeta ( ) ;
if ( isInPlayState ) playerMeta . setNotifyAboutChanges ( false ) ;
playerMeta . setDisplayedSkinParts ( displayedSkinParts ) ;
playerMeta . setRightMainHand ( this . mainHand = = MainHand . RIGHT ) ;
if ( isInPlayState ) playerMeta . setNotifyAboutChanges ( true ) ;
2020-12-13 23:01:01 +01:00
}
}
2024-01-06 08:43:43 +01:00
private int compareChunkDistance ( long chunkIndexA , long chunkIndexB ) {
int chunkAX = ChunkUtils . getChunkCoordX ( chunkIndexA ) ;
int chunkAZ = ChunkUtils . getChunkCoordZ ( chunkIndexA ) ;
int chunkBX = ChunkUtils . getChunkCoordX ( chunkIndexB ) ;
int chunkBZ = ChunkUtils . getChunkCoordZ ( chunkIndexB ) ;
int chunkDistanceA = Math . abs ( chunkAX - chunksLoadedByClient . blockX ( ) ) + Math . abs ( chunkAZ - chunksLoadedByClient . blockZ ( ) ) ;
int chunkDistanceB = Math . abs ( chunkBX - chunksLoadedByClient . blockX ( ) ) + Math . abs ( chunkBZ - chunksLoadedByClient . blockZ ( ) ) ;
return Integer . compare ( chunkDistanceA , chunkDistanceB ) ;
}
2019-08-03 15:25:24 +02:00
}