From 51aac7b214ccfb07232b50d906441f19120572b3 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 4 Oct 2023 01:25:03 -0700 Subject: [PATCH] code mess (SPLIT INTO MULTIPLE COMMITS) --- src/body.rs | 219 ++++++++++++++++++++++++++++++--------------- src/load_roblox.rs | 48 ++++++---- src/main.rs | 25 ++++-- src/model.rs | 19 ++++ 4 files changed, 213 insertions(+), 98 deletions(-) diff --git a/src/body.rs b/src/body.rs index 8a8915b..71f089f 100644 --- a/src/body.rs +++ b/src/body.rs @@ -45,20 +45,20 @@ trait MyHash{ fn hash(&self) -> u64; } impl MyHash for Body { - fn hash(&self) -> u64 { + fn hash(&self) -> u64 { let mut hasher=std::collections::hash_map::DefaultHasher::new(); - for &el in self.position.as_ref().iter() { - std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice()); - } - for &el in self.velocity.as_ref().iter() { - std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice()); - } - for &el in self.acceleration.as_ref().iter() { - std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice()); - } - std::hash::Hasher::write(&mut hasher, self.time.to_ne_bytes().as_slice()); + for &el in self.position.as_ref().iter() { + std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice()); + } + for &el in self.velocity.as_ref().iter() { + std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice()); + } + for &el in self.acceleration.as_ref().iter() { + std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice()); + } + std::hash::Hasher::write(&mut hasher, self.time.to_ne_bytes().as_slice()); return std::hash::Hasher::finish(&hasher);//hash check to see if walk target is valid - } + } } pub enum MoveRestriction { @@ -80,9 +80,9 @@ impl InputState { } impl crate::instruction::InstructionEmitter for InputState{ fn next_instruction(&self, time_limit:crate::body::TIME) -> Option> { - //this is polled by PhysicsState for actions like Jump - //no, it has to be the other way around. physics is run up until the jump instruction, and then the jump instruction is pushed. - self.queue.get(0) + //this is polled by PhysicsState for actions like Jump + //no, it has to be the other way around. physics is run up until the jump instruction, and then the jump instruction is pushed. + self.queue.get(0) } } impl crate::instruction::InstructionConsumer for InputState{ @@ -171,12 +171,12 @@ pub struct Camera { #[inline] fn mat3_from_rotation_y_f64(angle: f64) -> glam::Mat3 { - let (sina, cosa) = angle.sin_cos(); - glam::Mat3::from_cols( - glam::Vec3::new(cosa as f32, 0.0, -sina as f32), - glam::Vec3::Y, - glam::Vec3::new(sina as f32, 0.0, cosa as f32), - ) + let (sina, cosa) = angle.sin_cos(); + glam::Mat3::from_cols( + glam::Vec3::new(cosa as f32, 0.0, -sina as f32), + glam::Vec3::Y, + glam::Vec3::new(sina as f32, 0.0, cosa as f32), + ) } #[inline] fn perspective_rh(fov_x_slope: f32, fov_y_slope: f32, z_near: f32, z_far: f32) -> glam::Mat4 { @@ -192,11 +192,11 @@ fn perspective_rh(fov_x_slope: f32, fov_y_slope: f32, z_near: f32, z_far: f32) - impl Camera { pub fn from_offset(offset:glam::Vec3,aspect:f32) -> Self { Self{ - offset, - angles: glam::DVec2::ZERO, - fov: glam::vec2(aspect,1.0), - sensitivity: glam::dvec2(1.0/6144.0,1.0/6144.0), - time: 0, + offset, + angles: glam::DVec2::ZERO, + fov: glam::vec2(aspect,1.0), + sensitivity: glam::dvec2(1.0/16384.0,1.0/16384.0), + time: 0, } } fn simulate_move_angles(&self, delta: glam::IVec2) -> glam::DVec2 { @@ -221,13 +221,13 @@ impl Camera { } pub struct GameMechanicsState{ - pub spawn_id:u32, + pub stage_id:u32, //jump_counts:HashMap, } impl std::default::Default for GameMechanicsState{ fn default() -> Self { Self{ - spawn_id:0, + stage_id:0, } } } @@ -317,7 +317,8 @@ pub struct PhysicsState{ pub world:WorldState,//currently there is only one state the world can be in pub game:GameMechanicsState, pub style:StyleModifiers, - pub contacts:std::collections::HashSet::, + pub contacts:std::collections::HashMap::, + pub intersects:std::collections::HashMap::, //pub intersections: Vec, //camera must exist in state because wormholes modify the camera, also camera punch pub camera:Camera, @@ -329,6 +330,7 @@ pub struct PhysicsState{ pub models:Vec, pub modes:Vec, + pub mode_from_mode_id:std::collections::HashMap::, //the spawn point is where you spawn when you load into the map. //This is not the same as Reset which teleports you to Spawn0 pub spawn_point:glam::Vec3, @@ -458,6 +460,7 @@ pub struct ModelPhysics { //A model is a thing that has a hitbox. can be represented by a list of TreyMesh-es //in this iteration, all it needs is extents. mesh: TreyMesh, + transform:glam::Affine3A, attributes:PhysicsCollisionAttributes, } @@ -470,13 +473,14 @@ impl ModelPhysics { Self{ mesh:aabb, attributes, + transform:transform.clone(), } } pub fn from_model(model:&crate::model::IndexedModel,instance:&crate::model::ModelInstance) -> Option { match &instance.attributes{ - crate::model::CollisionAttributes::Decoration=>None, crate::model::CollisionAttributes::Contact{contacting,general}=>Some(ModelPhysics::from_model_transform_attributes(model,&instance.transform,PhysicsCollisionAttributes::Contact{contacting:contacting.clone(),general:general.clone()})), - crate::model::CollisionAttributes::Intersect{intersecting,general}=>None,//Some(ModelPhysics::from_model_transform_attributes(model,&instance.transform,PhysicsCollisionAttributes::Intersecting{intersecting,general})), + crate::model::CollisionAttributes::Intersect{intersecting,general}=>Some(ModelPhysics::from_model_transform_attributes(model,&instance.transform,PhysicsCollisionAttributes::Intersect{intersecting:intersecting.clone(),general:general.clone()})), + crate::model::CollisionAttributes::Decoration=>None, } } pub fn unit_vertices(&self) -> [glam::Vec3;8] { @@ -556,6 +560,15 @@ impl PhysicsState { pub fn clear(&mut self){ self.models.clear(); self.modes.clear(); + self.contacts.clear(); + self.intersects.clear(); + } + pub fn get_mode(&self,mode_id:u32)->Option<&crate::model::ModeDescription>{ + if let Some(&mode)=self.mode_from_mode_id.get(&mode_id){ + self.modes.get(mode) + }else{ + None + } } //tickless gaming pub fn run(&mut self, time_limit:TIME){ @@ -583,7 +596,7 @@ impl PhysicsState { } fn contact_constrain_velocity(&self,velocity:&mut glam::Vec3){ - for contact in self.contacts.iter() { + for (_,contact) in &self.contacts { let n=contact.normal(&self.models); let d=velocity.dot(n); if d<0f32{ @@ -592,7 +605,7 @@ impl PhysicsState { } } fn contact_constrain_acceleration(&self,acceleration:&mut glam::Vec3){ - for contact in self.contacts.iter() { + for (_,contact) in &self.contacts { let n=contact.normal(&self.models); let d=acceleration.dot(n); if d<0f32{ @@ -965,12 +978,19 @@ impl crate::instruction::InstructionEmitter for PhysicsState //JUST POLLING!!! NO MUTATION let mut collector = crate::instruction::InstructionCollector::new(time_limit); //check for collision stop instructions with curent contacts - for collision_data in self.contacts.iter() { + for (_,collision_data) in &self.contacts { collector.collect(self.predict_collision_end(self.time,time_limit,collision_data)); } + // for collision_data in &self.intersects{ + // collector.collect(self.predict_collision_end2(self.time,time_limit,collision_data)); + // } //check for collision start instructions (against every part in the game with no optimization!!) for i in 0..self.models.len() { - collector.collect(self.predict_collision_start(self.time,time_limit,i as u32)); + let i=i as u32; + if self.contacts.contains_key(&i)||self.intersects.contains_key(&i){ + continue; + } + collector.collect(self.predict_collision_start(self.time,time_limit,i)); } if self.grounded { //walk maintenance @@ -986,56 +1006,109 @@ impl crate::instruction::InstructionEmitter for PhysicsState impl crate::instruction::InstructionConsumer for PhysicsState { fn process_instruction(&mut self, ins:TimedInstruction) { match &ins.instruction { - PhysicsInstruction::StrafeTick => (), - PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (), - _=>println!("{:?}",ins), + PhysicsInstruction::StrafeTick => (), + PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (), + _=>println!("{:?}",ins), } //selectively update body match &ins.instruction { - PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (),//dodge time for mouse movement - PhysicsInstruction::Input(_) - |PhysicsInstruction::SetSpawnPosition(_) - |PhysicsInstruction::ReachWalkTargetVelocity - |PhysicsInstruction::CollisionStart(_) - |PhysicsInstruction::CollisionEnd(_) - |PhysicsInstruction::StrafeTick => self.advance_time(ins.time), + PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (),//dodge time for mouse movement + PhysicsInstruction::Input(_) + |PhysicsInstruction::SetSpawnPosition(_) + |PhysicsInstruction::ReachWalkTargetVelocity + |PhysicsInstruction::CollisionStart(_) + |PhysicsInstruction::CollisionEnd(_) + |PhysicsInstruction::StrafeTick => self.advance_time(ins.time), } match ins.instruction { PhysicsInstruction::SetSpawnPosition(position)=>{ self.spawn_point=position; } PhysicsInstruction::CollisionStart(c) => { - //check ground - match &c.face { - AabbFace::Top => { - //ground - self.grounded=true; - }, - _ => (), - } - self.contacts.insert(c); - //flatten v - let mut v=self.body.velocity; - self.contact_constrain_velocity(&mut v); - self.body.velocity=v; - if self.grounded&&self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){ - self.jump(); + let model=c.model(&self.models).unwrap(); + match &model.attributes{ + PhysicsCollisionAttributes::Contact{contacting,general}=>{ + match &contacting.surf{ + Some(surf)=>println!("I'm surfing!"), + None=>match &c.face { + AabbFace::Top => { + //ground + self.grounded=true; + }, + _ => (), + }, + } + match &general.booster{ + Some(booster)=>self.body.velocity+=booster.velocity, + None=>(), + } + match &general.stage_element{ + Some(stage_element)=>{ + if stage_element.force||self.game.stage_id(), + crate::model::StageElementBehaviour::Trigger + |crate::model::StageElementBehaviour::Teleport=>{ + //TODO make good + if let Some(mode)=self.get_mode(stage_element.mode_id){ + if let Some(&spawn)=mode.get_spawn_model_id(self.game.stage_id){ + if let Some(model)=self.models.get(spawn as usize){ + self.body.position=model.transform.transform_point3(glam::Vec3::Y)+glam::Vec3::Y*(self.style.hitbox_halfsize.y+0.1); + //manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))} + self.contacts.clear(); + self.intersects.clear(); + self.body.acceleration=self.style.gravity; + self.walk.state=WalkEnum::Reached; + self.grounded=false; + }else{println!("bad1");} + }else{println!("bad2");} + }else{println!("bad3");} + }, + crate::model::StageElementBehaviour::Platform=>(), + } + }, + None=>(), + } + //check ground + self.contacts.insert(c.model,c); + //flatten v + let mut v=self.body.velocity; + self.contact_constrain_velocity(&mut v); + self.body.velocity=v; + if self.grounded&&self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){ + self.jump(); + } + self.refresh_walk_target(); + }, + PhysicsCollisionAttributes::Intersect{intersecting,general}=>{ + //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop + self.intersects.insert(c.model,c); + }, } - self.refresh_walk_target(); }, PhysicsInstruction::CollisionEnd(c) => { - self.contacts.remove(&c);//remove contact before calling contact_constrain_acceleration - let mut a=self.style.gravity; - self.contact_constrain_acceleration(&mut a); - self.body.acceleration=a; - //check ground - match &c.face { - AabbFace::Top => { - self.grounded=false; - }, - _ => (), - } - self.refresh_walk_target(); + let model=c.model(&self.models).unwrap(); + match &model.attributes{ + PhysicsCollisionAttributes::Contact{contacting,general}=>{ + self.contacts.remove(&c.model);//remove contact before calling contact_constrain_acceleration + let mut a=self.style.gravity; + self.contact_constrain_acceleration(&mut a); + self.body.acceleration=a; + //check ground + match &c.face { + AabbFace::Top => { + self.grounded=false; + }, + _ => (), + } + self.refresh_walk_target(); + }, + PhysicsCollisionAttributes::Intersect{intersecting,general}=>{ + self.intersects.remove(&c.model); + }, + } }, PhysicsInstruction::StrafeTick => { let camera_mat=self.camera.simulate_move_rotation_y(self.mouse_interpolation.interpolated_position(self.time).x-self.mouse_interpolation.mouse0.x); diff --git a/src/load_roblox.rs b/src/load_roblox.rs index bb10df4..3df6e01 100644 --- a/src/load_roblox.rs +++ b/src/load_roblox.rs @@ -30,7 +30,7 @@ fn get_texture_refs(dom:&rbx_dom_weak::WeakDom) -> Vec //next class objects } -fn get_attributes(name:&str,can_collide:bool,velocity:glam::Vec3)->crate::model::CollisionAttributes{ +fn get_attributes(name:&str,can_collide:bool,velocity:glam::Vec3,force_intersecting:bool)->crate::model::CollisionAttributes{ let mut general=crate::model::GameMechanicAttributes::default(); let mut intersecting=crate::model::IntersectingAttributes::default(); let mut contacting=crate::model::ContactingAttributes::default(); @@ -46,7 +46,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:glam::Vec3)->crate::model: behaviour:crate::model::StageElementBehaviour::Platform, }), other=>{ - if let Some(captures)=lazy_regex::regex!(r"^(Force)?(SpawnAt|Trigger|Teleport|Platform)(\d+)$") + if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$") .captures(other){ general.stage_element=Some(crate::model::GameMechanicStageElement{ mode_id:0, @@ -56,7 +56,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:glam::Vec3)->crate::model: None=>false, }, behaviour:match &captures[2]{ - "SpawnAt"=>crate::model::StageElementBehaviour::SpawnAt, + "Spawn"|"SpawnAt"=>crate::model::StageElementBehaviour::SpawnAt, "Trigger"=>crate::model::StageElementBehaviour::Trigger, "Teleport"=>crate::model::StageElementBehaviour::Teleport, "Platform"=>crate::model::StageElementBehaviour::Platform, @@ -89,9 +89,21 @@ fn get_attributes(name:&str,can_collide:bool,velocity:glam::Vec3)->crate::model: //WormholeIn# } } - return crate::model::CollisionAttributes::Contact{contacting,general}; + crate::model::CollisionAttributes::Contact{contacting,general} + }, + false=>if force_intersecting + ||general.jump_limit.is_some() + ||general.booster.is_some() + ||general.zone.is_some() + ||general.stage_element.is_some() + ||general.wormhole.is_some() + ||intersecting.water.is_some() + ||intersecting.accelerator.is_some() + { + crate::model::CollisionAttributes::Intersect{intersecting,general} + }else{ + crate::model::CollisionAttributes::Decoration }, - false=>return crate::model::CollisionAttributes::Decoration, } } @@ -219,6 +231,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::Index ); //push TempIndexedAttributes + let mut force_intersecting=false; let mut temp_indexing_attributes=Vec::new(); if let Some(attr)=match &object.name[..]{ "MapStart"=>{ @@ -227,11 +240,11 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::Index }, "UnorderedCheckpoint"=>Some(crate::model::TempIndexedAttributes::UnorderedCheckpoint{mode_id:0}), other=>{ - let regman=lazy_regex::regex!(r"^(BonusStart|Spawn|OrderedCheckpoint)(\d+)$"); + let regman=lazy_regex::regex!(r"^(BonusStart|Spawn|ForceSpawn|OrderedCheckpoint)(\d+)$"); if let Some(captures) = regman.captures(other) { match &captures[1]{ "BonusStart"=>Some(crate::model::TempIndexedAttributes::Start{mode_id:captures[2].parse::().unwrap()}), - "Spawn"=>Some(crate::model::TempIndexedAttributes::Spawn{mode_id:0,stage_id:captures[2].parse::().unwrap()}), + "Spawn"|"ForceSpawn"=>Some(crate::model::TempIndexedAttributes::Spawn{mode_id:0,stage_id:captures[2].parse::().unwrap()}), "OrderedCheckpoint"=>Some(crate::model::TempIndexedAttributes::OrderedCheckpoint{mode_id:0,checkpoint_id:captures[2].parse::().unwrap()}), _=>None, } @@ -240,6 +253,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::Index } } }{ + force_intersecting=true; temp_indexing_attributes.push(attr); } @@ -354,17 +368,17 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::Index primitives::Primitives::Cylinder=>RobloxBasePartDescription::Cylinder, //use front face texture first and use top face texture as a fallback primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([ - f0,//Wedge::Right - if f5.is_some(){f5}else{f1},//Wedge::TopFront - f2,//Wedge::Back - f3,//Wedge::Left - f4,//Wedge::Bottom + f0,//Cube::Right->Wedge::Right + if f5.is_some(){f5}else{f1},//Cube::Front|Cube::Top->Wedge::TopFront + f2,//Cube::Back->Wedge::Back + f3,//Cube::Left->Wedge::Left + f4,//Cube::Bottom->Wedge::Bottom ]), primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([ - f0,//CornerWedge::Right - f1,//CornerWedge::Top - f4,//CornerWedge::Bottom - f5,//CornerWedge::Front + f0,//Cube::Right->CornerWedge::Right + f1,//Cube::Top->CornerWedge::Top + f4,//Cube::Bottom->CornerWedge::Bottom + f5,//Cube::Front->CornerWedge::Front ]), }; //make new model if unit cube has not been created before @@ -440,7 +454,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::Index indexed_models[model_id].instances.push(crate::model::ModelInstance { transform:model_transform, color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency), - attributes:get_attributes(&object.name,*can_collide,glam::vec3(velocity.x,velocity.y,velocity.z)), + attributes:get_attributes(&object.name,*can_collide,glam::vec3(velocity.x,velocity.y,velocity.z),force_intersecting), temp_indexing:temp_indexing_attributes, }); } diff --git a/src/main.rs b/src/main.rs index 8e14f2a..0fab7fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -104,8 +104,8 @@ impl GlobalState{ for model_instance in &model.instances{ if let Some(model_physics)=body::ModelPhysics::from_model(model,model_instance){ let model_id=self.physics.models.len() as u32; - //snoop it before it gets stolen - for attr in model_instance.temp_indexing.iter(){ + self.physics.models.push(model_physics); + for attr in &model_instance.temp_indexing{ match attr{ model::TempIndexedAttributes::Start{mode_id}=>starts.push((*mode_id,model_id)), model::TempIndexedAttributes::Spawn{mode_id,stage_id}=>spawns.push((*mode_id,model_id,*stage_id)), @@ -113,8 +113,6 @@ impl GlobalState{ model::TempIndexedAttributes::UnorderedCheckpoint{mode_id}=>unordered_checkpoints.push((*mode_id,model_id)), } } - //steal it - self.physics.models.push(model_physics); } } } @@ -147,14 +145,22 @@ impl GlobalState{ } } } + let num_modes=self.physics.modes.len(); + for (mode_id,mode) in eshmep{ + self.physics.mode_from_mode_id.insert(mode_id,num_modes+mode); + } self.physics.modes.append(&mut modedatas.into_iter().map(|mut tup|{ 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(); model::ModeDescription{ start:tup.0, - spawns:tup.1.into_iter().map(|tup|tup.1).collect(), - ordered_checkpoints:tup.2.into_iter().map(|tup|tup.1).collect(), + 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, } }).collect()); println!("Physics Objects: {}",self.physics.models.len()); @@ -228,7 +234,7 @@ impl GlobalState{ //the models received here are supposed to be tightly packed, i.e. no code needs to check if two models are using the same groups. let indexed_models_len=indexed_models.models.len(); let mut unique_texture_models=Vec::with_capacity(indexed_models_len); - for mut model in indexed_models.models.into_iter(){ + for model in indexed_models.models.into_iter(){ //convert ModelInstance into ModelGraphicsInstance let instances:Vec=model.instances.into_iter().filter_map(|instance|{ if instance.color.w==0.0{ @@ -581,7 +587,8 @@ impl framework::Example for GlobalState { time: 0, style:body::StyleModifiers::default(), grounded: false, - contacts: std::collections::HashSet::new(), + contacts: std::collections::HashMap::new(), + intersects: std::collections::HashMap::new(), models: Vec::new(), walk: body::WalkState::new(), camera: body::Camera::from_offset(glam::vec3(0.0,4.5-2.5,0.0),(config.width as f32)/(config.height as f32)), @@ -590,6 +597,7 @@ impl framework::Example for GlobalState { world:body::WorldState{}, game:body::GameMechanicsState::default(), modes:Vec::new(), + mode_from_mode_id:std::collections::HashMap::new(), }; //load textures @@ -904,6 +912,7 @@ impl framework::Example for GlobalState { //if generate_indexed_models succeeds, clear the previous ones self.physics.clear(); self.graphics.clear(); + self.physics.game.stage_id=0; self.generate_model_physics(&indexed_model_instances); self.generate_model_graphics(device,queue,indexed_model_instances); //manual reset diff --git a/src/model.rs b/src/model.rs index 0386abf..1a7a074 100644 --- a/src/model.rs +++ b/src/model.rs @@ -85,7 +85,26 @@ pub struct ModeDescription{ pub spawns:Vec,//spawns[spawn_id]=model_id pub ordered_checkpoints:Vec,//ordered_checkpoints[checkpoint_id]=model_id pub unordered_checkpoints:Vec,//unordered_checkpoints[checkpoint_id]=model_id + pub spawn_from_stage_id:std::collections::HashMap::, + pub ordered_checkpoint_from_checkpoint_id:std::collections::HashMap::, } +impl ModeDescription{ + pub fn get_spawn_model_id(&self,stage_id:u32)->Option<&u32>{ + if let Some(&spawn)=self.spawn_from_stage_id.get(&stage_id){ + self.spawns.get(spawn) + }else{ + None + } + } + pub fn get_ordered_checkpoint_model_id(&self,checkpoint_id:u32)->Option<&u32>{ + if let Some(&checkpoint)=self.ordered_checkpoint_from_checkpoint_id.get(&checkpoint_id){ + self.ordered_checkpoints.get(checkpoint) + }else{ + None + } + } +} +#[derive(Debug)] pub enum TempIndexedAttributes{ Start{ mode_id:u32,