Skip to content

Instantly share code, notes, and snippets.

@HGStyle
Created April 22, 2024 15:02
Show Gist options
  • Save HGStyle/a28be503e59b3042f99ebce97bc2f995 to your computer and use it in GitHub Desktop.
Save HGStyle/a28be503e59b3042f99ebce97bc2f995 to your computer and use it in GitHub Desktop.
Geometry Dash OmegaBot3/OBot V3 File Format Specification - From https://discord.gg/4vrxF335wh (their official Discord server)
use std::{fs::File, path::Path};
use dlhn::{Deserializer, Serializer};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ReplayError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialize(#[from] dlhn::ser::Error),
#[error("Deserialization error: {0}")]
Deserialize(#[from] dlhn::de::Error),
}
pub type ReplayResult<T> = Result<T, ReplayError>;
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Default, Debug)]
pub enum ClickType {
#[default]
None,
Player1Down,
Player1Up,
Player2Down,
Player2Up,
FpsChange(f32),
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub struct Click {
pub frame: u32,
pub click_type: ClickType,
}
#[derive(Serialize, Deserialize)]
pub struct Replay {
pub initial_fps: f32,
current_fps: f32,
pub clicks: Vec<Click>,
current: usize,
}
impl Replay {
pub fn new(fps: f32) -> Self {
Self {
initial_fps: fps,
current_fps: fps,
clicks: Vec::new(),
current: 0,
}
}
pub fn load(path: impl AsRef<Path>) -> ReplayResult<Self> {
let mut file = File::open(path)?;
let mut deserializer = Deserializer::new(&mut file);
Self::deserialize(&mut deserializer)
.map_err(|e| e.into())
.map(|mut replay| {
replay.current_fps = replay.initial_fps;
replay.current = 0;
replay
})
}
pub fn save(&self, path: impl AsRef<Path>) -> ReplayResult<()> {
let mut file = File::create(path)?;
let mut serializer = Serializer::new(&mut file);
self.serialize(&mut serializer).map_err(|e| e.into())
}
pub fn get_fps(&self) -> f32 {
self.current_fps
}
pub fn add_click(&mut self, click: Click) {
if let ClickType::FpsChange(fps) = click.click_type {
self.current_fps = fps;
}
self.clicks.push(click);
self.current = self.clicks.len();
}
pub fn on_reset(&mut self, frame: u32, recording: bool) {
if recording {
while self
.clicks
.last()
.map(|click| click.frame >= frame)
.unwrap_or(false)
{
self.clicks.pop();
}
self.current_fps = self.initial_fps;
self.current = self.clicks.len();
for click in self.clicks.iter().rev() {
if let ClickType::FpsChange(fps) = click.click_type {
self.current_fps = fps;
break;
}
}
} else {
self.current = 0;
self.current_fps = self.initial_fps;
for (i, click) in self.clicks.iter().enumerate() {
if click.frame >= frame {
self.current = i;
break;
}
if let ClickType::FpsChange(fps) = click.click_type {
self.current_fps = fps;
}
}
}
}
pub fn for_all_current_clicks(&mut self, frame: u32, mut f: impl FnMut(ClickType)) {
for click in self.clicks.iter().skip(self.current) {
if click.frame > frame {
break;
}
if let ClickType::FpsChange(fps) = click.click_type {
self.current_fps = fps;
}
f(click.click_type);
self.current += 1;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment