|
| 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 | +} |
0 commit comments