parent
a942c81ea8
commit
e4f710c83f
@ -133,11 +133,11 @@ impl std::str::FromStr for Wormhole{
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Counts{
|
||||
mode_start_counts:HashMap<ModeID,u64>,
|
||||
mode_finish_counts:HashMap<ModeID,u64>,
|
||||
mode_anticheat_counts:HashMap<ModeID,u64>,
|
||||
teleport_counts:HashMap<SpawnID,u64>,
|
||||
struct Counts<'a>{
|
||||
mode_start_counts:HashMap<ModeID,Vec<&'a str>>,
|
||||
mode_finish_counts:HashMap<ModeID,Vec<&'a str>>,
|
||||
mode_anticheat_counts:HashMap<ModeID,Vec<&'a str>>,
|
||||
teleport_counts:HashMap<SpawnID,Vec<&'a str>>,
|
||||
spawn_counts:HashMap<SpawnID,u64>,
|
||||
wormhole_in_counts:HashMap<WormholeID,u64>,
|
||||
wormhole_out_counts:HashMap<WormholeID,u64>,
|
||||
@ -147,7 +147,7 @@ pub struct ModelInfo<'a>{
|
||||
model_class:&'a str,
|
||||
model_name:&'a str,
|
||||
map_info:MapInfo<'a>,
|
||||
counts:Counts,
|
||||
counts:Counts<'a>,
|
||||
}
|
||||
|
||||
pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_dom_weak::Instance)->ModelInfo<'a>{
|
||||
@ -160,14 +160,14 @@ pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_d
|
||||
if class_is_a(instance.class.as_str(),"BasePart"){
|
||||
// Zones
|
||||
match instance.name.parse(){
|
||||
Ok(Zone::Start(mode_id))=>*counts.mode_start_counts.entry(mode_id).or_insert(0)+=1,
|
||||
Ok(Zone::Finish(mode_id))=>*counts.mode_finish_counts.entry(mode_id).or_insert(0)+=1,
|
||||
Ok(Zone::Anticheat(mode_id))=>*counts.mode_anticheat_counts.entry(mode_id).or_insert(0)+=1,
|
||||
Ok(Zone::Start(mode_id))=>counts.mode_start_counts.entry(mode_id).or_default().push(instance.name.as_str()),
|
||||
Ok(Zone::Finish(mode_id))=>counts.mode_finish_counts.entry(mode_id).or_default().push(instance.name.as_str()),
|
||||
Ok(Zone::Anticheat(mode_id))=>counts.mode_anticheat_counts.entry(mode_id).or_default().push(instance.name.as_str()),
|
||||
Err(_)=>(),
|
||||
}
|
||||
// Spawns & Teleports
|
||||
match instance.name.parse(){
|
||||
Ok(SpawnTeleport::Teleport(spawn_id))=>*counts.teleport_counts.entry(spawn_id).or_insert(0)+=1,
|
||||
Ok(SpawnTeleport::Teleport(spawn_id))=>counts.teleport_counts.entry(spawn_id).or_default().push(instance.name.as_str()),
|
||||
Ok(SpawnTeleport::Spawn(spawn_id))=>*counts.spawn_counts.entry(spawn_id).or_insert(0)+=1,
|
||||
Err(_)=>(),
|
||||
}
|
||||
@ -224,13 +224,13 @@ fn check_empty(value:&str)->Result<&str,StringEmpty>{
|
||||
}
|
||||
|
||||
// check for duplicate objects
|
||||
pub struct DuplicateCheckContext<ID>(HashMap<ID,u64>);
|
||||
pub struct DuplicateCheck<ID>(Result<(),DuplicateCheckContext<ID>>);
|
||||
impl<ID> DuplicateCheckContext<ID>{
|
||||
fn check(self)->DuplicateCheck<ID>{
|
||||
pub struct DuplicateCheckContext<ID,T>(HashMap<ID,T>);
|
||||
pub struct DuplicateCheck<ID,T>(Result<(),DuplicateCheckContext<ID,T>>);
|
||||
impl<ID,T> DuplicateCheckContext<ID,T>{
|
||||
fn check(self,f:impl Fn(&T)->bool)->DuplicateCheck<ID,T>{
|
||||
let Self(mut set)=self;
|
||||
// remove correct entries
|
||||
set.retain(|_,&mut c|c!=1);
|
||||
set.retain(|_,c|f(c));
|
||||
// if any entries remain, they are incorrect
|
||||
if set.is_empty(){
|
||||
DuplicateCheck(Ok(()))
|
||||
@ -241,23 +241,23 @@ impl<ID> DuplicateCheckContext<ID>{
|
||||
}
|
||||
|
||||
// check that there is at least one matching item for each item in a reference set, and no extra items
|
||||
pub struct SetDifferenceCheckContextAllowNone<ID>{
|
||||
extra:HashMap<ID,u64>,
|
||||
pub struct SetDifferenceCheckContextAllowNone<ID,T>{
|
||||
extra:HashMap<ID,T>,
|
||||
}
|
||||
pub struct SetDifferenceCheckContextAtLeastOne<ID>{
|
||||
extra:HashMap<ID,u64>,
|
||||
pub struct SetDifferenceCheckContextAtLeastOne<ID,T>{
|
||||
extra:HashMap<ID,T>,
|
||||
missing:HashSet<ID>,
|
||||
}
|
||||
pub struct SetDifferenceCheck<Context>(Result<(),Context>);
|
||||
impl<ID> SetDifferenceCheckContextAllowNone<ID>{
|
||||
fn new(initial_set:HashMap<ID,u64>)->Self{
|
||||
impl<ID,T> SetDifferenceCheckContextAllowNone<ID,T>{
|
||||
fn new(initial_set:HashMap<ID,T>)->Self{
|
||||
Self{
|
||||
extra:initial_set,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<ID:Eq+std::hash::Hash> SetDifferenceCheckContextAllowNone<ID>{
|
||||
fn check<T>(mut self,reference_set:&HashMap<ID,T>)->SetDifferenceCheck<Self>{
|
||||
impl<ID:Eq+std::hash::Hash,T> SetDifferenceCheckContextAllowNone<ID,T>{
|
||||
fn check<U>(mut self,reference_set:&HashMap<ID,U>)->SetDifferenceCheck<Self>{
|
||||
// remove correct entries
|
||||
for (id,_) in reference_set{
|
||||
self.extra.remove(id);
|
||||
@ -270,16 +270,16 @@ impl<ID:Eq+std::hash::Hash> SetDifferenceCheckContextAllowNone<ID>{
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<ID> SetDifferenceCheckContextAtLeastOne<ID>{
|
||||
fn new(initial_set:HashMap<ID,u64>)->Self{
|
||||
impl<ID,T> SetDifferenceCheckContextAtLeastOne<ID,T>{
|
||||
fn new(initial_set:HashMap<ID,T>)->Self{
|
||||
Self{
|
||||
extra:initial_set,
|
||||
missing:HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<ID:Copy+Eq+std::hash::Hash> SetDifferenceCheckContextAtLeastOne<ID>{
|
||||
fn check<T>(mut self,reference_set:&HashMap<ID,T>)->SetDifferenceCheck<Self>{
|
||||
impl<ID:Copy+Eq+std::hash::Hash,T> SetDifferenceCheckContextAtLeastOne<ID,T>{
|
||||
fn check<U>(mut self,reference_set:&HashMap<ID,U>)->SetDifferenceCheck<Self>{
|
||||
// remove correct entries
|
||||
for (id,_) in reference_set{
|
||||
if self.extra.remove(id).is_none(){
|
||||
@ -323,22 +323,22 @@ pub struct MapCheck<'a>{
|
||||
// MapStart must exist
|
||||
mapstart:Result<(),()>,
|
||||
// No duplicate map starts (including bonuses)
|
||||
mode_start_counts:DuplicateCheck<ModeID>,
|
||||
mode_start_counts:DuplicateCheck<ModeID,Vec<&'a str>>,
|
||||
// At least one finish zone for each start zone, and no finishes with no start
|
||||
mode_finish_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<ModeID>>,
|
||||
mode_finish_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<ModeID,Vec<&'a str>>>,
|
||||
// Check for dangling MapAnticheat zones (no associated MapStart)
|
||||
mode_anticheat_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<ModeID>>,
|
||||
mode_anticheat_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<ModeID,Vec<&'a str>>>,
|
||||
// Spawn1 must exist
|
||||
spawn1:Result<(),()>,
|
||||
// Check for dangling Teleport# (no associated Spawn#)
|
||||
teleport_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<SpawnID>>,
|
||||
teleport_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<SpawnID,Vec<&'a str>>>,
|
||||
// No duplicate Spawn#
|
||||
spawn_counts:DuplicateCheck<SpawnID>,
|
||||
spawn_counts:DuplicateCheck<SpawnID,u64>,
|
||||
// Check for dangling WormholeIn# (no associated WormholeOut#)
|
||||
wormhole_in_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<WormholeID>>,
|
||||
wormhole_in_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<WormholeID,u64>>,
|
||||
// No duplicate WormholeOut# (duplicate WormholeIn# ok)
|
||||
// No dangling WormholeOut#
|
||||
wormhole_out_counts:DuplicateCheck<WormholeID>,
|
||||
wormhole_out_counts:DuplicateCheck<WormholeID,u64>,
|
||||
}
|
||||
|
||||
impl<'a> ModelInfo<'a>{
|
||||
@ -394,7 +394,7 @@ impl<'a> ModelInfo<'a>{
|
||||
.check(&self.counts.mode_start_counts);
|
||||
|
||||
// There must be exactly one start zone for every mode in the map.
|
||||
let mode_start_counts=DuplicateCheckContext(self.counts.mode_start_counts).check();
|
||||
let mode_start_counts=DuplicateCheckContext(self.counts.mode_start_counts).check(|c|c.len()<=1);
|
||||
|
||||
// Check that there are no Teleports without a corresponding Spawn.
|
||||
// Spawns are allowed to have 0 Teleports.
|
||||
@ -402,7 +402,7 @@ impl<'a> ModelInfo<'a>{
|
||||
.check(&self.counts.spawn_counts);
|
||||
|
||||
// There must be exactly one of any perticular spawn id in the map.
|
||||
let spawn_counts=DuplicateCheckContext(self.counts.spawn_counts).check();
|
||||
let spawn_counts=DuplicateCheckContext(self.counts.spawn_counts).check(|&c|c<=1);
|
||||
|
||||
// Check that at least one WormholeIn exists for each WormholeOut.
|
||||
// This also checks that there are no WormholeIn without a corresponding WormholeOut.
|
||||
@ -410,7 +410,7 @@ impl<'a> ModelInfo<'a>{
|
||||
.check(&self.counts.wormhole_out_counts);
|
||||
|
||||
// There must be exactly one of any perticular wormhole out id in the map.
|
||||
let wormhole_out_counts=DuplicateCheckContext(self.counts.wormhole_out_counts).check();
|
||||
let wormhole_out_counts=DuplicateCheckContext(self.counts.wormhole_out_counts).check(|&c|c<=1);
|
||||
|
||||
MapCheck{
|
||||
model_class,
|
||||
@ -516,9 +516,9 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
|
||||
}
|
||||
if let DuplicateCheck(Err(DuplicateCheckContext(context)))=&self.mode_start_counts{
|
||||
write!(f,"Duplicate start zones: ")?;
|
||||
write_comma_separated(f,context.iter(),|f,(mode_id,count)|{
|
||||
write_comma_separated(f,context.iter(),|f,(mode_id,names)|{
|
||||
write_zone!(f,mode_id,"Start")?;
|
||||
write!(f,"({count} duplicates)")?;
|
||||
write!(f,"({} duplicates)",names.len())?;
|
||||
Ok(())
|
||||
})?;
|
||||
writeln!(f,"")?;
|
||||
@ -527,7 +527,7 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
|
||||
if !context.extra.is_empty(){
|
||||
let plural=if context.extra.len()==1{"zone"}else{"zones"};
|
||||
write!(f,"Extra finish {plural} with no matching start zone: ")?;
|
||||
write_comma_separated(f,context.extra.iter(),|f,(mode_id,_count)|
|
||||
write_comma_separated(f,context.extra.iter(),|f,(mode_id,_names)|
|
||||
write_zone!(f,mode_id,"Finish")
|
||||
)?;
|
||||
writeln!(f,"")?;
|
||||
@ -545,7 +545,7 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
|
||||
if !context.extra.is_empty(){
|
||||
let plural=if context.extra.len()==1{"zone"}else{"zones"};
|
||||
write!(f,"Extra anticheat {plural} with no matching start zone: ")?;
|
||||
write_comma_separated(f,context.extra.iter(),|f,(mode_id,_count)|
|
||||
write_comma_separated(f,context.extra.iter(),|f,(mode_id,_names)|
|
||||
write_zone!(f,mode_id,"Anticheat")
|
||||
)?;
|
||||
writeln!(f,"")?;
|
||||
@ -555,13 +555,12 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
|
||||
writeln!(f,"Model has no Spawn1")?;
|
||||
}
|
||||
if let SetDifferenceCheck(Err(context))=&self.teleport_counts{
|
||||
if !context.extra.is_empty(){
|
||||
// TODO: include original names of objects in hashmap value as Vec<&str>
|
||||
let plural=if context.extra.len()==1{"object"}else{"objects"};
|
||||
for (_,names) in &context.extra{
|
||||
let plural=if names.len()==1{"object"}else{"objects"};
|
||||
write!(f,"Extra Spawn-type {plural} with no matching Spawn: ")?;
|
||||
write_comma_separated(f,context.extra.iter(),|f,(SpawnID(spawn_id),_count)|
|
||||
write!(f,"Teleport or Trigger or SpawnAt #{spawn_id}")
|
||||
)?;
|
||||
write_comma_separated(f,names.iter(),|f,&name|{
|
||||
write!(f,"{name}")
|
||||
})?;
|
||||
writeln!(f,"")?;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user