Skip to content

Read player data

Changing. Follow: #158

This page shows how a plugin finds players, reads their data, tells their ids apart and changes them. In Paper a Player is an object with methods. In mcrs a player is an entity inside the world of a dimension, and its state is a set of components, so you write a query where Paper code would hold on to the object.

The page is short because the set of components grows with the project. The examples come from repository files, and we did not run them outside the repository tests.

Every player entity has the marker component Player. A system in a dimension world sees the players of that dimension only, as Dimensions explains. The test harness finds a player with this kind of query:

Find a player entity by its session
pub fn player_in(&mut self, dim: Entity, anchor: Entity) -> Option<Entity> {
let session = self.session_of(anchor)?;
let world = self.world_in(dim);
world
.query_filtered::<(Entity, &Owner), With<Player>>()
.iter(world)
.find(|(_, owner)| owner.0 == session)
.map(|(player, _)| player)
}

crates/mcrs_minecraft_server/tests/it/host_app/mod.rs:374-382

In a plugin you write the query as a system parameter, for example Query<(&GameProfile, &PlayerGameMode), With<Player>>. That line is an illustration and was not compiled. Its type names come from the files in the next section.

GameProfile is the profile that the client logged in with:

GameProfile
#[derive(Debug, Clone, Component)]
pub struct GameProfile {
pub id: uuid::Uuid,
pub username: String,
pub properties: Vec<Property<String>>,
}

crates/mcrs_minecraft_engine/src/game_profile.rs:8-13

The game mode, the flight state and the operator level are in one file:

PlayerGameMode and PlayerFlying
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Deref, DerefMut)]
pub struct PlayerGameMode(pub GameMode);
impl Default for PlayerGameMode {
fn default() -> Self {
Self(GameMode::Survival)
}
}
/// Whether the player flies, as its client reports it and its game mode allows.
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Deref, DerefMut, Default)]
pub struct PlayerFlying(pub bool);

crates/mcrs_minecraft_engine/src/ability.rs:12-23

PlayerOpLevel in the same file holds the operator level, which goes from 0 for a player who is not an operator up to 4. The test harness reads a game mode like this:

Read PlayerGameMode of a player
pub fn game_mode(&mut self, anchor: Entity) -> GameMode {
let player = self.player(anchor);
self.world().get::<PlayerGameMode>(player).unwrap().0
}

crates/mcrs_minecraft_server/tests/it/host_app/mod.rs:389-392

You cannot use one of these ids in place of another.

Id Where What it is
GameProfile::id component of the player the UUID of the profile, as in Paper
PlayerSession component Owner of the player in a dimension world; also on the host the name of one connection. The host never hands out a value twice, and a change of dimension keeps it
Entity the entity of the player inside one dimension world a Bevy id that exists in one world only. The host also has an anchor entity for the same player, and it is a different entity
PlayerSession
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PlayerSession(pub u64);

crates/mcrs_minecraft_engine_world/src/session/sessions.rs:12-13

Use the session to address a player across worlds and to send packets. Use the UUID for anything that must survive a reconnect.

Inside the world of a player you can insert or edit its components. Do not change a player from a system that runs in a different world. A change that crosses worlds is a message or an effect. The vanilla commands gamemode and teleport do this with command effects, see Add a command.

mcrs has no player.sendMessage(). The low-level way is to write a packet for a session. The chat plugin does this for a player who has chat hidden:

Write a system chat packet to the session of a player
if info.is_some_and(|info| info.chat_mode == ChatMode::Hidden) {
let disabled: Text = Text::translate("chat.disabled.options", vec![]).color(Color::RED);
if let Ok(disabled) = RawText::from_text(&disabled) {
packet_writer.write(bus::to(owner.0, system_chat(disabled)));
}
return;
}

crates/mcrs_minecraft_chat_server/src/chat_plugin.rs:56-62

A friendlier layer for this does not exist yet. We know of no issue for it, so we promise nothing.

Saving player data is not done either.2 The Status page shows the state of saving.

Next: Test a plugin without a client, where a test joins a fake player and reads these components.

  1. WIP#158 Plans wire entity ids that do not depend on the owning world. ↩ ↩2

  2. WIP#78 Tracks saving changed chunks back to region files. ↩

Sources and verification

Verified: 26.4-snapshot-7, 51d45c26a, 2026-11-09

Tracking: #158