Skip to content

Feature/entities #184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ members = [
"src/lib/utils/profiling",
"src/lib/world",
"src/lib/world_gen",
"src/lib/utils/threadpool",
"src/lib/utils/threadpool", "src/lib/entities",
]

#================== Lints ==================#
Expand Down Expand Up @@ -87,6 +87,7 @@ debug = true
ferrumc-anvil = { path = "src/lib/adapters/anvil" }
ferrumc-config = { path = "src/lib/utils/config" }
ferrumc-core = { path = "src/lib/core" }
ferrumc-entities = { path = "src/lib/entities" }
ferrumc-general-purpose = { path = "src/lib/utils/general_purpose" }
ferrumc-logging = { path = "src/lib/utils/logging" }
ferrumc-macros = { path = "src/lib/derive_macros" }
Expand Down Expand Up @@ -193,7 +194,10 @@ noise = "0.9.0"
ctrlc = "3.4.7"
num_cpus = "1.17.0"
typename = "0.1.2"

# Game Logic
bevy_ecs = { version = "0.16.1", features = ["multi_threaded", "trace"] }
glam = "0.30.3"

# I/O
memmap2 = "0.9.5"
Expand Down
1 change: 1 addition & 0 deletions src/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ferrumc-plugins = { workspace = true }
ferrumc-storage = { workspace = true }
ferrumc-utils = { workspace = true }
ferrumc-config = { workspace = true }
ferrumc-entities = { workspace = true }
ferrumc-profiling = { workspace = true }
ferrumc-logging = { workspace = true }
ferrumc-world = { workspace = true }
Expand Down
30 changes: 30 additions & 0 deletions src/bin/src/packet_handlers/play_packets/chat_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use bevy_ecs::entity::Entity;
use bevy_ecs::prelude::{EventWriter, Res};
use bevy_ecs::system::Query;
use ferrumc_core::transform::position::Position;
use ferrumc_entities::events::SpawnZombieEvent;
use ferrumc_net::connection::StreamWriter;
use ferrumc_net::ChatMessageReceiver;
use tracing::info;

pub fn handle(
events: Res<ChatMessageReceiver>,
conn_query: Query<(Entity, &StreamWriter)>,
pos_query: Query<&Position>,
mut ev_spawn_zombie: EventWriter<SpawnZombieEvent>,
) {
for (packet, entity_id) in events.0.try_iter() {
info!("[CHAT] Received message: {}", packet.message);
if matches!(packet.message.as_str(), "zombie") {
info!("[CHAT] Zombie command received, spawning zombie!");

let Ok(pos) = pos_query.get(entity_id) else {
tracing::error!("Failed to get position for entity {:?}", entity_id);
continue;
};
ev_spawn_zombie.write(SpawnZombieEvent {
position: pos.clone(),
});
}
}
}
4 changes: 3 additions & 1 deletion src/bin/src/packet_handlers/play_packets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use bevy_ecs::schedule::Schedule;

mod chunk_batch_ack;
pub mod chat_message;
pub mod chunk_batch_ack;
mod confirm_player_teleport;
mod keep_alive;
mod place_block;
Expand All @@ -15,6 +16,7 @@ mod swing_arm;
pub fn register_packet_handlers(schedule: &mut Schedule) {
// Added separately so if we mess up the signature of one of the systems we can know exactly
// which one
schedule.add_systems(chat_message::handle);
schedule.add_systems(chunk_batch_ack::handle);
schedule.add_systems(confirm_player_teleport::handle);
schedule.add_systems(keep_alive::handle);
Expand Down
4 changes: 4 additions & 0 deletions src/bin/src/register_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ use bevy_ecs::prelude::World;
use ferrumc_core::chunks::cross_chunk_boundary_event::CrossChunkBoundaryEvent;
use ferrumc_core::conn::conn_kill_event::ConnectionKillEvent;
use ferrumc_core::conn::force_player_recount_event::ForcePlayerRecountEvent;
use ferrumc_entities::events::SpawnZombieEvent;
use ferrumc_net::packets::packet_events::TransformEvent;

pub fn register_events(world: &mut World) {
EventRegistry::register_event::<TransformEvent>(world);
EventRegistry::register_event::<ConnectionKillEvent>(world);
EventRegistry::register_event::<CrossChunkBoundaryEvent>(world);
EventRegistry::register_event::<ForcePlayerRecountEvent>(world);

// idk if they should be added here, but just for testing purposes:
EventRegistry::register_event::<SpawnZombieEvent>(world);
}
4 changes: 4 additions & 0 deletions src/bin/src/systems/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod keep_alive_system;
pub mod new_connections;
mod player_count_update;
pub mod send_chunks;
pub mod spawn_entities;
mod world_sync;

pub fn register_game_systems(schedule: &mut bevy_ecs::schedule::Schedule) {
Expand All @@ -13,6 +14,9 @@ pub fn register_game_systems(schedule: &mut bevy_ecs::schedule::Schedule) {
schedule.add_systems(player_count_update::player_count_updater);
schedule.add_systems(world_sync::sync_world);

schedule.add_systems(spawn_entities::spawn_zombie::handle_spawn_zombie);
schedule.add_systems(spawn_entities::on_new_entity::broadcast_new_entities);

// Should always be last
schedule.add_systems(connection_killer::connection_killer);
}
3 changes: 3 additions & 0 deletions src/bin/src/systems/spawn_entities/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod spawn_zombie;

pub mod on_new_entity;
41 changes: 41 additions & 0 deletions src/bin/src/systems/spawn_entities/on_new_entity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use bevy_ecs::entity::Entity;
use bevy_ecs::query::Added;
use bevy_ecs::system::Query;
use bevy_ecs::world::World;
use ferrumc_core::entities::entity_kind::EntityKind;
use ferrumc_core::transform::position::Position;
use ferrumc_core::transform::rotation::Rotation;
use ferrumc_entities::components::Zombie;
use ferrumc_net::connection::StreamWriter;
use ferrumc_net::packets::outgoing::spawn_entity::SpawnEntityPacket;

pub fn broadcast_new_entities(
query: Query<(Entity, &Zombie), Added<Zombie>>,
players_query: Query<(Entity, &mut StreamWriter)>,
transforms: Query<(&Position, &Rotation, &EntityKind)>,
) {
for (entity, zombie) in query.iter() {
tracing::info!("New Zombie spawned: Entity {:?}, Zombie", entity);

let (pos, rot, kind) = transforms
.get(entity)
.unwrap_or_else(|_| panic!("Missing transform for {:?}", entity));

let packet = SpawnEntityPacket::entity(entity, pos, rot, kind);

if let Ok(packet) = packet {
for (player_entity, writer) in players_query.iter() {
tracing::debug!("Sending SpawnEntityPacket to player: {:?}", player_entity);
if let Err(e) = writer.send_packet(packet.clone()) {
tracing::error!(
"Failed to send SpawnEntityPacket to player {:?}: {}",
player_entity,
e
);
}
}
} else {
tracing::error!("Failed to create SpawnEntityPacket for entity {:?}", entity);
}
}
}
27 changes: 27 additions & 0 deletions src/bin/src/systems/spawn_entities/spawn_zombie.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use bevy_ecs::prelude::*;
use ferrumc_core::transform::position::Position;
use ferrumc_entities::bundles::ZombieBundle;
use ferrumc_entities::events::SpawnZombieEvent;

pub fn handle_spawn_zombie(mut commands: Commands, mut ev_zombie: EventReader<SpawnZombieEvent>) {
for ev in ev_zombie.read() {
commands.spawn(
ZombieBundle {
transform: ZombieBundle::default().transform, // copy defaults
..ZombieBundle::default() // other defaults
}
.with_position(ev.position.clone()),
); // see builder below
}
}

// quick builder helper
trait WithPosition {
fn with_position(self, pos: Position) -> Self;
}
impl WithPosition for ZombieBundle {
fn with_position(mut self, pos: Position) -> Self {
self.transform.position = pos;
self
}
}
2 changes: 1 addition & 1 deletion src/bin/src/systems/world_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub fn sync_world(state: Res<GlobalStateResource>, mut last_synced: ResMut<World

// Check if the world needs to be synced
if last_synced.last_synced.elapsed().as_secs() >= 15 {
tracing::info!("Syncing world...");
tracing::trace!("Syncing world...");
state.0.world.sync().expect("Failed to sync world");

// Update the last synced time
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 @@ -10,6 +10,8 @@ bevy_ecs = { workspace = true }
tracing = { workspace = true }
typename = { workspace = true }
ferrumc-net-codec = { workspace = true }
glam = { workspace = true}
ferrumc-macros = { workspace = true }

[dev-dependencies]
criterion = { workspace = true }
Expand Down
15 changes: 15 additions & 0 deletions src/lib/core/src/collisions/bounding_box.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use bevy_ecs::prelude::Component;
use glam::Vec3;

#[derive(Component)]
pub struct BoundingBox {
pub half_extents: Vec3, // (0.3, 0.9, 0.3) for zombie-ish size
}

impl BoundingBox {
pub fn new(half_extents: impl Into<Vec3>) -> Self {
BoundingBox {
half_extents: half_extents.into(),
}
}
}
1 change: 1 addition & 0 deletions src/lib/core/src/collisions/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod bounding_box;
pub mod bounds;
26 changes: 26 additions & 0 deletions src/lib/core/src/entities/entity_kind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use bevy_ecs::prelude::Component;
use ferrumc_macros::get_registry_entry;

#[derive(Component, Debug, Clone, Copy)]
pub struct EntityKind {
/// The id of the entity kind. (Found in the registry under minecraft:entity_type)
r#type: u64,
}

impl EntityKind {
/// Creates a new `EntityKind` with the given type id.
pub fn new(r#type: u64) -> Self {
Self { r#type }
}

/// Returns the type id of the entity kind.
pub fn get_id(&self) -> u64 {
self.r#type
}
}

impl From<u64> for EntityKind {
fn from(r#type: u64) -> Self {
Self::new(r#type)
}
}
18 changes: 18 additions & 0 deletions src/lib/core/src/entities/health.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use bevy_ecs::prelude::Component;
use typename::TypeName;

#[derive(TypeName, Debug, Component)]
pub struct Health {
pub current: f32,
pub max: f32,
}

impl Health {
pub fn new(current: f32, max: f32) -> Self {
Self { current, max }
}
/// New health with the same current and max value
pub fn new_max(max: f32) -> Self {
Self { current: max, max }
}
}
2 changes: 2 additions & 0 deletions src/lib/core/src/entities/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod entity_kind;
pub mod health;
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 entities;
pub mod identity;
pub mod state;
pub mod transform;
21 changes: 21 additions & 0 deletions src/lib/core/src/transform/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
use bevy_ecs::prelude::Bundle;

pub mod grounded;
pub mod position;
pub mod rotation;

#[derive(Bundle)]
pub struct Transform {
pub position: position::Position,
pub rotation: rotation::Rotation,
pub grounded: grounded::OnGround,
}
impl Transform {
pub fn new(
position: impl Into<position::Position>,
rotation: impl Into<rotation::Rotation>,
) -> Self {
Transform {
position: position.into(),
rotation: rotation.into(),
grounded: grounded::OnGround::default(),
}
}
}
2 changes: 1 addition & 1 deletion src/lib/core/src/transform/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ferrumc_net_codec::net_types::network_position::NetworkPosition;
use std::fmt::{Debug, Display, Formatter};
use typename::TypeName;

#[derive(TypeName, Component)]
#[derive(TypeName, Component, Clone)]
pub struct Position {
pub x: f64,
pub y: f64,
Expand Down
14 changes: 14 additions & 0 deletions src/lib/entities/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "ferrumc-entities"
version = "0.1.0"
edition = "2024"

[dependencies]
bevy_ecs = { workspace = true }

ferrumc-core = { workspace = true }
ferrumc-macros = { workspace = true }
glam = { workspace = true }

[lints]
workspace = true
30 changes: 30 additions & 0 deletions src/lib/entities/src/bundles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use crate::components::Zombie;
use bevy_ecs::bundle::Bundle;
use ferrumc_core::collisions::bounding_box::BoundingBox;
use ferrumc_core::entities::entity_kind::EntityKind;
use ferrumc_core::entities::health::Health;
use ferrumc_core::transform::Transform;
use ferrumc_macros::get_registry_entry;
use glam::vec3;

#[derive(Bundle)]
pub struct ZombieBundle {
pub zombie: Zombie,
pub entity_kind: EntityKind,
pub transform: Transform,
pub health: Health,
pub bounding_box: BoundingBox,
}

const ZOMBIE_ID: u64 = get_registry_entry!("minecraft:entity_type.entries.minecraft:zombie");
impl Default for ZombieBundle {
fn default() -> Self {
ZombieBundle {
zombie: Zombie,
entity_kind: EntityKind::new(ZOMBIE_ID),
transform: Transform::new((0.0, 64.0, 0.0), (0.0, 0.0)),
health: Health::new_max(20.0),
bounding_box: BoundingBox::new((0.3, 0.9, 0.3)),
}
}
}
5 changes: 5 additions & 0 deletions src/lib/entities/src/components.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use bevy_ecs::prelude::*;

/// Marker type for zombie
#[derive(Component)]
pub struct Zombie;
7 changes: 7 additions & 0 deletions src/lib/entities/src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use bevy_ecs::prelude::Event;
use ferrumc_core::transform::position::Position;

#[derive(Clone, Event)]
pub struct SpawnZombieEvent {
pub position: Position,
}
3 changes: 3 additions & 0 deletions src/lib/entities/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod bundles;
pub mod components;
pub mod events;
7 changes: 7 additions & 0 deletions src/lib/net/crates/codec/src/decode/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ pub enum NetDecodeError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),

#[error("Invalid length: expected {expected}, got {actual}")]
InvalidLength {
expected: usize,
actual: usize,
field: String,
},

#[error("Invalid UTF-8: {0}")]
Utf8Error(#[from] std::string::FromUtf8Error),

Expand Down
Loading
Loading