mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-10 06:41:30 +11:00
Advancement api (#329)
## Description Did an api for advancements. Issue: https://github.com/valence-rs/valence/issues/325 Each advancement is an entity, it's children is either criteria, either advancement. Root advancement has no parent. Also did an event AdvancementTabChange (listens if client changes advancement's tab) ## Test Plan Use an example "advancements"
This commit is contained in:
parent
1d2d8f888e
commit
41bcd1eb2c
|
@ -20,6 +20,7 @@ atty = "0.2.14"
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
bevy_app = { version = "0.10.1", default-features = false }
|
bevy_app = { version = "0.10.1", default-features = false }
|
||||||
bevy_ecs = { version = "0.10.1", default-features = false, features = ["trace"] }
|
bevy_ecs = { version = "0.10.1", default-features = false, features = ["trace"] }
|
||||||
|
bevy_hierarchy = { version = "0.10.1", default-features = false }
|
||||||
bevy_mod_debugdump = "0.7.0"
|
bevy_mod_debugdump = "0.7.0"
|
||||||
bitfield-struct = "0.3.1"
|
bitfield-struct = "0.3.1"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.4.3"
|
||||||
|
@ -71,6 +72,7 @@ tracing = "0.1.37"
|
||||||
tracing-subscriber = "0.3.16"
|
tracing-subscriber = "0.3.16"
|
||||||
url = { version = "2.2.2", features = ["serde"] }
|
url = { version = "2.2.2", features = ["serde"] }
|
||||||
uuid = "1.3.1"
|
uuid = "1.3.1"
|
||||||
|
valence_advancement.path = "crates/valence_advancement"
|
||||||
valence_anvil.path = "crates/valence_anvil"
|
valence_anvil.path = "crates/valence_anvil"
|
||||||
valence_biome.path = "crates/valence_biome"
|
valence_biome.path = "crates/valence_biome"
|
||||||
valence_block.path = "crates/valence_block"
|
valence_block.path = "crates/valence_block"
|
||||||
|
|
|
@ -21,4 +21,5 @@ graph TD
|
||||||
inventory --> client
|
inventory --> client
|
||||||
anvil --> instance
|
anvil --> instance
|
||||||
entity --> block
|
entity --> block
|
||||||
|
advancement --> client
|
||||||
```
|
```
|
||||||
|
|
|
@ -11,11 +11,12 @@ keywords = ["minecraft", "gamedev", "server", "ecs"]
|
||||||
categories = ["game-engines"]
|
categories = ["game-engines"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["network", "player_list", "inventory", "anvil"]
|
default = ["network", "player_list", "inventory", "anvil", "advancement"]
|
||||||
network = ["dep:valence_network"]
|
network = ["dep:valence_network"]
|
||||||
player_list = ["dep:valence_player_list"]
|
player_list = ["dep:valence_player_list"]
|
||||||
inventory = ["dep:valence_inventory"]
|
inventory = ["dep:valence_inventory"]
|
||||||
anvil = ["dep:valence_anvil"]
|
anvil = ["dep:valence_anvil"]
|
||||||
|
advancement = ["dep:valence_advancement"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_app.workspace = true
|
bevy_app.workspace = true
|
||||||
|
@ -35,6 +36,7 @@ valence_network = { workspace = true, optional = true }
|
||||||
valence_player_list = { workspace = true, optional = true }
|
valence_player_list = { workspace = true, optional = true }
|
||||||
valence_inventory = { workspace = true, optional = true }
|
valence_inventory = { workspace = true, optional = true }
|
||||||
valence_anvil = { workspace = true, optional = true }
|
valence_anvil = { workspace = true, optional = true }
|
||||||
|
valence_advancement = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
|
227
crates/valence/examples/advancement.rs
Normal file
227
crates/valence/examples/advancement.rs
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
use valence::prelude::*;
|
||||||
|
use valence_advancement::bevy_hierarchy::{BuildChildren, Children, Parent};
|
||||||
|
use valence_advancement::ForceTabUpdate;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct RootCriteria;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Root2Criteria;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct RootAdvancement;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct RootCriteriaDone(bool);
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct TabChangeCount(u8);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_systems((init_clients, init_advancements, sneak, tab_change))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
server: Res<Server>,
|
||||||
|
dimensions: Query<&DimensionType>,
|
||||||
|
biomes: Query<&Biome>,
|
||||||
|
) {
|
||||||
|
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
|
||||||
|
|
||||||
|
for z in -5..5 {
|
||||||
|
for x in -5..5 {
|
||||||
|
instance.insert_chunk([x, z], Chunk::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for z in -25..25 {
|
||||||
|
for x in -25..25 {
|
||||||
|
instance.set_block([x, 64, z], BlockState::GRASS_BLOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.spawn(instance);
|
||||||
|
|
||||||
|
let root_criteria = commands
|
||||||
|
.spawn((
|
||||||
|
AdvancementCriteria::new(ident!("custom:root_criteria").into()),
|
||||||
|
RootCriteria,
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
|
||||||
|
let root_advancement = commands
|
||||||
|
.spawn((
|
||||||
|
AdvancementBundle {
|
||||||
|
advancement: Advancement::new(ident!("custom:root").into()),
|
||||||
|
requirements: AdvancementRequirements(vec![vec![root_criteria]]),
|
||||||
|
cached_bytes: Default::default(),
|
||||||
|
},
|
||||||
|
AdvancementDisplay {
|
||||||
|
title: "Root".into(),
|
||||||
|
description: "Toggles when you sneak".into(),
|
||||||
|
icon: Some(ItemStack::new(ItemKind::Stone, 1, None)),
|
||||||
|
frame_type: AdvancementFrameType::Task,
|
||||||
|
show_toast: true,
|
||||||
|
hidden: false,
|
||||||
|
background_texture: Some(ident!("textures/block/stone.png").into()),
|
||||||
|
x_coord: 0.0,
|
||||||
|
y_coord: 0.0,
|
||||||
|
},
|
||||||
|
RootAdvancement,
|
||||||
|
))
|
||||||
|
.add_child(root_criteria)
|
||||||
|
.id();
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
AdvancementBundle {
|
||||||
|
advancement: Advancement::new(ident!("custom:first").into()),
|
||||||
|
requirements: AdvancementRequirements::default(),
|
||||||
|
cached_bytes: Default::default(),
|
||||||
|
},
|
||||||
|
AdvancementDisplay {
|
||||||
|
title: "First".into(),
|
||||||
|
description: "First advancement".into(),
|
||||||
|
icon: Some(ItemStack::new(ItemKind::OakWood, 1, None)),
|
||||||
|
frame_type: AdvancementFrameType::Task,
|
||||||
|
show_toast: false,
|
||||||
|
hidden: false,
|
||||||
|
background_texture: None,
|
||||||
|
x_coord: 1.0,
|
||||||
|
y_coord: -0.5,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.set_parent(root_advancement);
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
AdvancementBundle {
|
||||||
|
advancement: Advancement::new(ident!("custom:second").into()),
|
||||||
|
requirements: AdvancementRequirements::default(),
|
||||||
|
cached_bytes: Default::default(),
|
||||||
|
},
|
||||||
|
AdvancementDisplay {
|
||||||
|
title: "Second".into(),
|
||||||
|
description: "Second advancement".into(),
|
||||||
|
icon: Some(ItemStack::new(ItemKind::AcaciaWood, 1, None)),
|
||||||
|
frame_type: AdvancementFrameType::Task,
|
||||||
|
show_toast: false,
|
||||||
|
hidden: false,
|
||||||
|
background_texture: None,
|
||||||
|
x_coord: 1.0,
|
||||||
|
y_coord: 0.5,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.set_parent(root_advancement);
|
||||||
|
|
||||||
|
let root2_criteria = commands
|
||||||
|
.spawn((
|
||||||
|
AdvancementCriteria::new(ident!("custom:root2_criteria").into()),
|
||||||
|
Root2Criteria,
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
AdvancementBundle {
|
||||||
|
advancement: Advancement::new(ident!("custom:root2").into()),
|
||||||
|
requirements: AdvancementRequirements(vec![vec![root2_criteria]]),
|
||||||
|
cached_bytes: Default::default(),
|
||||||
|
},
|
||||||
|
AdvancementDisplay {
|
||||||
|
title: "Root2".into(),
|
||||||
|
description: "Go to this tab 5 times to earn this advancement".into(),
|
||||||
|
icon: Some(ItemStack::new(ItemKind::IronSword, 1, None)),
|
||||||
|
frame_type: AdvancementFrameType::Challenge,
|
||||||
|
show_toast: false,
|
||||||
|
hidden: false,
|
||||||
|
background_texture: Some(Ident::new("textures/block/andesite.png").unwrap()),
|
||||||
|
x_coord: 0.0,
|
||||||
|
y_coord: 0.0,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.add_child(root2_criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_clients(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut clients: Query<(Entity, &mut Location, &mut Position, &mut GameMode), Added<Client>>,
|
||||||
|
instances: Query<Entity, With<Instance>>,
|
||||||
|
) {
|
||||||
|
for (client, mut loc, mut pos, mut game_mode) in &mut clients {
|
||||||
|
loc.0 = instances.single();
|
||||||
|
pos.set([0.5, 65.0, 0.5]);
|
||||||
|
*game_mode = GameMode::Creative;
|
||||||
|
commands
|
||||||
|
.entity(client)
|
||||||
|
.insert((RootCriteriaDone(false), TabChangeCount(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_advancements(
|
||||||
|
mut clients: Query<&mut AdvancementClientUpdate, Added<AdvancementClientUpdate>>,
|
||||||
|
root_advancement_query: Query<Entity, (Without<Parent>, With<Advancement>)>,
|
||||||
|
children_query: Query<&Children>,
|
||||||
|
advancement_check_query: Query<(), With<Advancement>>,
|
||||||
|
) {
|
||||||
|
for mut advancement_client_update in clients.iter_mut() {
|
||||||
|
for root_advancement in root_advancement_query.iter() {
|
||||||
|
advancement_client_update.send_advancements(
|
||||||
|
root_advancement,
|
||||||
|
&children_query,
|
||||||
|
&advancement_check_query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sneak(
|
||||||
|
mut sneaking: EventReader<Sneaking>,
|
||||||
|
mut client: Query<(&mut AdvancementClientUpdate, &mut RootCriteriaDone)>,
|
||||||
|
root_criteria: Query<Entity, With<RootCriteria>>,
|
||||||
|
) {
|
||||||
|
let root_criteria = root_criteria.single();
|
||||||
|
for sneaking in sneaking.iter() {
|
||||||
|
if sneaking.state == SneakState::Stop {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Ok((mut advancement_client_update, mut root_criteria_done)) = client.get_mut(sneaking.client) else { continue; };
|
||||||
|
root_criteria_done.0 = !root_criteria_done.0;
|
||||||
|
match root_criteria_done.0 {
|
||||||
|
true => advancement_client_update.criteria_done(root_criteria),
|
||||||
|
false => advancement_client_update.criteria_undone(root_criteria),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab_change(
|
||||||
|
mut tab_change: EventReader<AdvancementTabChange>,
|
||||||
|
mut client: Query<(&mut AdvancementClientUpdate, &mut TabChangeCount)>,
|
||||||
|
root2_criteria: Query<Entity, With<Root2Criteria>>,
|
||||||
|
root: Query<Entity, With<RootAdvancement>>,
|
||||||
|
) {
|
||||||
|
let root2_criteria = root2_criteria.single();
|
||||||
|
let root = root.single();
|
||||||
|
for tab_change in tab_change.iter() {
|
||||||
|
let Ok((mut advancement_client_update, mut tab_change_count)) = client.get_mut(tab_change.client) else { continue; };
|
||||||
|
if let Some(ref opened) = tab_change.opened_tab {
|
||||||
|
if opened.as_str() == "custom:root2" {
|
||||||
|
tab_change_count.0 += 1;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if tab_change_count.0 == 5 {
|
||||||
|
advancement_client_update.criteria_done(root2_criteria);
|
||||||
|
} else if tab_change_count.0 >= 10 {
|
||||||
|
advancement_client_update.force_tab_update = ForceTabUpdate::Spec(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -97,6 +97,11 @@ pub mod prelude {
|
||||||
#[cfg(feature = "player_list")]
|
#[cfg(feature = "player_list")]
|
||||||
pub use player_list::{PlayerList, PlayerListEntry};
|
pub use player_list::{PlayerList, PlayerListEntry};
|
||||||
pub use text::{Color, Text, TextFormat};
|
pub use text::{Color, Text, TextFormat};
|
||||||
|
#[cfg(feature = "advancement")]
|
||||||
|
pub use valence_advancement::{
|
||||||
|
event::AdvancementTabChange, Advancement, AdvancementBundle, AdvancementClientUpdate,
|
||||||
|
AdvancementCriteria, AdvancementDisplay, AdvancementFrameType, AdvancementRequirements,
|
||||||
|
};
|
||||||
pub use valence_core::ident; // Export the `ident!` macro.
|
pub use valence_core::ident; // Export the `ident!` macro.
|
||||||
pub use valence_core::uuid::UniqueId;
|
pub use valence_core::uuid::UniqueId;
|
||||||
pub use valence_core::{translation_key, CoreSettings, Server};
|
pub use valence_core::{translation_key, CoreSettings, Server};
|
||||||
|
@ -145,6 +150,13 @@ impl PluginGroup for DefaultPlugins {
|
||||||
// No plugin... yet.
|
// No plugin... yet.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "advancement")]
|
||||||
|
{
|
||||||
|
group = group
|
||||||
|
.add(valence_advancement::AdvancementPlugin)
|
||||||
|
.add(valence_advancement::bevy_hierarchy::HierarchyPlugin);
|
||||||
|
}
|
||||||
|
|
||||||
group
|
group
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
crates/valence_advancement/Cargo.toml
Normal file
13
crates/valence_advancement/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "valence_advancement"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
valence_core.workspace = true
|
||||||
|
valence_client.workspace = true
|
||||||
|
bevy_app.workspace = true
|
||||||
|
bevy_ecs.workspace = true
|
||||||
|
bevy_hierarchy.workspace = true
|
||||||
|
rustc-hash.workspace = true
|
||||||
|
anyhow.workspace = true
|
7
crates/valence_advancement/README.md
Normal file
7
crates/valence_advancement/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# valence_advancement
|
||||||
|
|
||||||
|
Everything related to Minecraft advancements.
|
||||||
|
|
||||||
|
### Warning
|
||||||
|
- Each advancement should be scheduled to be sent to each unique client.
|
||||||
|
- Advancement identifier is not mutable and changing it can cause bugs.
|
29
crates/valence_advancement/src/event.rs
Normal file
29
crates/valence_advancement/src/event.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use bevy_ecs::prelude::{Entity, EventReader, EventWriter};
|
||||||
|
use valence_client::event_loop::PacketEvent;
|
||||||
|
use valence_core::ident::Ident;
|
||||||
|
use valence_core::packet::c2s::play::AdvancementTabC2s;
|
||||||
|
|
||||||
|
/// This event sends when the client changes or closes advancement's tab.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct AdvancementTabChange {
|
||||||
|
pub client: Entity,
|
||||||
|
/// If None then the client has closed advancement's tabs.
|
||||||
|
pub opened_tab: Option<Ident<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_advancement_tab_change(
|
||||||
|
mut packets: EventReader<PacketEvent>,
|
||||||
|
mut advancement_tab_change_events: EventWriter<AdvancementTabChange>,
|
||||||
|
) {
|
||||||
|
for packet in packets.iter() {
|
||||||
|
if let Some(pkt) = packet.decode::<AdvancementTabC2s>() {
|
||||||
|
advancement_tab_change_events.send(AdvancementTabChange {
|
||||||
|
client: packet.client,
|
||||||
|
opened_tab: match pkt {
|
||||||
|
AdvancementTabC2s::ClosedScreen => None,
|
||||||
|
AdvancementTabC2s::OpenedTab { tab_id } => Some(tab_id.into()),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
478
crates/valence_advancement/src/lib.rs
Normal file
478
crates/valence_advancement/src/lib.rs
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
#[doc = include_str!("../README.md")]
|
||||||
|
pub mod event;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use bevy_app::{CoreSet, Plugin};
|
||||||
|
use bevy_ecs::prelude::{Bundle, Component, Entity};
|
||||||
|
use bevy_ecs::query::{Added, Changed, Or, With};
|
||||||
|
use bevy_ecs::schedule::{IntoSystemConfig, IntoSystemSetConfig, SystemSet};
|
||||||
|
use bevy_ecs::system::{Commands, Query, SystemParam};
|
||||||
|
pub use bevy_hierarchy;
|
||||||
|
use bevy_hierarchy::{Children, Parent};
|
||||||
|
use event::{handle_advancement_tab_change, AdvancementTabChange};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use valence_client::{Client, FlushPacketsSet, SpawnClientsSet};
|
||||||
|
use valence_core::ident::Ident;
|
||||||
|
use valence_core::item::ItemStack;
|
||||||
|
use valence_core::packet::encode::WritePacket;
|
||||||
|
use valence_core::packet::raw::RawBytes;
|
||||||
|
use valence_core::packet::s2c::play::advancement_update::GenericAdvancementUpdateS2c;
|
||||||
|
use valence_core::packet::s2c::play::{
|
||||||
|
advancement_update as protocol, AdvancementUpdateS2c, SelectAdvancementTabS2c,
|
||||||
|
};
|
||||||
|
use valence_core::packet::var_int::VarInt;
|
||||||
|
use valence_core::packet::{Encode, Packet};
|
||||||
|
use valence_core::text::Text;
|
||||||
|
|
||||||
|
pub struct AdvancementPlugin;
|
||||||
|
|
||||||
|
#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)]
|
||||||
|
pub struct WriteAdvancementPacketToClientsSet;
|
||||||
|
|
||||||
|
#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)]
|
||||||
|
pub struct WriteAdvancementToCacheSet;
|
||||||
|
|
||||||
|
impl Plugin for AdvancementPlugin {
|
||||||
|
fn build(&self, app: &mut bevy_app::App) {
|
||||||
|
app.configure_sets((
|
||||||
|
WriteAdvancementPacketToClientsSet
|
||||||
|
.in_base_set(CoreSet::PostUpdate)
|
||||||
|
.before(FlushPacketsSet),
|
||||||
|
WriteAdvancementToCacheSet
|
||||||
|
.in_base_set(CoreSet::PostUpdate)
|
||||||
|
.before(WriteAdvancementPacketToClientsSet),
|
||||||
|
))
|
||||||
|
.add_event::<AdvancementTabChange>()
|
||||||
|
.add_system(
|
||||||
|
add_advancement_update_component_to_new_clients
|
||||||
|
.after(SpawnClientsSet)
|
||||||
|
.in_base_set(CoreSet::PreUpdate),
|
||||||
|
)
|
||||||
|
.add_system(handle_advancement_tab_change.in_base_set(CoreSet::PreUpdate))
|
||||||
|
.add_system(update_advancement_cached_bytes.in_set(WriteAdvancementToCacheSet))
|
||||||
|
.add_system(send_advancement_update_packet.in_set(WriteAdvancementPacketToClientsSet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Components for advancement that are required
|
||||||
|
/// Optional components:
|
||||||
|
/// [AdvancementDisplay]
|
||||||
|
/// [Parent] - parent advancement
|
||||||
|
#[derive(Bundle)]
|
||||||
|
pub struct AdvancementBundle {
|
||||||
|
pub advancement: Advancement,
|
||||||
|
pub requirements: AdvancementRequirements,
|
||||||
|
pub cached_bytes: AdvancementCachedBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_advancement_update_component_to_new_clients(
|
||||||
|
mut commands: Commands,
|
||||||
|
query: Query<Entity, Added<Client>>,
|
||||||
|
) {
|
||||||
|
for client in query.iter() {
|
||||||
|
commands
|
||||||
|
.entity(client)
|
||||||
|
.insert(AdvancementClientUpdate::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SystemParam, Debug)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
struct UpdateAdvancementCachedBytesQuery<'w, 's> {
|
||||||
|
advancement_id_query: Query<'w, 's, &'static Advancement>,
|
||||||
|
criteria_query: Query<'w, 's, &'static AdvancementCriteria>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's> UpdateAdvancementCachedBytesQuery<'w, 's> {
|
||||||
|
fn write(
|
||||||
|
&self,
|
||||||
|
a_identifier: &Advancement,
|
||||||
|
a_requirements: &AdvancementRequirements,
|
||||||
|
a_display: Option<&AdvancementDisplay>,
|
||||||
|
a_children: Option<&Children>,
|
||||||
|
a_parent: Option<&Parent>,
|
||||||
|
w: impl Write,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let Self {
|
||||||
|
advancement_id_query,
|
||||||
|
criteria_query,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let mut pkt = protocol::Advancement {
|
||||||
|
parent_id: None,
|
||||||
|
display_data: None,
|
||||||
|
criteria: vec![],
|
||||||
|
requirements: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(a_parent) = a_parent {
|
||||||
|
let a_identifier = advancement_id_query.get(a_parent.get())?;
|
||||||
|
pkt.parent_id = Some(a_identifier.0.borrowed());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(a_display) = a_display {
|
||||||
|
pkt.display_data = Some(protocol::AdvancementDisplay {
|
||||||
|
title: Cow::Borrowed(&a_display.title),
|
||||||
|
description: Cow::Borrowed(&a_display.description),
|
||||||
|
icon: &a_display.icon,
|
||||||
|
frame_type: VarInt(a_display.frame_type as _),
|
||||||
|
flags: a_display.flags(),
|
||||||
|
background_texture: a_display.background_texture.as_ref().map(|v| v.borrowed()),
|
||||||
|
x_coord: a_display.x_coord,
|
||||||
|
y_coord: a_display.y_coord,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(a_children) = a_children {
|
||||||
|
for a_child in a_children.iter() {
|
||||||
|
let Ok(c_identifier) = criteria_query.get(*a_child) else { continue; };
|
||||||
|
pkt.criteria.push((c_identifier.0.borrowed(), ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for requirements in a_requirements.0.iter() {
|
||||||
|
let mut requirements_p = vec![];
|
||||||
|
for requirement in requirements {
|
||||||
|
let c_identifier = criteria_query.get(*requirement)?;
|
||||||
|
requirements_p.push(c_identifier.0.as_str());
|
||||||
|
}
|
||||||
|
pkt.requirements.push(protocol::AdvancementRequirements {
|
||||||
|
requirement: requirements_p,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(&a_identifier.0, pkt).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn update_advancement_cached_bytes(
|
||||||
|
mut query: Query<
|
||||||
|
(
|
||||||
|
&Advancement,
|
||||||
|
&AdvancementRequirements,
|
||||||
|
&mut AdvancementCachedBytes,
|
||||||
|
Option<&AdvancementDisplay>,
|
||||||
|
Option<&Children>,
|
||||||
|
Option<&Parent>,
|
||||||
|
),
|
||||||
|
Or<(
|
||||||
|
Changed<AdvancementDisplay>,
|
||||||
|
Changed<Children>,
|
||||||
|
Changed<Parent>,
|
||||||
|
Changed<AdvancementRequirements>,
|
||||||
|
)>,
|
||||||
|
>,
|
||||||
|
update_advancement_cached_bytes_query: UpdateAdvancementCachedBytesQuery,
|
||||||
|
) {
|
||||||
|
for (a_identifier, a_requirements, mut a_bytes, a_display, a_children, a_parent) in
|
||||||
|
query.iter_mut()
|
||||||
|
{
|
||||||
|
a_bytes.0.clear();
|
||||||
|
update_advancement_cached_bytes_query
|
||||||
|
.write(
|
||||||
|
a_identifier,
|
||||||
|
a_requirements,
|
||||||
|
a_display,
|
||||||
|
a_children,
|
||||||
|
a_parent,
|
||||||
|
&mut a_bytes.0,
|
||||||
|
)
|
||||||
|
.expect("Failed to write an advancement");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SystemParam, Debug)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub(crate) struct SingleAdvancementUpdateQuery<'w, 's> {
|
||||||
|
advancement_bytes_query: Query<'w, 's, &'static AdvancementCachedBytes>,
|
||||||
|
advancement_id_query: Query<'w, 's, &'static Advancement>,
|
||||||
|
criteria_query: Query<'w, 's, &'static AdvancementCriteria>,
|
||||||
|
parent_query: Query<'w, 's, &'static Parent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct AdvancementUpdateEncodeS2c<'w, 's, 'a> {
|
||||||
|
client_update: AdvancementClientUpdate,
|
||||||
|
queries: &'a SingleAdvancementUpdateQuery<'w, 's>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, 'a> Encode for AdvancementUpdateEncodeS2c<'w, 's, 'a> {
|
||||||
|
fn encode(&self, w: impl Write) -> anyhow::Result<()> {
|
||||||
|
let SingleAdvancementUpdateQuery {
|
||||||
|
advancement_bytes_query,
|
||||||
|
advancement_id_query,
|
||||||
|
criteria_query,
|
||||||
|
parent_query,
|
||||||
|
} = self.queries;
|
||||||
|
|
||||||
|
let AdvancementClientUpdate {
|
||||||
|
new_advancements,
|
||||||
|
remove_advancements,
|
||||||
|
progress,
|
||||||
|
force_tab_update: _,
|
||||||
|
} = &self.client_update;
|
||||||
|
|
||||||
|
let mut pkt = GenericAdvancementUpdateS2c {
|
||||||
|
reset: false,
|
||||||
|
advancement_mapping: vec![],
|
||||||
|
identifiers: vec![],
|
||||||
|
progress_mapping: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
for new_advancement in new_advancements {
|
||||||
|
let a_cached_bytes = advancement_bytes_query.get(*new_advancement)?;
|
||||||
|
pkt.advancement_mapping
|
||||||
|
.push(RawBytes(a_cached_bytes.0.as_slice()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for remove_advancement in remove_advancements {
|
||||||
|
let a_identifier = advancement_id_query.get(*remove_advancement)?;
|
||||||
|
pkt.identifiers.push(a_identifier.0.borrowed());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut progress_mapping: FxHashMap<Entity, Vec<(Entity, Option<i64>)>> =
|
||||||
|
FxHashMap::default();
|
||||||
|
for progress in progress {
|
||||||
|
let a = parent_query.get(progress.0)?;
|
||||||
|
progress_mapping
|
||||||
|
.entry(a.get())
|
||||||
|
.and_modify(|v| v.push(*progress))
|
||||||
|
.or_insert(vec![*progress]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (a, c_progresses) in progress_mapping {
|
||||||
|
let a_identifier = advancement_id_query.get(a)?;
|
||||||
|
let mut c_progresses_p = vec![];
|
||||||
|
for (c, c_progress) in c_progresses {
|
||||||
|
let c_identifier = criteria_query.get(c)?;
|
||||||
|
c_progresses_p.push(protocol::AdvancementCriteria {
|
||||||
|
criterion_identifier: c_identifier.0.borrowed(),
|
||||||
|
criterion_progress: c_progress,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pkt.progress_mapping
|
||||||
|
.push((a_identifier.0.borrowed(), c_progresses_p));
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt.encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, 'a, 'b> Packet<'b> for AdvancementUpdateEncodeS2c<'w, 's, 'a> {
|
||||||
|
const PACKET_ID: i32 = AdvancementUpdateS2c::PACKET_ID;
|
||||||
|
|
||||||
|
fn packet_id(&self) -> i32 {
|
||||||
|
Self::PACKET_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
fn packet_name(&self) -> &str {
|
||||||
|
"AdvancementUpdateEncodeS2c"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_packet(&self, mut w: impl Write) -> anyhow::Result<()> {
|
||||||
|
VarInt(Self::PACKET_ID)
|
||||||
|
.encode(&mut w)
|
||||||
|
.context("failed to encode packet ID")?;
|
||||||
|
self.encode(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_packet(_r: &mut &'b [u8]) -> anyhow::Result<Self> {
|
||||||
|
panic!("Packet can not be decoded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn send_advancement_update_packet(
|
||||||
|
mut client: Query<(&mut AdvancementClientUpdate, &mut Client)>,
|
||||||
|
update_single_query: SingleAdvancementUpdateQuery,
|
||||||
|
) {
|
||||||
|
for (mut advancement_client_update, mut client) in client.iter_mut() {
|
||||||
|
match advancement_client_update.force_tab_update {
|
||||||
|
ForceTabUpdate::None => {}
|
||||||
|
ForceTabUpdate::First => {
|
||||||
|
client.write_packet(&SelectAdvancementTabS2c { identifier: None })
|
||||||
|
}
|
||||||
|
ForceTabUpdate::Spec(spec) => {
|
||||||
|
if let Ok(a_identifier) = update_single_query.advancement_id_query.get(spec) {
|
||||||
|
client.write_packet(&SelectAdvancementTabS2c {
|
||||||
|
identifier: Some(a_identifier.0.borrowed()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ForceTabUpdate::None != advancement_client_update.force_tab_update {
|
||||||
|
advancement_client_update.force_tab_update = ForceTabUpdate::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if advancement_client_update.new_advancements.is_empty()
|
||||||
|
&& advancement_client_update.progress.is_empty()
|
||||||
|
&& advancement_client_update.remove_advancements.is_empty()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let advancement_client_update = std::mem::take(advancement_client_update.as_mut());
|
||||||
|
|
||||||
|
client.write_packet(&AdvancementUpdateEncodeS2c {
|
||||||
|
queries: &update_single_query,
|
||||||
|
client_update: advancement_client_update,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advancement's id. May not be updated.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Advancement(Ident<Cow<'static, str>>);
|
||||||
|
|
||||||
|
impl Advancement {
|
||||||
|
pub fn new(ident: Ident<Cow<'static, str>>) -> Advancement {
|
||||||
|
Self(ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> &Ident<Cow<'static, str>> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum AdvancementFrameType {
|
||||||
|
Task,
|
||||||
|
Challenge,
|
||||||
|
Goal,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advancement display. Optional component
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct AdvancementDisplay {
|
||||||
|
pub title: Text,
|
||||||
|
pub description: Text,
|
||||||
|
pub icon: Option<ItemStack>,
|
||||||
|
pub frame_type: AdvancementFrameType,
|
||||||
|
pub show_toast: bool,
|
||||||
|
pub hidden: bool,
|
||||||
|
pub background_texture: Option<Ident<Cow<'static, str>>>,
|
||||||
|
pub x_coord: f32,
|
||||||
|
pub y_coord: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdvancementDisplay {
|
||||||
|
pub(crate) fn flags(&self) -> i32 {
|
||||||
|
let mut flags = 0;
|
||||||
|
flags |= self.background_texture.is_some() as i32;
|
||||||
|
flags |= (self.show_toast as i32) << 1;
|
||||||
|
flags |= (self.hidden as i32) << 2;
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Criteria's identifier. May not be updated
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct AdvancementCriteria(Ident<Cow<'static, str>>);
|
||||||
|
|
||||||
|
impl AdvancementCriteria {
|
||||||
|
pub fn new(ident: Ident<Cow<'static, str>>) -> Self {
|
||||||
|
Self(ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> &Ident<Cow<'static, str>> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Requirements for advancement to be completed.
|
||||||
|
/// All columns should be completed, column is completed when any of criteria in
|
||||||
|
/// this column is completed.
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
pub struct AdvancementRequirements(pub Vec<Vec<Entity>>);
|
||||||
|
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
pub struct AdvancementCachedBytes(pub(crate) Vec<u8>);
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq)]
|
||||||
|
pub enum ForceTabUpdate {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
First,
|
||||||
|
/// Should contain only root advancement otherwise the first will be chosen
|
||||||
|
Spec(Entity),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Default, Debug)]
|
||||||
|
pub struct AdvancementClientUpdate {
|
||||||
|
/// Which advancement's descriptions send to client
|
||||||
|
pub new_advancements: Vec<Entity>,
|
||||||
|
/// Which advancements remove from client
|
||||||
|
pub remove_advancements: Vec<Entity>,
|
||||||
|
/// Criteria progress update.
|
||||||
|
/// If None then criteria is not done otherwise it is done
|
||||||
|
pub progress: Vec<(Entity, Option<i64>)>,
|
||||||
|
/// Forces client to open a tab
|
||||||
|
pub force_tab_update: ForceTabUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdvancementClientUpdate {
|
||||||
|
pub(crate) fn walk_advancements(
|
||||||
|
root: Entity,
|
||||||
|
children_query: &Query<&Children>,
|
||||||
|
advancement_check_query: &Query<(), With<Advancement>>,
|
||||||
|
func: &mut impl FnMut(Entity),
|
||||||
|
) {
|
||||||
|
func(root);
|
||||||
|
if let Ok(children) = children_query.get(root) {
|
||||||
|
for child in children.iter() {
|
||||||
|
let child = *child;
|
||||||
|
if advancement_check_query.get(child).is_ok() {
|
||||||
|
Self::walk_advancements(child, children_query, advancement_check_query, func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends all advancements from the root
|
||||||
|
pub fn send_advancements(
|
||||||
|
&mut self,
|
||||||
|
root: Entity,
|
||||||
|
children_query: &Query<&Children>,
|
||||||
|
advancement_check_query: &Query<(), With<Advancement>>,
|
||||||
|
) {
|
||||||
|
Self::walk_advancements(root, children_query, advancement_check_query, &mut |e| {
|
||||||
|
self.new_advancements.push(e)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all advancements from the root
|
||||||
|
pub fn remove_advancements(
|
||||||
|
&mut self,
|
||||||
|
root: Entity,
|
||||||
|
children_query: &Query<&Children>,
|
||||||
|
advancement_check_query: &Query<(), With<Advancement>>,
|
||||||
|
) {
|
||||||
|
Self::walk_advancements(root, children_query, advancement_check_query, &mut |e| {
|
||||||
|
self.remove_advancements.push(e)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks criteria as done
|
||||||
|
pub fn criteria_done(&mut self, criteria: Entity) {
|
||||||
|
self.progress.push((
|
||||||
|
criteria,
|
||||||
|
Some(
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_millis() as i64,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks criteria as undone
|
||||||
|
pub fn criteria_undone(&mut self, criteria: Entity) {
|
||||||
|
self.progress.push((criteria, None))
|
||||||
|
}
|
||||||
|
}
|
|
@ -132,6 +132,12 @@ impl<S> Ident<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Ident<Cow<'a, str>> {
|
||||||
|
pub fn borrowed(&self) -> Ident<Cow<str>> {
|
||||||
|
Ident::new_unchecked(Cow::Borrowed(self.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse(string: Cow<str>) -> Result<Ident<Cow<str>>, IdentError> {
|
fn parse(string: Cow<str>) -> Result<Ident<Cow<str>>, IdentError> {
|
||||||
let check_namespace = |s: &str| {
|
let check_namespace = |s: &str| {
|
||||||
!s.is_empty()
|
!s.is_empty()
|
||||||
|
|
|
@ -7,18 +7,21 @@ use crate::packet::var_int::VarInt;
|
||||||
use crate::packet::{Decode, Encode};
|
use crate::packet::{Decode, Encode};
|
||||||
use crate::text::Text;
|
use crate::text::Text;
|
||||||
|
|
||||||
|
pub type AdvancementUpdateS2c<'a> =
|
||||||
|
GenericAdvancementUpdateS2c<'a, (Ident<Cow<'a, str>>, Advancement<'a, Option<ItemStack>>)>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Encode, Decode)]
|
#[derive(Clone, Debug, Encode, Decode)]
|
||||||
pub struct AdvancementUpdateS2c<'a> {
|
pub struct GenericAdvancementUpdateS2c<'a, AM: 'a> {
|
||||||
pub reset: bool,
|
pub reset: bool,
|
||||||
pub advancement_mapping: Vec<(Ident<Cow<'a, str>>, Advancement<'a>)>,
|
pub advancement_mapping: Vec<AM>,
|
||||||
pub identifiers: Vec<Ident<Cow<'a, str>>>,
|
pub identifiers: Vec<Ident<Cow<'a, str>>>,
|
||||||
pub progress_mapping: Vec<(Ident<Cow<'a, str>>, Vec<AdvancementCriteria<'a>>)>,
|
pub progress_mapping: Vec<(Ident<Cow<'a, str>>, Vec<AdvancementCriteria<'a>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
||||||
pub struct Advancement<'a> {
|
pub struct Advancement<'a, I> {
|
||||||
pub parent_id: Option<Ident<Cow<'a, str>>>,
|
pub parent_id: Option<Ident<Cow<'a, str>>>,
|
||||||
pub display_data: Option<AdvancementDisplay<'a>>,
|
pub display_data: Option<AdvancementDisplay<'a, I>>,
|
||||||
pub criteria: Vec<(Ident<Cow<'a, str>>, ())>,
|
pub criteria: Vec<(Ident<Cow<'a, str>>, ())>,
|
||||||
pub requirements: Vec<AdvancementRequirements<'a>>,
|
pub requirements: Vec<AdvancementRequirements<'a>>,
|
||||||
}
|
}
|
||||||
|
@ -29,10 +32,10 @@ pub struct AdvancementRequirements<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct AdvancementDisplay<'a> {
|
pub struct AdvancementDisplay<'a, I> {
|
||||||
pub title: Cow<'a, Text>,
|
pub title: Cow<'a, Text>,
|
||||||
pub description: Cow<'a, Text>,
|
pub description: Cow<'a, Text>,
|
||||||
pub icon: Option<ItemStack>,
|
pub icon: I,
|
||||||
pub frame_type: VarInt,
|
pub frame_type: VarInt,
|
||||||
pub flags: i32,
|
pub flags: i32,
|
||||||
pub background_texture: Option<Ident<Cow<'a, str>>>,
|
pub background_texture: Option<Ident<Cow<'a, str>>>,
|
||||||
|
@ -48,7 +51,7 @@ pub struct AdvancementCriteria<'a> {
|
||||||
pub criterion_progress: Option<i64>,
|
pub criterion_progress: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for AdvancementDisplay<'_> {
|
impl<I: Encode> Encode for AdvancementDisplay<'_, I> {
|
||||||
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
|
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
|
||||||
self.title.encode(&mut w)?;
|
self.title.encode(&mut w)?;
|
||||||
self.description.encode(&mut w)?;
|
self.description.encode(&mut w)?;
|
||||||
|
@ -68,11 +71,11 @@ impl Encode for AdvancementDisplay<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Decode<'a> for AdvancementDisplay<'a> {
|
impl<'a, I: Decode<'a>> Decode<'a> for AdvancementDisplay<'a, I> {
|
||||||
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
|
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
|
||||||
let title = <Cow<'a, Text>>::decode(r)?;
|
let title = <Cow<'a, Text>>::decode(r)?;
|
||||||
let description = <Cow<'a, Text>>::decode(r)?;
|
let description = <Cow<'a, Text>>::decode(r)?;
|
||||||
let icon = Option::<ItemStack>::decode(r)?;
|
let icon = I::decode(r)?;
|
||||||
let frame_type = VarInt::decode(r)?;
|
let frame_type = VarInt::decode(r)?;
|
||||||
let flags = i32::decode(r)?;
|
let flags = i32::decode(r)?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue