diff --git a/assets/config/actor.ron b/assets/config/actor.ron index c6b38c9..703f90b 100644 --- a/assets/config/actor.ron +++ b/assets/config/actor.ron @@ -16,22 +16,10 @@ ], ), - movement: Movement( - speed: 80.0, - ), - // TODO: Does this still work or does it get overwritten? - attack: Attack( - power: 2, - force: 4, - color: Srgba(Srgba(red: 0.855, green: 0.576, blue: 0.800, alpha: 1.000)), - ), - health: Health( - max: 100, - current: 100, - ), - deck: Deck( - cards: ["step", "pair"], - ), + movement: Movement(speed: 80.0), + attack: Attack(color: Srgba(Srgba(red: 0.855, green: 0.576, blue: 0.800, alpha: 1.000))), + health: Health(max: 100, current: 100), + deck: Deck(cards: ["step", "pair"]), ), }, @@ -52,15 +40,13 @@ ], ), - attack: Attack( - color: Srgba(Srgba(red: 0.929, green: 0.557, blue: 0.576, alpha: 1.000)), - ), - deck: Deck( - cards: ["step", "step", "pair"], - ), + attack: Attack(color: Srgba(Srgba(red: 0.929, green: 0.557, blue: 0.576, alpha: 1.000))), + deck: Deck(cards: ["step", "step", "pair"]), ), - "pink": Actor( + // TODO: If character select screen is implemented, uncomment this and include logic to + // avoid spawning enemies that look like the player (aka same key). + /*"pink": Actor( name: "Linus", texture: "image/actor/pink.png", @@ -76,13 +62,9 @@ ], ), - attack: Attack( - color: Srgba(Srgba(red: 0.855, green: 0.576, blue: 0.800, alpha: 1.000)), - ), - deck: Deck( - cards: ["step", "pair", "pair"], - ), - ), + attack: Attack(color: Srgba(Srgba(red: 0.855, green: 0.576, blue: 0.800, alpha: 1.000))), + deck: Deck(cards: ["step", "pair", "pair"]), + ),*/ "green": Actor( name: "Jade", @@ -100,12 +82,8 @@ ], ), - attack: Attack( - color: Srgba(Srgba(red: 0.557, green: 0.722, blue: 0.518, alpha: 1.000)), - ), - deck: Deck( - cards: ["pair"], - ), + attack: Attack(color: Srgba(Srgba(red: 0.557, green: 0.722, blue: 0.518, alpha: 1.000))), + deck: Deck(cards: ["pair"]), ), "blue": Actor( @@ -124,12 +102,8 @@ ], ), - attack: Attack( - color: Srgba(Srgba(red: 0.424, green: 0.694, blue: 0.725, alpha: 1.000)), - ), - deck: Deck( - cards: ["step", "pair"], - ), + attack: Attack(color: Srgba(Srgba(red: 0.424, green: 0.694, blue: 0.725, alpha: 1.000))), + deck: Deck(cards: ["step", "pair"]), ), "purple": Actor( @@ -148,12 +122,8 @@ ], ), - attack: Attack( - color: Srgba(Srgba(red: 0.694, green: 0.529, blue: 0.788, alpha: 1.000)), - ), - deck: Deck( - cards: ["step"], - ), + attack: Attack(color: Srgba(Srgba(red: 0.694, green: 0.529, blue: 0.788, alpha: 1.000))), + deck: Deck(cards: ["step"]), ), }, ) diff --git a/assets/config/card.ron b/assets/config/card.ron index 7bb9e74..dc3126a 100644 --- a/assets/config/card.ron +++ b/assets/config/card.ron @@ -40,25 +40,32 @@ ), }, card_icon_map: { - // TODO: Long-duration notes are stronger versions of short-duration notes (more damage and knockback, maybe less speed). + "step": CardIcon(texture: "image/card/icon/step.png"), + "splits": CardIcon(texture: "image/card/icon/splits.png"), + "ballet": CardIcon(texture: "image/card/icon/ballet.png"), + "moonwalk": CardIcon(texture: "image/card/icon/moonwalk.png"), + "cartwheel": CardIcon(texture: "image/card/icon/cartwheel.png"), + "eighth_note": CardIcon(texture: "image/card/icon/eighth_note.png"), - //"quarter_note": CardIcon(texture: "image/card/icon/quarter_note.png"), - //"half_note": CardIcon(texture: "image/card/icon/half_note.png"), - //"whole_note": CardIcon(texture: "image/card/icon/whole_note.png"), + "quarter_note": CardIcon(texture: "image/card/icon/quarter_note.png"), + "half_note": CardIcon(texture: "image/card/icon/half_note.png"), + "whole_note": CardIcon(texture: "image/card/icon/whole_note.png"), "pair": CardIcon(texture: "image/card/icon/pair.png"), - "major_chord": CardIcon(texture: "image/card/icon/major_chord.png"), - "cluster_chord": CardIcon(texture: "image/card/icon/cluster_chord.png"), - "rest": CardIcon(texture: "image/card/icon/rest.png"), + //"triplet": CardIcon(texture: "image/card/icon/triplet.png"), + "chord": CardIcon(texture: "image/card/icon/chord.png"), + "cluster": CardIcon(texture: "image/card/icon/cluster.png"), + + "eighth_rest": CardIcon(texture: "image/card/icon/eighth_rest.png"), + "quarter_rest": CardIcon(texture: "image/card/icon/quarter_rest.png"), + "half_rest": CardIcon(texture: "image/card/icon/half_rest.png"), + "whole_rest": CardIcon(texture: "image/card/icon/whole_rest.png"), + "sharp_flat": CardIcon(texture: "image/card/icon/sharp_flat.png"), "natural": CardIcon(texture: "image/card/icon/natural.png"), "fermata": CardIcon(texture: "image/card/icon/fermata.png"), "bass_clef": CardIcon(texture: "image/card/icon/bass_clef.png"), - - "step": CardIcon(texture: "image/card/icon/step.png"), - "splits": CardIcon(texture: "image/card/icon/splits.png"), - "ballet": CardIcon(texture: "image/card/icon/ballet.png"), - "moonwalk": CardIcon(texture: "image/card/icon/moonwalk.png"), }, + // TODO: Write descriptions. card_map: { "step": Card( name: "Basic Step", @@ -69,11 +76,11 @@ // TODO: "Movement card" sfx. play_sfx: "audio/sfx/Projectile Hits Enemy.ogg", action: Step, - action_config: CardActionConfig( + action_modifier: CardActionModifier( remove_on_beat: 8, ), ), - "splits": Card( + /*"splits": Card( name: "Splits", description: "Split in two!", background: "blue", @@ -81,9 +88,59 @@ // TODO: "Movement card" sfx. play_sfx: "audio/sfx/Projectile Hits Enemy.ogg", - action_config: CardActionConfig( + action_modifier: CardActionModifier( remove_on_beat: 16, ), + ),*/ + // TODO: Add other movement cards. + + "eighth_note": Card( + name: "Eighth Note", + description: "", + background: "pink", + icon: "eighth_note", + + action: Attack, + action_modifier: CardActionModifier( + remove_on_beat: 0, + attack: Attack(projectile: Some("eighth_note")), + ), + ), + "quarter_note": Card( + name: "Quarter Note", + description: "", + background: "pink", + icon: "quarter_note", + + action: Attack, + action_modifier: CardActionModifier( + remove_on_beat: 0, + attack: Attack(projectile: Some("quarter_note")), + ), + ), + "half_note": Card( + name: "Half Note", + description: "", + background: "pink", + icon: "half_note", + + action: Attack, + action_modifier: CardActionModifier( + remove_on_beat: 0, + attack: Attack(projectile: Some("half_note")), + ), + ), + "whole_note": Card( + name: "Whole Note", + description: "", + background: "pink", + icon: "whole_note", + + action: Attack, + action_modifier: CardActionModifier( + remove_on_beat: 0, + attack: Attack(projectile: Some("whole_note")), + ), ), "pair": Card( name: "Pair", @@ -91,25 +148,70 @@ background: "pink", icon: "pair", - action: DoubleBeat, - action_config: CardActionConfig( + action: Attack, + action_modifier: CardActionModifier( remove_on_beat: 4, - attack: Attack ( - power: 2.0, - force: 4.0, - projectile: Some("quarter_note"), - ), + attack: Attack(projectile: Some("eighth_note")), ), ), - "major_chord": Card( + /*"major_chord": Card( name: "Major Chord", - description: "Notes that move apart synchronously", + description: "", + background: "pink", + icon: "chord", + + action_modifier: CardActionModifier( + remove_on_beat: 16, + attack: Attack(projectile: Some("eighth_note")), + ), + ), + "cluster_chord": Card( + name: "Cluster Chord", + description: "", background: "pink", - icon: "major_chord", + icon: "chord", - action_config: CardActionConfig( + action_modifier: CardActionModifier( remove_on_beat: 16, + attack: Attack(projectile: Some("whole_note")), ), + ),*/ + + "eighth_rest": Card( + name: "Eighth Rest", + description: "Heal a little bit", + background: "green", + icon: "eighth_rest", + + action: Heal, + action_modifier: CardActionModifier(heal_flat: 5), + ), + "quarter_rest": Card( + name: "Quarter Rest", + description: "Heal 25% health", + background: "green", + icon: "quarter_rest", + + action: Heal, + action_modifier: CardActionModifier(heal_percent: 25), + ), + "half_rest": Card( + name: "Half Rest", + description: "Heal 50% health", + background: "green", + icon: "half_rest", + + action: Heal, + action_modifier: CardActionModifier(heal_percent: 50), + ), + "whole_rest": Card( + name: "Whole Rest", + description: "Heal 100% health", + background: "green", + icon: "whole_rest", + + action: Heal, + action_modifier: CardActionModifier(heal_percent: 100), ), } ) diff --git a/assets/config/projectile.ron b/assets/config/projectile.ron index dd09176..733ceb2 100644 --- a/assets/config/projectile.ron +++ b/assets/config/projectile.ron @@ -1,5 +1,19 @@ ( + // TODO: Tune stats for each projectile. projectiles: { + "eighth_note": Projectile( + name: "Eighth Note", + + texture: "image/projectile/eighth_note.png", + spawn_sfx: "audio/sfx/Projectile Hits Enemy.ogg", + spawn_sfx_volume: 0.5, + + lifetime: 1, + radius: 3.0, + speed: 20.0, + damage: 2.0, + knockback: 0.5, + ), "quarter_note": Projectile( name: "Quarter Note", @@ -13,18 +27,31 @@ damage: 2.0, knockback: 0.5, ), - "double_beat": Projectile( - name: "Double Beat", + "half_note": Projectile( + name: "Half Note", - texture: "image/projectile/quarter_note.png", + texture: "image/projectile/half_note.png", spawn_sfx: "audio/sfx/Projectile Hits Enemy.ogg", spawn_sfx_volume: 0.5, - lifetime: 2, + lifetime: 1, radius: 3.0, - speed: 25.0, - damage: 3.0, - knockback: 0.6, + speed: 20.0, + damage: 2.0, + knockback: 0.5, + ), + "whole_note": Projectile( + name: "Whole Note", + + texture: "image/projectile/whole_note.png", + spawn_sfx: "audio/sfx/Projectile Hits Enemy.ogg", + spawn_sfx_volume: 0.5, + + lifetime: 1, + radius: 3.0, + speed: 20.0, + damage: 2.0, + knockback: 0.5, ), }, ) diff --git a/assets/image/card/background/backgrounds.aseprite b/assets/image/card/background/backgrounds.aseprite index 59f5a2c..cad0c04 100644 Binary files a/assets/image/card/background/backgrounds.aseprite and b/assets/image/card/background/backgrounds.aseprite differ diff --git a/assets/image/card/icon/major_chord.png b/assets/image/card/icon/chord.png similarity index 100% rename from assets/image/card/icon/major_chord.png rename to assets/image/card/icon/chord.png diff --git a/assets/image/card/icon/cluster_chord.png b/assets/image/card/icon/cluster.png similarity index 100% rename from assets/image/card/icon/cluster_chord.png rename to assets/image/card/icon/cluster.png diff --git a/assets/image/card/icon/double_beat.png b/assets/image/card/icon/double_beat.png deleted file mode 100644 index 490e021..0000000 Binary files a/assets/image/card/icon/double_beat.png and /dev/null differ diff --git a/assets/image/card/icon/eighth_rest.png b/assets/image/card/icon/eighth_rest.png new file mode 100644 index 0000000..f359a29 Binary files /dev/null and b/assets/image/card/icon/eighth_rest.png differ diff --git a/assets/image/card/icon/half_note.png b/assets/image/card/icon/half_note.png new file mode 100644 index 0000000..c9a6fbb Binary files /dev/null and b/assets/image/card/icon/half_note.png differ diff --git a/assets/image/card/icon/half_rest.png b/assets/image/card/icon/half_rest.png new file mode 100644 index 0000000..c765ed5 Binary files /dev/null and b/assets/image/card/icon/half_rest.png differ diff --git a/assets/image/card/icon/icons.aseprite b/assets/image/card/icon/icons.aseprite index 379bef6..3b17ca6 100644 Binary files a/assets/image/card/icon/icons.aseprite and b/assets/image/card/icon/icons.aseprite differ diff --git a/assets/image/card/icon/quarter_note.png b/assets/image/card/icon/quarter_note.png new file mode 100644 index 0000000..4ff3deb Binary files /dev/null and b/assets/image/card/icon/quarter_note.png differ diff --git a/assets/image/card/icon/rest.png b/assets/image/card/icon/quarter_rest.png similarity index 100% rename from assets/image/card/icon/rest.png rename to assets/image/card/icon/quarter_rest.png diff --git a/assets/image/card/icon/whole_note.png b/assets/image/card/icon/whole_note.png new file mode 100644 index 0000000..0f38ff4 Binary files /dev/null and b/assets/image/card/icon/whole_note.png differ diff --git a/assets/image/card/icon/whole_rest.png b/assets/image/card/icon/whole_rest.png new file mode 100644 index 0000000..a194927 Binary files /dev/null and b/assets/image/card/icon/whole_rest.png differ diff --git a/assets/image/projectile/eighth_note.png b/assets/image/projectile/eighth_note.png new file mode 100644 index 0000000..4842c98 Binary files /dev/null and b/assets/image/projectile/eighth_note.png differ diff --git a/assets/image/projectile/half_note.png b/assets/image/projectile/half_note.png new file mode 100644 index 0000000..9f0ec95 Binary files /dev/null and b/assets/image/projectile/half_note.png differ diff --git a/assets/image/projectile/projectiles.aseprite b/assets/image/projectile/projectiles.aseprite new file mode 100644 index 0000000..54c304e Binary files /dev/null and b/assets/image/projectile/projectiles.aseprite differ diff --git a/assets/image/projectile/quarter_note.aseprite b/assets/image/projectile/quarter_note.aseprite deleted file mode 100644 index 4683316..0000000 Binary files a/assets/image/projectile/quarter_note.aseprite and /dev/null differ diff --git a/assets/image/projectile/whole_note.png b/assets/image/projectile/whole_note.png new file mode 100644 index 0000000..725101d Binary files /dev/null and b/assets/image/projectile/whole_note.png differ diff --git a/src/game/actor/health.rs b/src/game/actor/health.rs index 24009bc..7270788 100644 --- a/src/game/actor/health.rs +++ b/src/game/actor/health.rs @@ -50,10 +50,7 @@ impl Configure for Health { fn configure(app: &mut App) { app.register_type::(); app.observe(lose_health_on_damage); - app.add_systems( - Update, - trigger_death_from_health.in_set(UpdateSet::TriggerDeath), - ); + app.add_systems(Update, check_health.in_set(UpdateSet::TriggerDeath)); } } @@ -75,14 +72,15 @@ fn lose_health_on_damage(trigger: Trigger, mut health_query: Query<&mu health.current -= trigger.event().0; } -fn trigger_death_from_health( +fn check_health( mut commands: Commands, - health_query: Query<(Entity, &Health), (Changed, Without)>, + mut health_query: Query<(Entity, &mut Health), (Changed, Without)>, ) { - for (entity, health) in &health_query { + for (entity, mut health) in &mut health_query { if health.current <= 0.0 { commands.entity(entity).trigger(OnDeath); } + health.current = health.current.clamp(0.0, health.max); } } diff --git a/src/game/card.rs b/src/game/card.rs index 5bf2fbe..e16f026 100644 --- a/src/game/card.rs +++ b/src/game/card.rs @@ -8,9 +8,9 @@ use serde::Deserialize; use serde::Serialize; use crate::game::card::action::CardAction; -use crate::game::card::action::CardActionConfig; use crate::game::card::action::CardActionKey; use crate::game::card::action::CardActionMap; +use crate::game::card::action::CardActionModifier; use crate::ui::prelude::*; use crate::util::prelude::*; @@ -162,6 +162,7 @@ impl EntityCommand for CardIcon { } } +// TODO: `min_level` field before the card can be offered in the level up menu. #[derive(Reflect, Serialize, Deserialize, Clone)] pub struct Card { pub name: String, @@ -177,11 +178,11 @@ pub struct Card { pub play_sfx: Option>, #[serde(default = "one")] pub play_sfx_volume: f64, - #[serde(rename = "action", default)] + #[serde(rename = "action")] action_key: CardActionKey, #[serde(skip)] pub action: CardAction, - pub action_config: CardActionConfig, // TODO: Naming + pub action_modifier: CardActionModifier, } fn one() -> f64 { diff --git a/src/game/card/action.rs b/src/game/card/action.rs index b7a2dd4..0b3bdb8 100644 --- a/src/game/card/action.rs +++ b/src/game/card/action.rs @@ -5,8 +5,9 @@ use serde::Deserialize; use serde::Serialize; use crate::game::actor::attack::Attack; +use crate::game::actor::health::Health; use crate::game::card::attack::AimTowardsFacing; -use crate::game::card::attack::DoubleBeat; +use crate::game::card::attack::AttackOnBeat; use crate::game::card::movement::MoveTowardsFacing; use crate::game::cleanup::RemoveOnBeat; use crate::util::prelude::*; @@ -30,37 +31,45 @@ impl FromWorld for CardActionMap { fn from_world(world: &mut World) -> Self { Self( [ - ( - CardActionKey::Rest, - world.register_system(|_: In<(Entity, CardActionConfig)>, _: &mut World| {}), - ), ( CardActionKey::Step, world.register_system( - |In((entity, config)): In<(Entity, CardActionConfig)>, + |In((entity, modifier)): In<(Entity, CardActionModifier)>, world: &mut World| { r!(world.get_entity_mut(entity)).insert(RemoveOnBeat::bundle( MoveTowardsFacing, - config.remove_on_beat, + modifier.remove_on_beat, )); }, ), ), ( - CardActionKey::DoubleBeat, + CardActionKey::Attack, world.register_system( - |In((entity, config)): In<(Entity, CardActionConfig)>, + |In((entity, modifier)): In<(Entity, CardActionModifier)>, world: &mut World| { r!(world.get_entity_mut(entity)).insert(( RemoveOnBeat::bundle( - DoubleBeat(config.attack.clone()), - config.remove_on_beat, + AttackOnBeat(modifier.attack.clone()), + modifier.remove_on_beat, ), - RemoveOnBeat::bundle(AimTowardsFacing, config.remove_on_beat), + RemoveOnBeat::bundle(AimTowardsFacing, modifier.remove_on_beat), )); }, ), ), + ( + CardActionKey::Heal, + world.register_system( + |In((entity, modifier)): In<(Entity, CardActionModifier)>, + world: &mut World| { + let mut entity = r!(world.get_entity_mut(entity)); + let mut health = r!(entity.get_mut::()); + health.current += modifier.heal_flat; + health.current += modifier.heal_percent / 100.0 * health.max; + }, + ), + ), ] .into_iter() .map(|(key, sys)| (key, CardAction(sys))) @@ -69,18 +78,17 @@ impl FromWorld for CardActionMap { } } -#[derive(Reflect, Serialize, Deserialize, Eq, PartialEq, Hash, Copy, Clone, Default)] +#[derive(Reflect, Serialize, Deserialize, Eq, PartialEq, Hash, Copy, Clone)] pub enum CardActionKey { - #[default] - Rest, Step, - DoubleBeat, + Heal, + Attack, } -/// A newtyped `SystemId` with a `Default` impl. +/// A newtyped `SystemId` with a `Default` impl. #[derive(Reflect, Copy, Clone)] #[reflect(Default)] -pub struct CardAction(#[reflect(ignore)] pub SystemId<(Entity, CardActionConfig)>); +pub struct CardAction(#[reflect(ignore)] pub SystemId<(Entity, CardActionModifier)>); impl Default for CardAction { fn default() -> Self { @@ -90,13 +98,12 @@ impl Default for CardAction { #[derive(Default, Reflect, Serialize, Deserialize, Clone)] #[serde(default)] -pub struct CardActionConfig { +pub struct CardActionModifier { /// Remove component after this many eighth-beats. - #[serde(default)] remove_on_beat: usize, /// Remove component when this timer finishes. - #[serde(default)] remove_on_timer: Timer, - #[serde(default)] attack: Attack, + heal_percent: f32, + heal_flat: f32, } diff --git a/src/game/card/attack.rs b/src/game/card/attack.rs index c8f080a..ea1b4c0 100644 --- a/src/game/card/attack.rs +++ b/src/game/card/attack.rs @@ -9,7 +9,7 @@ use crate::game::cleanup::RemoveOnBeat; use crate::util::prelude::*; pub(super) fn plugin(app: &mut App) { - app.configure::<(AimTowardsFacing, DoubleBeat)>(); + app.configure::<(AimTowardsFacing, AttackOnBeat)>(); } #[derive(Component, Reflect)] @@ -37,26 +37,26 @@ fn apply_aim_towards_facing( #[derive(Component, Reflect)] #[reflect(Component)] -pub struct DoubleBeat(pub Attack); +pub struct AttackOnBeat(pub Attack); -impl Configure for DoubleBeat { +impl Configure for AttackOnBeat { fn configure(app: &mut App) { app.configure::>(); app.register_type::(); app.add_systems( Update, - double_beat + attack_on_beat .in_set(UpdateSet::RecordInput) .run_if(on_beat(4)), ); } } -fn double_beat(mut attack_query: Query<(&mut Attack, &mut AttackController, &DoubleBeat)>) { - for (mut attack, mut controller, double_beat) in &mut attack_query { - attack.power = double_beat.0.power; - attack.force = double_beat.0.force; - attack.projectile_key = double_beat.0.projectile_key.clone(); +fn attack_on_beat(mut attack_query: Query<(&mut Attack, &mut AttackController, &AttackOnBeat)>) { + for (mut attack, mut controller, attack_on_beat) in &mut attack_query { + attack.power = attack_on_beat.0.power; + attack.force = attack_on_beat.0.force; + attack.projectile_key = attack_on_beat.0.projectile_key.clone(); controller.fire = true; } diff --git a/src/game/card/deck.rs b/src/game/card/deck.rs index 1f22266..1b8f0a6 100644 --- a/src/game/card/deck.rs +++ b/src/game/card/deck.rs @@ -100,8 +100,8 @@ fn play_card_from_deck( } let action = card.action; - let action_config = card.action_config.clone(); - commands.run_system_with_input(action.0, (entity, action_config)); + let action_modifier = card.action_modifier.clone(); + commands.run_system_with_input(action.0, (entity, action_modifier)); } }