mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 06:21:31 +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"
|
||||
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"
|
||||
|
|
|
@ -21,4 +21,5 @@ graph TD
|
|||
inventory --> client
|
||||
anvil --> instance
|
||||
entity --> block
|
||||
advancement --> client
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
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")]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
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> {
|
||||
let check_namespace = |s: &str| {
|
||||
!s.is_empty()
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
|
|
Loading…
Reference in a new issue