use std::{fmt::Display, str::FromStr};
use anyhow::anyhow;
use smallvec::SmallVec;
use crate::repr_t;
const LIST_ALLOCSIZE: usize = 5;
macro_rules! parse {
($v:expr => $t:ty) => {
$v.parse::<$t>().unwrap_or_default()
};
}
pub mod animation_ids {
#![allow(missing_docs)]
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BigBeast {
Bite = 0,
Attack01 = 1,
Attack01End = 2,
Idle01 = 3,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Bat {
Idle01 = 0,
Idle02 = 1,
Idle03 = 2,
Attack01 = 3,
Attack02 = 4,
Attack02End = 5,
Sleep = 6,
SleepLoop = 7,
SleepEnd = 8,
Attack02Loop = 9,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Spikeball {
Idle01 = 0,
Idle02 = 1,
ToAttack01 = 2,
Attack01 = 3,
Attack02 = 4,
ToAttack03 = 5,
Attack03 = 6,
Idle03 = 7,
FromAttack03 = 8,
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Anim {
Other(i32),
BigBeast(animation_ids::BigBeast),
Bat(animation_ids::Bat),
Spikeball(animation_ids::Spikeball),
}
impl From<Anim> for i32 {
fn from(value: Anim) -> i32 {
match value {
Anim::Bat(b) => b as i32,
Anim::BigBeast(b) => b as i32,
Anim::Spikeball(s) => s as i32,
Anim::Other(i) => i,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum Item {
Counter(i16),
Timer(i16),
Points,
Attempts,
MainTime,
}
impl Item {
#[inline]
#[must_use]
pub fn get_type(&self) -> ItemType {
match self {
Self::Attempts => ItemType::Attempts,
Self::Counter(_) => ItemType::Counter,
Self::MainTime => ItemType::MainTime,
Self::Points => ItemType::Points,
Self::Timer(_) => ItemType::Timer,
}
}
#[inline]
#[must_use]
pub fn get_type_as_i32(&self) -> i32 {
self.get_type().to_num()
}
#[inline]
#[must_use]
pub fn as_special_mode(&self) -> Option<CounterMode> {
match self {
Self::Attempts => Some(CounterMode::Attempts),
Self::MainTime => Some(CounterMode::MainTime),
Self::Points => Some(CounterMode::Points),
_ => None,
}
}
#[inline]
#[must_use]
pub fn as_special_mode_i32(&self) -> Option<i32> {
self.as_special_mode().map(|m| m.to_num())
}
#[inline]
#[must_use]
pub fn id(&self) -> i16 {
match self {
Self::Counter(c) => *c,
Self::Timer(t) => *t,
_ => 0,
}
}
}
repr_t!(
ItemType: i32 {
Counter = 1,
Timer = 2,
Points = 3,
MainTime = 4,
Attempts = 5,
}
);
repr_t!(
CounterMode: i32 {
Attempts = -3,
Points = -2,
MainTime = -1,
}
);
#[repr(u8)]
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum GDObjPropType {
Int,
Float,
Text,
Bool,
Group,
Item,
Easing,
EventsList,
ColourChannel,
ProbabilitiesList,
SpawnRemapsList,
Toggle,
Unknown,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[allow(missing_docs)]
pub enum ZLayer {
B5 = -5,
B4 = -3,
B3 = -1,
B2 = 1,
B1 = 3,
#[default]
Default = 0,
T1 = 5,
T2 = 7,
T3 = 9,
T4 = 11,
}
impl From<i32> for ZLayer {
fn from(int: i32) -> Self {
match int {
-5 => Self::B5,
-3 => Self::B4,
-1 => Self::B3,
1 => Self::B2,
3 => Self::B1,
5 => Self::T1,
7 => Self::T2,
9 => Self::T3,
11 => Self::T4,
_ => Self::Default,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[allow(missing_docs)]
pub enum ColourChannel {
Channel(i16),
Background,
Ground1,
Ground2,
Line,
#[default]
Object,
ThreeDLine,
MiddleGround,
MiddleGround2,
P1,
P2,
}
impl From<i16> for ColourChannel {
fn from(c: i16) -> Self {
match c {
1000 => Self::Background,
1001 => Self::Ground1,
1009 => Self::Ground2,
1002 => Self::Line,
1004 => Self::Object,
1003 => Self::ThreeDLine,
1013 => Self::MiddleGround,
1014 => Self::MiddleGround2,
1005 => Self::P1,
1006 => Self::P2,
n => Self::Channel(n),
}
}
}
impl From<ColourChannel> for i16 {
fn from(value: ColourChannel) -> Self {
match value {
ColourChannel::Channel(n) => n,
ColourChannel::Background => 1000,
ColourChannel::Ground1 => 1001,
ColourChannel::Ground2 => 1009,
ColourChannel::Line => 1002,
ColourChannel::Object => 1004,
ColourChannel::ThreeDLine => 1003,
ColourChannel::MiddleGround => 1013,
ColourChannel::MiddleGround2 => 1014,
ColourChannel::P1 => 1005,
ColourChannel::P2 => 1006,
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[allow(missing_docs)]
pub enum MoveEasing {
#[default]
None = 0,
EaseInOut = 1,
EaseIn = 2,
EaseOut = 3,
ElasticInOut = 4,
ElasticIn = 5,
ElasticOut = 6,
BounceInOut = 7,
BounceIn = 8,
BounceOut = 9,
ExponentialInOut = 10,
ExponentialIn = 11,
ExponentialOut = 12,
SineInOut = 13,
SineIn = 14,
SineOut = 15,
BackInOut = 16,
BackIn = 17,
BackOut = 18,
}
impl From<i32> for MoveEasing {
fn from(i: i32) -> Self {
match i {
1 => Self::EaseInOut,
2 => Self::EaseIn,
3 => Self::EaseOut,
4 => Self::ElasticInOut,
5 => Self::ElasticIn,
6 => Self::ElasticOut,
7 => Self::BounceInOut,
8 => Self::BounceIn,
9 => Self::BounceOut,
10 => Self::ExponentialInOut,
11 => Self::ExponentialIn,
12 => Self::ExponentialOut,
13 => Self::SineInOut,
14 => Self::SineIn,
15 => Self::SineOut,
16 => Self::BackInOut,
17 => Self::BackIn,
18 => Self::BackOut,
_ => Self::None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[must_use]
#[non_exhaustive]
pub enum GDValue {
Int(i32),
Short(i16),
Float(f64),
Bool(bool),
Toggle(bool),
Group(i16),
Item(i16),
GroupList(smallvec::SmallVec<[i16; LIST_ALLOCSIZE]>),
ProbabilitiesList(smallvec::SmallVec<[(i16, i32); LIST_ALLOCSIZE]>),
SpawnRemapsList(smallvec::SmallVec<[(i16, i16); LIST_ALLOCSIZE]>),
Easing(MoveEasing),
ColourChannel(ColourChannel),
ZLayer(ZLayer),
Events(Vec<Event>),
String(String), }
impl GDValue {
pub fn from(t: GDObjPropType, s: &str) -> Self {
match t {
GDObjPropType::Bool => Self::Bool(s == "1"),
GDObjPropType::Toggle => Self::Toggle(s == "1"),
GDObjPropType::ColourChannel => {
Self::ColourChannel(ColourChannel::from(parse!(s => i16)))
}
GDObjPropType::Easing => Self::Easing(MoveEasing::from(parse!(s => i32))),
GDObjPropType::Float => Self::Float(parse!(s => f64)),
GDObjPropType::Int => Self::Int(parse!(s => i32)),
GDObjPropType::EventsList => Self::Events(
s.split('.')
.map(|i| Event::from(parse!(i => i32)))
.collect(),
),
GDObjPropType::ProbabilitiesList => {
let tuples = parse_sibling_items::<i16, i32>(s);
Self::ProbabilitiesList(SmallVec::from_vec(tuples))
}
GDObjPropType::SpawnRemapsList => {
let tuples = parse_sibling_items::<i16, i16>(s);
Self::SpawnRemapsList(SmallVec::from_vec(tuples))
}
GDObjPropType::Group => Self::Group(parse!(s => i16)),
GDObjPropType::Item => Self::Item(parse!(s => i16)),
GDObjPropType::Text | GDObjPropType::Unknown => Self::String(s.to_owned()),
}
}
#[inline]
pub fn from_group_list(g: &[Group]) -> Self {
Self::GroupList(SmallVec::from_vec(g.iter().map(|&g| g.id()).collect()))
}
#[inline]
pub fn parents_group_list(g: &[Group]) -> Self {
Self::GroupList(SmallVec::from_vec(
g.iter()
.filter_map(|g| match g {
Group::Parent(p) => Some(*p),
Group::Regular(_) => None,
})
.collect(),
))
}
#[inline]
pub fn from_prob_list(g: Vec<(i16, i32)>) -> Self {
Self::ProbabilitiesList(SmallVec::from_vec(g))
}
#[inline]
pub fn from_spawn_remaps(g: Vec<(i16, i16)>) -> Self {
Self::SpawnRemapsList(SmallVec::from_vec(g))
}
#[inline]
pub fn colour_channel(s: &str) -> Self {
Self::ColourChannel(ColourChannel::from(s.parse().unwrap_or(0)))
}
#[inline]
pub fn zlayer(s: &str) -> Self {
Self::ZLayer(ZLayer::from(s.parse().unwrap_or(0)))
}
}
macro_rules! fmt_intlist {
($vals:expr, $i_buf:expr) => {{
let mut items_str = String::with_capacity($vals.len() * 4);
for (idx, item) in $vals.iter().enumerate() {
if idx != 0 {
items_str.push('.');
}
items_str.push_str($i_buf.format(*item as i32));
}
items_str
}};
($vals:expr => $i_buf:expr) => {{
let mut items_str = String::with_capacity($vals.len() * 4);
for (idx, item) in $vals.iter().enumerate() {
if idx != 0 {
items_str.push('.');
}
items_str.push_str($i_buf.format(item.to_num()));
}
items_str
}};
}
macro_rules! fmt_inttuples {
($vals:expr, $i_buf:expr) => {{
let mut items_str = String::with_capacity($vals.len() * 8);
for (idx, item) in $vals.iter().enumerate() {
if idx != 0 {
items_str.push('.');
}
items_str.push_str($i_buf.format(item.0));
items_str.push('.');
items_str.push_str($i_buf.format(item.1));
}
items_str
}};
}
impl Display for GDValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut i_buf = itoa::Buffer::new();
let mut d_buf = dtoa::Buffer::new();
match self {
GDValue::Bool(b) => write!(f, "{}", if *b { '1' } else { '0' }),
GDValue::Toggle(b) => write!(
f,
"{}",
match b {
true => "1",
false => "-1",
}
),
GDValue::ColourChannel(v) => write!(f, "{}", i_buf.format(Into::<i16>::into(*v))),
GDValue::Easing(v) => write!(f, "{}", i_buf.format(*v as i32)),
GDValue::Float(v) => write!(f, "{}", d_buf.format(*v)),
GDValue::Group(v) | GDValue::Item(v) | GDValue::Short(v) => {
write!(f, "{}", i_buf.format(*v))
}
GDValue::GroupList(v) => write!(f, "{}", fmt_intlist!(v, i_buf)),
GDValue::ProbabilitiesList(v) => write!(f, "{}", fmt_inttuples!(v, i_buf)),
GDValue::SpawnRemapsList(v) => write!(f, "{}", fmt_inttuples!(v, i_buf)),
GDValue::Int(v) => write!(f, "{}", i_buf.format(*v)),
GDValue::String(v) => write!(f, "{v}"),
GDValue::ZLayer(v) => write!(f, "{}", i_buf.format(*v as i32)),
GDValue::Events(evts) => write!(f, "{}", fmt_intlist!(evts => i_buf)),
}
}
}
repr_t!(
#[allow(missing_docs)]
Event: i32 {
TinyLanding = 1,
FeatherLanding = 2,
SoftLanding = 3,
NormalLanding = 4,
HardLanding = 5,
HitHead = 6,
OrbTouched = 7,
OrbActivated = 8,
PadActivated = 9,
GravityInverted = 10,
GravityRestored = 11,
NormalJump = 12,
RobotBoostStart = 13,
RobotBoostStop = 14,
UFOJump = 15,
ShipBoostStart = 16,
ShipBoostEnd = 17,
SpiderTeleport = 18,
BallSwitch = 19,
SwingSwitch = 20,
WavePush = 21,
WaveRelease = 22,
DashStart = 23,
DashStop = 24,
Teleported = 25,
PortalNormal = 26,
PortalShip = 27,
PortalBall = 28,
PortalUFO = 29,
PortalWave = 30,
PortalRobot = 31,
PortalSpider = 32,
PortalSwing = 33,
YellowOrb = 34,
PinkOrb = 35,
RedOrb = 36,
GravityOrb = 37,
GreenOrb = 38,
DropOrb = 39,
CustomOrb = 40,
DashOrb = 41,
GravityDashOrb = 42,
SpiderOrb = 43,
TeleportOrb = 44,
YellowPad = 45,
PinkPad = 46,
RedPad = 47,
GravityPad = 48,
SpiderPad = 49,
PortalGravityFlip = 50,
PortalGravityNormal = 51,
PortalGravityInvert = 52,
PoratlFlip = 53,
PortalUnflip = 54,
PortalNormalScale = 55,
PortalMiniScale = 56,
PortalDualOn = 57,
PortalDualOff = 58,
PortalTeleport = 59,
Checkpoint = 60,
DestroyBlock = 61,
UserCoin = 62,
PickupItem = 63,
FallLow = 65,
FallMed = 66,
FallHigh = 67,
FallVHigh = 68,
JumpPush = 69,
JumpRelease = 70,
LeftPush = 71,
LeftRelease = 72,
RightPush = 73,
RightRelease = 74,
PlayerReversed = 75,
CheckpointRespawn = 64, FallSpeedLow = 76,
FallSpeedMed = 77,
FallSpeedHigh = 78,
}
);
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[allow(missing_docs)]
pub enum ExtraID2 {
#[default]
All = 0,
P1 = 1,
P2 = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum MoveTarget {
Group(i16),
Player1,
Player2,
}
repr_t!(
#[allow(missing_docs)]
strict Gamemode: i32 {
Cube = 0,
Ship = 1,
Ball = 2,
Ufo = 3,
Wave = 4,
Robot = 5,
Spider = 6,
Swing = 7,
}
default Cube
);
repr_t!(
StopMode: i32 {
Stop = 0,
Pause = 1,
Resume = 2,
}
);
repr_t!(
ItemAlign: i32 {
Center = 0,
Left = 1,
Right = 2,
}
);
repr_t!(
TransitionMode: i32 {
Both = 0,
Enter = 1,
Exit = 2,
}
);
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[allow(missing_docs)]
pub enum TransitionType {
Fade = 22,
FromBottom = 23,
FromTop = 24,
FromLeft = 25,
FromRight = 26,
ScaleIn = 27,
ScaleOut = 28,
Random = 55,
AwayToLeft = 56,
AwayToRight = 57,
AwayFromMiddle = 58,
TowardsMiddle = 59,
#[default]
None = 1915,
}
repr_t!(
Op: i32 {
Set = 0,
Add = 1,
Sub = 2,
Mul = 3,
Div = 4,
}
);
repr_t!(
CompareOp: i32 {
Equals = 0,
Greater = 1,
GreaterOrEquals = 2,
Less = 3,
LessOrEquals = 4,
NotEquals = 5,
}
);
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CompareOperand {
pub operand_item: Item,
pub modifier: f64,
pub mod_op: Op,
pub rounding: RoundMode,
pub sign: SignMode,
}
impl CompareOperand {
pub fn number_literal(num: f64) -> Self {
Self {
operand_item: Item::Counter(0),
modifier: num,
mod_op: Op::Mul,
rounding: RoundMode::None,
sign: SignMode::None,
}
}
}
impl From<Item> for CompareOperand {
fn from(value: Item) -> Self {
Self {
operand_item: value,
modifier: 1.0,
mod_op: Op::Mul,
rounding: RoundMode::None,
sign: SignMode::None,
}
}
}
repr_t!(
strict RoundMode: i32 {
None = 0,
Nearest = 1,
Floor = 2,
Ceiling = 3,
}
);
repr_t!(
strict SignMode: i32 {
None = 0,
Absolute = 1,
Negative = 2,
}
);
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TargetPlayer {
Player1 = 138,
Player2 = 200,
PlayerTarget = 201,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MoveMode {
Default(DefaultMove),
Targeting(TargetMove),
Directional(DirectionalMove),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum MoveLock {
Player,
Camera,
}
repr_t!(
strict UIReferencePos: i32 {
Auto = 1,
Center = 2,
Left = 3,
Right = 4,
}
);
#[derive(Debug, Clone, PartialEq)]
pub struct DefaultMove {
pub dx: f64,
pub dy: f64,
pub x_lock: Option<MoveLock>,
pub y_lock: Option<MoveLock>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TargetMove {
pub target_group_id: MoveTarget,
pub center_group_id: Option<i16>,
pub axis_only: Option<AxisOnlyMove>,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AxisOnlyMove {
X = 1,
Y = 2,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DirectionalMove {
pub target_group_id: MoveTarget,
pub center_group_id: Option<i16>,
pub distance: i32,
}
repr_t!(
#[allow(missing_docs)]
strict Speed: i32 {
X0Point5 = 1,
X1 = 0,
X2 = 2,
X3 = 3,
X4 = 4,
}
default X1
);
#[derive(Debug, Clone, PartialEq)]
pub struct HSVColour {
pub hue_shift: i32,
pub saturation_mult: f64,
pub brightness_mult: f64,
pub static_sat_scalar: bool,
pub static_bright_scalar: bool,
}
impl Display for HSVColour {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}a{}a{}a{}a{}",
self.hue_shift,
self.saturation_mult,
self.brightness_mult,
if self.static_sat_scalar { "1" } else { "0" },
if self.static_bright_scalar { "1" } else { "0" }
)
}
}
macro_rules! set_value {
($iter:expr => $t:ty) => {
match $iter.next() {
Some(v) => match v.parse::<$t>() {
Ok(v) => v,
Err(_) => return None,
},
None => return None,
}
};
($iter:expr) => {
match $iter.next() {
Some(v) => match v.parse::<i32>() {
Ok(v) => v != 0,
Err(_) => return None,
},
None => return None,
}
};
}
impl HSVColour {
pub fn parse(s: &str) -> Option<Self> {
let mut vals_iter = s.split("a").into_iter();
let mut new = Self {
hue_shift: 0,
saturation_mult: 1.0,
brightness_mult: 1.0,
static_bright_scalar: false,
static_sat_scalar: false,
};
new.hue_shift = set_value!(vals_iter => i32);
new.saturation_mult = set_value!(vals_iter => f64);
new.brightness_mult = set_value!(vals_iter => f64);
new.static_bright_scalar = set_value!(vals_iter);
new.static_sat_scalar = set_value!(vals_iter);
Some(new)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PulseTarget {
Group(PulseGroup),
Channel(PulseChannel),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PulseChannel {
pub channel_id: i16,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PulseGroup {
pub group_id: i16,
pub main_colour_only: bool,
pub detail_colour_only: bool,
}
pub enum PulseMode {
Colour(Colour),
HSV(PulseHSV),
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[allow(missing_docs)]
pub struct Colour {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl Colour {
pub fn from_rgb(rgb: (u8, u8, u8)) -> Self {
Self {
red: rgb.0,
green: rgb.1,
blue: rgb.2,
}
}
pub fn from_hex<S: AsRef<str>>(hex_str: S) -> Result<Self, anyhow::Error> {
let str = hex_str.as_ref();
if str.len() != 7 || !str.starts_with('#') {
return Err(anyhow!(
"Hex code must start with a hashtag followed by a 6-digit RGB hex tuple."
));
}
let mut owned = str.to_string();
owned.remove(0);
let hex = i32::from_str_radix(&owned, 16)?;
Ok(Self {
red: (hex >> 16 & 0xFF) as u8,
green: (hex >> 8 & 0xFF) as u8,
blue: (hex & 0xFF) as u8,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScaleConfig {
pub x_scale: f64,
pub y_scale: f64,
pub div_by_value_x: bool,
pub div_by_value_y: bool,
pub only_move: bool,
pub relative_scale: bool,
pub relative_rotation: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PulseHSV {
pub hsv_config: HSVColour,
pub use_static_hsv: bool,
pub colour_id: ColourChannel,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CopyColourConfig {
pub original_ch: ColourChannel,
pub hsv_config: HSVColour,
pub use_legacy_hsv: bool,
pub copy_opacity: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RotationMode {
Default(RotationNormal),
Aim(RotationAim),
Follow(RotationAim),
}
#[derive(Debug, Clone, PartialEq)]
pub struct RotationConfig {
pub mode: RotationMode,
pub dynamic_mode: bool,
pub lock_object_rotation: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TimeTriggerConfig {
pub start_time: f64,
pub stop_time: f64,
pub pause_when_reached: bool,
pub time_mod: f64,
pub timer_id: i16,
pub ignore_timewarp: bool,
pub start_paused: bool,
pub dont_override: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RotationNormal {
pub degrees: f64,
pub x360: i32,
}
impl RotationNormal {
pub fn from_degrees(deg: f64) -> Self {
Self {
degrees: deg % 360.0,
x360: (deg as i32 / 360),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum RotationPlayerTarget {
Player1,
Player2,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RotationAim {
pub aim_target: i16,
pub rot_offset: f64,
pub player_target: Option<RotationPlayerTarget>,
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct ParticleSpawnConfig {
pub position_offsets: Option<(i32, i32)>,
pub position_variation: Option<(i32, i32)>,
pub rotation_config: Option<(i32, i32)>,
pub scale_config: Option<(f64, f64)>,
pub match_rotation: bool,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct StartposConfig {
pub start_speed: Speed,
pub starting_gamemode: Gamemode,
pub starting_as_mini: bool,
pub starting_as_dual: bool,
pub starting_mirrored: bool,
pub reset_camera: bool,
pub rotate_gameplay: bool,
pub reverse_gameplay: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ColliderConfig {
pub collider1: i16,
pub collider2: i16,
pub collide_player1: bool,
pub collide_player2: bool,
pub collide_both_players: bool,
}
impl ColliderConfig {
pub fn two_colliders(collider1_id: i16, collider2_id: i16) -> Self {
Self {
collider1: collider1_id,
collider2: collider2_id,
collide_player1: false,
collide_player2: false,
collide_both_players: false,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ColourTriggerConfig {
pub colour: Colour,
pub channel: ColourChannel,
pub opacity: f64,
pub blending: bool,
pub use_player_col_1: bool,
pub use_player_col_2: bool,
}
#[repr(i32)]
#[allow(missing_docs)]
pub enum MiddleGround {
None = 0,
SeasweptMountains = 1,
RockyMountains = 2,
Clouds = 3,
}
#[repr(i32)]
pub enum OptionalPlayerTarget {
None = 0,
Player1 = 1,
Player2 = 2,
}
#[repr(i32)]
pub enum TouchToggle {
None = 0,
ToggleOn = 1,
ToggleOff = 2,
}
fn parse_sibling_items<T, S>(s: &str) -> Vec<(T, S)>
where
T: Default + FromStr + Copy + Clone,
S: Default + FromStr + Copy + Clone,
{
let mut tuples = Vec::new();
let mut iter = s.split('.');
while let (Some(group), Some(chance)) = (iter.next(), iter.next()) {
let g = parse!(group => T);
let c = parse!(chance => S);
tuples.push((g, c));
}
tuples
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum Group {
Regular(i16),
Parent(i16),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum GroupType {
Regular,
Parent,
}
impl Group {
pub fn id(&self) -> i16 {
match self {
Self::Regular(id) | Self::Parent(id) => *id,
}
}
#[must_use]
pub fn get_type(&self) -> GroupType {
match self {
Group::Parent(_) => GroupType::Parent,
Group::Regular(_) => GroupType::Regular,
}
}
}
impl From<i16> for Group {
fn from(value: i16) -> Self {
Self::Regular(value)
}
}