Skip to content

Commit

Permalink
Potions / Status Effects (#558)
Browse files Browse the repository at this point in the history
# Objective

- Potion effects aren't supported by valence yet.
- You can send a packet to tell the client about a potion effect, but
the server still has no idea what they are
<details>
<summary>Example code</summary>

```rs
pub fn add_potion_effect(mut clients: Query<&mut Client>, mut events: EventReader<SneakEvent>) {
    for event in events.iter() {
        if event.state == SneakState::Start {
            if let Ok(mut client) = clients.get_mut(event.client) {
                client.write_packet(&EntityStatusEffectS2c {
                    entity_id: VarInt(0),
                    effect_id: VarInt(22),
                    amplifier: 0,
                    duration: VarInt(600),
                    flags: entity_status_effect_s2c::Flags::new()
                        .with_show_particles(true)
                        .with_show_icon(true),
                    factor_codec: None,
                });
            }
        }
    }
}
```
</details>

- Closes #401
- Also, when the potion effect expires, we need to tell the client that
their potion effect is no longer. Right now, with sending a packet, the
effect doesn't get removed when it goes down to 00:00.

# Solution

I want to add the necessary components and stuff to facilitate potion
effects.

> Note: I'm still somewhat new to rust and very new to bevy, so please
lmk if I can improve anything or if I should do anything differently.
Thanks!

# To do:

- [x] Extractor
- [x] `ActiveStatusEffects` component to handle the actual status effect
applied to the mc entity
- [x] Add `ActiveStatusEffects` component to all entities.
- [x] Make a plugin to handle potion effects
  - [x] Decrease tick count
  - [x] Remove effect (& tell client) when tick count is 0
  - [x] Make the effects do stuff
    - [x] Particles
    - [x] Tell client
- [x] Add tests
- [x] Add example
- [x] Add examples on how to implement potion effects (speed, instant
health, etc.)
- [x] Extract stuff to make it easier to implement potions. See #593 

# Playground

<details>
<summary>Current playground</summary>

```rs
use valence::client::despawn_disconnected_clients;
use valence::entity::active_status_effects::{ActiveStatusEffect, ActiveStatusEffects};
use valence::entity::status_effects::StatusEffect;
use valence::log::LogPlugin;
use valence::network::ConnectionMode;
use valence::prelude::*;

#[allow(unused_imports)]
use crate::extras::*;

const SPAWN_Y: i32 = 64;

pub fn build_app(app: &mut App) {
    app.insert_resource(NetworkSettings {
        connection_mode: ConnectionMode::Offline,
        ..Default::default()
    })
    .add_plugins(DefaultPlugins.build().disable::<LogPlugin>())
    .add_systems(Startup, setup)
    .add_systems(EventLoopUpdate, add_potion_effect)
    .add_systems(Update, (init_clients, despawn_disconnected_clients))
    .run();
}

fn setup(
    mut commands: Commands,
    server: Res<Server>,
    biomes: Res<BiomeRegistry>,
    dimensions: Res<DimensionTypeRegistry>,
) {
    let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server);

    for z in -5..5 {
        for x in -5..5 {
            layer.chunk.insert_chunk([x, z], UnloadedChunk::new());
        }
    }

    for z in -25..25 {
        for x in -25..25 {
            layer
                .chunk
                .set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
        }
    }

    commands.spawn(layer);
}

fn init_clients(
    mut clients: Query<
        (
            &mut EntityLayerId,
            &mut VisibleChunkLayer,
            &mut VisibleEntityLayers,
            &mut Position,
            &mut GameMode,
        ),
        Added<Client>,
    >,
    layers: Query<Entity, (With<ChunkLayer>, With<EntityLayer>)>,
) {
    for (
        mut layer_id,
        mut visible_chunk_layer,
        mut visible_entity_layers,
        mut pos,
        mut game_mode,
    ) in &mut clients
    {
        let layer = layers.single();

        layer_id.0 = layer;
        visible_chunk_layer.0 = layer;
        visible_entity_layers.0.insert(layer);
        pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]);
        *game_mode = GameMode::Survival;
    }
}

pub fn add_potion_effect(
    mut clients: Query<&mut ActiveStatusEffects>,
    mut events: EventReader<SneakEvent>,
) {
    for event in events.iter() {
        if event.state == SneakState::Start {
            if let Ok(mut status) = clients.get_mut(event.client) {
                status.add(
                    ActiveStatusEffect::from_effect(StatusEffect::Wither)
                        .with_amplifier(2)
                        .with_duration(200),
                );
            }
        }
    }
}
```
</details>

---------

Co-authored-by: Carson McManus <[email protected]>
  • Loading branch information
SelfMadeSystem and dyc3 authored Dec 29, 2023
1 parent 257cb64 commit 5123826
Show file tree
Hide file tree
Showing 11 changed files with 1,147 additions and 8 deletions.
15 changes: 12 additions & 3 deletions crates/valence_entity/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,12 @@ type Entities = BTreeMap<String, Entity>;
pub fn main() -> anyhow::Result<()> {
rerun_if_changed(["extracted/misc.json", "extracted/entities.json"]);

write_generated_file(build()?, "entity.rs")
write_generated_file(build_entities()?, "entity.rs")?;

Ok(())
}

fn build() -> anyhow::Result<TokenStream> {
fn build_entities() -> anyhow::Result<TokenStream> {
let entity_types = serde_json::from_str::<EntityTypes>(include_str!("extracted/misc.json"))
.context("failed to deserialize misc.json")?
.entity_type;
Expand Down Expand Up @@ -399,13 +401,20 @@ fn build() -> anyhow::Result<TokenStream> {
bundle_init_fields.extend([quote! {
living_attributes: super::attributes::EntityAttributes::new() #attribute_default_values,
}]);

bundle_fields.extend([quote! {
pub living_attributes_tracker: super::attributes::TrackedEntityAttributes,
}]);

bundle_init_fields.extend([quote! {
living_attributes_tracker: Default::default(),
}]);

bundle_fields.extend([quote! {
pub living_active_status_effects: super::active_status_effects::ActiveStatusEffects,
}]);
bundle_init_fields.extend([quote! {
living_active_status_effects: Default::default(),
}]);
}
"PlayerEntity" => {
bundle_fields.extend([quote! {
Expand Down
8 changes: 4 additions & 4 deletions crates/valence_entity/extracted/entities.json
Original file line number Diff line number Diff line change
Expand Up @@ -4768,9 +4768,9 @@
}
],
"default_bounding_box": {
"size_x": 0.699999988079071,
"size_y": 0.5,
"size_z": 0.699999988079071
"size_x": 1.399999976158142,
"size_y": 0.8999999761581421,
"size_z": 1.399999976158142
}
},
"SquidEntity": {
Expand Down Expand Up @@ -6105,7 +6105,7 @@
"type": "villager_data",
"default_value": {
"type": "plains",
"profession": "nitwit",
"profession": "none",
"level": 1
}
}
Expand Down
Loading

0 comments on commit 5123826

Please sign in to comment.