Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Player anatomy

Verified against Minecraft 26.2 · Part VIII · You open your own inventory and look at what you are made of: five classes deep, forty-three slots wide, and one of those slots is an alias.

You are a LivingEntity that a human is steering through a socket. Almost everything on this page follows from that sentence: the class ladder exists to separate what any living thing does from what a thing with an inventory and a game mode does from what a thing with a connection does. But two of the rungs are not where a reader expects them. There is an abstract class between LivingEntity and Player that holds no instance state at all, and the main-hand item is not stored anywhere — the hotbar slot you are looking at and the item LivingEntity.getMainHandItem returns are the same bytes, aliased through the equipment container a horse also has.

The cast

classwhat it decidesthread
Avatarthe player-shaped hitbox and the two cosmetic synched values — and nothing elseboth main threads
Playereverything a reader means by the player: inventory, abilities, experience, sleep, reachboth
ServerPlayerthe connection, the advancements, the statistics, and every last sent fieldserver main
LocalPlayerthe one a human steers: input, prediction, what to sendclient main
RemotePlayerevery other player on your screen — interpolated, never derivedclient main
Inventorythirty-six stacks, and a window onto EntityEquipment for the restboth
ServerPlayerGameMode / MultiPlayerGameModewhat the current GameType allows, one object per sideserver / client main
Mannequinthe other Avatar: a posable dummy that gets the whole skin pipelineboth

The ladder, and the class 26.2 put in the middle

flowchart TD
    E["Entity"] --> LE["LivingEntity"]
    LE --> AV["Avatar — no instance fields"]
    AV --> P["Player — abstract"]
    AV --> M["Mannequin"]
    P --> SP["ServerPlayer"]
    P --> ACP["AbstractClientPlayer"]
    ACP --> LP["LocalPlayer"]
    ACP --> RP["RemotePlayer"]
    M --> CM["ClientMannequin"]

Entity and LivingEntity belong to Part VI. The rung above them, Avatar (world/entity), is fifty-seven lines and no instance fields at all. It owns the player-shaped dimensions (Avatar.POSES, Avatar.STANDING_DIMENSIONS, Avatar.CROUCH_BB_HEIGHT, Avatar.SWIMMING_BB_WIDTH, Avatar.SWIMMING_BB_HEIGHT), the 1.62 eye height (Avatar.DEFAULT_EYE_HEIGHT), the two cosmetic synched values (Avatar.DATA_PLAYER_MAIN_HAND, Avatar.DATA_PLAYER_MODE_CUSTOMISATION) read back through Avatar.getMainArm and Avatar.isModelPartShown, and one abstract method, Avatar.getProfile, returning a ResolvableProfile. That is the whole class.

It exists for the renderer. AvatarRenderer is generic over an Avatar that is also a ClientAvatarEntity, and exactly two classes satisfy that: AbstractClientPlayer and ClientMannequin. The swap is worth knowing, because it is the same server/client split Player has, one class lower: Mannequin (world/entity/decoration) holds a mutable static factory, Mannequin.constructor, and ClientMannequin.registerOverrides replaces it during client startup, so a mannequin spawned into a ClientLevel is really a ClientMannequin with the full PlayerModel and skin pipeline behind it. The mannequin is therefore a sibling of Player, not of ArmorStand; only the package is shared with the armour stand. It is posable (Mannequin.VALID_POSES), profiled (Mannequin.DATA_PROFILE), describable (Mannequin.DATA_DESCRIPTION) and optionally immovable (Mannequin.DATA_IMMOVABLE).

Player itself is abstract for one method above all: Player.gameMode, returning a nullable GameType. A long tail of its other methods are empty hooks that exist so the two sides can disagree — Player.onUpdateAbilities, Player.awardStat, Player.triggerRecipeCrafted, Player.crit, Player.magicCrit, Player.sendSystemMessage, Player.doCloseContainer, Player.openTextEdit, Player.sendMerchantOffers, Player.handleCreativeModeItemDrop. On Player they do nothing; the subclass with somewhere to send a packet overrides them.

What Player owns

whatthe fieldswho explains it
storagePlayer.inventory, Player.enderChestInventory (a PlayerEnderChestContainer)below
the open windowPlayer.inventoryMenu (final) and Player.containerMenu, which is Player.inventoryMenu when nothing is opencontainers and menus
what the mode allowsPlayer.abilitiesbelow
the food barPlayer.foodDatahunger and experience
experiencePlayer.experienceLevel, Player.experienceProgress, Player.totalExperience, Player.enchantmentSeed, Player.lastLevelUpTime, Player.takeXpDelayhunger and experience
sleepPlayer.sleepCounter, Player.startSleepInBed / Player.stopSleepInBed, the Player.BedSleepingProblem refusals, Player.SLEEP_DURATION (100) and Player.WAKE_UP_DURATION (10)ServerLevel owns the everyone is asleep half
the two combat clocksLivingEntity.attackStrengthTicker and LivingEntity.itemSwapTicker, declared one rung up but read, reset and incremented only herethe sword swing
cooldownsPlayer.cooldowns, built by Player.createItemCooldowns, which only ServerPlayer overridesusing an item
four synched valuesPlayer.DATA_PLAYER_ABSORPTION_ID, Player.DATA_SCORE_ID, Player.DATA_SHOULDER_PARROT_LEFT, Player.DATA_SHOULDER_PARROT_RIGHTsynched entity data
addressingPlayer.ENDER_SLOT_OFFSET (200), Player.HELD_ITEM_SLOT (499), Player.CRAFTING_SLOT_OFFSET (500), decoded by Player.getSlotcommands and containers
the odds and endsPlayer.gameProfile, Player.lastDeathLocation, Player.fishing, Player.reducedDebugInfo, Player.lastItemInMainHand, Player.hurtDir, Player.jumpTriggerTime, Player.wasUnderwater

None of those four synched values is the hand, which went up to Avatar; and a player’s skin is not synched data at all: it arrives out of band, from the tab-list entry. A mannequin’s does travel as synched data, in Mannequin.DATA_PROFILE.

Reach is two attributes, not one. Player.blockInteractionRange and Player.entityInteractionRange read Attributes.BLOCK_INTERACTION_RANGE and Attributes.ENTITY_INTERACTION_RANGE (attributes), whose defaults — 4.5 and 3.0 — live on the attributes themselves. Player.DEFAULT_BLOCK_INTERACTION_RANGE and Player.DEFAULT_ENTITY_INTERACTION_RANGE name the same two numbers and are read by nothing. The checks the server makes are Player.isWithinBlockInteractionRange and Player.isWithinEntityInteractionRange. Note which class supplies which: Player.createAttributes adds the block range, Attributes.BLOCK_BREAK_SPEED, Attributes.SUBMERGED_MINING_SPEED, Attributes.SNEAKING_SPEED, Attributes.MINING_EFFICIENCY, Attributes.SWEEPING_DAMAGE_RATIO and the waypoint attributes, while the entity range comes from LivingEntity.createLivingAttributes.

Two smaller seams: Player.permissions returns PermissionSet.NO_PERMISSIONS and both sides override it — it is what Player.canUseGameMasterBlocks consults alongside Abilities.instabuild for Permissions.COMMANDS_GAMEMASTER — and Player implements ContainerUser, which is how a chest decides you are still close enough to keep it open (Player.getContainerInteractionRange).

Forty-three slots, and one of them is an alias

Inventory implements Container in a particular shape: one Inventory.items list of thirty-six stacks (Inventory.INVENTORY_SIZE) plus a reference to the player’s EntityEquipment. Slots at or above thirty-six are not stored here at all. Inventory.EQUIPMENT_SLOT_MAPPING routes them into the equipment object — the four armour indices, Inventory.SLOT_OFFHAND (40), Inventory.SLOT_BODY_ARMOR (41) and Inventory.SLOT_SADDLE (42) — so Inventory.getContainerSize is forty-three, and a player carries the same body-armour and saddle slots a horse does.

Player.createEquipment returns a PlayerEquipment, which overrides the map so that EquipmentSlot.MAINHAND resolves to Inventory.getSelectedItem. That is the alias: the held item is not stored twice, and the main hand is the selected hotbar slot seen through the equipment interface.

The rest of the class is the vocabulary the whole game uses to put things in a player: Inventory.add, Inventory.getFreeSlot, Inventory.getSlotWithRemainingSpace, Inventory.placeItemBackInInventory, Inventory.findSlotMatchingItem, Inventory.contains, Inventory.removeItem, Inventory.clearOrCountMatchingItems, Inventory.dropAll, Inventory.getSuitableHotbarSlot, Inventory.addAndPickItem and Inventory.pickSlot (pick-block), and Inventory.fillStackedContents (the recipe book). Inventory.save and Inventory.load cover the thirty-six — the equipment half is persisted by LivingEntity — and Inventory.setSelectedSlot throws rather than accept a non-hotbar index.

Abilities, GameType, and the one method that connects them

Abilities is five public booleans and two floats: Abilities.invulnerable, Abilities.flying, Abilities.mayfly, Abilities.instabuild, Abilities.mayBuild, plus Abilities.getFlyingSpeed and Abilities.getWalkingSpeed. It does not serialise itself by hand — it packs into the record Abilities.Packed and Abilities.Packed.CODEC does the work, through Abilities.pack and Abilities.apply.

GameType is the four-constant enum (GameType.SURVIVAL, GameType.CREATIVE, GameType.ADVENTURE, GameType.SPECTATOR) with GameType.DEFAULT_MODE, a GameType.CODEC and a GameType.STREAM_CODEC. The method that matters is GameType.updatePlayerAbilities: the single place in the game that decides which abilities a mode grants. Both sides call it — the server from ServerPlayerGameMode, the client from MultiPlayerGameMode on login, on respawn and on a mode-change event. GameType.isBlockPlacingRestricted is what sets Abilities.mayBuild, and GameType.isSurvival is true for GameType.ADVENTURE too.

The two game-mode objects

ServerPlayerGameMode (server/level)MultiPlayerGameMode (client/multiplayer)
owns the modeServerPlayerGameMode.getGameModeForPlayerMultiPlayerGameMode.getPlayerMode
changes itServerPlayerGameMode.changeGameModeForPlayerMultiPlayerGameMode.setLocalMode
breaking stateServerPlayerGameMode.isDestroyingBlock, ServerPlayerGameMode.destroyProgressStart, ServerPlayerGameMode.hasDelayedDestroyMultiPlayerGameMode.isDestroying, MultiPlayerGameMode.destroyProgress, MultiPlayerGameMode.destroyDelay
the block hooksServerPlayerGameMode.handleBlockBreakAction, ServerPlayerGameMode.useItemOn, ServerPlayerGameMode.useItemMultiPlayerGameMode.startDestroyBlock, MultiPlayerGameMode.continueDestroyBlock, MultiPlayerGameMode.useItemOn, MultiPlayerGameMode.useItem
attacking— (ServerGamePacketListenerImpl handles it)MultiPlayerGameMode.attack, MultiPlayerGameMode.interact
containersMultiPlayerGameMode.handleContainerInput

Neither object is held by Player itself, and only one of them is held by a player at all: Minecraft.gameMode holds the client one — LocalPlayer has no such field — while ServerPlayer.gameMode is a field on the server player. Block interaction and block breaking own the block halves of both columns, and prediction and acknowledgement owns the ledger they share — which, note, MultiPlayerGameMode does not hold either: MultiPlayerGameMode.startPrediction reaches for ClientLevel.getBlockStatePredictionHandler per call.

The three sides of one player

ServerPlayer is everything that needs a server: ServerPlayer.connection (the ServerGamePacketListenerImpl), ServerPlayer.gameMode, ServerPlayer.advancements, ServerPlayer.stats, ServerPlayer.recipeBook (a ServerRecipeBook), ServerPlayer.chunkTrackingView and ServerPlayer.lastSectionPos — what the client has been sent (tickets and loading) — ServerPlayer.respawnConfig, ServerPlayer.camera, ServerPlayer.textFilter, ServerPlayer.wardenSpawnTracker, ServerPlayer.enderPearls, ServerPlayer.containerSynchronizer, and a row of mirror fields that exist so the server can notice a change: the ServerPlayer.lastSentHealth, ServerPlayer.lastSentFood and ServerPlayer.lastSentExp trio, which turn a difference into one packet, and a second row led by ServerPlayer.lastRecordedArmor, which turns one into a scoreboard criterion update instead. It also remembers what the client said: ServerPlayer.lastClientInput (an Input) and ServerPlayer.lastKnownClientMovement, both explained by input to movement. Players and sessions owns this object’s lifecycle; it is constructed during the configuration phase by PrepareSpawnTask, before the play listener exists.

AbstractClientPlayer adds the tab-list entry (AbstractClientPlayer.playerInfo, fetched lazily from the connection), the per-frame animation state AvatarRenderer reads (AbstractClientPlayer.clientAvatarState), AbstractClientPlayer.getSkin and the field-of-view modifier. LocalPlayer is the one the human steers: LocalPlayer.connection (a ClientPacketListener), LocalPlayer.input (a ClientInput at construction, swapped for a KeyboardInput by the connection on login and respawn), LocalPlayer.lastSentInput, the last-sent position block, LocalPlayer.recipeBook (a ClientRecipeBook), LocalPlayer.dropSpamThrottler, LocalPlayer.permissions, LocalPlayer.autoJumpEnabled, LocalPlayer.startedUsingItem and the view-bob fields LocalPlayer.yBob / LocalPlayer.xBob. RemotePlayer is every other player on the client: it sets Entity.noPhysics, interpolates through RemotePlayer.lerpDeltaMovement, and has an empty RemotePlayer.updatePlayerPose — another player’s pose is told to you, not derived.

Which of the three is allowed to decide anything is Part VI’s authority, stated once there: a Player is client-authoritative on both sides, and the server runs the physics anyway. The two-phase tick is what that costs.

What a player is on disk

Player.addAdditionalSaveData and Player.readAdditionalSaveData are where a player becomes a file: the inventory as a sparse slot/stack list, the selected slot, the sleep timer, the four experience fields including the enchanting seed, the score, the abilities through Abilities.Packed, the ender chest, and the last death location. ServerPlayer adds the game-type history through ServerPlayer.storeGameTypes, the thrown ender pearls through ServerPlayer.saveEnderPearls, the vehicle through ServerPlayer.saveParentVehicle, and ServerPlayer.SavedPosition — which is read before the entity exists, by the configuration-phase spawn task.

Questions players ask

Why does the creative flag not come from my abilities? Player.isCreative and Player.isSpectator do not read Abilities at all; they compare Player.gameMode against GameType constants. The flags themselves are read almost entirely through two accessors on Player: Player.hasInfiniteMaterials, which reads Abilities.instabuild, has more call sites than every other ability accessor combined — it is the most widely read of them, not the narrowest — with Player.preventsBlockDrops next and Player.mayBuild, Player.isSwimming and Player.isPushedByFluid well behind.

Whose game mode arrives late — and why is it yours? On the client every player’s mode comes from the tab list: AbstractClientPlayer.gameMode resolves through AbstractClientPlayer.getPlayerInfo, and returns null when there is no entry. For another player that window cannot be observed — ClientPacketListener refuses to spawn a RemotePlayer whose PlayerInfo has not arrived, and a PlayerInfo starts at GameType.DEFAULT_MODE, so their mode is survival until an update packet says otherwise, never null. The one player who really does have a null window is you: LocalPlayer is built during login, before your own tab-list entry is sent. What covers it is a second, independent source — MultiPlayerGameMode.localPlayerMode, set from the spawn info on login and respawn and from the game-event packet — and that is the one that drives Abilities, block breaking and the creative screen.

Why does building permission survive a packet that says otherwise? Abilities.mayBuild never goes on the wire, and nothing recomputes it on receipt. ClientboundPlayerAbilitiesPacket carries four flag bits and two floats, none of them the build permission; the client’s copy is written only by MultiPlayerGameMode on a mode change, so an abilities packet with no mode change leaves it as it was. The other direction is smaller still: ServerboundPlayerAbilitiesPacket carries only the flying bit.

Why does the save file say flySpeed when the accessor is Abilities.getFlyingSpeed? Because Abilities.Packed.CODEC names the keys, and it does not match the accessors. While reading that class, note the misspelled constant: Abilities.DEFAULY_FLYING.

Why does item ticking need two callers? Because of the forty-three slots. Inventory.tick runs over the thirty-six ordinary ones from Player.aiStep, and EntityEquipment.tick covers the other seven from LivingEntity.aiStep; and phase two walks all forty-three separately, offering each stack to ServerPlayer.synchronizeSpecialItemUpdates for the map-update packet a filled map needs.

What crosses the wire about the player itself? ClientboundLoginPacket and ClientboundRespawnPacket, both carrying a CommonPlayerSpawnInfo built by ServerPlayer.createCommonSpawnInfo — which is also where the client’s local game mode comes from; ClientboundPlayerAbilitiesPacket; ClientboundGameEventPacket.CHANGE_GAME_MODE and ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE for a mode change; ClientboundSetHeldSlotPacket and the serverbound ServerboundSetCarriedItemPacket for the hotbar; and ClientboundSetPlayerInventoryPacket, built by Inventory.createInventoryUpdatePacket.

Can a data pack redefine any of this? Almost none of it. Player.createAttributes supplies the attribute defaults, and game rules and server properties set the starting GameType. The player is one of the few systems in the game a data pack cannot redefine.

Which names will I hunt for under other spellings? The renderer is AvatarRenderer, and pick-block is Inventory.addAndPickItem.

Where to look

Player · Avatar · ServerPlayer · AbstractClientPlayer · LocalPlayer · RemotePlayer · Inventory · PlayerEquipment · Abilities · GameType · ServerPlayerGameMode · MultiPlayerGameMode · PrepareSpawnTask · Mannequin · ClientMannequin · AvatarRenderer · ClientAvatarEntity


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.