Skip to content

Commit

Permalink
Add abilities components on the player (#439)
Browse files Browse the repository at this point in the history
# Objective

see #438 

# Solution

The client is updated when a change is detected on one of the player
abilities component.
  • Loading branch information
Bafbi authored Aug 8, 2023
1 parent add642c commit 8759a21
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 4 deletions.
162 changes: 162 additions & 0 deletions crates/valence_client/src/abilities.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
pub use valence_packet::packets::play::player_abilities_s2c::PlayerAbilitiesFlags;
use valence_packet::packets::play::{PlayerAbilitiesS2c, UpdatePlayerAbilitiesC2s};

use super::*;
use crate::event_loop::{EventLoopPreUpdate, PacketEvent};

/// [`Component`] that stores the player's flying speed ability.
///
/// [`Default`] value: `0.05`.
#[derive(Component)]
pub struct FlyingSpeed(pub f32);

impl Default for FlyingSpeed {
fn default() -> Self {
Self(0.05)
}
}

/// [`Component`] that stores the player's field of view modifier ability.
/// The lower the value, the higher the field of view.
///
/// [`Default`] value: `0.1`.
#[derive(Component)]
pub struct FovModifier(pub f32);

impl Default for FovModifier {
fn default() -> Self {
Self(0.1)
}
}

/// Send if the client sends [`UpdatePlayerAbilitiesC2s::StartFlying`]
#[derive(Event)]
pub struct PlayerStartFlyingEvent {
pub client: Entity,
}

/// Send if the client sends [`UpdatePlayerAbilitiesC2s::StopFlying`]
#[derive(Event)]
pub struct PlayerStopFlyingEvent {
pub client: Entity,
}

/// Order of execution :
/// 1. [`update_game_mode`] : Watch [`GameMode`] changes => Send
/// [`GameStateChangeS2c`] to update the client's gamemode
/// 2. [`update_client_player_abilities`] : Watch [`PlayerAbilitiesFlags`],
/// [`FlyingSpeed`] and [`FovModifier`] changes => Send [`PlayerAbilitiesS2c`]
/// to update the client's abilities 3. [`update_player_abilities`] : Watch
/// [`GameMode`] changes => Update [`PlayerAbilitiesFlags`] according to the
/// [`GameMode`] 4. [`update_server_player_abilities`] : Watch
/// [`UpdatePlayerAbilitiesC2s`] packets => Update [`PlayerAbilitiesFlags`]
/// according to the packet
pub(super) fn build(app: &mut App) {
app.add_event::<PlayerStartFlyingEvent>()
.add_event::<PlayerStopFlyingEvent>()
.add_systems(
PostUpdate,
(
update_client_player_abilities,
update_player_abilities.before(update_client_player_abilities),
)
.in_set(UpdateClientsSet)
.after(update_game_mode),
)
.add_systems(EventLoopPreUpdate, update_server_player_abilities);
}

fn update_client_player_abilities(
mut clients_query: Query<
(
&mut Client,
&PlayerAbilitiesFlags,
&FlyingSpeed,
&FovModifier,
),
Or<(
Changed<PlayerAbilitiesFlags>,
Changed<FlyingSpeed>,
Changed<FovModifier>,
)>,
>,
) {
for (mut client, flags, flying_speed, fov_modifier) in clients_query.iter_mut() {
client.write_packet(&PlayerAbilitiesS2c {
flags: *flags,
flying_speed: flying_speed.0,
fov_modifier: fov_modifier.0,
})
}
}

/// /!\ This system does not trigger change detection on
/// [`PlayerAbilitiesFlags`]
fn update_player_abilities(
mut player_start_flying_event_writer: EventWriter<PlayerStartFlyingEvent>,
mut player_stop_flying_event_writer: EventWriter<PlayerStopFlyingEvent>,
mut client_query: Query<(Entity, &mut PlayerAbilitiesFlags, &GameMode), Changed<GameMode>>,
) {
for (entity, mut mut_flags, gamemode) in client_query.iter_mut() {
let flags = mut_flags.bypass_change_detection();
match gamemode {
GameMode::Creative => {
flags.set_invulnerable(true);
flags.set_allow_flying(true);
flags.set_instant_break(true);
}
GameMode::Spectator => {
flags.set_invulnerable(true);
flags.set_allow_flying(true);
flags.set_instant_break(false);
flags.set_flying(true);
player_start_flying_event_writer.send(PlayerStartFlyingEvent { client: entity });
}
GameMode::Survival => {
flags.set_invulnerable(false);
flags.set_allow_flying(false);
flags.set_instant_break(false);
flags.set_flying(false);
player_stop_flying_event_writer.send(PlayerStopFlyingEvent { client: entity });
}
GameMode::Adventure => {
flags.set_invulnerable(false);
flags.set_allow_flying(false);
flags.set_instant_break(false);
flags.set_flying(false);
player_stop_flying_event_writer.send(PlayerStopFlyingEvent { client: entity });
}
}
}
}

/// /!\ This system does not trigger change detection on
/// [`PlayerAbilitiesFlags`]
fn update_server_player_abilities(
mut packet_events: EventReader<PacketEvent>,
mut player_start_flying_event_writer: EventWriter<PlayerStartFlyingEvent>,
mut player_stop_flying_event_writer: EventWriter<PlayerStopFlyingEvent>,
mut client_query: Query<&mut PlayerAbilitiesFlags>,
) {
for packets in packet_events.iter() {
if let Some(pkt) = packets.decode::<UpdatePlayerAbilitiesC2s>() {
if let Ok(mut mut_flags) = client_query.get_mut(packets.client) {
let flags = mut_flags.bypass_change_detection();
match pkt {
UpdatePlayerAbilitiesC2s::StartFlying => {
flags.set_flying(true);
player_start_flying_event_writer.send(PlayerStartFlyingEvent {
client: packets.client,
});
}
UpdatePlayerAbilitiesC2s::StopFlying => {
flags.set_flying(false);
player_stop_flying_event_writer.send(PlayerStopFlyingEvent {
client: packets.client,
});
}
}
}
}
}
}
8 changes: 8 additions & 0 deletions crates/valence_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ use valence_packet::protocol::encode::{PacketEncoder, WritePacket};
use valence_packet::protocol::Packet;
use valence_registry::RegistrySet;

pub mod abilities;
pub mod action;
pub mod command;
pub mod custom_payload;
Expand Down Expand Up @@ -160,6 +161,7 @@ impl Plugin for ClientPlugin {
op_level::build(app);
resource_pack::build(app);
status::build(app);
abilities::build(app);
}
}

Expand Down Expand Up @@ -196,6 +198,9 @@ pub struct ClientBundle {
pub is_debug: spawn::IsDebug,
pub is_flat: spawn::IsFlat,
pub portal_cooldown: spawn::PortalCooldown,
pub player_abilities_flags: abilities::PlayerAbilitiesFlags,
pub flying_speed: abilities::FlyingSpeed,
pub fov_modifier: abilities::FovModifier,
pub player: PlayerEntityBundle,
}

Expand Down Expand Up @@ -234,6 +239,9 @@ impl ClientBundle {
reduced_debug_info: spawn::ReducedDebugInfo::default(),
is_debug: spawn::IsDebug::default(),
portal_cooldown: spawn::PortalCooldown::default(),
player_abilities_flags: abilities::PlayerAbilitiesFlags::default(),
flying_speed: abilities::FlyingSpeed::default(),
fov_modifier: abilities::FovModifier::default(),
player: PlayerEntityBundle {
uuid: UniqueId(args.uuid),
..Default::default()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use bevy_ecs::prelude::Component;

use super::*;

#[derive(Clone, Debug, Encode, Decode, Packet)]
Expand All @@ -8,8 +10,9 @@ pub struct PlayerAbilitiesS2c {
pub fov_modifier: f32,
}

/// [`Component`] that stores the player's abilities flags.
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
#[derive(PartialEq, Eq, Encode, Decode, Component, Default)]
pub struct PlayerAbilitiesFlags {
pub invulnerable: bool,
pub flying: bool,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::*;

#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[derive(Copy, Clone, Debug, Encode, Decode, Packet, PartialEq, Eq)]
#[packet(id = packet_id::UPDATE_PLAYER_ABILITIES_C2S)]
pub enum UpdatePlayerAbilitiesC2s {
#[packet(tag = 0b00)]
Expand Down
14 changes: 13 additions & 1 deletion examples/anvil_loading.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#![allow(clippy::type_complexity)]

use std::path::PathBuf;

use clap::Parser;
use valence::prelude::*;
use valence_anvil::{AnvilLevel, ChunkLoadEvent, ChunkLoadStatus};
use valence_client::abilities::{FlyingSpeed, FovModifier, PlayerAbilitiesFlags};
use valence_client::message::SendMessage;

const SPAWN_POS: DVec3 = DVec3::new(0.0, 256.0, 0.0);
Expand Down Expand Up @@ -78,6 +81,9 @@ fn init_clients(
&mut VisibleEntityLayers,
&mut Position,
&mut GameMode,
&mut PlayerAbilitiesFlags,
&mut FlyingSpeed,
&mut FovModifier,
),
Added<Client>,
>,
Expand All @@ -89,6 +95,9 @@ fn init_clients(
mut visible_entity_layers,
mut pos,
mut game_mode,
mut abilities,
mut flying_speed,
mut fov_modifier,
) in &mut clients
{
let layer = layers.single();
Expand All @@ -97,7 +106,10 @@ fn init_clients(
visible_chunk_layer.0 = layer;
visible_entity_layers.0.insert(layer);
pos.set(SPAWN_POS);
*game_mode = GameMode::Spectator;
*game_mode = GameMode::Adventure;
abilities.set_allow_flying(true);
flying_speed.0 = 0.1;
fov_modifier.0 = 0.05;
}
}

Expand Down
29 changes: 28 additions & 1 deletion examples/cow_sphere.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use std::f64::consts::TAU;

use glam::{DQuat, EulerRot};
use valence::prelude::*;
use valence_client::abilities::{PlayerStartFlyingEvent, PlayerStopFlyingEvent};
use valence_client::message::SendMessage;
use valence_core::text::color::NamedColor;

type SpherePartBundle = valence::entity::cow::CowEntityBundle;

Expand All @@ -25,7 +28,12 @@ fn main() {
.add_systems(Startup, setup)
.add_systems(
Update,
(init_clients, update_sphere, despawn_disconnected_clients),
(
init_clients,
update_sphere,
despawn_disconnected_clients,
display_is_flying,
),
)
.run();
}
Expand Down Expand Up @@ -142,3 +150,22 @@ fn fibonacci_spiral(n: usize) -> impl Iterator<Item = DVec3> {
fn lerp(a: f64, b: f64, t: f64) -> f64 {
a * (1.0 - t) + b * t
}

// Send an actionbar message to all clients when their flying state changes.
fn display_is_flying(
mut player_start_flying_events: EventReader<PlayerStartFlyingEvent>,
mut player_stop_flying_events: EventReader<PlayerStopFlyingEvent>,
mut clients: Query<&mut Client>,
) {
for event in player_start_flying_events.iter() {
if let Ok(mut client) = clients.get_mut(event.client) {
client.send_action_bar_message("You are flying!".color(NamedColor::Green));
}
}

for event in player_stop_flying_events.iter() {
if let Ok(mut client) = clients.get_mut(event.client) {
client.send_action_bar_message("You are no longer flying!".color(NamedColor::Red));
}
}
}
43 changes: 43 additions & 0 deletions src/tests/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use glam::DVec3;
use valence_client::abilities::PlayerAbilitiesFlags;
use valence_core::chunk_pos::ChunkPos;
use valence_core::game_mode::GameMode;
use valence_layer::chunk::UnloadedChunk;
use valence_layer::ChunkLayer;
use valence_packet::packets::play::{
Expand Down Expand Up @@ -60,3 +62,44 @@ fn client_teleport_and_move() {
.collect_received()
.assert_count::<MoveRelativeS2c>(1);
}

#[test]
fn client_gamemode_changed_ability() {
let mut senario = ScenarioSingleClient::new();

*senario
.app
.world
.get_mut::<GameMode>(senario.client)
.unwrap() = GameMode::Creative;

senario.app.update();

let abilities = senario
.app
.world
.get::<PlayerAbilitiesFlags>(senario.client)
.unwrap();

assert!(abilities.allow_flying());
assert!(abilities.instant_break());
assert!(abilities.invulnerable());

*senario
.app
.world
.get_mut::<GameMode>(senario.client)
.unwrap() = GameMode::Adventure;

senario.app.update();

let abilities = senario
.app
.world
.get::<PlayerAbilitiesFlags>(senario.client)
.unwrap();

assert!(!abilities.allow_flying());
assert!(!abilities.instant_break());
assert!(!abilities.invulnerable());
}

0 comments on commit 8759a21

Please sign in to comment.