Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add abilities components on the player #439

Merged
merged 10 commits into from
Aug 8, 2023
Merged
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
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());
}
Loading