Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f22b0cdb27
|
|||
|
6b395b970c
|
|||
|
0ea00baeb5
|
|||
|
6b4e56ca82
|
|||
|
d153e8284b
|
|||
|
f54af6600b
|
|||
|
fb4fb5b5b3
|
|||
|
5dafa3971c
|
|||
|
a1cd627836
|
|||
|
39049fa12e
|
|||
|
236e297cc1
|
|||
|
838aecbda5
|
|||
|
a5d5b30e0c
|
|||
|
99fb761a1d
|
|||
|
59fcc7c0fb
|
|||
|
775c510ee6
|
|||
|
22c01e5910
|
|||
|
8c3e3c9463
|
|||
|
f315069f96
|
|||
|
881a3bad11
|
|||
|
bd8207ac2d
|
|||
|
60ed1e2661
|
|||
|
680d49f6db
|
|||
|
504c3ff354
|
|||
|
89c5f94a28
|
|||
|
673255383d
|
|||
|
677cb86987
|
|||
|
59b36c8821
|
|||
|
0dc231d972
|
|||
|
4de222b1b6
|
|||
|
40e8f7c595
|
|||
|
6038241d7a
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -85,7 +85,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_roblox_bot_file"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1-pre1"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"bitflags",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "strafesnet_roblox_bot_file"
|
||||
version = "0.7.0"
|
||||
edition = "2021"
|
||||
version = "0.7.1-pre1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
binrw = "0.15.0"
|
||||
|
||||
26
src/tests.rs
26
src/tests.rs
@@ -1,7 +1,7 @@
|
||||
use crate::v0::{Block,BlockTimelines,FileHeader,Timed};
|
||||
|
||||
#[test]
|
||||
fn _1(){
|
||||
fn deserialize_manual(){
|
||||
let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
|
||||
let mut input=std::io::Cursor::new(file);
|
||||
let header=FileHeader::from_reader(&mut input).unwrap();
|
||||
@@ -25,7 +25,7 @@ fn _1(){
|
||||
|
||||
#[test]
|
||||
#[cfg(feature="itertools")]
|
||||
fn _2()->Result<(),crate::v0::Error>{
|
||||
fn deserialize_all()->Result<(),crate::v0::Error>{
|
||||
let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
|
||||
|
||||
let t0=std::time::Instant::now();
|
||||
@@ -37,4 +37,24 @@ fn _2()->Result<(),crate::v0::Error>{
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: file serialization test
|
||||
#[test]
|
||||
#[cfg(feature="itertools")]
|
||||
fn serialize_round_trip()->Result<(),binrw::Error>{
|
||||
use crate::v0::serialize;
|
||||
|
||||
let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
|
||||
let mut block=crate::v0::read_all_to_block(std::io::Cursor::new(file.as_slice())).unwrap();
|
||||
|
||||
let mut data=Vec::with_capacity(file.len());
|
||||
serialize(&block,&mut std::io::Cursor::new(&mut data))?;
|
||||
|
||||
let block_rt=crate::v0::read_all_to_block(std::io::Cursor::new(data.as_slice())).unwrap();
|
||||
|
||||
assert_eq!(block_rt,block);
|
||||
|
||||
block.output_events.pop();
|
||||
|
||||
assert_eq!(block_rt,block);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
320
src/v0.rs
320
src/v0.rs
@@ -10,31 +10,50 @@ fn read_trey_float(bits:u32)->f32{
|
||||
let m=(bits>>(1+8))&((1<<23)-1);
|
||||
f32::from_bits(m|(e<<23)|(s<<31))
|
||||
}
|
||||
fn write_trey_float(value:&f32)->u32{
|
||||
let bits=value.to_bits();
|
||||
let s=(bits>>31)&1;
|
||||
let e=(bits>>23)&((1<<8)-1);
|
||||
let m=bits&((1<<23)-1);
|
||||
m<<(1+8)|(e<<1)|s
|
||||
}
|
||||
fn read_trey_double(bits:u64)->f64{
|
||||
let s=bits&1;
|
||||
let e=(bits>>1)&((1<<11)-1);
|
||||
let m=(bits>>(1+11))&((1<<52)-1);
|
||||
f64::from_bits(m|(e<<52)|(s<<63))
|
||||
}
|
||||
fn write_trey_double(value:&f64)->u64{
|
||||
let bits=value.to_bits();
|
||||
let s=(bits>>63)&1;
|
||||
let e=(bits>>52)&((1<<11)-1);
|
||||
let m=bits&((1<<52)-1);
|
||||
m<<(1+11)|(e<<1)|s
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct Vector2{
|
||||
#[br(map=read_trey_float)]
|
||||
#[bw(map=write_trey_float)]
|
||||
pub x:f32,
|
||||
#[br(map=read_trey_float)]
|
||||
#[bw(map=write_trey_float)]
|
||||
pub y:f32,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct Vector3{
|
||||
#[br(map=read_trey_float)]
|
||||
#[bw(map=write_trey_float)]
|
||||
pub x:f32,
|
||||
#[br(map=read_trey_float)]
|
||||
#[bw(map=write_trey_float)]
|
||||
pub y:f32,
|
||||
#[br(map=read_trey_float)]
|
||||
#[bw(map=write_trey_float)]
|
||||
pub z:f32,
|
||||
}
|
||||
|
||||
@@ -84,6 +103,7 @@ pub struct Timed<E>
|
||||
E:for<'a>binrw::BinWrite<Args<'a>=()>,
|
||||
{
|
||||
#[br(map=read_trey_double)]
|
||||
#[bw(map=write_trey_double)]
|
||||
pub time:f64,
|
||||
pub event:E,
|
||||
}
|
||||
@@ -114,7 +134,7 @@ impl<A,B> PartialOrd<Timed<B>> for Timed<A>
|
||||
// input
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct InputEvent{
|
||||
#[br(try_map=GameControls::try_from_bits)]
|
||||
#[bw(map=GameControls::bits)]
|
||||
@@ -147,7 +167,7 @@ impl TickInfo{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct OutputEvent{
|
||||
#[br(try_map=TickInfo::try_from_bits)]
|
||||
#[bw(map=TickInfo::bits)]
|
||||
@@ -182,7 +202,7 @@ pub enum SoundType{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct SoundEvent{
|
||||
pub sound_type:SoundType,
|
||||
/// Roblox enum
|
||||
@@ -192,13 +212,13 @@ pub struct SoundEvent{
|
||||
// world
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct WorldEventReset{
|
||||
pub position:Vector3,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct WorldEventButton{
|
||||
pub button_id:u32,
|
||||
// This field does not exist in the final struct and
|
||||
@@ -210,9 +230,10 @@ pub struct WorldEventButton{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct WorldEventSetTime{
|
||||
#[br(map=read_trey_double)]
|
||||
#[bw(map=write_trey_double)]
|
||||
pub time:f64,
|
||||
#[br(temp)]
|
||||
#[bw(ignore)]
|
||||
@@ -221,7 +242,7 @@ pub struct WorldEventSetTime{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct WorldEventSetPaused{
|
||||
#[br(map=|paused:u32|paused!=0)]
|
||||
#[bw(map=|&paused:&bool|paused as u32)]
|
||||
@@ -233,7 +254,7 @@ pub struct WorldEventSetPaused{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub enum WorldEvent{
|
||||
#[brw(magic=0u32)]
|
||||
Reset(WorldEventReset),
|
||||
@@ -248,7 +269,7 @@ pub enum WorldEvent{
|
||||
// gravity
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct GravityEvent{
|
||||
pub gravity:Vector3,
|
||||
}
|
||||
@@ -330,14 +351,14 @@ pub enum FlagReason{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventPrepare{
|
||||
pub mode:ModeID,
|
||||
pub style:Style,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventZone{
|
||||
pub mode:ModeID,
|
||||
#[br(temp)]
|
||||
@@ -347,7 +368,7 @@ pub struct RunEventZone{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventClear{
|
||||
pub mode:ModeSpec,
|
||||
#[br(temp)]
|
||||
@@ -357,21 +378,21 @@ pub struct RunEventClear{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventFlag{
|
||||
pub mode:ModeSpec,
|
||||
pub flag_reason:FlagReason,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct RunEventPractice{
|
||||
pub mode:ModeSpec,
|
||||
pub state_id:u32,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub enum RunEvent{
|
||||
#[brw(magic=0u32)]
|
||||
Prepare(RunEventPrepare),
|
||||
@@ -401,7 +422,7 @@ pub enum CameraEventType{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct CameraEvent{
|
||||
pub camera_event_type:CameraEventType,
|
||||
pub value:Vector3,
|
||||
@@ -425,15 +446,16 @@ pub enum SettingType{
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub struct SettingEvent{
|
||||
pub setting_type:SettingType,
|
||||
#[br(map=read_trey_double)]
|
||||
#[bw(map=write_trey_double)]
|
||||
pub value:f64,
|
||||
}
|
||||
|
||||
/// A segment of event timelines.
|
||||
#[derive(Default)]
|
||||
#[derive(Debug,Default,PartialEq)]
|
||||
pub struct Block{
|
||||
pub input_events:Vec<Timed<InputEvent>>,
|
||||
pub output_events:Vec<Timed<OutputEvent>>,
|
||||
@@ -447,6 +469,7 @@ pub struct Block{
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Clone,Copy)]
|
||||
enum EventType{
|
||||
#[brw(magic=1u32)]
|
||||
Input,
|
||||
@@ -465,6 +488,22 @@ enum EventType{
|
||||
#[brw(magic=8u32)]
|
||||
Setting,
|
||||
}
|
||||
impl EventType{
|
||||
// internal function meant for array indexing
|
||||
fn from_usize(value:usize)->Self{
|
||||
match value{
|
||||
0=>Self::Input,
|
||||
1=>Self::Output,
|
||||
2=>Self::Sound,
|
||||
3=>Self::World,
|
||||
4=>Self::Gravity,
|
||||
5=>Self::Run,
|
||||
6=>Self::Camera,
|
||||
7=>Self::Setting,
|
||||
_=>panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct EventChunkHeader{
|
||||
@@ -552,11 +591,19 @@ impl std::error::Error for Error{}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
pub struct BlockId(#[br(map=|i:u32|i-1)]u32);
|
||||
pub struct BlockId(
|
||||
#[br(map=|i:u32|i-1)]
|
||||
#[bw(map=|&i:&u32|i+1)]
|
||||
u32
|
||||
);
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug,Clone)]
|
||||
struct BlockPosition(#[br(map=|i:u32|i-1)]u32);
|
||||
struct BlockPosition(
|
||||
#[br(map=|i:u32|i-1)]
|
||||
#[bw(map=|&i:&u32|i+1)]
|
||||
u32
|
||||
);
|
||||
|
||||
/// The first 16 bytes of the file.
|
||||
#[binrw]
|
||||
@@ -680,3 +727,232 @@ pub fn read_all_to_block<R:BinReaderExt>(mut data:R)->Result<Block,Error>{
|
||||
let block=read_to_block(data,&block_timelines,itertools::merge(block_timelines.offline_blocks(),block_timelines.realtime_blocks()))?;
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
#[cfg(feature="itertools")]
|
||||
pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),binrw::Error>{
|
||||
use std::ops::Range;
|
||||
const MAX_BLOCK_SIZE:usize=1<<14;
|
||||
const FILE_VERSION:u32=0;
|
||||
const EVENT_SIZE:[usize;8]=[
|
||||
8+4+2*4, // Input
|
||||
8+4+4*3*4, // Output
|
||||
8+4+4, // Sound
|
||||
8+4+12, // World
|
||||
8+3*4, // Gravity
|
||||
8+4+4+4, // Run
|
||||
8+4+3*4, // Camera
|
||||
8+4+8, // Setting
|
||||
];
|
||||
#[derive(Clone,Default)]
|
||||
struct Plan<T>([T;8]);
|
||||
// A plan of how many events of each type to include in a data block.
|
||||
impl Plan<usize>{
|
||||
/// Predict the size increment from adding a new event.
|
||||
fn size_increase(&self,event_type:EventType)->usize{
|
||||
let new_chunk_header=self.0[event_type as usize]==0;
|
||||
let mask=(-(new_chunk_header as isize)) as usize;
|
||||
EVENT_SIZE[event_type as usize]+(mask&size_of::<EventChunkHeader>())
|
||||
}
|
||||
/// Add the new event.
|
||||
fn accumulate(&mut self,event_type:EventType){
|
||||
self.0[event_type as usize]+=1;
|
||||
}
|
||||
fn range(&self,end:&Plan<usize>)->Plan<Range<usize>>{
|
||||
Plan(core::array::from_fn(|i|self.0[i]..end.0[i]))
|
||||
}
|
||||
}
|
||||
// A plan of what range of events to include in a data block.
|
||||
impl Plan<Range<usize>>{
|
||||
/// Calculate the predicted size of the planned block.
|
||||
fn size(&self)->usize{
|
||||
self.0.iter()
|
||||
.zip(EVENT_SIZE)
|
||||
.filter_map(|(range,event_size)|match range.len(){
|
||||
0=>None,
|
||||
other=>Some(other*event_size+size_of::<EventChunkHeader>()),
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
// compare an event at the head of the plan to the best event collected so far.
|
||||
fn collect_event<E>(
|
||||
best:&mut Option<(f64,EventType)>,
|
||||
list:&[Timed<E>],
|
||||
plan:&Plan<usize>,
|
||||
event_type:EventType,
|
||||
)
|
||||
where
|
||||
E:for<'a>binrw::BinRead<Args<'a>=()>,
|
||||
E:for<'a>binrw::BinWrite<Args<'a>=()>,
|
||||
{
|
||||
if let Some(event)=list.get(plan.0[event_type as usize])
|
||||
&&best.is_none_or(|(time,_)|event.time<time)
|
||||
{
|
||||
*best=Some((event.time,event_type));
|
||||
}
|
||||
}
|
||||
// plan a single block: collect events until the block is full
|
||||
fn plan_block(plan:&mut Plan<usize>,next_event:impl Fn(&Plan<usize>)->Option<(f64,EventType)>)->Option<f64>{
|
||||
let mut size=0;
|
||||
let (start_time,first_event)=next_event(plan)?;
|
||||
|
||||
size+=plan.size_increase(first_event);
|
||||
if MAX_BLOCK_SIZE<size{
|
||||
return None;
|
||||
}
|
||||
plan.accumulate(first_event);
|
||||
|
||||
while let Some((_,event_type))=next_event(plan){
|
||||
size+=plan.size_increase(event_type);
|
||||
if MAX_BLOCK_SIZE<size{
|
||||
break;
|
||||
}
|
||||
plan.accumulate(event_type);
|
||||
}
|
||||
|
||||
Some(start_time)
|
||||
}
|
||||
|
||||
struct PlannedBlock{
|
||||
// index is not the same as BlockId.
|
||||
// It is list-local for both plan_offline and plan_realtime.
|
||||
index:usize,
|
||||
time:f64,
|
||||
plan:Plan<Range<usize>>,
|
||||
}
|
||||
fn plan_timeline<F>(next_event:F)->std::collections::VecDeque<PlannedBlock>
|
||||
where
|
||||
F:Copy,
|
||||
F:Fn(&Plan<usize>)->Option<(f64,EventType)>
|
||||
{
|
||||
let mut timeline=std::collections::VecDeque::new();
|
||||
let mut plan=Plan::default();
|
||||
let mut last_plan=plan.clone();
|
||||
let mut index=0;
|
||||
while let Some(time)=plan_block(&mut plan,next_event){
|
||||
timeline.push_back(PlannedBlock{
|
||||
index,
|
||||
time,
|
||||
plan:last_plan.range(&plan),
|
||||
});
|
||||
last_plan=plan.clone();
|
||||
index+=1;
|
||||
}
|
||||
timeline
|
||||
}
|
||||
// plan events into segments without spilling over max size threshold
|
||||
// each plan describes the range of events included in the block.
|
||||
let mut plan_offline=plan_timeline(|plan|{
|
||||
let mut next_event=None;
|
||||
collect_event(&mut next_event,&block.world_events,plan,EventType::World);
|
||||
collect_event(&mut next_event,&block.gravity_events,plan,EventType::Gravity);
|
||||
collect_event(&mut next_event,&block.run_events,plan,EventType::Run);
|
||||
collect_event(&mut next_event,&block.camera_events,plan,EventType::Camera);
|
||||
collect_event(&mut next_event,&block.setting_events,plan,EventType::Setting);
|
||||
next_event
|
||||
});
|
||||
let mut plan_realtime=plan_timeline(|plan|{
|
||||
let mut next_event=None;
|
||||
collect_event(&mut next_event,&block.input_events,plan,EventType::Input);
|
||||
collect_event(&mut next_event,&block.output_events,plan,EventType::Output);
|
||||
collect_event(&mut next_event,&block.sound_events,plan,EventType::Sound);
|
||||
next_event
|
||||
});
|
||||
|
||||
let file_header=FileHeader{
|
||||
file_version:FILE_VERSION,
|
||||
num_offline_blocks:plan_offline.len() as u32,
|
||||
num_realtime_blocks:plan_realtime.len() as u32,
|
||||
};
|
||||
|
||||
let mut plan_order=Vec::with_capacity(plan_offline.len()+plan_realtime.len());
|
||||
let mut block_positions=Vec::with_capacity(file_header.block_position_count() as usize);
|
||||
// Fill the timelines with dummy values, we don't know the block ids yet.
|
||||
// This can be done with Vec::spare_capacity_mut and unsafe, but whatever.
|
||||
const DUMMY_BLOCK:Timed<BlockId>=Timed{time:0.0,event:BlockId(0)};
|
||||
let mut offline_blocks_timeline=vec![DUMMY_BLOCK;plan_offline.len()];
|
||||
let mut realtime_blocks_timeline=vec![DUMMY_BLOCK;plan_realtime.len()];
|
||||
|
||||
{
|
||||
// position starts after the *predicted* end of the BlockTimelines
|
||||
let mut position=file_header.block_timelines_info().end;
|
||||
let mut block_id=0;
|
||||
let mut push_block=|timeline:&mut Vec<Timed<BlockId>>,planned:PlannedBlock|{
|
||||
block_positions.push(BlockPosition(position));
|
||||
position+=planned.plan.size() as u32;
|
||||
|
||||
// write the block id to the correct index
|
||||
timeline[planned.index]=Timed{
|
||||
time:planned.time,
|
||||
event:BlockId(block_id),
|
||||
};
|
||||
block_id+=1;
|
||||
|
||||
plan_order.push(planned.plan);
|
||||
};
|
||||
// the first block in the file is an offline block to
|
||||
// initialize the state of things like the current style
|
||||
if let Some(plan)=plan_offline.pop_front(){
|
||||
push_block(&mut offline_blocks_timeline,plan);
|
||||
}
|
||||
// the second block is the first realtime block which
|
||||
// includes the starting position of the replay
|
||||
if let Some(plan)=plan_realtime.pop_front(){
|
||||
push_block(&mut realtime_blocks_timeline,plan);
|
||||
}
|
||||
// the third block is the last realtime block which
|
||||
// is used by the game client to determine the duration
|
||||
if let Some(plan)=plan_realtime.pop_back(){
|
||||
push_block(&mut realtime_blocks_timeline,plan);
|
||||
}
|
||||
// push the remaining blocks in chronological order
|
||||
for either_plan in itertools::merge_join_by(
|
||||
plan_offline,
|
||||
plan_realtime,
|
||||
|offline,realtime|offline.time<=realtime.time,
|
||||
){
|
||||
match either_plan{
|
||||
itertools::Either::Left(offline)=>push_block(&mut offline_blocks_timeline,offline),
|
||||
itertools::Either::Right(realtime)=>push_block(&mut realtime_blocks_timeline,realtime),
|
||||
}
|
||||
}
|
||||
// final position
|
||||
block_positions.push(BlockPosition(position));
|
||||
}
|
||||
|
||||
let block_timelines=BlockTimelines{
|
||||
block_positions,
|
||||
offline_blocks_timeline,
|
||||
realtime_blocks_timeline,
|
||||
};
|
||||
|
||||
use binrw::BinWrite;
|
||||
file_header.write_le(writer)?;
|
||||
block_timelines.write_le(writer)?;
|
||||
for plan in plan_order{
|
||||
for (event_type_id,range) in plan.0.into_iter().enumerate(){
|
||||
let num_events=range.len();
|
||||
if num_events==0{
|
||||
continue;
|
||||
}
|
||||
let event_type=EventType::from_usize(event_type_id);
|
||||
let event_chunk_header=EventChunkHeader{
|
||||
event_type,
|
||||
num_events:num_events as u32,
|
||||
};
|
||||
event_chunk_header.write_le(writer)?;
|
||||
match event_type{
|
||||
EventType::Input=>block.input_events[range].write_le(writer)?,
|
||||
EventType::Output=>block.output_events[range].write_le(writer)?,
|
||||
EventType::Sound=>block.sound_events[range].write_le(writer)?,
|
||||
EventType::World=>block.world_events[range].write_le(writer)?,
|
||||
EventType::Gravity=>block.gravity_events[range].write_le(writer)?,
|
||||
EventType::Run=>block.run_events[range].write_le(writer)?,
|
||||
EventType::Camera=>block.camera_events[range].write_le(writer)?,
|
||||
EventType::Setting=>block.setting_events[range].write_le(writer)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user