Skip to content

Commit 151be16

Browse files
committed
feat : enable to spawn an entity with /spawnpig
1 parent c20941e commit 151be16

31 files changed

+656
-2
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ members = [
2323
"src/lib/core/state",
2424
"src/lib/derive_macros",
2525
"src/lib/derive_macros",
26+
"src/lib/entities",
2627
"src/lib/net",
2728
"src/lib/net/crates/codec",
2829
"src/lib/net/crates/encryption",
@@ -93,6 +94,7 @@ ferrumc-anvil = { path = "src/lib/adapters/anvil" }
9394
ferrumc-config = { path = "src/lib/config" }
9495
ferrumc-core = { path = "src/lib/core" }
9596
ferrumc-default-commands = { path = "src/lib/default_commands" }
97+
ferrumc-entities = { path = "src/lib/entities" }
9698
ferrumc-commands = { path = "src/lib/commands" }
9799
ferrumc-general-purpose = { path = "src/lib/utils/general_purpose" }
98100
ferrumc-logging = { path = "src/lib/utils/logging" }
@@ -213,4 +215,4 @@ criterion = { version = "0.7.0", features = ["html_reports"] }
213215

214216

215217
bitcode = { git = "https://github.com/SoftbearStudios/bitcode" }
216-
bitcode_derive = { git = "https://github.com/SoftbearStudios/bitcode" }
218+
bitcode_derive = { git = "https://github.com/SoftbearStudios/bitcode" }

src/bin/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ thiserror = { workspace = true }
1212
ferrumc-core = { workspace = true }
1313
bevy_ecs = { workspace = true }
1414
ferrumc-scheduler = { workspace = true }
15+
ferrumc-entities = { workspace = true }
1516

1617
ferrumc-net = { workspace = true }
1718
ferrumc-net-codec = { workspace = true }

src/bin/src/register_events.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use bevy_ecs::prelude::World;
33
use ferrumc_commands::events::{CommandDispatchEvent, ResolvedCommandDispatchEvent};
44
use ferrumc_core::chunks::cross_chunk_boundary_event::CrossChunkBoundaryEvent;
55
use ferrumc_core::conn::force_player_recount_event::ForcePlayerRecountEvent;
6+
use ferrumc_entities::SpawnEntityEvent;
67
use ferrumc_net::packets::packet_events::TransformEvent;
78

89
pub fn register_events(world: &mut World) {
@@ -11,4 +12,5 @@ pub fn register_events(world: &mut World) {
1112
EventRegistry::register_event::<ForcePlayerRecountEvent>(world);
1213
EventRegistry::register_event::<CommandDispatchEvent>(world);
1314
EventRegistry::register_event::<ResolvedCommandDispatchEvent>(world);
15+
EventRegistry::register_event::<SpawnEntityEvent>(world);
1416
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use bevy_ecs::prelude::*;
2+
use ferrumc_core::transform::grounded::OnGround;
3+
use ferrumc_core::transform::position::Position;
4+
use ferrumc_entities::components::*;
5+
6+
const GRAVITY: f64 = -0.08; // Blocks per tick^2
7+
const TERMINAL_VELOCITY: f64 = -3.92; // Max fall speed
8+
9+
/// System that apply basic physics to entity
10+
pub fn entity_physics_system(
11+
mut query: Query<(&mut Position, &mut Velocity, &OnGround), With<EntityType>>,
12+
) {
13+
for (mut pos, mut vel, on_ground) in query.iter_mut() {
14+
// Apply gravity if not on ground
15+
if !on_ground.0 {
16+
vel.y = (vel.y + GRAVITY).max(TERMINAL_VELOCITY);
17+
} else {
18+
// Reset velocity Y if on ground
19+
if vel.y < 0.0 {
20+
vel.y = 0.0;
21+
}
22+
}
23+
24+
pos.x += vel.x;
25+
pos.y += vel.y;
26+
pos.z += vel.z;
27+
28+
if on_ground.0 {
29+
vel.x *= 0.6;
30+
vel.z *= 0.6;
31+
} else {
32+
vel.x *= 0.98;
33+
vel.z *= 0.98;
34+
}
35+
}
36+
}
37+
38+
pub fn entity_age_system(mut query: Query<&mut Age, With<EntityType>>) {
39+
for mut age in query.iter_mut() {
40+
age.tick();
41+
}
42+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use bevy_ecs::prelude::*;
2+
use ferrumc_core::transform::position::Position;
3+
use ferrumc_entities::components::SyncedToPlayers;
4+
use ferrumc_entities::types::passive::pig::PigBundle;
5+
use ferrumc_entities::SpawnEntityEvent;
6+
use ferrumc_state::GlobalStateResource;
7+
use tracing::info;
8+
9+
/// System that listen spawn event and create entity
10+
pub fn entity_spawner_system(
11+
mut commands: Commands,
12+
mut spawn_events: EventReader<SpawnEntityEvent>,
13+
_global_state: Res<GlobalStateResource>,
14+
) {
15+
for event in spawn_events.read() {
16+
// Generate new entity ID
17+
let entity_id = generate_entity_id();
18+
19+
match event.entity_type {
20+
ferrumc_entities::components::EntityType::Pig => {
21+
let pig = PigBundle::new(
22+
entity_id,
23+
Position::new(event.position.x, event.position.y, event.position.z),
24+
);
25+
commands.spawn((pig, SyncedToPlayers::default()));
26+
info!("Spawned pig at {:?}", event.position);
27+
}
28+
_ => {
29+
tracing::warn!("Entity type {:?} not yet implemented", event.entity_type);
30+
}
31+
}
32+
}
33+
}
34+
35+
// TODO: Implémente true ID generator
36+
static mut NEXT_ENTITY_ID: i32 = 1000;
37+
fn generate_entity_id() -> i32 {
38+
unsafe {
39+
let id = NEXT_ENTITY_ID;
40+
NEXT_ENTITY_ID += 1;
41+
id
42+
}
43+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use bevy_ecs::prelude::*;
2+
use ferrumc_core::identity::player_identity::PlayerIdentity;
3+
use ferrumc_core::transform::position::Position;
4+
use ferrumc_core::transform::rotation::Rotation;
5+
use ferrumc_entities::components::*; // Includes SyncedToPlayers
6+
use ferrumc_entities::types::passive::pig::EntityUuid;
7+
use ferrumc_net::connection::StreamWriter;
8+
use ferrumc_net::packets::outgoing::spawn_entity::SpawnEntityPacket;
9+
use tracing::{debug, error};
10+
11+
/// System that send new entities to players
12+
pub fn entity_sync_system(
13+
// All non-player entities they needed to be sync
14+
mut entity_query: Query<
15+
(
16+
Entity,
17+
&EntityType,
18+
&EntityId,
19+
&EntityUuid,
20+
&Position,
21+
&Rotation,
22+
&mut SyncedToPlayers,
23+
),
24+
Without<PlayerIdentity>,
25+
>,
26+
27+
// All connected players
28+
player_query: Query<(Entity, &StreamWriter, &Position), With<PlayerIdentity>>,
29+
) {
30+
for (entity, entity_type, entity_id, entity_uuid, pos, rot, mut synced) in
31+
entity_query.iter_mut()
32+
{
33+
for (player_entity, stream_writer, player_pos) in player_query.iter() {
34+
// Skip if already send to the player
35+
if synced.player_entities.contains(&player_entity) {
36+
continue;
37+
}
38+
39+
// TODO: Check distance (render distance)
40+
let distance = ((pos.x - player_pos.x).powi(2) + (pos.z - player_pos.z).powi(2)).sqrt();
41+
42+
if distance > 128.0 {
43+
// 8 chunks de distance
44+
continue;
45+
}
46+
47+
// Create and send spawn packet
48+
let protocol_id = entity_type.protocol_id();
49+
debug!(
50+
"Spawning {:?} (protocol_id={}) at ({:.2}, {:.2}, {:.2}) for player {:?}",
51+
entity_type, protocol_id, pos.x, pos.y, pos.z, player_entity
52+
);
53+
54+
let spawn_packet = SpawnEntityPacket::entity(
55+
entity_id.0,
56+
entity_uuid.0.as_u128(),
57+
protocol_id,
58+
pos,
59+
rot,
60+
);
61+
62+
if let Err(e) = stream_writer.send_packet(spawn_packet) {
63+
error!("Failed to send spawn packet: {:?}", e);
64+
continue;
65+
}
66+
67+
// TODO: Send EntityMetadataPacket here to properly display the entity
68+
// The EntityMetadata constructors are not publicly exported yet
69+
// This might be why the pig appears as a phantom!
70+
71+
synced.player_entities.push(player_entity);
72+
debug!(
73+
"Successfully sent entity {:?} (ID: {}) to player {:?}",
74+
entity, entity_id.0, player_entity
75+
);
76+
}
77+
}
78+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use bevy_ecs::prelude::*;
2+
use ferrumc_entities::types::passive::pig_data::PigData;
3+
use ferrumc_entities::GameEntity;
4+
use ferrumc_state::GlobalStateResource;
5+
6+
/// System that ticks pig entities to update their AI/behavior
7+
pub fn pig_tick_system(
8+
mut pigs: Query<&mut PigData>,
9+
state: Res<GlobalStateResource>,
10+
mut commands: Commands,
11+
) {
12+
for mut pig_data in pigs.iter_mut() {
13+
pig_data.tick(&state.0, &mut commands);
14+
}
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pub mod entity_movement;
2+
pub mod entity_spawner;
3+
pub mod entity_sync;
4+
pub mod entity_tick;
5+
pub mod spawn_command_processor;
6+
7+
use bevy_ecs::schedule::Schedule;
8+
9+
/// Save all systems bind to entities
10+
pub fn register_entity_systems(schedule: &mut Schedule) {
11+
schedule.add_systems((
12+
spawn_command_processor::spawn_command_processor_system, // Process spawn commands from /spawnpig
13+
entity_spawner::entity_spawner_system,
14+
entity_tick::pig_tick_system, // Tick AI/behavior for pigs
15+
entity_movement::entity_physics_system,
16+
entity_movement::entity_age_system,
17+
entity_sync::entity_sync_system,
18+
));
19+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use bevy_ecs::prelude::*;
2+
use ferrumc_core::transform::position::Position;
3+
use ferrumc_entities::{drain_spawn_requests, SpawnEntityEvent};
4+
use tracing::{debug, warn};
5+
6+
/// System that processes spawn commands from the queue and sends spawn events
7+
pub fn spawn_command_processor_system(
8+
query: Query<&Position>,
9+
mut spawn_events: EventWriter<SpawnEntityEvent>,
10+
) {
11+
let requests = drain_spawn_requests();
12+
13+
for request in requests {
14+
// Get player position
15+
if let Ok(pos) = query.get(request.player_entity) {
16+
// Spawn entity slightly in front of the player (2 blocks away)
17+
let spawn_pos = Position::new(pos.x + 2.0, pos.y, pos.z + 2.0);
18+
19+
debug!(
20+
"Processing spawn command: {:?} at ({:.2}, {:.2}, {:.2})",
21+
request.entity_type, spawn_pos.x, spawn_pos.y, spawn_pos.z
22+
);
23+
24+
spawn_events.write(SpawnEntityEvent {
25+
entity_type: request.entity_type,
26+
position: spawn_pos,
27+
});
28+
} else {
29+
warn!(
30+
"Failed to get position for entity {:?}",
31+
request.player_entity
32+
);
33+
}
34+
}
35+
}

src/bin/src/systems/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod connection_killer;
22
mod cross_chunk_boundary;
3+
pub mod entities;
34
pub mod keep_alive_system;
45
pub mod lan_pinger;
56
mod mq;
@@ -15,7 +16,7 @@ pub fn register_game_systems(schedule: &mut bevy_ecs::schedule::Schedule) {
1516
schedule.add_systems(new_connections::accept_new_connections);
1617
schedule.add_systems(cross_chunk_boundary::cross_chunk_boundary);
1718
schedule.add_systems(mq::process);
18-
19+
entities::register_entity_systems(schedule);
1920
// Should always be last
2021
schedule.add_systems(connection_killer::connection_killer);
2122
}

0 commit comments

Comments
 (0)