456 lines
12 KiB
Rust
456 lines
12 KiB
Rust
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));
|
|
// }
|
|
}
|