159 lines
4.9 KiB
Rust
159 lines
4.9 KiB
Rust
use heck::ToPascalCase;
|
||
use proc_macro2::TokenStream;
|
||
use quote::{format_ident, quote};
|
||
use serde::Deserialize;
|
||
use serde_json::Value;
|
||
use std::collections::BTreeMap;
|
||
use std::fs;
|
||
|
||
/// Supplementary data for a jukebox song entry from the synced registries' asset.
|
||
#[derive(Deserialize)]
|
||
struct JukeboxSongData {
|
||
/// Duration of the song in seconds.
|
||
length_in_seconds: f32,
|
||
/// Redstone comparator output signal strength (0–15) while this disc is playing.
|
||
comparator_output: u8,
|
||
}
|
||
|
||
/// Generates the `TokenStream` for the `JukeboxSong` enum and its length, comparator, and name methods.
|
||
pub fn build() -> TokenStream {
|
||
let songs: BTreeMap<String, u32> = serde_json::from_str(
|
||
&fs::read_to_string("../assets/jukebox_song.json").expect("Missing jukebox_song.json"),
|
||
)
|
||
.expect("Failed to parse jukebox_song.json");
|
||
|
||
let registries: BTreeMap<String, Value> = serde_json::from_str(
|
||
&fs::read_to_string("../assets/registry/1_21_11_synced_registries.json")
|
||
.expect("Missing synced_registries.json"),
|
||
)
|
||
.expect("Failed to parse synced_registries.json");
|
||
|
||
let song_data: BTreeMap<String, JukeboxSongData> = serde_json::from_value(
|
||
registries
|
||
.get("jukebox_song")
|
||
.expect("Missing jukebox_song in synced registries")
|
||
.clone(),
|
||
)
|
||
.expect("Failed to parse jukebox_song data");
|
||
|
||
let make_variant_ident = |name: &str| {
|
||
let pascal = name.to_pascal_case();
|
||
if pascal.chars().next().is_some_and(|c| c.is_ascii_digit()) {
|
||
format_ident!("Id{}", pascal)
|
||
} else {
|
||
format_ident!("{}", pascal)
|
||
}
|
||
};
|
||
|
||
let variants = songs
|
||
.keys()
|
||
.map(|name| {
|
||
let variant_name = make_variant_ident(name);
|
||
quote! { #variant_name, }
|
||
})
|
||
.collect::<TokenStream>();
|
||
|
||
let type_from_name = songs
|
||
.keys()
|
||
.map(|name| {
|
||
let variant_name = make_variant_ident(name);
|
||
quote! { #name => Some(Self::#variant_name), }
|
||
})
|
||
.collect::<TokenStream>();
|
||
|
||
let type_to_name = songs
|
||
.keys()
|
||
.map(|name| {
|
||
let variant_name = make_variant_ident(name);
|
||
quote! { Self::#variant_name => #name, }
|
||
})
|
||
.collect::<TokenStream>();
|
||
|
||
let type_to_id = songs
|
||
.iter()
|
||
.map(|(name, id)| {
|
||
let variant_name = make_variant_ident(name);
|
||
quote! { Self::#variant_name => #id, }
|
||
})
|
||
.collect::<TokenStream>();
|
||
|
||
let type_to_length = songs
|
||
.keys()
|
||
.map(|name| {
|
||
let variant_name = make_variant_ident(name);
|
||
let length = song_data
|
||
.get(name)
|
||
.map_or(0, |d| d.length_in_seconds as u32);
|
||
quote! { Self::#variant_name => #length, }
|
||
})
|
||
.collect::<TokenStream>();
|
||
|
||
let type_to_comparator = songs
|
||
.keys()
|
||
.map(|name| {
|
||
let variant_name = make_variant_ident(name);
|
||
let output = song_data.get(name).map_or(0, |d| d.comparator_output);
|
||
quote! { Self::#variant_name => #output, }
|
||
})
|
||
.collect::<TokenStream>();
|
||
|
||
quote! {
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||
#[repr(u32)]
|
||
pub enum JukeboxSong {
|
||
#variants
|
||
}
|
||
|
||
impl JukeboxSong {
|
||
#[doc = r" Returns the `JukeboxSong` from the string name (e.g., 'pigstep')."]
|
||
#[must_use]
|
||
pub fn from_name(name: &str) -> Option<Self> {
|
||
match name {
|
||
#type_from_name
|
||
_ => None
|
||
}
|
||
}
|
||
|
||
#[doc = r" Returns the string name of the song."]
|
||
#[must_use]
|
||
pub const fn to_name(&self) -> &'static str {
|
||
match self {
|
||
#type_to_name
|
||
}
|
||
}
|
||
|
||
#[doc = r" Returns the numeric ID associated with the song."]
|
||
#[must_use]
|
||
pub const fn get_id(&self) -> u32 {
|
||
match self {
|
||
#type_to_id
|
||
}
|
||
}
|
||
|
||
#[doc = r" Returns the comparator output value (0-15) for this song."]
|
||
#[must_use]
|
||
pub const fn comparator_output(&self) -> u8 {
|
||
#[allow(clippy::match_same_arms)]
|
||
match self {
|
||
#type_to_comparator
|
||
}
|
||
}
|
||
|
||
#[doc = r" Returns the song length in seconds."]
|
||
#[must_use]
|
||
pub const fn length_in_seconds(&self) -> u32 {
|
||
#[allow(clippy::match_same_arms)]
|
||
match self {
|
||
#type_to_length
|
||
}
|
||
}
|
||
|
||
#[doc = r" Returns the song length in ticks (20 ticks per second)."]
|
||
#[must_use]
|
||
pub const fn length_in_ticks(&self) -> u64 {
|
||
self.length_in_seconds() as u64 * 20
|
||
}
|
||
}
|
||
}
|
||
}
|