Player anatomy
Verified against Minecraft 26.3 · Part VIII · You open your own inventory and look at what you are made of: six classes deep on your own screen, forty-three slots wide, and the item in your hand has no slot of its own at all.
You are a LivingEntity that a human is steering down a connection. 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.
Six rungs of it reach from Entity down to the object on your own screen,
and five from Entity down to the one the server holds. But two of the things a
reader goes looking for are not where they look. One rung of that ladder
holds no instance state at all, and the main-hand item has no storage of
its own — 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
| class | what it decides | thread |
|---|---|---|
Avatar | the player-shaped hitbox, the eye height and the two cosmetic synched values — and no instance state | both |
Player | everything a reader means by the player: inventory, abilities, experience, sleep, reach | both |
ServerPlayer | the connection, the advancements, the statistics, and the server’s last sent fields | Server |
AbstractClientPlayer | the rung both client players share: the tab-list entry, the skin, the animation state | Render |
LocalPlayer / RemotePlayer | the two that sit on it: the one a human steers, and every other player on your screen — interpolated, never derived | Render |
Inventory | thirty-six stacks, and a window onto EntityEquipment for the rest | both |
ServerPlayerGameMode / MultiPlayerGameMode | what the current GameType allows, one object per side | Server / Render |
Mannequin | the other Avatar: a posable dummy that gets the whole skin pipeline | both |
The ladder, and the class in the middle
classDiagram
class Entity
<<abstract>> Entity
class LivingEntity
<<abstract>> LivingEntity
class Avatar {
<<abstract>>
no instance fields at all
}
class Player
<<abstract>> Player
class AbstractClientPlayer
<<abstract>> AbstractClientPlayer
Entity <|-- LivingEntity
LivingEntity <|-- Avatar
Avatar <|-- Player
Avatar <|-- Mannequin
Player <|-- ServerPlayer
Player <|-- AbstractClientPlayer
AbstractClientPlayer <|-- LocalPlayer
AbstractClientPlayer <|-- RemotePlayer
Mannequin <|-- ClientMannequin
Every abstract rung is marked, so the five unmarked classes are the only
ones the game instantiates outside its test framework — and the branch beside
Player is the reason Avatar exists at all.
Entity and LivingEntity belong to Part
VI.
The rung below them, Avatar (world/entity), is short and has no
instance fields at all — every name below it is a static
constant or a static EntityDataAccessor, a key onto the synched table
Entity owns, so owning a synched value here means holding the key and
not the storage. 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, a setter and a few defaults aside, is the whole class.
It exists because two things in the game are shaped like a person and only
one of them is a player. Avatar ships in the server jar, and what it holds
is what Player and Mannequin both need and nothing else does: the
body, the eye height, the arm you favour and the model parts you have turned
off. Everything a player has on top of that — the inventory, the abilities,
the hunger, the attribute set — is Player’s, which is why
Player.createAttributes is on Player and a mannequin gets the plain
living set (attributes).
The renderer follows the same line rather than drawing it: AvatarRenderer
is generic over an Avatar that is also a ClientAvatarEntity, and exactly
two classes satisfy that, AbstractClientPlayer and ClientMannequin.
That pairing is worth knowing, because the mannequin repeats Player’s
server/client split on its own branch, and in one class instead of two:
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;
ServerPlayer, which has somewhere to send a packet, overrides all but the
last, and LocalPlayer overrides six, the last among them.
What Player owns
| what | the fields | who explains it |
|---|---|---|
| storage | Player.inventory, Player.enderChestInventory (a PlayerEnderChestContainer) | below |
| the open window | Player.inventoryMenu (final) and Player.containerMenu, which is Player.inventoryMenu when nothing is open | containers and menus |
| what the mode allows | Player.abilities | below |
| the food bar | Player.foodData | hunger and experience |
| experience | Player.experienceLevel, Player.experienceProgress, Player.totalExperience, Player.enchantmentSeed, Player.lastLevelUpTime, Player.takeXpDelay | hunger and experience |
| sleep | Player.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 attack clocks | LivingEntity.attackStrengthTicker and LivingEntity.itemSwapTicker, declared two rungs up but read, reset and incremented only here | the sword swing |
| cooldowns | Player.cooldowns, built by Player.createItemCooldowns, which only ServerPlayer overrides | using an item |
| four synched values | Player.DATA_PLAYER_ABSORPTION_ID, Player.DATA_SCORE_ID, Player.DATA_SHOULDER_PARROT_LEFT, Player.DATA_SHOULDER_PARROT_RIGHT | synched entity data |
| addressing | Player.ENDER_SLOT_OFFSET (200), Player.HELD_ITEM_SLOT (499), Player.CRAFTING_SLOT_OFFSET (500), decoded by Player.getSlot — the numbering a command’s slot argument speaks | below |
| the odds and ends | Player.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 is Avatar’s;
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.
The skin itself is a small closed family in the same package, and it is
worth naming because a player’s skin is not on the entity at all and the
toggles are one byte there. PlayerSkin is a
record of three textures — body, cape and elytra — plus a PlayerModelType
(PlayerModelType.SLIM or PlayerModelType.WIDE, the two arm widths) and a
secure flag saying whether the textures came signed. It lives on the
tab-list entry, not on the player, which is why a skin can change without an
entity packet. PlayerModelPart is the other half: the seven toggles in
the skin-customisation screen, each a bit in the byte
Avatar.DATA_PLAYER_MODE_CUSTOMISATION carries, read back through
Avatar.isModelPartShown. Drawing any of it is Part XI’s — the skin and
the seven toggles land in an AvatarRenderState at extract
time.
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. The
checks the server makes are
Player.isWithinBlockInteractionRange and
Player.isWithinEntityInteractionRange. Note which class supplies which:
Player.createAttributes adds attack damage, attack speed, luck, a player’s
own movement speed, 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 counts who still has it open, over a
box Player.getContainerInteractionRange sizes.
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, which
hands the thirty-six ordinary slots to a StackedItemContents, which keeps
the stacks a recipe may draw on and tallies them in StackedContents — the
matcher that lives in this package and belongs to recipes. 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.
Why the forty-three need two ticking calls
The split is visible once a tick. Inventory.tick runs over the thirty-six
ordinary slots, called from Player.aiStep; EntityEquipment.tick covers
the other seven, called from LivingEntity.aiStep two rungs up. Each walks
only its own storage, though the two reach into each other — Inventory
routes the last seven indices into the equipment, and PlayerEquipment
answers the main hand from the inventory. A third
walk then crosses the whole forty-three at once — the second half of the
player’s tick offers every stack to
ServerPlayer.synchronizeSpecialItemUpdates, which is how a filled map gets
its update packet (the two-phase
tick).
Two callers for one tick hook is not an accident of history; it is the
storage split showing through.
The other numbering, the one a command speaks
Those forty-three are not the numbering a command speaks. Player.getSlot
decodes a second, sparser addressing over the same storage and past it: an
index inside the thirty-six is an ordinary slot, Player.ENDER_SLOT_OFFSET
plus n reaches the ender chest, Player.CRAFTING_SLOT_OFFSET plus n the
four crafting-grid slots, Player.HELD_ITEM_SLOT is the stack on the cursor
rather than any stored slot, and anything else falls through to the equipment
slots two rungs up. That is the map behind a slot argument in a command.
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 save file does not use the accessors’ names for any of this:
Abilities.Packed.CODEC names the keys, and it writes flySpeed where the
accessor is Abilities.getFlyingSpeed. While reading that class, note the
misspelled constant beside it — Abilities.DEFAULY_FLYING.
That is also where a data pack’s reach over the player ends, before it
begins. The attribute defaults Player.createAttributes supplies are code,
and the starting GameType comes from the world’s settings and the server’s
properties; the ladder, the slot count and what each mode grants are code too,
which makes the player one of the few systems in the game a data pack cannot
redefine.
The two game-mode objects
ServerPlayerGameMode (server/level) | MultiPlayerGameMode (client/multiplayer) | |
|---|---|---|
| owns the mode | ServerPlayerGameMode.getGameModeForPlayer | MultiPlayerGameMode.getPlayerMode |
| changes it | ServerPlayerGameMode.changeGameModeForPlayer | MultiPlayerGameMode.setLocalMode |
| breaking state | ServerPlayerGameMode.isDestroyingBlock, ServerPlayerGameMode.destroyProgressStart, ServerPlayerGameMode.hasDelayedDestroy | MultiPlayerGameMode.isDestroying, MultiPlayerGameMode.destroyProgress, MultiPlayerGameMode.destroyDelay |
| the block hooks | ServerPlayerGameMode.handleBlockBreakAction, ServerPlayerGameMode.useItemOn, ServerPlayerGameMode.useItem | MultiPlayerGameMode.startDestroyBlock, MultiPlayerGameMode.continueDestroyBlock, MultiPlayerGameMode.useItemOn, MultiPlayerGameMode.useItem |
| attacking | — (ServerGamePacketListenerImpl handles it) | MultiPlayerGameMode.attack, MultiPlayerGameMode.interact |
| containers | — | MultiPlayerGameMode.handleContainerInput |
ServerPlayerGameMode has exactly one subclass, DemoMode, which
MinecraftServer hands a player instead when the server is in demo mode. It
watches the level’s own gameTime for the demo’s days, keeping counters of
its own only for the intro screen and to space out the reminders: it fires
the tutorial prompts on the first morning, a message on each of the
DemoMode.DEMO_DAYS, and past DemoMode.TOTAL_PLAY_TICKS it overrides the
block-break and use hooks to answer with a reminder instead of doing
anything. It is the only place in the game where the kind of game-mode
object, rather than the GameType, decides what you may do.
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
Three concrete classes carry a player, and the fourth name in this section is
not a fourth side: AbstractClientPlayer is the rung the two client ones
share, and it holds what both of them need.
ServerPlayer is everything that needs a server. ServerPlayer.connection
is the ServerGamePacketListenerImpl everything else reaches the client
through; ServerPlayer.advancements, ServerPlayer.stats and
ServerPlayer.recipeBook (a ServerRecipeBook) are the three ledgers a
session accumulates; ServerPlayer.chunkTrackingView and
ServerPlayer.lastSectionPos record what the client has been sent (what the
client is
told);
and ServerPlayer.chatSession, a RemoteChatSession, holds the public
half of message signing, since the key pair never leaves the client (chat and
signing).
Around those sit the fields that exist because a server has to remember
something the world cannot re-derive: ServerPlayer.respawnConfig for where
you come back, ServerPlayer.camera for what you are looking through,
ServerPlayer.textFilter for what a chat message is allowed to be,
ServerPlayer.wardenSpawnTracker for how loud you have been,
ServerPlayer.enderPearls for the pearls in flight, and
ServerPlayer.containerSynchronizer for the open window’s own outbound
channel.
What is worth more than the roll-call is the shape of two rows of fields on
it, because both exist for the same reason and answer to different readers.
ServerPlayer.lastSentHealth, ServerPlayer.lastSentFood,
ServerPlayer.lastFoodSaturationZero and ServerPlayer.lastSentExp turn a
difference into a packet — the first three into one, experience into
another; a second row, from
ServerPlayer.lastRecordedHealthAndAbsorption through
ServerPlayer.lastRecordedArmor and on, turns a difference into a
scoreboard criterion update instead. Nothing on the server watches a
value change: it compares the value against the copy it last acted on. A
third pair remembers what the client said rather than what the server
found — 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 what a client needs to draw a player and to
name one. Its AbstractClientPlayer.playerInfo is the tab-list entry,
fetched lazily from the connection — and that entry is enough of a directory
that the client registers LocalPlayerResolver in front of the ordinary
profile lookup, so a profile the client needs for a skin, a mannequin’s or a
head’s, resolves out of the tab list before anything asks a server. It also carries the per-frame animation state
AvatarRenderer reads (AbstractClientPlayer.clientAvatarState),
AbstractClientPlayer.getSkin and the field-of-view modifier.
LocalPlayer is the one the human steers, and everything it adds is
about sending: LocalPlayer.connection (a ClientPacketListener),
LocalPlayer.input (a ClientInput at construction, swapped for a
KeyboardInput by the connection on login and respawn),
LocalPlayer.lastSentInput and the last-sent position block, and
LocalPlayer.dropSpamThrottler, which is the client’s own rate limit on
throwing things away. The rest is what only a first-person view needs:
LocalPlayer.recipeBook (a ClientRecipeBook), LocalPlayer.permissions,
LocalPlayer.startedUsingItem, the auto-jump pair, and
LocalPlayer.yBob and LocalPlayer.xBob, which lag the view so the
first-person hand sways behind it.
RemotePlayer is every other player on the client, and
adds almost nothing: it sets Entity.noPhysics when it is built (each
Player.tick then sets it to whether the player is a spectator), 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 crosses between them
Very little of a player is entity-synched, so the packets that carry one are
their own small set. ClientboundLoginPacket and ClientboundRespawnPacket
both carry a CommonPlayerSpawnInfo built by
ServerPlayer.createCommonSpawnInfo — which is also where the client’s
local game mode comes from. A mode change afterwards arrives as
ClientboundGameEventPacket.CHANGE_GAME_MODE or
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, and the
abilities as ClientboundPlayerAbilitiesPacket. The hotbar selection travels
both ways, as ClientboundSetHeldSlotPacket and
ServerboundSetCarriedItemPacket, and the inventory itself through the open
menu’s sync — with ClientboundSetPlayerInventoryPacket, built by
Inventory.createInventoryUpdatePacket, for each slot
Inventory.placeItemBackInInventory fills.
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 food data, 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 reached through accessors on Player that consult Abilities
for you, and the widest by far is Player.hasInfiniteMaterials, which reads
Abilities.instabuild and has about twice the call sites of the next,
Player.canUseGameMasterBlocks, which reads the same flag — with
Player.preventsBlockDrops, Player.mayBuild and Player.isPushedByFluid
well behind. The most
permissive flag in the game is also the most widely consulted one.
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 gap 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 tab-list entry is you:
LocalPlayer is built during login, before your own entry is sent. What
covers the gap 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
block breaking and Abilities.mayBuild, the one ability no packet carries.
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 login, respawn and 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.
Where to look
Open Player first and read it as the class the two sides argue over:
Player.getSlot is the one method that shows the command numbering,
Player.createAttributes the one that shows what a player has that a
mannequin does not. Then Avatar above it, which takes two minutes
and explains the Mannequin beside it. Inventory and
PlayerEquipment are the pair to read together, in that order, because
the second is three short methods and is the whole of the alias.
ServerPlayer and LocalPlayer are the two big faces: read them
for their last sent fields rather than end to end. For the modes,
GameType.updatePlayerAbilities is the single decision point and
ServerPlayerGameMode / MultiPlayerGameMode are the two objects
that call it. Two doors this page only points at: PrepareSpawnTask,
where a ServerPlayer is built before the play listener exists, and
AvatarRenderer with its ClientAvatarEntity bound, which is where the
skin and the seven toggles go.
Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes tools/verify_names.py.