mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: add advancement progress (#2261)
* implementing the advancement progress so each advancement has certain requirements to be mark has complete * cargo fmt * fix test --------- Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
This commit is contained in:
@@ -5,7 +5,7 @@ use pumpkin_util::resource_location::ResourceLocation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::text::TextContent::Translate;
|
||||
use quote::{ToTokens, format_ident, quote};
|
||||
use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::cmp::PartialEq;
|
||||
use std::fmt::Display;
|
||||
use std::hash::{Hash, Hasher};
|
||||
@@ -430,9 +430,7 @@ pub(crate) fn build() -> TokenStream {
|
||||
send_telemetry : #send_telemetry,
|
||||
display : #display,
|
||||
reward : &#reward,
|
||||
requirements: AdvancementRequirement{
|
||||
requirements : &[#(#requirements),*]
|
||||
}
|
||||
requirements: &[#(#requirements),*],
|
||||
};
|
||||
}]);
|
||||
let minecraft_name = identifier.to_string();
|
||||
@@ -463,7 +461,7 @@ pub(crate) fn build() -> TokenStream {
|
||||
pub send_telemetry : bool,
|
||||
pub display : Option<&'static AdvancementDisplay>,
|
||||
pub reward : &'static AdvancementReward,
|
||||
pub requirements: AdvancementRequirement,
|
||||
pub requirements: &'static[&'static[&'static str]],
|
||||
}
|
||||
|
||||
impl Display for Advancement {
|
||||
|
||||
@@ -110,7 +110,7 @@ potion_brewing = []
|
||||
phf = { workspace = true, features = ["macros"] }
|
||||
pumpkin-util.workspace = true
|
||||
pumpkin-nbt.workspace = true
|
||||
serde.workspace = true
|
||||
serde = {workspace = true, features = ["rc"]}
|
||||
crc-fast.workspace = true
|
||||
rand.workspace = true
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use crate::item_stack::ItemStack;
|
||||
use crate::potion_brewing::ItemRecipe;
|
||||
use crate::translation::java::{
|
||||
CHAT_TYPE_ADVANCEMENT_CHALLENGE, CHAT_TYPE_ADVANCEMENT_GOAL, CHAT_TYPE_ADVANCEMENT_TASK,
|
||||
};
|
||||
use crate::{ADVANCEMENT_TREE, Advancement};
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
use pumpkin_util::resource_location::ResourceLocation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::text::color::{Color, NamedColor};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Display;
|
||||
use std::hash::{Hash, Hasher};
|
||||
@@ -85,11 +88,11 @@ impl FrameType {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> &'static str {
|
||||
pub fn get_translation(&self) -> &'static str {
|
||||
match self {
|
||||
FrameType::Task => "task",
|
||||
FrameType::Challenge => "challenge",
|
||||
FrameType::Goal => "goal",
|
||||
FrameType::Task => CHAT_TYPE_ADVANCEMENT_TASK,
|
||||
FrameType::Challenge => CHAT_TYPE_ADVANCEMENT_CHALLENGE,
|
||||
FrameType::Goal => CHAT_TYPE_ADVANCEMENT_GOAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,8 +193,48 @@ pub struct Criteria {
|
||||
pub criterion_id: Identifier,
|
||||
pub achieve_date: Option<i64>,
|
||||
}
|
||||
|
||||
/// Represents the requirements needed to complete an advancement.
|
||||
/// To mark as done, at least one criterion from each requirement group must be satisfied.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct AdvancementRequirement {
|
||||
pub requirements: &'static [&'static [&'static str]],
|
||||
pub requirements: Vec<Vec<Arc<str>>>,
|
||||
}
|
||||
|
||||
impl AdvancementRequirement {
|
||||
pub fn from_const(static_requirements: &[&[&str]]) -> Self {
|
||||
let requirements = static_requirements
|
||||
.iter()
|
||||
.map(|inner| inner.iter().map(|s| Arc::from(*s)).collect())
|
||||
.collect();
|
||||
AdvancementRequirement { requirements }
|
||||
}
|
||||
|
||||
/// test if the requirements is complete
|
||||
pub fn test(&self, predicate: impl Fn(&str) -> bool) -> bool {
|
||||
if self.requirements.is_empty() {
|
||||
false
|
||||
} else {
|
||||
for requirement in &self.requirements {
|
||||
if !Self::any_match(requirement, &predicate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// check if any test pass
|
||||
fn any_match(requirements: &Vec<Arc<str>>, predicate: impl Fn(&str) -> bool) -> bool {
|
||||
for requirement in requirements {
|
||||
if predicate(requirement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn names(&self) -> Vec<Arc<str>> {
|
||||
self.requirements.iter().flatten().cloned().collect()
|
||||
}
|
||||
}
|
||||
pub trait Criterion {}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,8 @@ use crate::data::advancement_data::AdvancementManager;
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::player::Player;
|
||||
use indexmap::IndexMap;
|
||||
use pumpkin_data::Advancement;
|
||||
use pumpkin_data::advancement_data::{AdvancementNode, AdvancementReward};
|
||||
use pumpkin_data::advancement_data::{AdvancementNode, AdvancementRequirement, AdvancementReward};
|
||||
use pumpkin_data::{Advancement, translation};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
@@ -12,30 +12,134 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::SystemTime;
|
||||
use tracing::{error, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
pub struct CriterionProgress(pub Option<SystemTime>);
|
||||
|
||||
impl CriterionProgress {
|
||||
pub fn grant(&mut self) {
|
||||
self.0 = Some(SystemTime::now());
|
||||
}
|
||||
|
||||
pub const fn revoke(&mut self) {
|
||||
self.0 = None;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn is_done(&self) -> bool {
|
||||
self.0.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the progress of a given advancement for a player.
|
||||
///
|
||||
/// Tracks whether the advancement has been fully completed. In the future,
|
||||
/// this will also track specific criteria progress.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct AdvancementProgress {
|
||||
/// Indicates if the advancement is fully completed.
|
||||
pub complete: bool,
|
||||
/// Indicates the different progress of all criteria currently only a boolean
|
||||
pub criteria: HashMap<Arc<str>, CriterionProgress>,
|
||||
/// The Requirement for the Advancement to be mark as complete
|
||||
pub requirements: AdvancementRequirement,
|
||||
}
|
||||
|
||||
impl AdvancementProgress {
|
||||
/// Returns `true` if the advancement is completely done.
|
||||
/// Returns `true` if the advancement is done.
|
||||
#[must_use]
|
||||
pub const fn is_done(&self) -> bool {
|
||||
self.complete
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.requirements.test(|s| self.is_criterion_done(s))
|
||||
}
|
||||
|
||||
/// Check if a criterion his mark has complete
|
||||
fn is_criterion_done(&self, criterion: &str) -> bool {
|
||||
self.criteria
|
||||
.get(criterion)
|
||||
.is_some_and(CriterionProgress::is_done)
|
||||
}
|
||||
|
||||
/// Returns `true` if the advancement has any progress. Currently just returns if it is fully complete.
|
||||
#[must_use]
|
||||
pub const fn has_progress(&self) -> bool {
|
||||
self.complete
|
||||
pub fn has_progress(&self) -> bool {
|
||||
for value in self.criteria.values() {
|
||||
if value.is_done() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn grant_progress(&mut self, name: &str) -> bool {
|
||||
if let Some(value) = self.criteria.get_mut(name)
|
||||
&& !value.is_done()
|
||||
{
|
||||
value.grant();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn revoke_progress(&mut self, name: &str) -> bool {
|
||||
if let Some(value) = self.criteria.get_mut(name)
|
||||
&& value.is_done()
|
||||
{
|
||||
value.revoke();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, requirements: AdvancementRequirement) {
|
||||
let names = requirements.names();
|
||||
self.criteria.retain(|key, _criterion| names.contains(key));
|
||||
for name in names {
|
||||
self.criteria.entry(name).or_default();
|
||||
}
|
||||
self.requirements = requirements;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct AdvancementProgressMap {
|
||||
pub map: IndexMap<&'static Advancement, AdvancementProgress>,
|
||||
}
|
||||
|
||||
impl AdvancementProgressMap {
|
||||
/// Gets a mutable reference to the current progress for a given advancement. Creates the state entry if missing.
|
||||
pub fn get_mut_or_start_progress(
|
||||
&mut self,
|
||||
advancement: &'static Advancement,
|
||||
) -> &mut AdvancementProgress {
|
||||
self.map.entry(advancement).or_insert_with(|| {
|
||||
let mut progress = AdvancementProgress::default();
|
||||
progress.update(AdvancementRequirement::from_const(advancement.requirements));
|
||||
progress
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
self.map.clear();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert(&mut self, advancement: &'static Advancement, progress: AdvancementProgress) {
|
||||
self.map.insert(advancement, progress);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +147,7 @@ impl AdvancementProgress {
|
||||
///
|
||||
/// This handles saving, loading, and tracking the state of granted / revoked advancements.
|
||||
pub struct PlayerAdvancement {
|
||||
progress: IndexMap<&'static Advancement, AdvancementProgress>,
|
||||
progress: AdvancementProgressMap,
|
||||
is_first_packet: bool,
|
||||
roots_to_update: HashSet<&'static AdvancementNode>,
|
||||
visible: HashSet<&'static Advancement>,
|
||||
@@ -69,7 +173,7 @@ impl PlayerAdvancement {
|
||||
#[must_use]
|
||||
pub fn new(manager: Arc<AdvancementManager>, uuid: Uuid) -> Self {
|
||||
Self {
|
||||
progress: IndexMap::new(),
|
||||
progress: AdvancementProgressMap::default(),
|
||||
path: manager.advancement_path.join(format!("{}.json", &uuid)),
|
||||
manager,
|
||||
player: Weak::new(),
|
||||
@@ -142,9 +246,14 @@ impl PlayerAdvancement {
|
||||
from_reader(file).map_err(AdvancementDataError::Json)?;
|
||||
|
||||
self.progress.clear();
|
||||
for (advancement_id, progress) in loaded_data {
|
||||
for (advancement_id, mut progress) in loaded_data {
|
||||
if let Some(advancement_ref) = Advancement::from_minecraft_name(&advancement_id) {
|
||||
progress.update(AdvancementRequirement::from_const(
|
||||
advancement_ref.requirements,
|
||||
));
|
||||
self.progress.insert(advancement_ref, progress);
|
||||
self.progress_changed.insert(advancement_ref);
|
||||
//TODO self.mark_for_visibility(advancement_ref);
|
||||
} else {
|
||||
warn!("The Advancement name {} is invalid", advancement_id);
|
||||
}
|
||||
@@ -152,65 +261,65 @@ impl PlayerAdvancement {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the current progress for a given advancement, creating a default uncompleted progress if it doesn't exist.
|
||||
pub fn get_or_start_progress(
|
||||
&mut self,
|
||||
advancement: &'static Advancement,
|
||||
) -> &AdvancementProgress {
|
||||
self.get_mut_or_start_progress(advancement)
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the current progress for a given advancement. Creates the state entry if missing.
|
||||
pub fn get_mut_or_start_progress(
|
||||
&mut self,
|
||||
advancement: &'static Advancement,
|
||||
) -> &mut AdvancementProgress {
|
||||
self.progress.entry(advancement).or_default()
|
||||
}
|
||||
|
||||
/// Grants the rewards (like experience) associated with completing an advancement.
|
||||
pub async fn grant_reward(player: Arc<Player>, reward: &AdvancementReward) {
|
||||
player.add_experience_points(reward.experience).await;
|
||||
}
|
||||
|
||||
/// Fully awards an advancement to the player, updating its status to complete and granting rewards if applicable.
|
||||
pub async fn award(&mut self, advancement: &'static Advancement) {
|
||||
pub async fn award(&mut self, advancement: &'static Advancement, criterion: &str) -> bool {
|
||||
//TODO call and creates Events for plugins
|
||||
let mut result = false;
|
||||
let player = self.player.upgrade().unwrap().clone();
|
||||
let progress = self.get_mut_or_start_progress(advancement);
|
||||
let is_done = progress.is_done();
|
||||
if !progress.is_done() {
|
||||
progress.complete = true;
|
||||
Self::grant_reward(player.clone(), advancement.reward).await;
|
||||
if let Some(display) = advancement.display
|
||||
&& display.announce_to_chat
|
||||
{
|
||||
let component = TextComponent::translate(
|
||||
format!("chat.type.advancement.{}", display.frame_type.get_name()),
|
||||
[player.get_display_name().await, advancement.name()],
|
||||
);
|
||||
player
|
||||
.world()
|
||||
.broadcast_system_message(&component, false)
|
||||
.await; //send translate component for the event
|
||||
let progress = self.progress.get_mut_or_start_progress(advancement);
|
||||
let was_done = progress.is_done();
|
||||
if !progress.grant_progress(criterion) {
|
||||
result = true;
|
||||
self.progress_changed.insert(advancement);
|
||||
if !was_done && progress.is_done() {
|
||||
Self::grant_reward(player.clone(), advancement.reward).await;
|
||||
if let Some(display) = advancement.display
|
||||
&& display.announce_to_chat
|
||||
&& player
|
||||
.world()
|
||||
.level_info
|
||||
.load()
|
||||
.game_rules
|
||||
.show_advancement_messages
|
||||
{
|
||||
let component = TextComponent::translate_cross(
|
||||
display.frame_type.get_translation(),
|
||||
translation::bedrock::CHAT_TYPE_ACHIEVEMENT,
|
||||
[player.get_display_name().await, advancement.name()],
|
||||
);
|
||||
player
|
||||
.world()
|
||||
.broadcast_system_message(&component, false)
|
||||
.await; //send translate component for the event
|
||||
}
|
||||
}
|
||||
}
|
||||
if !is_done && progress.is_done() {
|
||||
if !was_done && progress.is_done() {
|
||||
//TODO self.mark_for_visibility_update(advancement);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Revokes a previously awarded advancement, clearing its progress state.
|
||||
pub fn revoke(&mut self, advancement: &'static Advancement) {
|
||||
let progress = self.get_mut_or_start_progress(advancement);
|
||||
pub fn revoke(&mut self, advancement: &'static Advancement, criterion: &str) -> bool {
|
||||
let mut result = false;
|
||||
let progress = self.progress.get_mut_or_start_progress(advancement);
|
||||
let was_done = progress.is_done();
|
||||
if progress.is_done() {
|
||||
progress.complete = false;
|
||||
if progress.revoke_progress(criterion) {
|
||||
//TODO listener
|
||||
self.progress_changed.insert(advancement);
|
||||
result = true;
|
||||
}
|
||||
|
||||
if was_done && !progress.is_done() {
|
||||
//TODO self.mark_for_visibility_update(advancement);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +330,7 @@ impl Serialize for PlayerAdvancement {
|
||||
{
|
||||
let mut map = serializer.serialize_map(Some(self.progress.len()))?;
|
||||
|
||||
for (advancement, progress) in &self.progress {
|
||||
for (advancement, progress) in &self.progress.map {
|
||||
map.serialize_entry(&advancement.id, progress)?;
|
||||
}
|
||||
map.end()
|
||||
@@ -237,13 +346,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn advancement_progress() {
|
||||
let progress = AdvancementProgress { complete: false };
|
||||
let mut criteria = HashMap::new();
|
||||
criteria.insert(Arc::from("testCriteria"), CriterionProgress::default());
|
||||
criteria.insert(Arc::from("testCriteria2"), CriterionProgress::default());
|
||||
let requirements = AdvancementRequirement {
|
||||
requirements: vec![
|
||||
vec![Arc::from("testCriteria")],
|
||||
vec![Arc::from("testCriteria2")],
|
||||
],
|
||||
};
|
||||
let mut progress = AdvancementProgress {
|
||||
criteria,
|
||||
requirements,
|
||||
};
|
||||
assert!(!progress.is_done());
|
||||
assert!(!progress.has_progress());
|
||||
|
||||
let complete_progress = AdvancementProgress { complete: true };
|
||||
assert!(complete_progress.is_done());
|
||||
assert!(complete_progress.has_progress());
|
||||
progress.grant_progress("testCriteria");
|
||||
assert!(!progress.is_done());
|
||||
assert!(progress.has_progress());
|
||||
progress.grant_progress("testCriteria2");
|
||||
assert!(progress.is_done());
|
||||
assert!(progress.has_progress());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -265,7 +388,7 @@ mod tests {
|
||||
let id = Uuid::new_v4();
|
||||
let mut pa = PlayerAdvancement::new(manager, id);
|
||||
let adv = Advancement::STORY_ROOT;
|
||||
let progress = pa.get_or_start_progress(adv);
|
||||
let progress = pa.progress.get_mut_or_start_progress(adv);
|
||||
assert!(
|
||||
!progress.is_done(),
|
||||
"New progress should not be marked done by default"
|
||||
@@ -280,12 +403,12 @@ mod tests {
|
||||
let mut pa = PlayerAdvancement::new(manager, id);
|
||||
let adv = Advancement::STORY_ROOT;
|
||||
{
|
||||
let progress_mut = pa.get_mut_or_start_progress(adv);
|
||||
progress_mut.complete = true;
|
||||
let progress_mut = pa.progress.get_mut_or_start_progress(adv);
|
||||
progress_mut.grant_progress("crafting_table");
|
||||
};
|
||||
assert!(pa.get_or_start_progress(adv).is_done());
|
||||
pa.revoke(adv);
|
||||
assert!(!pa.get_or_start_progress(adv).is_done());
|
||||
assert!(pa.progress.get_mut_or_start_progress(adv).is_done());
|
||||
pa.revoke(adv, "crafting_table");
|
||||
assert!(!pa.progress.get_mut_or_start_progress(adv).is_done());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -298,8 +421,8 @@ mod tests {
|
||||
// Add some advancement progress
|
||||
let adv = Advancement::STORY_ROOT;
|
||||
{
|
||||
let progress_mut = pa.get_mut_or_start_progress(adv);
|
||||
progress_mut.complete = true;
|
||||
let progress_mut = pa.progress.get_mut_or_start_progress(adv);
|
||||
progress_mut.grant_progress("crafting_table");
|
||||
};
|
||||
|
||||
// Save should succeed
|
||||
@@ -325,8 +448,8 @@ mod tests {
|
||||
// Add some advancement progress
|
||||
let adv = Advancement::STORY_ROOT;
|
||||
{
|
||||
let progress_mut = pa.get_mut_or_start_progress(adv);
|
||||
progress_mut.complete = true;
|
||||
let progress_mut = pa.progress.get_mut_or_start_progress(adv);
|
||||
progress_mut.grant_progress("crafting_table");
|
||||
};
|
||||
|
||||
// Save should return Ok but not actually save
|
||||
@@ -364,16 +487,19 @@ mod tests {
|
||||
let mut pa = PlayerAdvancement::new(manager, id);
|
||||
// Create a JSON file with advancement data
|
||||
let adv = Advancement::STORY_ROOT;
|
||||
let data = serde_json::json!({ adv.id.to_string(): { "complete": true } });
|
||||
let mut progress = AdvancementProgress::default();
|
||||
progress.update(AdvancementRequirement::from_const(adv.requirements));
|
||||
progress.grant_progress("crafting_table");
|
||||
let data = serde_json::json!({ adv.id.to_string():progress });
|
||||
std::fs::write(&pa.path, data.to_string()).unwrap();
|
||||
|
||||
// Load the file
|
||||
assert!(pa.load().is_ok(), "Load should succeed");
|
||||
|
||||
// Verify the advancement was loaded
|
||||
let progress = pa.get_or_start_progress(adv);
|
||||
let loaded_progress = pa.progress.get_mut_or_start_progress(adv);
|
||||
assert!(
|
||||
progress.is_done(),
|
||||
loaded_progress.is_done(),
|
||||
"Loaded advancement should be marked complete"
|
||||
);
|
||||
}
|
||||
@@ -389,8 +515,8 @@ mod tests {
|
||||
|
||||
let adv = Advancement::STORY_ROOT;
|
||||
{
|
||||
let progress_mut = pa.get_mut_or_start_progress(adv);
|
||||
progress_mut.complete = true;
|
||||
let progress_mut = pa.progress.get_mut_or_start_progress(adv);
|
||||
progress_mut.grant_progress("crafting_table");
|
||||
};
|
||||
|
||||
assert!(pa.save().is_ok(), "Save should succeed");
|
||||
@@ -400,7 +526,7 @@ mod tests {
|
||||
assert!(pa_loaded.load().is_ok(), "Load should succeed");
|
||||
|
||||
// Verify the loaded data matches the saved data
|
||||
let loaded_progress = pa_loaded.get_or_start_progress(adv);
|
||||
let loaded_progress = pa_loaded.progress.get_mut_or_start_progress(adv);
|
||||
assert!(
|
||||
loaded_progress.is_done(),
|
||||
"Loaded progress should match saved progress"
|
||||
@@ -418,8 +544,17 @@ mod tests {
|
||||
let manager = Arc::new(AdvancementManager::new(temp_dir.path(), true));
|
||||
|
||||
// Create a JSON file with invalid advancement ID
|
||||
let mut criteria = HashMap::new();
|
||||
criteria.insert(Arc::from("testCriteria"), CriterionProgress::default());
|
||||
let requirements = AdvancementRequirement {
|
||||
requirements: vec![vec![Arc::from("testCriteria")]],
|
||||
};
|
||||
let progress = AdvancementProgress {
|
||||
criteria,
|
||||
requirements,
|
||||
};
|
||||
let data = serde_json::json!({
|
||||
"invalid_advancement_id_12345": { "complete": true }
|
||||
"invalid_advancement_id_12345": progress
|
||||
});
|
||||
let id = Uuid::new_v4();
|
||||
let mut pa = PlayerAdvancement::new(manager, id);
|
||||
@@ -449,12 +584,12 @@ mod tests {
|
||||
let adv2 = Advancement::NETHER_ROOT;
|
||||
|
||||
{
|
||||
let progress_mut1 = pa.get_mut_or_start_progress(adv1);
|
||||
progress_mut1.complete = true;
|
||||
let progress_mut1 = pa.progress.get_mut_or_start_progress(adv1);
|
||||
progress_mut1.grant_progress("crafting_table");
|
||||
};
|
||||
{
|
||||
let progress_mut2 = pa.get_mut_or_start_progress(adv2);
|
||||
progress_mut2.complete = false;
|
||||
let progress_mut2 = pa.progress.get_mut_or_start_progress(adv2);
|
||||
progress_mut2.grant_progress("entered_nether");
|
||||
};
|
||||
|
||||
assert!(pa.save().is_ok(), "Save should succeed");
|
||||
|
||||
Reference in New Issue
Block a user