use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
fn default_damage() -> f32 {
1.0
}
fn default_lifetime() -> f32 {
5.0
}
fn default_z_index() -> f32 {
15.0
}
fn default_scale() -> f32 {
1.0
}
fn default_frame_duration() -> f32 {
0.05
}
fn default_linear_direction() -> (f32, f32) {
(0.0, -1.0)
}
fn default_linear_speed() -> f32 {
100.0
}
fn default_angular_velocity() -> f32 {
1.0
}
fn default_sine_axis() -> (f32, f32) {
(1.0, 0.0)
}
fn default_sine_amplitude() -> f32 {
20.0
}
fn default_sine_frequency() -> f32 {
2.0
}
fn default_line_spacing() -> f32 {
20.0
}
fn default_edge_spacing() -> f32 {
30.0
}
fn default_edge_margin() -> f32 {
200.0
}
#[derive(Asset, Debug, Clone, Deserialize, Serialize, Reflect)]
#[reflect(Debug)]
pub struct DanmakuPerformance {
#[serde(default)]
pub prototypes: HashMap<String, BulletPrototype>,
#[serde(default)]
pub behaviors: HashMap<String, BulletBehavior>,
pub timeline: Vec<TimelineEvent>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect, Default)]
pub enum HitBehaviorPreset {
#[default]
Default,
Persistent,
DamageWhenMoving,
DamageWhenStationary,
Custom {
#[serde(default = "default_despawn_on_hit")]
despawn_on_hit: bool,
#[serde(default)]
damage_on_player_moving: bool,
#[serde(default)]
damage_on_player_stationary: bool,
#[serde(default)]
invincibility_duration: f32,
},
}
fn default_despawn_on_hit() -> bool {
true
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect, Default)]
pub struct ColorTint {
#[serde(default)]
pub hex: String,
#[serde(default)]
pub rgba: Option<(f32, f32, f32, f32)>,
}
impl ColorTint {
pub fn to_color(&self) -> Option<Color> {
if !self.hex.is_empty() {
parse_hex_color(&self.hex)
} else if let Some((r, g, b, a)) = self.rgba {
Some(Color::srgba(r, g, b, a))
} else {
None
}
}
}
fn parse_hex_color(hex: &str) -> Option<Color> {
let hex = hex.trim_start_matches('#');
match hex.len() {
3 => {
let r = u8::from_str_radix(&hex[0..1], 16).ok()? * 17;
let g = u8::from_str_radix(&hex[1..2], 16).ok()? * 17;
let b = u8::from_str_radix(&hex[2..3], 16).ok()? * 17;
Some(Color::srgb_u8(r, g, b))
}
4 => {
let r = u8::from_str_radix(&hex[0..1], 16).ok()? * 17;
let g = u8::from_str_radix(&hex[1..2], 16).ok()? * 17;
let b = u8::from_str_radix(&hex[2..3], 16).ok()? * 17;
let a = u8::from_str_radix(&hex[3..4], 16).ok()? * 17;
Some(Color::srgba_u8(r, g, b, a))
}
6 => {
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some(Color::srgb_u8(r, g, b))
}
8 => {
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
Some(Color::srgba_u8(r, g, b, a))
}
_ => None,
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub struct BulletPrototype {
pub visual: BulletVisual,
#[serde(default)]
pub collider: ColliderShape,
#[serde(default = "default_damage")]
pub damage: f32,
#[serde(default = "default_lifetime")]
pub lifetime: f32,
#[serde(default = "default_z_index")]
pub z_index: f32,
#[serde(default = "default_scale")]
pub scale: f32,
#[serde(default)]
pub hit_behavior: HitBehaviorPreset,
#[serde(default)]
pub color_tint: ColorTint,
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub enum BulletVisual {
Sprite { path: String },
SpriteRef { module: String, name: String },
Animation {
module: String,
name: String,
#[serde(default = "default_frame_duration")]
frame_duration: f32,
},
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub enum ColliderShape {
CircleCollider(f32),
BoxCollider(f32, f32),
}
impl Default for ColliderShape {
fn default() -> Self {
ColliderShape::CircleCollider(4.0)
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub enum BulletBehavior {
Linear(LinearConfig),
Tween(TweenConfig),
Orbital(OrbitalConfig),
Sine(SineConfig),
Custom {
id: String,
#[serde(default)]
props: HashMap<String, f32>,
},
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub struct LinearConfig {
#[serde(default = "default_linear_direction")]
pub dir: (f32, f32),
#[serde(default = "default_linear_speed")]
pub speed: f32,
}
impl Default for LinearConfig {
fn default() -> Self {
Self {
dir: default_linear_direction(),
speed: default_linear_speed(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub struct OrbitalConfig {
#[serde(default = "default_angular_velocity")]
pub angular_velocity: f32,
#[serde(default)]
pub radial_velocity: f32,
}
impl Default for OrbitalConfig {
fn default() -> Self {
Self {
angular_velocity: default_angular_velocity(),
radial_velocity: 0.0,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub struct SineConfig {
#[serde(default = "default_sine_axis")]
pub axis: (f32, f32),
#[serde(default = "default_sine_amplitude")]
pub amplitude: f32,
#[serde(default = "default_sine_frequency")]
pub frequency: f32,
#[serde(default)]
pub phase: f32,
}
impl Default for SineConfig {
fn default() -> Self {
Self {
axis: default_sine_axis(),
amplitude: default_sine_amplitude(),
frequency: default_sine_frequency(),
phase: 0.0,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub struct TweenConfig {
pub target: TweenTarget,
pub duration: f32,
#[serde(default)]
pub ease: Easing,
pub range: (f32, f32),
#[serde(default)]
pub delay: f32,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Reflect, Default)]
pub enum TweenTarget {
#[default]
Opacity,
Scale,
ScaleX,
ScaleY,
PositionX,
PositionY,
Rotation,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, Reflect)]
pub enum Easing {
#[default]
Linear,
QuadIn,
QuadOut,
QuadInOut,
CubicIn,
CubicOut,
CubicInOut,
SineIn,
SineOut,
SineInOut,
}
impl Easing {
pub fn apply(self, t: f32) -> f32 {
match self {
Easing::Linear => t,
Easing::QuadIn => t * t,
Easing::QuadOut => 1.0 - (1.0 - t) * (1.0 - t),
Easing::QuadInOut => {
if t < 0.5 {
2.0 * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
}
}
Easing::CubicIn => t * t * t,
Easing::CubicOut => 1.0 - (1.0 - t).powi(3),
Easing::CubicInOut => {
if t < 0.5 {
4.0 * t * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
}
}
Easing::SineIn => 1.0 - (t * std::f32::consts::FRAC_PI_2).cos(),
Easing::SineOut => (t * std::f32::consts::FRAC_PI_2).sin(),
Easing::SineInOut => -(t * std::f32::consts::PI).cos() / 2.0 + 0.5,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect)]
pub struct TimelineEvent {
pub t: f32,
#[serde(default)]
pub absolute: bool,
pub spawn: String,
#[serde(default)]
pub pattern: SpawnPattern,
#[serde(default)]
pub apply: Vec<String>,
#[serde(default)]
pub behaviors: Vec<BulletBehavior>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Reflect, Default)]
pub enum SpawnPattern {
#[default]
Single,
RingGenerator {
count: usize,
#[serde(default)]
radius: f32,
#[serde(default)]
start_angle: f32,
},
LineGenerator {
count: usize,
#[serde(default = "default_line_spacing")]
spacing: f32,
#[serde(default = "default_linear_direction")]
direction: (f32, f32),
},
EdgeGenerator {
count: usize,
#[serde(default)]
side: EdgeSide,
#[serde(default = "default_edge_spacing")]
spacing: f32,
#[serde(default = "default_edge_margin")]
margin: f32,
},
CustomGenerator {
id: String,
#[serde(default)]
params: HashMap<String, f32>,
},
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, Reflect)]
pub enum EdgeSide {
#[default]
Left,
Right,
Top,
Bottom,
}
impl EdgeSide {
pub fn to_direction(self) -> Vec2 {
match self {
EdgeSide::Left => Vec2::new(1.0, 0.0),
EdgeSide::Right => Vec2::new(-1.0, 0.0),
EdgeSide::Top => Vec2::new(0.0, -1.0),
EdgeSide::Bottom => Vec2::new(0.0, 1.0),
}
}
pub fn to_offset(self, margin: f32) -> Vec2 {
match self {
EdgeSide::Left => Vec2::new(-margin, 0.0),
EdgeSide::Right => Vec2::new(margin, 0.0),
EdgeSide::Top => Vec2::new(0.0, margin),
EdgeSide::Bottom => Vec2::new(0.0, -margin),
}
}
}
#[derive(Resource, Default)]
pub struct PendingPerformanceLoads {
pub pending: Vec<(Handle<DanmakuPerformance>, PlayPerformanceEvent)>,
}
#[derive(bevy::ecs::message::Message, Clone)]
pub struct PlayPerformanceEvent {
pub performance_path: String,
pub position: Vec2,
}
impl PlayPerformanceEvent {
pub fn new(performance_path: impl Into<String>) -> Self {
Self {
performance_path: performance_path.into(),
position: Vec2::ZERO,
}
}
pub fn at_position(mut self, position: Vec2) -> Self {
self.position = position;
self
}
}