diff --git a/Cargo.lock b/Cargo.lock index d204ff3..00c62e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2096,14 +2096,6 @@ dependencies = [ "thread_local", ] -[[package]] -name = "bevy_wind_waker_shader" -version = "0.6.0" -source = "git+https://github.com/lavaeater/bevy_wind_waker_shader.git#1b017c3ef6a4fbdaff115df9ec5ae4954b2fb6ba" -dependencies = [ - "bevy", -] - [[package]] name = "bevy_window" version = "0.19.0" @@ -2232,32 +2224,6 @@ dependencies = [ "varinteger", ] -[[package]] -name = "bitfields" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5a55e8cbc3b75d1f002a3b62261ab67954755f7e7866f079b8aa63a409b549" -dependencies = [ - "bitfields-impl", -] - -[[package]] -name = "bitfields-impl" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "797cb6b6e8d5e8c2ecbb5ef52b7c273596d58ce1ab79ef2a6c38fa8dca0817c5" -dependencies = [ - "derive_more", - "getset", - "proc-macro2", - "quote", - "rustversion", - "strum", - "strum_macros", - "syn 2.0.119", - "thiserror 2.0.19", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -3523,17 +3489,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getset" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "ggrs" version = "0.13.0" @@ -6170,9 +6125,7 @@ dependencies = [ "bevy_asset_loader", "bevy_egui", "bevy_ggrs", - "bevy_wind_waker_shader", "bincode", - "bitfields", "clap", "enum_cycling", "enum_cycling_derive", diff --git a/Cargo.toml b/Cargo.toml index 6083800..5dc950d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,7 @@ serde_json = "1.0.145" bincode = "1.3.3" gltf = "1.4.1" ron = "0.12.0" -bitfields = "3.0.0" clap = { version = "4.6.6", features = ["derive"] } -bevy_wind_waker_shader = { git = "https://github.com/lavaeater/bevy_wind_waker_shader.git" } fixed = { version = "1.31.0", features = ["serde", "serde-str"] } strum = "0.28.0" strum_macros = "0.28.0" diff --git a/src/fighter/attack/mod.rs b/src/fighter/attack/mod.rs index c9f6ef9..6696767 100644 --- a/src/fighter/attack/mod.rs +++ b/src/fighter/attack/mod.rs @@ -32,9 +32,15 @@ use crate::{ #[derive(Deref, Component, Default, Hash, Debug, Copy, Clone)] pub struct FighterKnockbackVelocity(pub FGVec2); -#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Component, Hash)] +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Component, Hash, PartialEq)] pub struct FighterDamage(FGi32); +impl std::fmt::Display for FighterDamage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:.2}", self.0) + } +} + #[derive(Debug, Hash, Clone)] pub enum HitReactionKind { Flinch, @@ -48,6 +54,7 @@ pub struct FighterHitReaction { pub hitstop_frames: HitStop, pub hitstun_frames: u32, pub kind: HitReactionKind, + pub damage: FighterDamage, } #[derive(Component, DerefMut, Deref, Debug, Default, Hash, Clone)] diff --git a/src/fighter/attack/reaction.rs b/src/fighter/attack/reaction.rs index f9105f2..cc792e0 100644 --- a/src/fighter/attack/reaction.rs +++ b/src/fighter/attack/reaction.rs @@ -11,9 +11,9 @@ use crate::{ pub fn resolve_fighter_hits( mut pending_hits: ResMut, mut attacker: Query<( - &FighterStaleMoveQueue, + &mut FighterStaleMoveQueue, &FighterFacingDirection, - &FighterHitboxes, + &mut FighterHitboxes, &Player, &mut FighterHitstop, &mut FighterHitstopShake, @@ -42,7 +42,7 @@ pub fn resolve_fighter_hits( let damage_data = hits[hit_offset..next_defender] .iter() .filter_map(|hit| { - let Ok((stale_move_queue, facing_dir, _hitboxes, player, _, _)) = + let Ok((stale_move_queue, facing_dir, _, player, _, _)) = attacker.get(hit.attacker) else { return None; @@ -52,7 +52,6 @@ pub fn resolve_fighter_hits( let damage_received = stale_move_queue .get_attack_damage_multiplier(hit.attack_kind) * hitbox.damage.0; - Some(( hit.attacker, damage_received, @@ -90,6 +89,7 @@ pub fn resolve_fighter_hits( angle }; + // Attacker hitstop and data let hitstop = HitStop::calculate(FighterDamage(attack_damage), false); reactions.push(( attacker_entity, @@ -100,6 +100,7 @@ pub fn resolve_fighter_hits( hitstun_frames: knockback.get_hitstun_frames(&game_settings), hitstop_frames: hitstop, kind: HitReactionKind::Flinch, + damage: FighterDamage(attack_damage) }, )); } @@ -107,7 +108,7 @@ pub fn resolve_fighter_hits( receiver_damage.0 += total_damage_received_this_frame; for (attacker_entity, _, reaction) in &reactions { - let Ok((.., mut hitstop, mut hitstop_shake)) = attacker.get_mut(*attacker_entity) else { + let Ok((.., _, mut hitstop, mut hitstop_shake)) = attacker.get_mut(*attacker_entity) else { continue; }; @@ -134,6 +135,19 @@ pub fn resolve_fighter_hits( hit_offset = next_defender; } + for hit in hits { + let Ok((mut stale_move_queue, _facing_direction, mut hitboxes, ..)) = attacker.get_mut(hit.attacker) else { + continue; + }; + + if let Some(ref mut hitbox) = hitboxes.0 { + if !hitbox.stalled { + hitbox.stalled = true; + stale_move_queue.push(hitbox.kind); + } + } + } + Ok(()) } diff --git a/src/fighter/mod.rs b/src/fighter/mod.rs index 24d1317..9eb70f5 100644 --- a/src/fighter/mod.rs +++ b/src/fighter/mod.rs @@ -259,6 +259,7 @@ pub struct FighterActiveAttack { pub active_hitboxes: Vec, pub solved_hitboxes: Vec, pub hit_fighters: Vec, + pub stalled: bool } #[derive(Component, Clone, Debug, Default, Hash)] diff --git a/src/fighter/state/ground_attack.rs b/src/fighter/state/ground_attack.rs index 3005266..59bfee2 100644 --- a/src/fighter/state/ground_attack.rs +++ b/src/fighter/state/ground_attack.rs @@ -47,13 +47,13 @@ impl FighterStateImpl for GroundAttackState { fn on_enter(&mut self, state_context: &mut EnterContext) { state_context.play_animation(self.attack_kind.get_animation(), false); let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone(); - state_context.stale_move_queue.push(self.attack_kind); state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack { kind: self.attack_kind, script, active_hitboxes: Vec::new(), solved_hitboxes: Vec::new(), hit_fighters: Vec::new(), + stalled: false }); } diff --git a/src/lib.rs b/src/lib.rs index 077b068..93d25cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,13 +28,11 @@ use bevy::{ tasks::block_on, }; use bevy_asset_loader::prelude::*; -use bevy_wind_waker_shader::prelude::*; use clap::Parser; use ron_asset_manager::RonAssetPlugin; use crate::{ - camera::MatchCameraPlugin, fighter::manifest::FighterManifest, game_settings::GameSettings, - schedule::GameplaySchedulePlugin, stage::manifest::StageManifest, + camera::MatchCameraPlugin, fighter::manifest::FighterManifest, game_settings::GameSettings, schedule::GameplaySchedulePlugin, stage::manifest::StageManifest, ui::InGameUIPlugin, }; mod args; @@ -53,6 +51,7 @@ pub mod schedule; pub mod scripting; pub mod stage; pub mod texture_reference; +pub mod ui; #[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] pub enum AppState { @@ -89,7 +88,6 @@ impl Plugin for GamePlugin { app.init_state::() .add_plugins(( SettingsPlugin::new("moe.eirteam.shinespark.debug_settings"), - WindWakerShaderPlugin::default(), // Brings up GGRS (and with it `GgrsSchedule`), so it goes first. netcode::FighterNetcodePlugin::default(), GameplaySchedulePlugin, @@ -104,6 +102,7 @@ impl Plugin for GamePlugin { RonAssetPlugin::::create("fighter.ron"), RonAssetPlugin::::create("stage.ron"), RonAssetPlugin::::create("ron"), + InGameUIPlugin, )) .add_plugins(menus::MenuPlugin) .add_loading_state( diff --git a/src/ui/battle_ui.rs b/src/ui/battle_ui.rs new file mode 100644 index 0000000..3013b58 --- /dev/null +++ b/src/ui/battle_ui.rs @@ -0,0 +1,57 @@ +use bevy::prelude::*; + +use crate::{ + fighter::manifest::{FighterManifest, FighterManifestRegistry}, match_loading::{PendingMatch}, ui::{errors::UIErrors, fighter_info::FighterInfoUI}, +}; + +#[derive(Component, FromTemplate)] +pub struct BattleUI { +} + +pub fn spawn_battle_ui(mut commands: Commands, request: Res, fighter_registry: Res, manifests: Res>) -> Result { + + let mut fighter_uis = vec![]; + + for player in &request.players { + let manifest = fighter_registry.get(player.fighter).ok_or_else(|| UIErrors::FighterManifestHandleNotFound(player.fighter))?; + let manifest = manifests.get(manifest).ok_or_else(|| UIErrors::FighterManifestNotFound(player.fighter))?; + + fighter_uis.push(FighterInfoUI::build(manifest, player)); + } + + let scene = bsn! { + BattleUI { + } + Node { + width: percent(100), + height: percent(100), + flex_direction: FlexDirection::Column, + align_items: AlignItems::Center, + justify_content: JustifyContent::End, + } + Children [ + ( + #FighterInfos + Node { + width: percent(100), + flex_direction: FlexDirection::Row, + align_items: AlignItems::End, + justify_content: JustifyContent::SpaceEvenly, + } + Children [ + {fighter_uis} + ] + ) + ] + }; + + commands.spawn_scene(scene); + + Ok(()) +} + +pub fn cleanup_battle_ui(mut commands: Commands, battle_ui: Query>) { + for entity in battle_ui { + commands.entity(entity).despawn(); + } +} \ No newline at end of file diff --git a/src/ui/errors.rs b/src/ui/errors.rs new file mode 100644 index 0000000..09ebbc7 --- /dev/null +++ b/src/ui/errors.rs @@ -0,0 +1,14 @@ +use bevy::{scene::SpawnSceneError}; + +use crate::{fighter::FighterId}; + +#[derive(thiserror::Error, Debug)] +pub enum UIErrors { + #[error("Scene spawn failed: {0:?}")] + SceneSpawnFailed(#[from] SpawnSceneError), + #[error("Fighter manifest handle not found for fighter id {0:?}")] + FighterManifestHandleNotFound(FighterId), + + #[error("Fighter manifest not found for fighter id {0:?}")] + FighterManifestNotFound(FighterId), +} diff --git a/src/ui/fighter_info.rs b/src/ui/fighter_info.rs new file mode 100644 index 0000000..f1b8681 --- /dev/null +++ b/src/ui/fighter_info.rs @@ -0,0 +1,57 @@ +use bevy::prelude::*; + +use crate::{fighter::{attack::FighterDamage, manifest::{FighterManifest}}, match_loading::MatchPlayer, player::Player}; + +#[derive(Component, FromTemplate)] +pub struct FighterInfoUI { + percentage_text: Entity, + _name_text: Entity, + player_handle: usize, + visible_damage: FighterDamage, +} + +impl FighterInfoUI { + pub fn build(_manifest: &FighterManifest, match_player: &MatchPlayer) -> impl Scene { + let fighter_name = "El grande padre"; + bsn! { + FighterInfoUI { + percentage_text: #PercentageText, + _name_text: #NameText, + player_handle: {match_player.handle} + } + Node { + flex_direction: FlexDirection::Column, + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + } + Children [ + ( + #PercentageText + Text("0.00%") + TextFont { + font_size: px(crate::menus::style::H2_SIZE), + } + ), + ( + #NameText + Text({fighter_name}) + ) + ] + } + } +} + +pub fn update_fighter_info_damage(fighters: Query<(&Player, &FighterDamage)>, ui_elements: Query<&mut FighterInfoUI>, mut commands: Commands) { + for mut ui_element in ui_elements { + let Some((_, damage)) = fighters.iter().find(|(player, _)| { + player.handle == ui_element.player_handle + }) else { + continue; + }; + + if *damage != ui_element.visible_damage { + commands.entity(ui_element.percentage_text).insert(Text(format!("{}%", damage))); + ui_element.visible_damage = damage.clone(); + } + } +} \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..f393c32 --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,18 @@ +use bevy::prelude::*; + +use crate::AppState; + +pub mod battle_ui; +pub mod errors; +pub mod fighter_info; + +#[derive(Default)] +pub struct InGameUIPlugin; + +impl Plugin for InGameUIPlugin { + fn build(&self, app: &mut App) { + app.add_systems(OnEnter(AppState::InMatch), battle_ui::spawn_battle_ui) + .add_systems(OnExit(AppState::InMatch), battle_ui::cleanup_battle_ui) + .add_systems(PostUpdate, fighter_info::update_fighter_info_damage); + } +}