use std::collections::{HashSet,HashMap}; use crate::model::ModelId; use crate::gameplay_style; #[derive(Clone)] pub struct StageElement{ stage_id:StageId,//which stage spawn to send to force:bool,//allow setting to lower spawn id i.e. 7->3 behaviour:StageElementBehaviour, jump_limit:Option<u8>, } impl StageElement{ #[inline] pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option<u8>)->Self{ Self{ stage_id, force, behaviour, jump_limit, } } #[inline] pub const fn stage_id(&self)->StageId{ self.stage_id } #[inline] pub const fn force(&self)->bool{ self.force } #[inline] pub const fn behaviour(&self)->StageElementBehaviour{ self.behaviour } #[inline] pub const fn jump_limit(&self)->Option<u8>{ self.jump_limit } } #[derive(Clone,Copy,Hash,Eq,PartialEq)] pub enum StageElementBehaviour{ SpawnAt,//must be standing on top to get effect. except cancollide false Trigger, Teleport, Platform, //Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet. //Note that all stage elements act like this, this is just the isolated behaviour. Check, Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both. } #[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)] pub struct CheckpointId(u32); impl CheckpointId{ pub const FIRST:Self=Self(0); } #[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)] pub struct StageId(u32); impl StageId{ pub const FIRST:Self=Self(0); } #[derive(Clone)] pub struct Stage{ spawn:ModelId, //open world support lol ordered_checkpoints_count:u32, unordered_checkpoints_count:u32, //currently loaded checkpoint models ordered_checkpoints:HashMap<CheckpointId,ModelId>, unordered_checkpoints:HashSet<ModelId>, } impl Stage{ pub fn new( spawn:ModelId, ordered_checkpoints_count:u32, unordered_checkpoints_count:u32, ordered_checkpoints:HashMap<CheckpointId,ModelId>, unordered_checkpoints:HashSet<ModelId>, )->Self{ Self{ spawn, ordered_checkpoints_count, unordered_checkpoints_count, ordered_checkpoints, unordered_checkpoints, } } pub fn empty(spawn:ModelId)->Self{ Self{ spawn, ordered_checkpoints_count:0, unordered_checkpoints_count:0, ordered_checkpoints:HashMap::new(), unordered_checkpoints:HashSet::new(), } } #[inline] pub const fn spawn(&self)->ModelId{ self.spawn } #[inline] pub const fn ordered_checkpoints_count(&self)->u32{ self.ordered_checkpoints_count } #[inline] pub const fn unordered_checkpoints_count(&self)->u32{ self.unordered_checkpoints_count } pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){ (self.ordered_checkpoints,self.unordered_checkpoints) } /// Returns true if the stage has no checkpoints. #[inline] pub const fn is_empty(&self)->bool{ self.is_complete(0,0) } #[inline] pub const fn is_complete(&self,ordered_checkpoints_count:u32,unordered_checkpoints_count:u32)->bool{ self.ordered_checkpoints_count==ordered_checkpoints_count&&self.unordered_checkpoints_count==unordered_checkpoints_count } #[inline] pub fn is_next_ordered_checkpoint(&self,next_ordered_checkpoint_id:CheckpointId,model_id:ModelId)->bool{ self.ordered_checkpoints.get(&next_ordered_checkpoint_id).is_some_and(|&next_checkpoint|model_id==next_checkpoint) } #[inline] pub fn is_unordered_checkpoint(&self,model_id:ModelId)->bool{ self.unordered_checkpoints.contains(&model_id) } } #[derive(Clone,Copy,Hash,Eq,PartialEq)] pub enum Zone{ Start, Finish, Anticheat, } #[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)] pub struct ModeId(u32); impl ModeId{ pub const MAIN:Self=Self(0); pub const BONUS:Self=Self(1); } #[derive(Clone)] pub struct Mode{ style:gameplay_style::StyleModifiers, start:ModelId,//when you press reset you go here zones:HashMap<ModelId,Zone>, stages:Vec<Stage>,//when you load the map you go to stages[0].spawn //mutually exlusive stage element behaviour elements:HashMap<ModelId,StageElement>, } impl Mode{ pub fn new( style:gameplay_style::StyleModifiers, start:ModelId, zones:HashMap<ModelId,Zone>, stages:Vec<Stage>, elements:HashMap<ModelId,StageElement>, )->Self{ Self{ style, start, zones, stages, elements, } } pub fn empty(style:gameplay_style::StyleModifiers,start:ModelId)->Self{ Self{ style, start, zones:HashMap::new(), stages:Vec::new(), elements:HashMap::new(), } } pub fn into_inner(self)->( gameplay_style::StyleModifiers, ModelId, HashMap<ModelId,Zone>, Vec<Stage>, HashMap<ModelId,StageElement>, ){ ( self.style, self.start, self.zones, self.stages, self.elements, ) } pub const fn get_start(&self)->ModelId{ self.start } pub const fn get_style(&self)->&gameplay_style::StyleModifiers{ &self.style } pub fn push_stage(&mut self,stage:Stage){ self.stages.push(stage) } pub fn get_stage_mut(&mut self,StageId(stage_id):StageId)->Option<&mut Stage>{ self.stages.get_mut(stage_id as usize) } pub fn get_spawn_model_id(&self,StageId(stage_id):StageId)->Option<ModelId>{ self.stages.get(stage_id as usize).map(|s|s.spawn) } pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{ self.zones.get(&model_id) } pub fn get_stage(&self,StageId(stage_id):StageId)->Option<&Stage>{ self.stages.get(stage_id as usize) } pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{ self.elements.get(&model_id) } } #[derive(Clone)] pub struct NormalizedMode(Mode); impl NormalizedMode{ pub fn new(mode:Mode)->Self{ Self(mode) } pub fn into_inner(self)->Mode{ let Self(mode)=self; mode } //TODO: put this in the SNF pub fn denormalize(self)->Mode{ let NormalizedMode(mut mode)=self; //expand and index normalized data mode.zones.insert(mode.start,Zone::Start); for (stage_id,stage) in mode.stages.iter().enumerate(){ mode.elements.insert(stage.spawn,StageElement{ stage_id:StageId(stage_id as u32), force:false, behaviour:StageElementBehaviour::SpawnAt, jump_limit:None, }); for (_,&model) in &stage.ordered_checkpoints{ mode.elements.insert(model,StageElement{ stage_id:StageId(stage_id as u32), force:false, behaviour:StageElementBehaviour::Checkpoint, jump_limit:None, }); } for &model in &stage.unordered_checkpoints{ mode.elements.insert(model,StageElement{ stage_id:StageId(stage_id as u32), force:false, behaviour:StageElementBehaviour::Checkpoint, jump_limit:None, }); } } mode } } #[derive(Default,Clone)] pub struct Modes{ modes:Vec<Mode>, } impl Modes{ pub const fn new(modes:Vec<Mode>)->Self{ Self{ modes, } } pub fn into_inner(self)->Vec<Mode>{ self.modes } pub fn push_mode(&mut self,mode:Mode){ self.modes.push(mode) } pub fn get_mode(&self,ModeId(mode_id):ModeId)->Option<&Mode>{ self.modes.get(mode_id as usize) } } #[derive(Clone)] pub struct NormalizedModes{ modes:Vec<NormalizedMode>, } impl NormalizedModes{ pub fn new(modes:Vec<NormalizedMode>)->Self{ Self{modes} } pub fn len(&self)->usize{ self.modes.len() } pub fn denormalize(self)->Modes{ Modes{ modes:self.modes.into_iter().map(NormalizedMode::denormalize).collect(), } } } impl IntoIterator for NormalizedModes{ type Item=<Vec<NormalizedMode> as IntoIterator>::Item; type IntoIter=<Vec<NormalizedMode> as IntoIterator>::IntoIter; fn into_iter(self)->Self::IntoIter{ self.modes.into_iter() } } #[derive(Default)] pub struct StageUpdate{ //other behaviour models of this stage can have ordered_checkpoints:HashMap<CheckpointId,ModelId>, unordered_checkpoints:HashSet<ModelId>, } impl StageUpdate{ fn apply_to(self,stage:&mut Stage){ stage.ordered_checkpoints.extend(self.ordered_checkpoints); stage.unordered_checkpoints.extend(self.unordered_checkpoints); } } //this would be nice as a macro #[derive(Default)] pub struct ModeUpdate{ zones:Option<(ModelId,Zone)>, stages:Option<(StageId,StageUpdate)>, //mutually exlusive stage element behaviour elements:Option<(ModelId,StageElement)>, } impl ModeUpdate{ fn apply_to(self,mode:&mut Mode){ mode.zones.extend(self.zones); if let Some((StageId(stage_id),stage_update))=self.stages{ if let Some(stage)=mode.stages.get_mut(stage_id as usize){ stage_update.apply_to(stage); } } mode.elements.extend(self.elements); } } impl ModeUpdate{ pub fn zone(model_id:ModelId,zone:Zone)->Self{ Self{ zones:Some((model_id,zone)), stages:None, elements:None, } } pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{ Self{ zones:None, stages:Some((stage_id,stage_update)), elements:None, } } pub fn element(model_id:ModelId,element:StageElement)->Self{ Self{ zones:None, stages:None, elements:Some((model_id,element)), } } pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){ for (_,stage_element) in self.elements.iter_mut(){ stage_element.stage_id=f(stage_element.stage_id); } } } struct ModeBuilder{ mode:Mode, stage_id_map:HashMap<StageId,StageId>, } #[derive(Default)] pub struct ModesBuilder{ modes:HashMap<ModeId,Mode>, stages:HashMap<ModeId,HashMap<StageId,Stage>>, mode_updates:Vec<(ModeId,ModeUpdate)>, stage_updates:Vec<(ModeId,StageId,StageUpdate)>, } impl ModesBuilder{ pub fn build_normalized(mut self)->NormalizedModes{ //collect modes and stages into contiguous arrays let mut unique_modes:Vec<(ModeId,Mode)> =self.modes.into_iter().collect(); unique_modes.sort_by_key(|&(mode_id,_)|mode_id); let (mut modes,mode_id_map):(Vec<ModeBuilder>,HashMap<ModeId,ModeId>) =unique_modes.into_iter().enumerate() .map(|(final_mode_id,(builder_mode_id,mut mode))|{ ( ModeBuilder{ stage_id_map:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{ let mut unique_stages:Vec<(StageId,Stage)> =stages.into_iter().collect(); unique_stages.sort_by_key(|&(StageId(stage_id),_)|stage_id); unique_stages.into_iter().enumerate() .map(|(final_stage_id,(builder_stage_id,stage))|{ mode.push_stage(stage); (builder_stage_id,StageId::new(final_stage_id as u32)) }).collect() }), mode, }, ( builder_mode_id, ModeId::new(final_mode_id as u32) ) ) }).unzip(); //TODO: failure messages or errors or something //push stage updates for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{ if let Some(final_mode_id)=mode_id_map.get(&builder_mode_id){ if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){ if let Some(&final_stage_id)=mode.stage_id_map.get(&builder_stage_id){ if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){ stage_update.apply_to(stage); } } } } } //push mode updates for (builder_mode_id,mut mode_update) in self.mode_updates{ if let Some(final_mode_id)=mode_id_map.get(&builder_mode_id){ if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){ //map stage id on stage elements mode_update.map_stage_element_ids(|stage_id| //walk down one stage id at a time until a stage is found //TODO use better logic like BTreeMap::upper_bound instead of walking // final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id)) // .value().copied().unwrap_or(StageId::FIRST) (0..=stage_id.get()).rev().find_map(|builder_stage_id| //map the stage element to that stage mode.stage_id_map.get(&StageId::new(builder_stage_id)).copied() ).unwrap_or(StageId::FIRST) ); mode_update.apply_to(&mut mode.mode); } } } NormalizedModes::new(modes.into_iter().map(|mode_builder|NormalizedMode(mode_builder.mode)).collect()) } pub fn insert_mode(&mut self,mode_id:ModeId,mode:Mode){ assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode"); } pub fn insert_stage(&mut self,mode_id:ModeId,stage_id:StageId,stage:Stage){ assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage"); } pub fn push_mode_update(&mut self,mode_id:ModeId,mode_update:ModeUpdate){ self.mode_updates.push((mode_id,mode_update)); } // fn push_stage_update(&mut self,mode_id:ModeId,stage_id:StageId,stage_update:StageUpdate){ // self.stage_updates.push((mode_id,stage_id,stage_update)); // } }