Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e823b3e
feat: player data storage initialized
KingTimer12 Aug 23, 2025
cae8c54
fix: cargo fmt and cargo audit
KingTimer12 Aug 23, 2025
2eeab0e
fix: cargo clippy
KingTimer12 Aug 23, 2025
4f8db95
feat: added sqlite support
KingTimer12 Aug 24, 2025
ed2e353
fix: cargo clippy
KingTimer12 Aug 24, 2025
67a9bf5
fix: benches test of storage
KingTimer12 Aug 24, 2025
4fff8d3
chore: changed to sqlite
KingTimer12 Sep 13, 2025
d9b0bfa
fix: cargo audit error
KingTimer12 Sep 13, 2025
400d746
fix: cargo fmt
KingTimer12 Sep 13, 2025
b7bea43
fix: cargo clippy
KingTimer12 Sep 13, 2025
a2b92ff
fix: cargo clippy
KingTimer12 Sep 13, 2025
234fb78
Merge branch 'master' of github.com:KingTimer12/ferrumc into feature/…
KingTimer12 Oct 19, 2025
64491ab
feat: player data storage initialized
KingTimer12 Oct 19, 2025
8ac0648
fix: cargo fmt and cargo audit
KingTimer12 Aug 23, 2025
cbfa307
fix: cargo clippy
KingTimer12 Aug 23, 2025
7fc22ef
feat: added sqlite support
KingTimer12 Aug 24, 2025
6d3c4c5
fix: cargo clippy
KingTimer12 Aug 24, 2025
9f0d3aa
fix: benches test of storage
KingTimer12 Aug 24, 2025
e093a28
chore: changed to sqlite
KingTimer12 Sep 13, 2025
cc4d935
fix: cargo audit error
KingTimer12 Sep 13, 2025
c383f0c
fix: cargo fmt
KingTimer12 Sep 13, 2025
813e9a3
fix: cargo clippy
KingTimer12 Sep 13, 2025
b35c2f5
fix: cargo clippy
KingTimer12 Sep 13, 2025
1ad2bca
Merge branch 'feature/player-state' of github.com:KingTimer12/ferrumc…
KingTimer12 Oct 19, 2025
b0e6aa7
chore: now is updating player data
KingTimer12 Oct 19, 2025
c408791
fix: i forgot fmt :p
KingTimer12 Oct 19, 2025
1a5ca03
chore: updated the way playerdata is used
KingTimer12 Oct 22, 2025
c31ee14
chore: saving data through event
KingTimer12 Oct 22, 2025
01f24fb
Merge branch 'master' of github.com:KingTimer12/ferrumc into feature/…
KingTimer12 Oct 23, 2025
b70b7b2
fix: problem in my gitignore; fix: cargo.toml with old dep;
KingTimer12 Oct 23, 2025
dcd5148
chore: added player disconnect event in connection killer
KingTimer12 Oct 23, 2025
3d8d02c
chore: removed PlayerState
KingTimer12 Oct 26, 2025
d386bd8
chore: added Display to player data
KingTimer12 Oct 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@ resolver = "2"
members = [
"src/bin",
"src/lib/adapters/anvil",
"src/lib/adapters/anvil",
"src/lib/adapters/nbt",
"src/lib/adapters/nbt",
"src/lib/commands",
"src/lib/default_commands",
"src/lib/core",
"src/lib/core/state",
"src/lib/derive_macros",
"src/lib/derive_macros",
"src/lib/net",
"src/lib/net/crates/codec",
"src/lib/net/crates/encryption",
Expand Down Expand Up @@ -115,11 +112,19 @@ ferrumc-world-gen = { path = "src/lib/world_gen" }
ferrumc-inventories = { path = "src/lib/inventories" }

# Asynchronous
tokio = { version = "1.47.1", features = ["macros", "net", "rt", "sync", "time", "io-util", "test-util"], default-features = false }
tokio = { version = "1.47.1", features = [
"macros",
"net",
"rt",
"sync",
"time",
"io-util",
"test-util",
], default-features = false }

# Logging
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tracing-subscriber = { version = ">=0.3.20", features = ["env-filter"] }
tracing-appender = "0.2.3"
log = "0.4.27"
console-subscriber = "0.4.1"
Expand Down Expand Up @@ -189,6 +194,7 @@ lz4_flex = "0.11.5"
# Database
heed = "0.22.0"
moka = "0.12.10"
rusqlite = { version = "0.37.0", features = ["bundled", "serde_json"] }

# CLI
clap = "4.5.45"
Expand Down
2 changes: 2 additions & 0 deletions src/bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ferrumc_config::server_config::get_global_config;
use ferrumc_config::whitelist::create_whitelist;
use ferrumc_general_purpose::paths::get_root_path;
use ferrumc_state::player_list::PlayerList;
use ferrumc_state::player_state::PlayerState;
use ferrumc_state::{GlobalState, ServerState};
use ferrumc_threadpool::ThreadPool;
use ferrumc_world::World;
Expand Down Expand Up @@ -159,6 +160,7 @@ fn create_state(start_time: Instant) -> Result<ServerState, BinaryError> {
world: World::new(&get_global_config().database.db_path),
terrain_generator: WorldGenerator::new(0),
shut_down: false.into(),
player_state: PlayerState::default(),
players: PlayerList::default(),
thread_pool: ThreadPool::new(),
start_time,
Expand Down
65 changes: 52 additions & 13 deletions src/bin/src/packet_handlers/play_packets/player_loaded.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bevy_ecs::prelude::{Entity, Query, Res};
use ferrumc_core::transform::position::Position;
use ferrumc_core::data::player::PlayerData;
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_net::connection::StreamWriter;
use ferrumc_net::packets::outgoing::synchronize_player_position::SynchronizePlayerPositionPacket;
use ferrumc_net::PlayerLoadedReceiver;
Expand All @@ -10,10 +11,10 @@ use tracing::warn;
pub fn handle(
ev: Res<PlayerLoadedReceiver>,
state: Res<GlobalStateResource>,
query: Query<(Entity, &Position, &StreamWriter)>,
mut query: Query<(Entity, &PlayerIdentity, &mut PlayerData, &StreamWriter)>,
) {
for (_, player) in ev.0.try_iter() {
let Ok((entity, player_pos, conn)) = query.get(player) else {
let Ok((entity, player_identity, mut player_data, conn)) = query.get_mut(player) else {
warn!("Player position not found in query.");
continue;
};
Expand All @@ -24,28 +25,66 @@ pub fn handle(
);
continue;
}

// Default player data
*player_data = PlayerData::default();

// Save the player's position in the world
if let Ok(loaded) = state
.0
.world
.load_player_state(player_identity.uuid.as_u128())
{
match loaded {
Some(loaded_data) => {
*player_data = loaded_data;
tracing::info!(
"Loaded player state for {}: position=({}, {}, {}), dimension={}",
player_identity.uuid.as_u128(),
player_data.pos.x,
player_data.pos.y,
player_data.pos.z,
player_data.dimension
);
}
None => {
if let Err(e) = state
.0
.world
.save_player_state(player_identity.uuid.as_u128(), &player_data)
{
tracing::error!(
"Failed to save player state for {} ({}): {:?}",
player_identity.username,
player_identity.uuid.as_u128(),
e
);
}
}
}
}
let head_block = state.0.world.get_block_and_fetch(
player_pos.x as i32,
player_pos.y as i32,
player_pos.z as i32,
player_data.pos.x as i32,
player_data.pos.y as i32,
player_data.pos.z as i32,
"overworld",
);
if let Ok(head_block) = head_block {
if head_block == BlockId(0) {
tracing::info!(
"Player {} loaded at position: ({}, {}, {})",
player,
player_pos.x,
player_pos.y,
player_pos.z
player_data.pos.x,
player_data.pos.y,
player_data.pos.z
);
} else {
tracing::info!(
"Player {} loaded at position: ({}, {}, {}) with head block: {:?}",
player,
player_pos.x,
player_pos.y,
player_pos.z,
player_data.pos.x,
player_data.pos.y,
player_data.pos.z,
head_block
);
// Teleport the player to the world center if their head block is not air
Expand All @@ -66,7 +105,7 @@ pub fn handle(
} else {
warn!(
"Failed to fetch head block for player {} at position: ({}, {}, {})",
player, player_pos.x, player_pos.y, player_pos.z
player, player_data.pos.x, player_data.pos.y, player_data.pos.z
);
}
}
Expand Down
31 changes: 26 additions & 5 deletions src/bin/src/packet_handlers/play_packets/set_player_position.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use bevy_ecs::prelude::{Entity, EventWriter, Query, Res};
use ferrumc_core::chunks::cross_chunk_boundary_event::CrossChunkBoundaryEvent;
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_core::{
chunks::cross_chunk_boundary_event::CrossChunkBoundaryEvent, data::player::PlayerData,
};
use ferrumc_net::SetPlayerPositionPacketReceiver;
use tracing::{debug, error, trace, warn};

Expand All @@ -18,7 +20,13 @@ use ferrumc_state::{GlobalState, GlobalStateResource};

pub fn handle(
events: Res<SetPlayerPositionPacketReceiver>,
mut pos_query: Query<(&mut Position, &mut OnGround, &Rotation, &PlayerIdentity)>,
mut pos_query: Query<(
&mut Position,
&mut OnGround,
&Rotation,
&PlayerIdentity,
&mut PlayerData,
)>,
pass_conn_query: Query<(Entity, &StreamWriter)>,
mut cross_chunk_events: EventWriter<CrossChunkBoundaryEvent>,
state: Res<GlobalStateResource>,
Expand All @@ -36,7 +44,7 @@ pub fn handle(

let new_position = Position::new(event.x, event.feet_y, event.z);

let (mut position, mut on_ground, _, _) = pos_query
let (mut position, mut on_ground, _, _, mut player_data) = pos_query
.get_mut(eid)
.expect("Failed to get position and on_ground components");

Expand All @@ -62,6 +70,13 @@ pub fn handle(

*on_ground = OnGround(event.on_ground);

player_data.update_position(Position::new(
new_position.x,
new_position.y,
new_position.z,
));
player_data.update_on_ground(event.on_ground);

if let Err(err) = update_pos_for_all(
eid,
delta_pos,
Expand Down Expand Up @@ -95,7 +110,13 @@ fn update_pos_for_all(
entity_id: Entity,
delta_pos: Option<(i16, i16, i16)>,
new_rot: Option<Rotation>,
pos_query: &Query<(&mut Position, &mut OnGround, &Rotation, &PlayerIdentity)>,
pos_query: &Query<(
&mut Position,
&mut OnGround,
&Rotation,
&PlayerIdentity,
&mut PlayerData,
)>,
conn_query: &Query<(Entity, &StreamWriter)>,
state: GlobalState,
) -> Result<(), BinaryError> {
Expand All @@ -107,7 +128,7 @@ fn update_pos_for_all(
);
return Ok(());
}
let (pos, grounded, rot, identity) = pos_query.get(entity_id)?;
let (pos, grounded, rot, identity, _) = pos_query.get(entity_id)?;

// If any delta of (x|y|z) exceeds 7.5, then it's "not recommended" to use this packet
// As docs say: "If the movement exceeds these limits, Teleport Entity should be sent instead."
Expand Down
9 changes: 6 additions & 3 deletions src/bin/src/packet_handlers/player/head_rot.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bevy_ecs::event::EventReader;
use bevy_ecs::prelude::Query;
use ferrumc_core::data::player::PlayerData;
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_core::transform::rotation::Rotation;
use ferrumc_net::connection::StreamWriter;
Expand All @@ -10,13 +11,12 @@ use tracing::error;

pub fn handle_player_move(
mut events: EventReader<TransformEvent>,
query: Query<(&Rotation, &PlayerIdentity)>,
mut query: Query<(&Rotation, &PlayerIdentity, &mut PlayerData)>,
broadcast_query: Query<&StreamWriter>,
) {
for event in events.read() {
let entity = event.entity;

let (rot, identity) = query.get(entity).unwrap();
let (rot, identity, mut player_data) = query.get_mut(entity).unwrap();
let head_rot_packet = SetHeadRotationPacket::new(
identity.uuid.as_u128() as i32,
NetAngle::from_degrees(rot.yaw as f64),
Expand All @@ -25,6 +25,9 @@ pub fn handle_player_move(
#[cfg(debug_assertions)]
let start = std::time::Instant::now();

player_data.update_pitch(rot.pitch);
player_data.update_yaw(rot.yaw);

for writer in broadcast_query.iter() {
if !writer.running.load(std::sync::atomic::Ordering::Relaxed) {
continue;
Expand Down
3 changes: 2 additions & 1 deletion src/bin/src/systems/new_connections.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use bevy_ecs::prelude::{Commands, Res, Resource};
use crossbeam_channel::Receiver;
use ferrumc_core::chunks::chunk_receiver::ChunkReceiver;
use ferrumc_core::conn::keepalive::KeepAliveTracker;
use ferrumc_core::transform::grounded::OnGround;
use ferrumc_core::transform::position::Position;
use ferrumc_core::transform::rotation::Rotation;
use ferrumc_core::{chunks::chunk_receiver::ChunkReceiver, data::player::PlayerData};
use ferrumc_inventories::hotbar::Hotbar;
use ferrumc_inventories::inventory::Inventory;
use ferrumc_net::connection::NewConnection;
Expand All @@ -28,6 +28,7 @@ pub fn accept_new_connections(
let entity = cmd.spawn((
new_connection.stream,
Position::default(),
PlayerData::default(),
ChunkReceiver::default(),
Rotation::default(),
OnGround::default(),
Expand Down
20 changes: 17 additions & 3 deletions src/bin/src/systems/shutdown_systems/send_shutdown_packet.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
use bevy_ecs::prelude::{Entity, Query, Res};
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_core::{data::player::PlayerData, identity::player_identity::PlayerIdentity};
use ferrumc_net::connection::StreamWriter;
use ferrumc_state::GlobalStateResource;
use ferrumc_text::TextComponent;

pub fn handle(
query: Query<(Entity, &StreamWriter, &PlayerIdentity)>,
query: Query<(Entity, &StreamWriter, &PlayerIdentity, &PlayerData)>,
state: Res<GlobalStateResource>,
) {
let packet = ferrumc_net::packets::outgoing::disconnect::DisconnectPacket {
reason: TextComponent::from("Server is shutting down"),
};

for (entity, conn, identity) in query.iter() {
for (entity, conn, identity, player_data) in query.iter() {
// I guess that save player state before sending shutdown packet is important to ensure data integrity and prevent data loss.
if let Err(e) = state
.0
.world
.save_player_state(identity.uuid.as_u128(), player_data)
{
tracing::error!(
"Failed to save player state for {}: {}",
identity.username,
e
);
} else {
tracing::info!("Player state saved for {}", identity.username);
}
if state.0.players.is_connected(entity) {
if let Err(e) = conn.send_packet_ref(&packet) {
tracing::error!(
Expand Down
2 changes: 2 additions & 0 deletions src/lib/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ ferrumc-text = { workspace = true }
ferrumc-net-codec = { workspace = true }
uuid = { workspace = true }
crossbeam-queue = { workspace = true }
serde = { workspace = true }
bitcode = { workspace = true }

[dev-dependencies]
criterion = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions src/lib/core/src/data/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod player;
51 changes: 51 additions & 0 deletions src/lib/core/src/data/player.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use bevy_ecs::component::Component;
use bitcode::{Decode, Encode};
use serde::{Deserialize, Serialize};

use crate::transform::position::Position;

// https://minecraft.fandom.com/wiki/Player.dat_format
#[derive(
Serialize, Deserialize, Clone, Debug, Encode, Decode, Component, typename::TypeName, PartialEq,
)]
pub struct PlayerData {
pub pos: Position,
pub on_ground: bool,
pub dimension: String,
pub yaw: f32,
pub pitch: f32,
}

impl Default for PlayerData {
fn default() -> Self {
Self::new(Position::default(), false, "overworld", 0.0, 0.0)
}
}

impl PlayerData {
pub fn new(pos: Position, on_ground: bool, dimension: &str, yaw: f32, pitch: f32) -> Self {
Self {
pos,
on_ground,
dimension: dimension.to_string(),
yaw,
pitch,
}
}

pub fn update_position(&mut self, new_position: Position) {
self.pos = new_position;
}

pub fn update_on_ground(&mut self, on_ground: bool) {
self.on_ground = on_ground;
}

pub fn update_yaw(&mut self, new_yaw: f32) {
self.yaw = new_yaw;
}

pub fn update_pitch(&mut self, new_pitch: f32) {
self.pitch = new_pitch;
}
}
1 change: 1 addition & 0 deletions src/lib/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod errors;
pub mod chunks;
pub mod collisions;
pub mod conn;
pub mod data;
pub mod identity;
pub mod mq;
pub mod state;
Expand Down
Loading
Loading