From bbb1857377fcc050ccc3d657e668ff8e7b07ff0d Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Sat, 28 Oct 2023 16:38:16 -0700
Subject: [PATCH] attributes: checkpoints, jump count

---
 src/load_roblox.rs | 11 +++++---
 src/model.rs       | 39 +++++++++++++++-------------
 src/physics.rs     | 63 +++++++++++++++++++++++-----------------------
 3 files changed, 61 insertions(+), 52 deletions(-)

diff --git a/src/load_roblox.rs b/src/load_roblox.rs
index 0798cf00..ed8ef603 100644
--- a/src/load_roblox.rs
+++ b/src/load_roblox.rs
@@ -52,6 +52,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse
 			force_can_collide=false;
 			general.accelerator=Some(crate::model::GameMechanicAccelerator{acceleration:velocity});
 		},
+		"UnorderedCheckpoint"=>general.checkpoint=Some(crate::model::GameMechanicCheckpoint::Unordered{mode_id:0}),
 		"SetVelocity"=>general.trajectory=Some(crate::model::GameMechanicSetTrajectory::Velocity(velocity)),
 		"MapFinish"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Finish})},
 		"MapAnticheat"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Anitcheat})},
@@ -110,6 +111,12 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse
 					"WormholeIn"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::Wormhole(crate::model::GameMechanicWormhole{destination_model_id:captures[2].parse::<u32>().unwrap()})),
 					_=>panic!("regex3[1] messed up bad"),
 				}
+			}else if let Some(captures)=lazy_regex::regex!(r"^(OrderedCheckpoint)(\d+)$")
+			.captures(other){
+				match &captures[1]{
+					"OrderedCheckpoint"=>general.checkpoint=Some(crate::model::GameMechanicCheckpoint::Ordered{mode_id:0,checkpoint_id:captures[2].parse::<u32>().unwrap()}),
+					_=>panic!("regex3[1] messed up bad"),
+				}
 			}
 		}
 	}
@@ -257,14 +264,12 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::Index
 						spawn_point=model_transform.transform_point3(Planar64Vec3::ZERO)+Planar64Vec3::Y*5/2;
 						Some(crate::model::TempIndexedAttributes::Start(crate::model::TempAttrStart{mode_id:0}))
 					},
-					"UnorderedCheckpoint"=>Some(crate::model::TempIndexedAttributes::UnorderedCheckpoint(crate::model::TempAttrUnorderedCheckpoint{mode_id:0})),
 					other=>{
-						let regman=lazy_regex::regex!(r"^(BonusStart|Spawn|ForceSpawn|OrderedCheckpoint|WormholeOut)(\d+)$");
+						let regman=lazy_regex::regex!(r"^(BonusStart|Spawn|ForceSpawn|WormholeOut)(\d+)$");
 						if let Some(captures) = regman.captures(other) {
 							match &captures[1]{
 								"BonusStart"=>Some(crate::model::TempIndexedAttributes::Start(crate::model::TempAttrStart{mode_id:captures[2].parse::<u32>().unwrap()})),
 								"Spawn"|"ForceSpawn"=>Some(crate::model::TempIndexedAttributes::Spawn(crate::model::TempAttrSpawn{mode_id:0,stage_id:captures[2].parse::<u32>().unwrap()})),
-								"OrderedCheckpoint"=>Some(crate::model::TempIndexedAttributes::OrderedCheckpoint(crate::model::TempAttrOrderedCheckpoint{mode_id:0,checkpoint_id:captures[2].parse::<u32>().unwrap()})),
 								"WormholeOut"=>Some(crate::model::TempIndexedAttributes::Wormhole(crate::model::TempAttrWormhole{wormhole_id:captures[2].parse::<u32>().unwrap()})),
 								_=>None,
 							}
diff --git a/src/model.rs b/src/model.rs
index a7bd22a3..c53e5aca 100644
--- a/src/model.rs
+++ b/src/model.rs
@@ -52,8 +52,6 @@ pub struct IndexedModelInstances{
 pub struct ModeDescription{
 	pub start:usize,//start=model_id
 	pub spawns:Vec<usize>,//spawns[spawn_id]=model_id
-	pub ordered_checkpoints:Vec<usize>,//ordered_checkpoints[checkpoint_id]=model_id
-	pub unordered_checkpoints:Vec<usize>,//unordered_checkpoints[checkpoint_id]=model_id
 	pub spawn_from_stage_id:std::collections::HashMap::<u32,usize>,
 	pub ordered_checkpoint_from_checkpoint_id:std::collections::HashMap::<u32,usize>,
 }
@@ -61,9 +59,6 @@ impl ModeDescription{
 	pub fn get_spawn_model_id(&self,stage_id:u32)->Option<&usize>{
 		self.spawns.get(*self.spawn_from_stage_id.get(&stage_id)?)
 	}
-	pub fn get_ordered_checkpoint_model_id(&self,checkpoint_id:u32)->Option<&usize>{
-		self.ordered_checkpoints.get(*self.ordered_checkpoint_from_checkpoint_id.get(&checkpoint_id)?)
-	}
 }
 //I don't want this code to exist!
 #[derive(Clone)]
@@ -76,23 +71,12 @@ pub struct TempAttrSpawn{
 	pub stage_id:u32,
 }
 #[derive(Clone)]
-pub struct TempAttrOrderedCheckpoint{
-	pub mode_id:u32,
-	pub checkpoint_id:u32,
-}
-#[derive(Clone)]
-pub struct TempAttrUnorderedCheckpoint{
-	pub mode_id:u32,
-}
-#[derive(Clone)]
 pub struct TempAttrWormhole{
 	pub wormhole_id:u32,
 }
 pub enum TempIndexedAttributes{
 	Start(TempAttrStart),
 	Spawn(TempAttrSpawn),
-	OrderedCheckpoint(TempAttrOrderedCheckpoint),
-	UnorderedCheckpoint(TempAttrUnorderedCheckpoint),
 	Wormhole(TempAttrWormhole),
 }
 
@@ -126,6 +110,16 @@ pub enum GameMechanicBooster{
 	Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
 }
 #[derive(Clone)]
+pub enum GameMechanicCheckpoint{
+	Ordered{
+		mode_id:u32,
+		checkpoint_id:u32,
+	},
+	Unordered{
+		mode_id:u32,
+	},
+}
+#[derive(Clone)]
 pub enum TrajectoryChoice{
 	HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time
 	LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time
@@ -171,6 +165,13 @@ pub enum StageElementBehaviour{
  	Trigger,
  	Teleport,
  	Platform,
+ 	//Acts like a trigger if you haven't hit all the checkpoints.
+ 	Checkpoint{
+ 		//if this is 2 you must have hit OrderedCheckpoint(0) OrderedCheckpoint(1) OrderedCheckpoint(2) to pass
+ 		ordered_checkpoint_id:Option<u32>,
+ 		//if this is 2 you must have hit at least 2 UnorderedCheckpoints to pass
+ 		unordered_checkpoint_count:u32,
+ 	},
  	JumpLimit(u32),
  	//Speedtrap(TrapCondition),//Acts as a trigger with a speed condition
 }
@@ -199,15 +200,17 @@ pub enum TeleportBehaviour{
 pub struct GameMechanicAttributes{
 	pub zone:Option<GameMechanicZone>,
 	pub booster:Option<GameMechanicBooster>,
+	pub checkpoint:Option<GameMechanicCheckpoint>,
 	pub trajectory:Option<GameMechanicSetTrajectory>,
 	pub teleport_behaviour:Option<TeleportBehaviour>,
 	pub accelerator:Option<GameMechanicAccelerator>,
 }
 impl GameMechanicAttributes{
 	pub fn any(&self)->bool{
-		self.booster.is_some()
+		self.zone.is_some()
+		||self.booster.is_some()
+		||self.checkpoint.is_some()
 		||self.trajectory.is_some()
-		||self.zone.is_some()
 		||self.teleport_behaviour.is_some()
 		||self.accelerator.is_some()
 	}
diff --git a/src/physics.rs b/src/physics.rs
index d0c25253..97fd93b5 100644
--- a/src/physics.rs
+++ b/src/physics.rs
@@ -258,13 +258,18 @@ impl std::default::Default for PhysicsCamera{
 }
 
 pub struct GameMechanicsState{
-	pub stage_id:u32,
-	//jump_counts:HashMap<u32,u32>,
+	stage_id:u32,
+	jump_counts:std::collections::HashMap<usize,u32>,//model_id -> jump count
+	next_ordered_checkpoint_id:u32,//which OrderedCheckpoint model_id you must pass next (if 0 you haven't passed OrderedCheckpoint0)
+	unordered_checkpoints:std::collections::HashSet<usize>,//hashset of UnorderedCheckpoint model ids
 }
 impl std::default::Default for GameMechanicsState{
-	fn default() -> Self {
+	fn default()->Self{
 		Self{
 			stage_id:0,
+			next_ordered_checkpoint_id:0,
+			unordered_checkpoints:std::collections::HashSet::new(),
+			jump_counts:std::collections::HashMap::new(),
 		}
 	}
 }
@@ -784,8 +789,6 @@ impl PhysicsState {
 	pub fn generate_models(&mut self,indexed_models:&crate::model::IndexedModelInstances){
 		let mut starts=Vec::new();
 		let mut spawns=Vec::new();
-		let mut ordered_checkpoints=Vec::new();
-		let mut unordered_checkpoints=Vec::new();
 		for model in &indexed_models.models{
 			//make aabb and run vertices to get realistic bounds
 			for model_instance in &model.instances{
@@ -795,8 +798,6 @@ impl PhysicsState {
 						match attr{
 							crate::model::TempIndexedAttributes::Start(s)=>starts.push((model_id,s.clone())),
 							crate::model::TempIndexedAttributes::Spawn(s)=>spawns.push((model_id,s.clone())),
-							crate::model::TempIndexedAttributes::OrderedCheckpoint(s)=>ordered_checkpoints.push((model_id,s.clone())),
-							crate::model::TempIndexedAttributes::UnorderedCheckpoint(s)=>unordered_checkpoints.push((model_id,s.clone())),
 							crate::model::TempIndexedAttributes::Wormhole(s)=>{self.models.model_id_from_wormhole_id.insert(s.wormhole_id,model_id);},
 						}
 					}
@@ -808,9 +809,9 @@ impl PhysicsState {
 		//this code builds ModeDescriptions from the unsorted lists at the top of the function
 		starts.sort_by_key(|tup|tup.1.mode_id);
 		let mut mode_id_from_map_mode_id=std::collections::HashMap::new();
-		let mut modedatas:Vec<(usize,Vec<(u32,usize)>,Vec<(u32,usize)>,Vec<usize>,u32)>=starts.into_iter().enumerate().map(|(i,(model_id,s))|{
+		let mut modedatas:Vec<(usize,Vec<(u32,usize)>,u32)>=starts.into_iter().enumerate().map(|(i,(model_id,s))|{
 			mode_id_from_map_mode_id.insert(s.mode_id,i);
-			(model_id,Vec::new(),Vec::new(),Vec::new(),s.mode_id)
+			(model_id,Vec::new(),s.mode_id)
 		}).collect();
 		for (model_id,s) in spawns{
 			if let Some(mode_id)=mode_id_from_map_mode_id.get(&s.mode_id){
@@ -819,30 +820,13 @@ impl PhysicsState {
 				}
 			}
 		}
-		for (model_id,s) in ordered_checkpoints{
-			if let Some(mode_id)=mode_id_from_map_mode_id.get(&s.mode_id){
-				if let Some(modedata)=modedatas.get_mut(*mode_id){
-					modedata.2.push((s.checkpoint_id,model_id));
-				}
-			}
-		}
-		for (model_id,s) in unordered_checkpoints{
-			if let Some(mode_id)=mode_id_from_map_mode_id.get(&s.mode_id){
-				if let Some(modedata)=modedatas.get_mut(*mode_id){
-					modedata.3.push(model_id);
-				}
-			}
-		}
 		for mut tup in modedatas.into_iter(){
 			tup.1.sort_by_key(|tup|tup.0);
-			tup.2.sort_by_key(|tup|tup.0);
 			let mut eshmep1=std::collections::HashMap::new();
 			let mut eshmep2=std::collections::HashMap::new();
-			self.modes.insert(tup.4,crate::model::ModeDescription{
+			self.modes.insert(tup.2,crate::model::ModeDescription{
 				start:tup.0,
 				spawns:tup.1.into_iter().enumerate().map(|(i,tup)|{eshmep1.insert(tup.0,i);tup.1}).collect(),
-				ordered_checkpoints:tup.2.into_iter().enumerate().map(|(i,tup)|{eshmep2.insert(tup.0,i);tup.1}).collect(),
-				unordered_checkpoints:tup.3,
 				spawn_from_stage_id:eshmep1,
 				ordered_checkpoint_from_checkpoint_id:eshmep2,
 			});
@@ -1273,6 +1257,11 @@ fn teleport(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,poi
 	//TODO: calculate contacts and determine the actual state
 	//touching.recalculate(body);
 }
+fn teleport_to_spawn(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,mode:&crate::model::ModeDescription,models:&PhysicsModels,stage_id:u32)->Option<MoveState>{
+	let model=models.get(*mode.get_spawn_model_id(stage_id)? as usize)?;
+	let point=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox_halfsize.y()+Planar64::ONE/16);
+	Some(teleport(body,touching,style,point))
+}
 
 fn run_teleport_behaviour(teleport_behaviour:&Option<crate::model::TeleportBehaviour>,game:&mut GameMechanicsState,models:&PhysicsModels,modes:&Modes,style:&StyleModifiers,touching:&mut TouchingState,body:&mut Body,model:&ModelPhysics)->Option<MoveState>{
 	match teleport_behaviour{
@@ -1285,12 +1274,24 @@ fn run_teleport_behaviour(teleport_behaviour:&Option<crate::model::TeleportBehav
 				crate::model::StageElementBehaviour::Trigger
 				|crate::model::StageElementBehaviour::Teleport=>{
 					//I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird
-					let model=models.get(*modes.get_mode(stage_element.mode_id)?.get_spawn_model_id(game.stage_id)? as usize)?;
-					let point=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox_halfsize.y()+Planar64::ONE/16);
-					Some(teleport(body,touching,style,point))
+					teleport_to_spawn(body,touching,style,modes.get_mode(stage_element.mode_id)?,models,game.stage_id)
 				},
 				crate::model::StageElementBehaviour::Platform=>None,
-				crate::model::StageElementBehaviour::JumpLimit(_)=>None,//TODO
+				&crate::model::StageElementBehaviour::JumpLimit(jump_limit)=>{
+					//let count=game.jump_counts.get(&model.id);
+					//TODO
+					None
+				},
+				&crate::model::StageElementBehaviour::Checkpoint{ordered_checkpoint_id,unordered_checkpoint_count}=>{
+					if (ordered_checkpoint_id.is_none()||ordered_checkpoint_id.is_some_and(|id|id<game.next_ordered_checkpoint_id))
+						&&unordered_checkpoint_count<=game.unordered_checkpoints.len() as u32{
+						//pass
+						None
+					}else{
+						//fail
+						teleport_to_spawn(body,touching,style,modes.get_mode(stage_element.mode_id)?,models,game.stage_id)
+					}
+				},
 			}
 		},
 		Some(crate::model::TeleportBehaviour::Wormhole(wormhole))=>{