Skip to content
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::prelude::{EventWriter, Res};
use bevy_ecs::system::Query;
use ferrumc_core::transform::position::Position;
use ferrumc_entities::events::SpawnEntityEvent;
use ferrumc_entities::bundles::ZOMBIE_ID;
use ferrumc_core::entities::entity_kind::EntityKind;
use ferrumc_net::ChatMessageReceiver;
use tracing::info;

pub fn handle(
events: Res<ChatMessageReceiver>,
pos_query: Query<&Position>,
mut ev_spawn_entity: EventWriter<SpawnEntityEvent>,
) {
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_entity.write(SpawnEntityEvent {
entity_kind: EntityKind::new(ZOMBIE_ID),
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::SpawnEntityEvent;
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::<SpawnEntityEvent>(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_entity::handle_spawn_entity);
schedule.add_systems(spawn_entities::on_new_entity::broadcast_new_entities);

// Should always be last
schedule.add_systems(connection_killer::connection_killer);
}
2 changes: 2 additions & 0 deletions src/bin/src/systems/spawn_entities/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod spawn_entity;
pub mod on_new_entity;
39 changes: 39 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,39 @@
use bevy_ecs::entity::Entity;
use bevy_ecs::query::Added;
use bevy_ecs::system::Query;
use ferrumc_core::entities::entity_kind::EntityKind;
use ferrumc_core::transform::position::Position;
use ferrumc_core::transform::rotation::Rotation;
use ferrumc_net::connection::StreamWriter;
use ferrumc_net::packets::outgoing::spawn_entity::SpawnEntityPacket;

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

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);
}
}
}
19 changes: 19 additions & 0 deletions src/bin/src/systems/spawn_entities/spawn_entity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use bevy_ecs::prelude::*;
use ferrumc_entities::events::SpawnEntityEvent;
use ferrumc_entities::factory::EntityFactory;

pub fn handle_spawn_entity(
mut commands: Commands,
mut events: EventReader<SpawnEntityEvent>,
) {
for ev in events.read() {
if let Some(entity_id) = EntityFactory::spawn_entity(
&mut commands,
ev.entity_kind,
ev.position.clone(),
) {
tracing::info!("Spawned entity {:?} of kind {:?} at {:?}",
entity_id, ev.entity_kind, ev.position);
}
}
}
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;
25 changes: 25 additions & 0 deletions src/lib/core/src/entities/entity_kind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use bevy_ecs::prelude::Component;

#[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
44 changes: 44 additions & 0 deletions src/lib/entities/src/bundles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::components::{AiComponent, Hostile, Movable, Zombie};
use crate::spawner::SpawnBundleExt;
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_core::transform::position::Position;
use ferrumc_macros::get_registry_entry;

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

pub 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)),
ai: AiComponent::default(),
movable: Movable { speed: 0.25 }, // Slow zombie movement
hostile: Hostile { damage: 2.0, range: 1.5 }, // Zombie specific stats
}
}
}

impl SpawnBundleExt for ZombieBundle {
fn with_position(mut self, position: Position) -> Self {
self.transform.position = position;
self
}
}
Loading