diff --git a/validation/src/check.rs b/validation/src/check.rs index bf37214..8523f6b 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -47,12 +47,20 @@ impl From for CheckRequest{ } } -#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] +#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,Ord,PartialOrd)] struct ModeID(u64); impl ModeID{ const MAIN:Self=Self(0); const BONUS:Self=Self(1); } +impl std::fmt::Display for ModeID{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + match self{ + &ModeID::MAIN=>write!(f,"Main"), + &ModeID(mode_id)=>write!(f,"Bonus{mode_id}"), + } + } +} enum Zone{ Start, Finish, @@ -446,6 +454,8 @@ struct MapCheck<'a>{ mode_finish_counts:SetDifferenceCheck>>, // Check for dangling MapAnticheat zones (no associated MapStart) mode_anticheat_counts:SetDifferenceCheck>>, + // Check that modes are sequential + modes_sequential:Result<(),Vec>, // Spawn1 must exist spawn1:Result, // Check for dangling Teleport# (no associated Spawn#) @@ -514,6 +524,33 @@ impl<'a> ModelInfo<'a>{ let mode_anticheat_counts=SetDifferenceCheckContextAllowNone::new(self.counts.mode_anticheat_counts) .check(&self.counts.mode_start_counts); + // There must not be non-sequential modes. If Bonus100 exists, Bonuses 1-99 had better also exist. + fn count_sequential(modes:&HashMap>)->usize{ + for mode_id in 0..modes.len(){ + if !modes.contains_key(&ModeID(mode_id as u64)){ + return mode_id; + } + } + return modes.len(); + } + let modes_sequential={ + let sequential=count_sequential(&self.counts.mode_start_counts); + if sequential==self.counts.mode_start_counts.len(){ + Ok(()) + }else{ + let mut non_sequential=Vec::with_capacity(self.counts.mode_start_counts.len()-sequential); + for (&mode_id,_) in &self.counts.mode_start_counts{ + let ModeID(mode_id_u64)=mode_id; + if !(mode_id_u64 ModelInfo<'a>{ mode_start_counts, mode_finish_counts, mode_anticheat_counts, + modes_sequential, spawn1, teleport_counts, spawn_counts, @@ -573,6 +611,7 @@ impl MapCheck<'_>{ mode_start_counts:DuplicateCheck(Ok(())), mode_finish_counts:SetDifferenceCheck(Ok(())), mode_anticheat_counts:SetDifferenceCheck(Ok(())), + modes_sequential:Ok(()), spawn1:Ok(Exists), teleport_counts:SetDifferenceCheck(Ok(())), spawn_counts:DuplicateCheck(Ok(())), @@ -746,6 +785,15 @@ impl MapCheck<'_>{ } } }; + let sequential_modes=match &self.modes_sequential{ + Ok(())=>passed!("SequentialModes"), + Err(context)=>{ + let non_sequential=context.len(); + let plural_non_sequential=if non_sequential==1{"mode"}else{"modes"}; + let comma_separated=Separated::new(", ",||context.iter()); + summary_format!("SequentialModes","{non_sequential} {plural_non_sequential} should use a lower ModeID (no gaps): {comma_separated}") + } + }; let spawn1=match &self.spawn1{ Ok(Exists)=>passed!("Spawn1"), Err(Absent)=>summary_format!("Spawn1","Model has no Spawn1"), @@ -824,6 +872,7 @@ impl MapCheck<'_>{ extra_finish, missing_finish, dangling_anticheat, + sequential_modes, spawn1, dangling_teleport, duplicate_spawns,