Compare commits

...

2 Commits

@ -40,25 +40,24 @@ impl From<crate::nats_types::CheckSubmissionRequest> for CheckRequest{
}
}
enum Zone{
Start(ModeID),
Finish(ModeID),
Anticheat(ModeID),
}
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
struct ModeID(u64);
impl ModeID{
const MAIN:Self=Self(0);
const BONUS:Self=Self(1);
}
enum Zone{
Start(ModeID),
Finish(ModeID),
Anticheat(ModeID),
}
#[allow(dead_code)]
pub enum ZoneParseError{
pub enum IDParseError{
NoCaptures,
ParseInt(core::num::ParseIntError)
}
impl std::str::FromStr for Zone{
type Err=ZoneParseError;
type Err=IDParseError;
fn from_str(s:&str)->Result<Self,Self::Err>{
match s{
"MapStart"=>Ok(Self::Start(ModeID::MAIN)),
@ -70,38 +69,78 @@ impl std::str::FromStr for Zone{
other=>{
let bonus_start_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Start$|^BonusStart(\d+)$");
if let Some(captures)=bonus_start_pattern.captures(other){
return Ok(Self::Start(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?)));
return Ok(Self::Start(ModeID(captures[1].parse().map_err(IDParseError::ParseInt)?)));
}
let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Finish$|^BonusFinish(\d+)$");
if let Some(captures)=bonus_finish_pattern.captures(other){
return Ok(Self::Finish(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?)));
return Ok(Self::Finish(ModeID(captures[1].parse().map_err(IDParseError::ParseInt)?)));
}
let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Anticheat$|^BonusAnticheat(\d+)$");
if let Some(captures)=bonus_finish_pattern.captures(other){
return Ok(Self::Anticheat(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?)));
return Ok(Self::Anticheat(ModeID(captures[1].parse().map_err(IDParseError::ParseInt)?)));
}
Err(ZoneParseError::NoCaptures)
Err(IDParseError::NoCaptures)
}
}
}
}
#[derive(Debug,Hash,Eq,PartialEq)]
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
struct SpawnID(u64);
impl SpawnID{
const FIRST:Self=Self(1);
}
#[derive(Debug,Hash,Eq,PartialEq)]
struct WormholeOutID(u64);
enum SpawnTeleport{
Teleport(SpawnID),
Spawn(SpawnID),
}
impl std::str::FromStr for SpawnTeleport{
type Err=IDParseError;
fn from_str(s:&str)->Result<Self,Self::Err>{
// Trigger ForceTrigger Teleport ForceTeleport SpawnAt ForceSpawnAt
let bonus_start_pattern=lazy_regex::lazy_regex!(r"^(?:Force)?(Teleport|SpawnAt|Trigger)(\d+)$");
if let Some(captures)=bonus_start_pattern.captures(s){
return Ok(Self::Teleport(SpawnID(captures[1].parse().map_err(IDParseError::ParseInt)?)));
}
// Spawn
let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^Spawn(\d+)$");
if let Some(captures)=bonus_finish_pattern.captures(s){
return Ok(Self::Spawn(SpawnID(captures[1].parse().map_err(IDParseError::ParseInt)?)));
}
Err(IDParseError::NoCaptures)
}
}
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
struct WormholeID(u64);
enum Wormhole{
In(WormholeID),
Out(WormholeID),
}
impl std::str::FromStr for Wormhole{
type Err=IDParseError;
fn from_str(s:&str)->Result<Self,Self::Err>{
let bonus_start_pattern=lazy_regex::lazy_regex!(r"^WormholeIn(\d+)$");
if let Some(captures)=bonus_start_pattern.captures(s){
return Ok(Self::In(WormholeID(captures[1].parse().map_err(IDParseError::ParseInt)?)));
}
let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^WormholeOut(\d+)$");
if let Some(captures)=bonus_finish_pattern.captures(s){
return Ok(Self::Out(WormholeID(captures[1].parse().map_err(IDParseError::ParseInt)?)));
}
Err(IDParseError::NoCaptures)
}
}
#[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>,
spawn_counts:HashMap<SpawnID,u64>,
wormhole_out_counts:HashMap<WormholeOutID,u64>,
wormhole_in_counts:HashMap<WormholeID,u64>,
wormhole_out_counts:HashMap<WormholeID,u64>,
}
pub struct ModelInfo<'a>{
@ -126,19 +165,17 @@ pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_d
Ok(Zone::Anticheat(mode_id))=>*counts.mode_anticheat_counts.entry(mode_id).or_insert(0)+=1,
Err(_)=>(),
}
// Spawns
let spawn_pattern=lazy_regex::lazy_regex!(r"^Spawn(\d+)$");
if let Some(captures)=spawn_pattern.captures(instance.name.as_str()){
if let Ok(spawn_id)=captures[1].parse(){
*counts.spawn_counts.entry(SpawnID(spawn_id)).or_insert(0)+=1;
}
// Spawns & Teleports
match instance.name.parse(){
Ok(SpawnTeleport::Teleport(spawn_id))=>*counts.teleport_counts.entry(spawn_id).or_insert(0)+=1,
Ok(SpawnTeleport::Spawn(spawn_id))=>*counts.spawn_counts.entry(spawn_id).or_insert(0)+=1,
Err(_)=>(),
}
// WormholeOuts
let wormhole_out_pattern=lazy_regex::lazy_regex!(r"^WormholeOut(\d+)$");
if let Some(captures)=wormhole_out_pattern.captures(instance.name.as_str()){
if let Ok(wormhole_out_id)=captures[1].parse(){
*counts.wormhole_out_counts.entry(WormholeOutID(wormhole_out_id)).or_insert(0)+=1;
}
// Wormholes
match instance.name.parse(){
Ok(Wormhole::In(wormhole_id))=>*counts.wormhole_in_counts.entry(wormhole_id).or_insert(0)+=1,
Ok(Wormhole::Out(wormhole_id))=>*counts.wormhole_out_counts.entry(wormhole_id).or_insert(0)+=1,
Err(_)=>(),
}
}
}
@ -289,14 +326,19 @@ pub struct MapCheck<'a>{
mode_start_counts:DuplicateCheck<ModeID>,
// At least one finish zone for each start zone, and no finishes with no start
mode_finish_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<ModeID>>,
// check for dangling MapAnticheat zones (no associated MapStart)
// Check for dangling MapAnticheat zones (no associated MapStart)
mode_anticheat_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<ModeID>>,
// Spawn1 must exist
spawn1:Result<(),()>,
// Check for dangling Teleport# (no associated Spawn#)
teleport_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<SpawnID>>,
// No duplicate Spawn#
spawn_counts:DuplicateCheck<SpawnID>,
// Check for dangling WormholeIn# (no associated WormholeOut#)
wormhole_in_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<WormholeID>>,
// No duplicate WormholeOut# (duplicate WormholeIn# ok)
wormhole_out_counts:DuplicateCheck<WormholeOutID>,
// No dangling WormholeOut#
wormhole_out_counts:DuplicateCheck<WormholeID>,
}
impl<'a> ModelInfo<'a>{
@ -354,9 +396,19 @@ impl<'a> ModelInfo<'a>{
// There must be exactly one start zone for every mode in the map.
let mode_start_counts=DuplicateCheckContext(self.counts.mode_start_counts).check();
// Check that there are no Teleports without a corresponding Spawn.
// Spawns are allowed to have 0 Teleports.
let teleport_counts=SetDifferenceCheckContextAllowNone::new(self.counts.teleport_counts)
.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();
// Check that at least one WormholeIn exists for each WormholeOut.
// This also checks that there are no WormholeIn without a corresponding WormholeOut.
let wormhole_in_counts=SetDifferenceCheckContextAtLeastOne::new(self.counts.wormhole_in_counts)
.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();
@ -371,7 +423,9 @@ impl<'a> ModelInfo<'a>{
mode_finish_counts,
mode_anticheat_counts,
spawn1,
teleport_counts,
spawn_counts,
wormhole_in_counts,
wormhole_out_counts,
}
}
@ -391,7 +445,9 @@ impl<'a> MapCheck<'a>{
mode_finish_counts:SetDifferenceCheck(Ok(())),
mode_anticheat_counts:SetDifferenceCheck(Ok(())),
spawn1:Ok(()),
teleport_counts:SetDifferenceCheck(Ok(())),
spawn_counts:DuplicateCheck(Ok(())),
wormhole_in_counts:SetDifferenceCheck(Ok(())),
wormhole_out_counts:DuplicateCheck(Ok(())),
}=>{
Ok(MapInfoOwned{
@ -469,14 +525,16 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
}
if let SetDifferenceCheck(Err(context))=&self.mode_finish_counts{
if !context.extra.is_empty(){
write!(f,"Extra finish zones with no matching start zone: ")?;
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_zone!(f,mode_id,"Finish")
)?;
writeln!(f,"")?;
}
if !context.missing.is_empty(){
write!(f,"Missing finish zones: ")?;
let plural=if context.missing.len()==1{"zone"}else{"zones"};
write!(f,"Missing finish {plural}: ")?;
write_comma_separated(f,context.missing.iter(),|f,mode_id|
write_zone!(f,mode_id,"Finish")
)?;
@ -485,7 +543,8 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
}
if let SetDifferenceCheck(Err(context))=&self.mode_anticheat_counts{
if !context.extra.is_empty(){
write!(f,"Extra anticheat zones with no matching start zone: ")?;
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_zone!(f,mode_id,"Anticheat")
)?;
@ -495,17 +554,45 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
if let Err(())=&self.spawn1{
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 HashSet<&str>
write!(f,"Extra Teleport with no matching Spawn: ")?;
write_comma_separated(f,context.extra.iter(),|f,(SpawnID(spawn_id),_count)|
write!(f,"Teleport{spawn_id}")
)?;
writeln!(f,"")?;
}
}
if let DuplicateCheck(Err(DuplicateCheckContext(context)))=&self.spawn_counts{
write!(f,"Duplicate spawn zones: ")?;
write!(f,"Duplicate Spawn: ")?;
write_comma_separated(f,context.iter(),|f,(SpawnID(spawn_id),count)|
write!(f,"Spawn{spawn_id}({count} duplicates)")
)?;
writeln!(f,"")?;
}
if let SetDifferenceCheck(Err(context))=&self.wormhole_in_counts{
if !context.extra.is_empty(){
write!(f,"WormholeIn with no matching WormholeOut: ")?;
write_comma_separated(f,context.extra.iter(),|f,(WormholeID(wormhole_id),_count)|
write!(f,"WormholeIn{wormhole_id}")
)?;
writeln!(f,"")?;
}
if !context.missing.is_empty(){
// This counts WormholeIn objects, but
// flipped logic is easier to understand
write!(f,"WormholeOut with no matching WormholeIn: ")?;
write_comma_separated(f,context.missing.iter(),|f,WormholeID(wormhole_id)|
write!(f,"WormholeOut{wormhole_id}")
)?;
writeln!(f,"")?;
}
}
if let DuplicateCheck(Err(DuplicateCheckContext(context)))=&self.wormhole_out_counts{
write!(f,"Duplicate wormhole out: ")?;
write_comma_separated(f,context.iter(),|f,(WormholeOutID(wormhole_out_id),count)|
write!(f,"WormholeOut{wormhole_out_id}({count} duplicates)")
write!(f,"Duplicate WormholeOut: ")?;
write_comma_separated(f,context.iter(),|f,(WormholeID(wormhole_id),count)|
write!(f,"WormholeOut{wormhole_id}({count} duplicates)")
)?;
writeln!(f,"")?;
}