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:
Jenya705 2023-05-02 10:35:35 +02:00 committed by GitHub
parent 1d2d8f888e
commit 41bcd1eb2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 790 additions and 10 deletions

View file

@ -20,6 +20,7 @@ atty = "0.2.14"
base64 = "0.21.0"
bevy_app = { version = "0.10.1", default-features = false }
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"
bitfield-struct = "0.3.1"
byteorder = "1.4.3"
@ -71,6 +72,7 @@ tracing = "0.1.37"
tracing-subscriber = "0.3.16"
url = { version = "2.2.2", features = ["serde"] }
uuid = "1.3.1"
valence_advancement.path = "crates/valence_advancement"
valence_anvil.path = "crates/valence_anvil"
valence_biome.path = "crates/valence_biome"
valence_block.path = "crates/valence_block"

View file

@ -21,4 +21,5 @@ graph TD
inventory --> client
anvil --> instance
entity --> block
advancement --> client
```

View file

@ -11,11 +11,12 @@ keywords = ["minecraft", "gamedev", "server", "ecs"]
categories = ["game-engines"]
[features]
default = ["network", "player_list", "inventory", "anvil"]
default = ["network", "player_list", "inventory", "anvil", "advancement"]
network = ["dep:valence_network"]
player_list = ["dep:valence_player_list"]
inventory = ["dep:valence_inventory"]
anvil = ["dep:valence_anvil"]
advancement = ["dep:valence_advancement"]
[dependencies]
bevy_app.workspace = true
@ -35,6 +36,7 @@ valence_network = { workspace = true, optional = true }
valence_player_list = { workspace = true, optional = true }
valence_inventory = { workspace = true, optional = true }
valence_anvil = { workspace = true, optional = true }
valence_advancement = { workspace = true, optional = true }
[dev-dependencies]
anyhow.workspace = true

View 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);
}
}
}

View file

@ -97,6 +97,11 @@ pub mod prelude {
#[cfg(feature = "player_list")]
pub use player_list::{PlayerList, PlayerListEntry};
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::uuid::UniqueId;
pub use valence_core::{translation_key, CoreSettings, Server};
@ -145,6 +150,13 @@ impl PluginGroup for DefaultPlugins {
// No plugin... yet.
}
#[cfg(feature = "advancement")]
{
group = group
.add(valence_advancement::AdvancementPlugin)
.add(valence_advancement::bevy_hierarchy::HierarchyPlugin);
}
group
}
}

View 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

View 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.

View 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()),
},
})
}
}
}

View 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))
}
}

View file

@ -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> {
let check_namespace = |s: &str| {
!s.is_empty()

View file

@ -7,18 +7,21 @@ use crate::packet::var_int::VarInt;
use crate::packet::{Decode, Encode};
use crate::text::Text;
pub type AdvancementUpdateS2c<'a> =
GenericAdvancementUpdateS2c<'a, (Ident<Cow<'a, str>>, Advancement<'a, Option<ItemStack>>)>;
#[derive(Clone, Debug, Encode, Decode)]
pub struct AdvancementUpdateS2c<'a> {
pub struct GenericAdvancementUpdateS2c<'a, AM: 'a> {
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 progress_mapping: Vec<(Ident<Cow<'a, str>>, Vec<AdvancementCriteria<'a>>)>,
}
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
pub struct Advancement<'a> {
pub struct Advancement<'a, I> {
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 requirements: Vec<AdvancementRequirements<'a>>,
}
@ -29,10 +32,10 @@ pub struct AdvancementRequirements<'a> {
}
#[derive(Clone, PartialEq, Debug)]
pub struct AdvancementDisplay<'a> {
pub struct AdvancementDisplay<'a, I> {
pub title: Cow<'a, Text>,
pub description: Cow<'a, Text>,
pub icon: Option<ItemStack>,
pub icon: I,
pub frame_type: VarInt,
pub flags: i32,
pub background_texture: Option<Ident<Cow<'a, str>>>,
@ -48,7 +51,7 @@ pub struct AdvancementCriteria<'a> {
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<()> {
self.title.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> {
let title = <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 flags = i32::decode(r)?;