Implement post-match screen and match finishing. (#76)
Reviewed-on: #76 Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #76.
This commit is contained in:
@@ -79,6 +79,7 @@ pub enum AppState {
|
|||||||
LoadingMatch,
|
LoadingMatch,
|
||||||
PreparingMatch,
|
PreparingMatch,
|
||||||
InMatch,
|
InMatch,
|
||||||
|
MatchResults,
|
||||||
MatchDisconnected,
|
MatchDisconnected,
|
||||||
MatchLoadFailed,
|
MatchLoadFailed,
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-5
@@ -49,6 +49,45 @@ impl MatchState {
|
|||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_match_finished(&self, config: &MatchConfig) -> bool {
|
||||||
|
let time_limit = match config.rules {
|
||||||
|
MatchRules::Stock {
|
||||||
|
starting_stocks: _,
|
||||||
|
time_limit_frames,
|
||||||
|
} => time_limit_frames,
|
||||||
|
MatchRules::Timed { time_limit_frames } => Some(time_limit_frames),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(time_limit) = time_limit {
|
||||||
|
if self.elapsed_frames >= time_limit {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let MatchRules::Stock {
|
||||||
|
starting_stocks: _,
|
||||||
|
time_limit_frames: _,
|
||||||
|
} = config.rules
|
||||||
|
{
|
||||||
|
let alive_fighter_count = self
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.filter(|fighter| {
|
||||||
|
fighter
|
||||||
|
.stocks_remaining
|
||||||
|
.map(|stocks| stocks)
|
||||||
|
.unwrap_or_default()
|
||||||
|
> 0
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
if alive_fighter_count <= 1 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MatchRuntimePlugin;
|
pub struct MatchRuntimePlugin;
|
||||||
@@ -63,7 +102,8 @@ impl Plugin for MatchRuntimePlugin {
|
|||||||
.add_systems(OnExit(AppState::InMatch), reset_pause)
|
.add_systems(OnExit(AppState::InMatch), reset_pause)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
toggle_pause
|
(toggle_pause, check_match_finished)
|
||||||
|
.chain()
|
||||||
.after(InputSystems)
|
.after(InputSystems)
|
||||||
.before(RunGgrsSystems)
|
.before(RunGgrsSystems)
|
||||||
.run_if(in_state(AppState::InMatch)),
|
.run_if(in_state(AppState::InMatch)),
|
||||||
@@ -98,14 +138,21 @@ fn toggle_pause(
|
|||||||
virtual_time.toggle();
|
virtual_time.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_pause(
|
fn reset_pause(mut pause: ResMut<PauseState>, mut virtual_time: ResMut<Time<Virtual>>) {
|
||||||
mut pause: ResMut<PauseState>,
|
|
||||||
mut virtual_time: ResMut<Time<Virtual>>,
|
|
||||||
) {
|
|
||||||
pause.paused = false;
|
pause.paused = false;
|
||||||
virtual_time.unpause();
|
virtual_time.unpause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_match_finished(
|
||||||
|
match_state: Res<MatchState>,
|
||||||
|
match_config: Res<MatchConfig>,
|
||||||
|
mut next_state: ResMut<NextState<AppState>>,
|
||||||
|
) {
|
||||||
|
if match_state.is_match_finished(&match_config) {
|
||||||
|
next_state.set(AppState::MatchResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use bevy::{prelude::*, text::FontSourceTemplate};
|
||||||
|
|
||||||
|
use crate::{AppState, menus::{MenuMarker, spawn_menu}};
|
||||||
|
|
||||||
|
pub fn setup_post_match_menu(
|
||||||
|
existing_menu: Option<Single<Entity, With<MenuMarker>>>,
|
||||||
|
commands: Commands,
|
||||||
|
) {
|
||||||
|
let menu = bsn! {
|
||||||
|
Node {
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
width: percent(100),
|
||||||
|
height: percent(100),
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
align_items: AlignItems::Center
|
||||||
|
}
|
||||||
|
Children [
|
||||||
|
(
|
||||||
|
Text("[Insert beautiful post-match screen here]")
|
||||||
|
TextFont {
|
||||||
|
font: FontSourceTemplate::Handle("fonts/roboto.ttf"),
|
||||||
|
font_size: px(super::style::H1_SIZE),
|
||||||
|
}
|
||||||
|
TextColor(Color::srgb(0.9, 0.9, 0.9))
|
||||||
|
),
|
||||||
|
Text("Press any button/key to continue...")
|
||||||
|
TextFont {
|
||||||
|
font: FontSourceTemplate::Handle("fonts/roboto.ttf"),
|
||||||
|
font_size: px(super::style::H3_SIZE),
|
||||||
|
}
|
||||||
|
TextColor(Color::srgb(0.9, 0.9, 0.9))
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
spawn_menu(existing_menu, commands, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_return_to_character_select(gamepads: Query<&Gamepad>, keyboard: Res<ButtonInput<KeyCode>>, mut next_state: ResMut<NextState<AppState>>) {
|
||||||
|
let any_pressed = gamepads.iter().any(|gp| gp.get_just_pressed().count() > 0);
|
||||||
|
let any_pressed = any_pressed || keyboard.get_just_pressed().count() > 0;
|
||||||
|
if any_pressed {
|
||||||
|
next_state.set(AppState::MainMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
+121
-114
@@ -18,6 +18,7 @@ pub mod errors;
|
|||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod main_menu;
|
pub mod main_menu;
|
||||||
pub mod match_config;
|
pub mod match_config;
|
||||||
|
pub mod match_results;
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
pub mod scaling;
|
pub mod scaling;
|
||||||
pub mod stage_select;
|
pub mod stage_select;
|
||||||
@@ -77,11 +78,8 @@ pub fn despawn_menu(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
if let Some(entity) = existing_menu {
|
if let Some(entity) = existing_menu {
|
||||||
commands
|
commands.entity(entity.into_inner()).despawn();
|
||||||
.entity(entity.into_inner())
|
|
||||||
.despawn();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_menu(
|
pub fn spawn_menu(
|
||||||
@@ -90,9 +88,7 @@ pub fn spawn_menu(
|
|||||||
menu: impl Scene,
|
menu: impl Scene,
|
||||||
) {
|
) {
|
||||||
if let Some(entity) = existing_menu {
|
if let Some(entity) = existing_menu {
|
||||||
commands
|
commands.entity(entity.into_inner()).despawn();
|
||||||
.entity(entity.into_inner())
|
|
||||||
.despawn();
|
|
||||||
}
|
}
|
||||||
commands.queue_spawn_scene(wrap_menu(menu));
|
commands.queue_spawn_scene(wrap_menu(menu));
|
||||||
}
|
}
|
||||||
@@ -117,115 +113,126 @@ where
|
|||||||
|
|
||||||
impl Plugin for MenuPlugin {
|
impl Plugin for MenuPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(OnEnter(AppState::MainMenu), build_menu::<MainMenu>)
|
||||||
OnEnter(AppState::MainMenu),
|
.add_message::<character_select::PlayerSlotAssigned>()
|
||||||
build_menu::<MainMenu>,
|
.init_resource::<navigation::UINavigationActionState>()
|
||||||
)
|
.init_resource::<navigation::UINavigationRepeat>()
|
||||||
.add_message::<character_select::PlayerSlotAssigned>()
|
.init_resource::<input::MenuInputState>()
|
||||||
.init_resource::<navigation::UINavigationActionState>()
|
.insert_resource(InputFocusVisible(true))
|
||||||
.init_resource::<navigation::UINavigationRepeat>()
|
.insert_resource(GlobalUiDebugOptions { ..default() })
|
||||||
.init_resource::<input::MenuInputState>()
|
// Configure auto-navigation behavior
|
||||||
.insert_resource(InputFocusVisible(true))
|
.insert_resource(AutoNavigationConfig {
|
||||||
.insert_resource(GlobalUiDebugOptions {
|
// Require at least 10% overlap in perpendicular axis for cardinal directions
|
||||||
enabled: true,
|
min_alignment_factor: 0.1,
|
||||||
..default()
|
// Don't connect nodes more than 500 pixels apart between their closest edges
|
||||||
})
|
max_search_distance: Some(500.0),
|
||||||
// Configure auto-navigation behavior
|
// Prefer nodes that are well-aligned
|
||||||
.insert_resource(AutoNavigationConfig {
|
prefer_aligned: true,
|
||||||
// Require at least 10% overlap in perpendicular axis for cardinal directions
|
})
|
||||||
min_alignment_factor: 0.1,
|
.add_plugins(DirectionalNavigationPlugin)
|
||||||
// Don't connect nodes more than 500 pixels apart between their closest edges
|
.add_systems(
|
||||||
max_search_distance: Some(500.0),
|
PreUpdate,
|
||||||
// Prefer nodes that are well-aligned
|
(
|
||||||
prefer_aligned: true,
|
navigation::give_default_focus,
|
||||||
})
|
scaling::ui_scaling_system,
|
||||||
.add_plugins(DirectionalNavigationPlugin)
|
button::button_setup,
|
||||||
.add_systems(
|
button::button_style_system.after(bevy::picking::PickingSystems::Hover),
|
||||||
PreUpdate,
|
button::button_focus_style_system,
|
||||||
(
|
)
|
||||||
navigation::give_default_focus,
|
.run_if(resource_exists::<CommonAssets>),
|
||||||
scaling::ui_scaling_system,
|
|
||||||
button::button_setup,
|
|
||||||
button::button_style_system.after(bevy::picking::PickingSystems::Hover),
|
|
||||||
button::button_focus_style_system,
|
|
||||||
)
|
)
|
||||||
.run_if(resource_exists::<CommonAssets>),
|
.add_systems(Startup, scaling::ui_update_scale_on_startup)
|
||||||
)
|
// CURSOR
|
||||||
.add_systems(Startup, scaling::ui_update_scale_on_startup)
|
.add_systems(
|
||||||
// CURSOR
|
PreUpdate,
|
||||||
.add_systems(
|
(cursor::cursor_input).before(bevy::picking::PickingSystems::ProcessInput),
|
||||||
PreUpdate,
|
|
||||||
(cursor::cursor_input).before(bevy::picking::PickingSystems::ProcessInput),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(cursor::copy_mouse_input, cursor::update_cursor_transform),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
PreUpdate,
|
|
||||||
input::sample_menu_inputs
|
|
||||||
.before(cursor::cursor_input)
|
|
||||||
.run_if(resource_exists::<crate::input::MenuInputMap>),
|
|
||||||
)
|
|
||||||
//---------
|
|
||||||
// CHARACTER SELECT START
|
|
||||||
//---------
|
|
||||||
.add_systems(
|
|
||||||
OnEnter(AppState::CharacterSelect),
|
|
||||||
character_select::setup_character_select,
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
PostUpdate,
|
|
||||||
character_select::copy_cursor_transform_to_token
|
|
||||||
.run_if(in_state(AppState::CharacterSelect))
|
|
||||||
.run_if(any_with_component::<CharacterSelectScreen>),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
PreUpdate,
|
|
||||||
(
|
|
||||||
character_select::css_auto_assign_slots,
|
|
||||||
character_select::handle_fighter_slot_addition,
|
|
||||||
)
|
)
|
||||||
.chain()
|
.add_systems(
|
||||||
.run_if(in_state(AppState::CharacterSelect))
|
Update,
|
||||||
.run_if(any_with_component::<CharacterSelectScreen>),
|
(cursor::copy_mouse_input, cursor::update_cursor_transform),
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
OnExit(AppState::CharacterSelect),
|
|
||||||
(cursor::despawn_cursors, character_select::despawn_tokens),
|
|
||||||
)
|
|
||||||
.add_observer(character_select::select_fighter.run_if(in_state(AppState::CharacterSelect)))
|
|
||||||
.add_observer(
|
|
||||||
character_select::update_start_banner_visibility
|
|
||||||
.run_if(in_state(AppState::CharacterSelect)),
|
|
||||||
)
|
|
||||||
//---------
|
|
||||||
// CHARACTER SELECT END
|
|
||||||
//---------
|
|
||||||
//---------
|
|
||||||
// STAGE SELECT START
|
|
||||||
//---------
|
|
||||||
.add_systems(
|
|
||||||
OnEnter(AppState::StageSelect),
|
|
||||||
stage_select::setup_stage_select,
|
|
||||||
)
|
|
||||||
.add_observer(stage_select::stage_selected.run_if(in_state(AppState::StageSelect)))
|
|
||||||
.add_systems(
|
|
||||||
OnExit(AppState::StageSelect),
|
|
||||||
(cursor::despawn_cursors, despawn_menu),
|
|
||||||
)
|
|
||||||
//---------
|
|
||||||
// STAGE SELECT END
|
|
||||||
//---------
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(
|
|
||||||
navigation::process_inputs,
|
|
||||||
navigation::navigate,
|
|
||||||
navigation::interact_with_focused_button,
|
|
||||||
)
|
)
|
||||||
.chain(),
|
.add_systems(
|
||||||
)
|
PreUpdate,
|
||||||
.init_resource::<InputFocus>();
|
input::sample_menu_inputs
|
||||||
|
.before(cursor::cursor_input)
|
||||||
|
.run_if(resource_exists::<crate::input::MenuInputMap>),
|
||||||
|
)
|
||||||
|
//---------
|
||||||
|
// CHARACTER SELECT START
|
||||||
|
//---------
|
||||||
|
.add_systems(
|
||||||
|
OnEnter(AppState::CharacterSelect),
|
||||||
|
character_select::setup_character_select,
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
character_select::copy_cursor_transform_to_token
|
||||||
|
.run_if(in_state(AppState::CharacterSelect))
|
||||||
|
.run_if(any_with_component::<CharacterSelectScreen>),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
PreUpdate,
|
||||||
|
(
|
||||||
|
character_select::css_auto_assign_slots,
|
||||||
|
character_select::handle_fighter_slot_addition,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.run_if(in_state(AppState::CharacterSelect))
|
||||||
|
.run_if(any_with_component::<CharacterSelectScreen>),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
OnExit(AppState::CharacterSelect),
|
||||||
|
(cursor::despawn_cursors, character_select::despawn_tokens),
|
||||||
|
)
|
||||||
|
.add_observer(
|
||||||
|
character_select::select_fighter.run_if(in_state(AppState::CharacterSelect)),
|
||||||
|
)
|
||||||
|
.add_observer(
|
||||||
|
character_select::update_start_banner_visibility
|
||||||
|
.run_if(in_state(AppState::CharacterSelect)),
|
||||||
|
)
|
||||||
|
//---------
|
||||||
|
// CHARACTER SELECT END
|
||||||
|
//---------
|
||||||
|
//---------
|
||||||
|
// STAGE SELECT START
|
||||||
|
//---------
|
||||||
|
.add_systems(
|
||||||
|
OnEnter(AppState::StageSelect),
|
||||||
|
stage_select::setup_stage_select,
|
||||||
|
)
|
||||||
|
.add_observer(stage_select::stage_selected.run_if(in_state(AppState::StageSelect)))
|
||||||
|
.add_systems(
|
||||||
|
OnExit(AppState::StageSelect),
|
||||||
|
(cursor::despawn_cursors, despawn_menu),
|
||||||
|
)
|
||||||
|
//---------
|
||||||
|
// STAGE SELECT END
|
||||||
|
//---------
|
||||||
|
//---------
|
||||||
|
// MATCH RESULT START
|
||||||
|
//---------
|
||||||
|
.add_systems(
|
||||||
|
OnEnter(AppState::MatchResults),
|
||||||
|
match_results::setup_post_match_menu,
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
match_results::check_return_to_character_select
|
||||||
|
.run_if(in_state(AppState::MatchResults)),
|
||||||
|
)
|
||||||
|
//---------
|
||||||
|
// MATCH RESULT END
|
||||||
|
//---------
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
navigation::process_inputs,
|
||||||
|
navigation::navigate,
|
||||||
|
navigation::interact_with_focused_button,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
)
|
||||||
|
.init_resource::<InputFocus>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ pub fn process_inputs(
|
|||||||
.insert(DirectionalNavigationAction::Right);
|
.insert(DirectionalNavigationAction::Right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if input.accept.is_pressed() {
|
if input.accept.is_just_pressed() {
|
||||||
action_state
|
action_state
|
||||||
.pressed_actions
|
.pressed_actions
|
||||||
.insert(DirectionalNavigationAction::Select);
|
.insert(DirectionalNavigationAction::Select);
|
||||||
|
|||||||
Reference in New Issue
Block a user