Skip to content

Commit 4df2350

Browse files
committed
feat: allow player to swim
1 parent 89eb66d commit 4df2350

File tree

7 files changed

+140
-1
lines changed

7 files changed

+140
-1
lines changed

src/bin/src/systems/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod lan_pinger;
66
pub mod listeners;
77
mod mq;
88
pub mod new_connections;
9+
mod player_swimming;
910
pub mod shutdown_systems;
1011
pub mod world_sync;
1112

@@ -15,6 +16,7 @@ pub fn register_game_systems(schedule: &mut bevy_ecs::schedule::Schedule) {
1516
schedule.add_systems(chunk_calculator::handle);
1617
schedule.add_systems(chunk_sending::handle);
1718
schedule.add_systems(mq::process);
19+
schedule.add_systems(player_swimming::detect_player_swimming);
1820

1921
// Should always be last
2022
schedule.add_systems(connection_killer::connection_killer);

src/bin/src/systems/new_connections.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use ferrumc_components::{
1010
gameplay_state::ender_chest::EnderChest,
1111
hunger::Hunger,
1212
player_bundle::PlayerBundle,
13+
swimming::SwimmingState,
1314
},
1415
};
1516
use ferrumc_core::{
@@ -102,6 +103,7 @@ pub fn accept_new_connections(
102103
hunger,
103104
experience,
104105
active_effects,
106+
swimming: SwimmingState::default(),
105107
};
106108

107109
// --- 3. Spawn the PlayerBundle, then .insert() the network components ---
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use bevy_ecs::prelude::*;
2+
use bevy_math::IVec3;
3+
use ferrumc_components::player::swimming::SwimmingState;
4+
use ferrumc_core::identity::player_identity::PlayerIdentity;
5+
use ferrumc_core::transform::position::Position;
6+
use ferrumc_net::connection::StreamWriter;
7+
use ferrumc_net::packets::outgoing::entity_metadata::{EntityMetadata, EntityMetadataPacket};
8+
use ferrumc_net_codec::net_types::var_int::VarInt;
9+
use ferrumc_state::GlobalStateResource;
10+
use tracing::error;
11+
12+
/// Height of player's eyes from feet (blocks)
13+
const PLAYER_EYE_HEIGHT: f64 = 1.62;
14+
15+
/// Check if a player is in water by testing at eye level
16+
fn is_player_in_water(state: &ferrumc_state::GlobalState, pos: &Position) -> bool {
17+
let eye_pos = IVec3::new(
18+
pos.x.floor() as i32,
19+
(pos.y + PLAYER_EYE_HEIGHT).floor() as i32,
20+
pos.z.floor() as i32,
21+
);
22+
23+
state
24+
.world
25+
.get_block_and_fetch(eye_pos.x, eye_pos.y, eye_pos.z, "overworld")
26+
.map(|block_state| (86..=101).contains(&block_state.0))
27+
.unwrap_or(false)
28+
}
29+
30+
/// System that detects when players enter/exit water and updates their swimming state
31+
/// Also broadcasts the swimming pose to all connected clients
32+
pub fn detect_player_swimming(
33+
mut swimmers: Query<(&PlayerIdentity, &Position, &mut SwimmingState)>,
34+
all_connections: Query<(Entity, &StreamWriter)>,
35+
state: Res<GlobalStateResource>,
36+
) {
37+
for (identity, pos, mut swimming_state) in swimmers.iter_mut() {
38+
let in_water = is_player_in_water(&state.0, pos);
39+
40+
if in_water && !swimming_state.is_swimming {
41+
swimming_state.start_swimming();
42+
43+
let entity_id = VarInt::new(identity.short_uuid);
44+
let packet = EntityMetadataPacket::new(
45+
entity_id,
46+
[
47+
EntityMetadata::entity_swimming_state(),
48+
EntityMetadata::entity_swimming_pose(),
49+
],
50+
);
51+
52+
broadcast_metadata(&packet, &all_connections, &state);
53+
} else if !in_water && swimming_state.is_swimming {
54+
swimming_state.stop_swimming();
55+
56+
let entity_id = VarInt::new(identity.short_uuid);
57+
let packet = EntityMetadataPacket::new(
58+
entity_id,
59+
[
60+
EntityMetadata::entity_clear_state(),
61+
EntityMetadata::entity_standing(),
62+
],
63+
);
64+
65+
broadcast_metadata(&packet, &all_connections, &state);
66+
}
67+
}
68+
}
69+
70+
/// Helper function to broadcast entity metadata to all connected players
71+
fn broadcast_metadata(
72+
packet: &EntityMetadataPacket,
73+
connections: &Query<(Entity, &StreamWriter)>,
74+
state: &GlobalStateResource,
75+
) {
76+
for (entity, conn) in connections {
77+
if !state.0.players.is_connected(entity) {
78+
continue;
79+
}
80+
if let Err(err) = conn.send_packet_ref(packet) {
81+
error!("Failed to send entity metadata packet: {:?}", err);
82+
}
83+
}
84+
}

src/lib/components/src/player/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ pub mod gamemode;
55
pub mod gameplay_state;
66
pub mod hunger;
77
pub mod player_bundle;
8+
pub mod swimming;
89
pub mod view_distance;

src/lib/components/src/player/player_bundle.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
health::Health,
44
player::{
55
abilities::PlayerAbilities, experience::Experience, gamemode::GameModeComponent,
6-
gameplay_state::ender_chest::EnderChest, hunger::Hunger,
6+
gameplay_state::ender_chest::EnderChest, hunger::Hunger, swimming::SwimmingState,
77
},
88
};
99
use bevy_ecs::prelude::Bundle;
@@ -40,4 +40,7 @@ pub struct PlayerBundle {
4040
pub hunger: Hunger,
4141
pub experience: Experience,
4242
pub active_effects: ActiveEffects,
43+
44+
// Movement State
45+
pub swimming: SwimmingState,
4346
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use bevy_ecs::prelude::Component;
2+
3+
/// Component tracking whether a player is currently swimming
4+
#[derive(Component, Debug, Clone, Copy, Default)]
5+
pub struct SwimmingState {
6+
pub is_swimming: bool,
7+
}
8+
9+
impl SwimmingState {
10+
pub fn new(is_swimming: bool) -> Self {
11+
Self { is_swimming }
12+
}
13+
14+
pub fn start_swimming(&mut self) {
15+
self.is_swimming = true;
16+
}
17+
18+
pub fn stop_swimming(&mut self) {
19+
self.is_swimming = false;
20+
}
21+
}

src/lib/net/src/packets/outgoing/entity_metadata.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,32 @@ pub mod constructors {
8989
EntityMetadataValue::Entity6(EntityPose::Standing),
9090
)
9191
}
92+
93+
/// Entity state with swimming bit set
94+
pub fn entity_swimming_state() -> Self {
95+
Self::new(
96+
EntityMetadataIndexType::Byte,
97+
EntityMetadataValue::Entity0(EntityStateMask::from_state(
98+
EntityState::Swimming,
99+
)),
100+
)
101+
}
102+
103+
/// Entity in swimming pose
104+
pub fn entity_swimming_pose() -> Self {
105+
Self::new(
106+
EntityMetadataIndexType::Pose,
107+
EntityMetadataValue::Entity6(EntityPose::Swimming),
108+
)
109+
}
110+
111+
/// Entity state with all flags cleared (default state)
112+
pub fn entity_clear_state() -> Self {
113+
Self::new(
114+
EntityMetadataIndexType::Byte,
115+
EntityMetadataValue::Entity0(EntityStateMask::new()),
116+
)
117+
}
92118
}
93119
}
94120

0 commit comments

Comments
 (0)