Make CSS and SSS reach a match
This commit is contained in:
+49
-14
@@ -1,11 +1,20 @@
|
||||
use bevy::{input_focus::InputFocus, picking::{hover::PickingInteraction, Pickable}, prelude::*, text::FontSourceTemplate, ui::auto_directional_navigation::AutoDirectionalNavigation};
|
||||
use bevy::{
|
||||
input_focus::InputFocus,
|
||||
picking::{Pickable, hover::PickingInteraction},
|
||||
prelude::*,
|
||||
text::FontSourceTemplate,
|
||||
ui::auto_directional_navigation::AutoDirectionalNavigation,
|
||||
};
|
||||
|
||||
use crate::{game_settings::CommonAssets, menus::style::{FGUiButtonType, FGUiStyle}};
|
||||
use crate::{
|
||||
game_settings::CommonAssets,
|
||||
menus::style::{FGUiButtonType, FGUiStyle},
|
||||
};
|
||||
|
||||
#[derive(Component, Default, Clone)]
|
||||
#[require(Button)]
|
||||
pub struct FGUiButton {
|
||||
pub button_type: FGUiButtonType
|
||||
pub button_type: FGUiButtonType,
|
||||
}
|
||||
|
||||
pub fn menu_button(label: &str) -> impl Scene {
|
||||
@@ -37,16 +46,35 @@ pub fn menu_button(label: &str) -> impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_setup(query: Query<(Entity, &FGUiButton, &mut Node), Added<FGUiButton>>, common_assets: Res<CommonAssets>, styles: Res<Assets<FGUiStyle>>, mut commands: Commands) {
|
||||
let styles = styles.get(&common_assets.ui_style).expect("UI styles should be loaded");
|
||||
pub fn button_setup(
|
||||
query: Query<(Entity, &FGUiButton, &mut Node), Added<FGUiButton>>,
|
||||
common_assets: Res<CommonAssets>,
|
||||
styles: Res<Assets<FGUiStyle>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let styles = styles
|
||||
.get(&common_assets.ui_style)
|
||||
.expect("UI styles should be loaded");
|
||||
for (entity, button, mut node) in query {
|
||||
styles.button_styles[&button.button_type].normal.apply(&mut commands, entity, &mut node);
|
||||
styles.button_styles[&button.button_type]
|
||||
.normal
|
||||
.apply(&mut commands, entity, &mut node);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_style_system(query: Query<(Entity, &FGUiButton, &PickingInteraction, &mut Node), Changed<PickingInteraction>>, common_assets: Res<CommonAssets>, styles: Res<Assets<FGUiStyle>>, mut commands: Commands) {
|
||||
let styles = styles.get(&common_assets.ui_style).expect("UI styles should be loaded");
|
||||
|
||||
pub fn button_style_system(
|
||||
query: Query<
|
||||
(Entity, &FGUiButton, &PickingInteraction, &mut Node),
|
||||
Changed<PickingInteraction>,
|
||||
>,
|
||||
common_assets: Res<CommonAssets>,
|
||||
styles: Res<Assets<FGUiStyle>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let styles = styles
|
||||
.get(&common_assets.ui_style)
|
||||
.expect("UI styles should be loaded");
|
||||
|
||||
for (entity, button, interaction, mut node) in query {
|
||||
let button_style_to_use = match interaction {
|
||||
PickingInteraction::Pressed => &styles.button_styles[&button.button_type].press,
|
||||
@@ -57,14 +85,21 @@ pub fn button_style_system(query: Query<(Entity, &FGUiButton, &PickingInteractio
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_focus_style_system(query: Query<(Entity, &FGUiButton)>, focus: Res<InputFocus>, mut commands: Commands, common_assets: Res<CommonAssets>, styles: Res<Assets<FGUiStyle>>) {
|
||||
pub fn button_focus_style_system(
|
||||
query: Query<(Entity, &FGUiButton)>,
|
||||
focus: Res<InputFocus>,
|
||||
mut commands: Commands,
|
||||
common_assets: Res<CommonAssets>,
|
||||
styles: Res<Assets<FGUiStyle>>,
|
||||
) {
|
||||
if let Some(focused_entity) = focus.get() {
|
||||
|
||||
if !query.contains(focused_entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
let styles = styles.get(&common_assets.ui_style).expect("UI styles should be loaded");
|
||||
|
||||
let styles = styles
|
||||
.get(&common_assets.ui_style)
|
||||
.expect("UI styles should be loaded");
|
||||
|
||||
for (entity, button) in query {
|
||||
if focused_entity == entity {
|
||||
@@ -72,7 +107,7 @@ pub fn button_focus_style_system(query: Query<(Entity, &FGUiButton)>, focus: Res
|
||||
commands.entity(focused_entity).insert(Outline {
|
||||
width: px(style.focus_outline_width_px),
|
||||
offset: px(style.focus_outline_offset_px),
|
||||
color: style.focus_outline_color
|
||||
color: style.focus_outline_color,
|
||||
});
|
||||
} else {
|
||||
commands.entity(entity).remove::<Outline>();
|
||||
|
||||
@@ -6,15 +6,7 @@ use bevy_egui::egui::accesskit::TextAlign;
|
||||
|
||||
use crate::{
|
||||
AppState, fighter::manifest::{FighterManifest, FighterManifestRegistry}, game_settings::CommonAssets, input::{LocalInputAssignments, LocalInputSource}, menus::{
|
||||
MenuMarker, build_menu_with_props,
|
||||
button::FGUiButton,
|
||||
character_select::character_box::CharacterBox,
|
||||
cursor::{FGMenuCursor, MenuCursorInputSource},
|
||||
errors::MenuErrors::{self, FighterManifestNotFound},
|
||||
match_config::{MenuMatchConfig, MenuMatchPlayerSlot},
|
||||
scaling, spawn_menu,
|
||||
style::{FGUiButtonType, FGUiStyle},
|
||||
wrap_menu,
|
||||
MenuMarker, build_menu_with_props, button::FGUiButton, character_select::{character_box::CharacterBox, token::CharacterSelectToken}, cursor::{FGMenuCursor, MenuCursorInputSource}, errors::MenuErrors::{self, FighterManifestNotFound}, match_config::{MenuMatchConfig, MenuMatchPlayerSlot}, scaling, spawn_menu, style::{FGUiButtonType, FGUiStyle}, wrap_menu,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -67,31 +59,6 @@ pub fn setup_character_select(
|
||||
}
|
||||
Children [
|
||||
{character_boxes},
|
||||
(
|
||||
// Start banner thingy
|
||||
StartButtonBanner
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
width: percent(100),
|
||||
}
|
||||
FGUiButton {
|
||||
button_type: FGUiButtonType::CharacterSelectStartBanner
|
||||
}
|
||||
Text::new("Peenids")
|
||||
Visibility::Hidden
|
||||
TextLayout {
|
||||
justify: Justify::Center
|
||||
}
|
||||
TextFont {
|
||||
font_size: FontSize::Px(48.0),
|
||||
}
|
||||
TextColor(Color::WHITE)
|
||||
BackgroundColor(Color::BLACK)
|
||||
GlobalZIndex(500)
|
||||
on(|_ev: On<Pointer<Press>>, mut next_state: ResMut<NextState<AppState>>| next_state.set(AppState::StageSelect))
|
||||
)
|
||||
]
|
||||
),
|
||||
(
|
||||
@@ -99,9 +66,34 @@ pub fn setup_character_select(
|
||||
Node {
|
||||
flex_direction: FlexDirection::Row,
|
||||
width: percent(100),
|
||||
height: px(400),
|
||||
min_height: px(400),
|
||||
justify_content: JustifyContent::Center
|
||||
}
|
||||
),
|
||||
(
|
||||
// Start banner thingy
|
||||
StartButtonBanner
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
width: percent(100),
|
||||
}
|
||||
FGUiButton {
|
||||
button_type: FGUiButtonType::CharacterSelectStartBanner
|
||||
}
|
||||
Text::new("Start poetry")
|
||||
Visibility::Hidden
|
||||
TextLayout {
|
||||
justify: Justify::Center
|
||||
}
|
||||
TextFont {
|
||||
font_size: FontSize::Px(48.0),
|
||||
}
|
||||
TextColor(Color::WHITE)
|
||||
BackgroundColor(Color::BLACK)
|
||||
GlobalZIndex(500)
|
||||
on(|_ev: On<Pointer<Press>>, mut next_state: ResMut<NextState<AppState>>| next_state.set(AppState::StageSelect))
|
||||
)
|
||||
]
|
||||
};
|
||||
@@ -113,7 +105,7 @@ pub fn setup_character_select(
|
||||
pub fn create_portrait(
|
||||
player_slot: usize,
|
||||
container: Entity,
|
||||
mut commands: Commands,
|
||||
commands: &mut Commands,
|
||||
style: &FGUiStyle,
|
||||
input_source: &LocalInputSource,
|
||||
) {
|
||||
@@ -123,7 +115,7 @@ pub fn create_portrait(
|
||||
commands.entity(container).add_child(entity);
|
||||
|
||||
let cursor = commands
|
||||
.spawn(FGMenuCursor::create_cursor(
|
||||
.spawn(FGMenuCursor::create_player_cursor(
|
||||
player_slot,
|
||||
style,
|
||||
input_source,
|
||||
@@ -137,36 +129,40 @@ pub fn create_portrait(
|
||||
commands.entity(container).add_child(entity);
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Message)]
|
||||
pub struct PlayerSlotAssigned {
|
||||
pub slot: usize,
|
||||
pub input_source: LocalInputSource,
|
||||
}
|
||||
|
||||
pub fn handle_fighter_slot_addition(
|
||||
addition: On<PlayerSlotAssigned>,
|
||||
mut addition: MessageReader<PlayerSlotAssigned>,
|
||||
query: Single<&CharacterSelectScreen>,
|
||||
commands: Commands,
|
||||
mut commands: Commands,
|
||||
common_assets: Res<CommonAssets>,
|
||||
styles: Res<Assets<FGUiStyle>>,
|
||||
) {
|
||||
let slot = addition.slot;
|
||||
create_portrait(
|
||||
slot,
|
||||
query.fighter_portait_container,
|
||||
commands,
|
||||
styles
|
||||
.get(&common_assets.ui_style)
|
||||
.expect("UI Style should be loaded by now"),
|
||||
&addition.input_source,
|
||||
);
|
||||
|
||||
for addition in addition.read() {
|
||||
let slot = addition.slot;
|
||||
create_portrait(
|
||||
slot,
|
||||
query.fighter_portait_container,
|
||||
&mut commands,
|
||||
styles
|
||||
.get(&common_assets.ui_style)
|
||||
.expect("UI Style should be loaded by now"),
|
||||
&addition.input_source,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn css_auto_assign_slots(
|
||||
menu_inputs: Res<crate::menus::input::MenuInputState>,
|
||||
mut assignments: ResMut<LocalInputAssignments>,
|
||||
mut match_config: ResMut<MenuMatchConfig>,
|
||||
mut commands: Commands,
|
||||
mut ev_assigned: MessageWriter<PlayerSlotAssigned>,
|
||||
) {
|
||||
for (source, input) in &menu_inputs.inputs {
|
||||
if input.movement != Vec2::ZERO {
|
||||
@@ -178,7 +174,7 @@ pub fn css_auto_assign_slots(
|
||||
fighter: None,
|
||||
});
|
||||
info!("New input assignment");
|
||||
commands.trigger(PlayerSlotAssigned {
|
||||
ev_assigned.write(PlayerSlotAssigned {
|
||||
slot: free_idx,
|
||||
input_source: source.clone(),
|
||||
});
|
||||
@@ -268,3 +264,9 @@ pub fn copy_cursor_transform_to_token(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn despawn_tokens(tokens: Query<Entity, With<CharacterSelectToken>>, mut commands: Commands) {
|
||||
for token in tokens {
|
||||
commands.entity(token).despawn();
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ impl CharacterSelectToken {
|
||||
},
|
||||
ImageNode {
|
||||
image: style.css_token.handle.handle.clone(),
|
||||
color: style.player_colors.get(slot).copied().unwrap_or(Color::WHITE),
|
||||
..default()
|
||||
}
|
||||
)
|
||||
|
||||
+28
-7
@@ -28,20 +28,21 @@ pub struct FGMenuCursor {
|
||||
|
||||
impl FGMenuCursor {
|
||||
pub fn create_cursor(
|
||||
player_slot: usize,
|
||||
input_source: MenuCursorInputSource,
|
||||
style: &FGUiStyle,
|
||||
input_source: &LocalInputSource,
|
||||
pointer_id: PointerId
|
||||
) -> impl Bundle {
|
||||
let pointer_id = if matches!(input_source, LocalInputSource::Keyboard) {PointerId::Mouse} else {PointerId::Custom(Uuid::new_v4())};
|
||||
let circle_icon = style.cursor.handle().clone();
|
||||
(
|
||||
FGMenuCursor {
|
||||
input_source: super::cursor::MenuCursorInputSource::Player(player_slot),
|
||||
input_source: input_source,
|
||||
..default()
|
||||
},
|
||||
Node {
|
||||
width: px(64),
|
||||
height: px(64),
|
||||
left: percent(50),
|
||||
top: percent(50),
|
||||
position_type: PositionType::Absolute,
|
||||
..default()
|
||||
},
|
||||
@@ -55,6 +56,25 @@ impl FGMenuCursor {
|
||||
Pickable::IGNORE,
|
||||
)
|
||||
}
|
||||
pub fn create_shared_cursor(style: &FGUiStyle) -> impl Bundle {
|
||||
Self::create_cursor(
|
||||
MenuCursorInputSource::Any,
|
||||
style,
|
||||
PointerId::Mouse
|
||||
)
|
||||
}
|
||||
pub fn create_player_cursor(
|
||||
player_slot: usize,
|
||||
style: &FGUiStyle,
|
||||
input_source: &LocalInputSource,
|
||||
) -> impl Bundle {
|
||||
let pointer_id = if matches!(input_source, LocalInputSource::Keyboard) {PointerId::Mouse} else {PointerId::Custom(Uuid::new_v4())};
|
||||
Self::create_cursor(
|
||||
MenuCursorInputSource::Player(player_slot),
|
||||
style,
|
||||
pointer_id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The player using the keyboard can input things with the mouse, so we should copy their movement input
|
||||
@@ -159,14 +179,15 @@ pub fn cursor_input(
|
||||
|
||||
pub fn update_cursor_transform(
|
||||
camera: Single<&Camera, With<IsDefaultUiCamera>>,
|
||||
query: Query<(&mut UiTransform, &PointerLocation), With<FGMenuCursor>>,
|
||||
query: Query<(&mut Node, &PointerLocation), With<FGMenuCursor>>,
|
||||
ui_scale: Res<UiScale>,
|
||||
) {
|
||||
let camera = camera.into_inner();
|
||||
|
||||
for (mut ui_trf, location) in query {
|
||||
for (mut node, location) in query {
|
||||
let ui_pos = scaling::logical_to_ui_position(location.location.as_ref().map(|d| d.position).unwrap_or_default(), camera, &ui_scale);
|
||||
ui_trf.translation = Val2::new(px(ui_pos.x), px(ui_pos.y));
|
||||
node.left = px(ui_pos.x);
|
||||
node.top = px(ui_pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+59
-10
@@ -1,37 +1,86 @@
|
||||
use bevy::prelude::*;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::fighter::FighterId;
|
||||
use crate::{
|
||||
fighter::FighterId,
|
||||
match_loading::{MatchPlayer, PendingMatch},
|
||||
stage::manifest::StageId,
|
||||
};
|
||||
|
||||
pub struct MenuMatchPlayerSlot {
|
||||
pub slot: usize,
|
||||
pub fighter: Option<FighterId>
|
||||
pub fighter: Option<FighterId>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct MenuMatchConfig {
|
||||
pub players: Vec<MenuMatchPlayerSlot>
|
||||
pub stage: Option<StageId>,
|
||||
pub players: Vec<MenuMatchPlayerSlot>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Reflect)]
|
||||
pub enum MenuMatchConfigErrors {
|
||||
#[error("Fighter slot {0} was invalid!")]
|
||||
PlayerSlotInvalid(usize)
|
||||
PlayerSlotInvalid(usize),
|
||||
#[error("Invalid configuration: Slot {0} doesn't have a fighter selected")]
|
||||
FighterNotSelectedForSlot(usize),
|
||||
#[error("Invalid configuration: No stage selected")]
|
||||
StageNotSelected,
|
||||
}
|
||||
|
||||
impl MenuMatchConfig {
|
||||
pub fn set_player_fighter(&mut self, player_slot: usize, fighter_id: Option<FighterId>) -> Result<(), MenuMatchConfigErrors> {
|
||||
let slot = self.players.get_mut(player_slot).ok_or(MenuMatchConfigErrors::PlayerSlotInvalid(player_slot))?;
|
||||
pub fn set_player_fighter(
|
||||
&mut self,
|
||||
player_slot: usize,
|
||||
fighter_id: Option<FighterId>,
|
||||
) -> Result<(), MenuMatchConfigErrors> {
|
||||
let slot = self
|
||||
.players
|
||||
.get_mut(player_slot)
|
||||
.ok_or(MenuMatchConfigErrors::PlayerSlotInvalid(player_slot))?;
|
||||
slot.fighter = fighter_id;
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_player_fighter(&self, player_slot: usize) -> Result<Option<FighterId>, MenuMatchConfigErrors> {
|
||||
let slot = self.players.get(player_slot).ok_or(MenuMatchConfigErrors::PlayerSlotInvalid(player_slot))?;
|
||||
pub fn get_player_fighter(
|
||||
&self,
|
||||
player_slot: usize,
|
||||
) -> Result<Option<FighterId>, MenuMatchConfigErrors> {
|
||||
let slot = self
|
||||
.players
|
||||
.get(player_slot)
|
||||
.ok_or(MenuMatchConfigErrors::PlayerSlotInvalid(player_slot))?;
|
||||
Ok(slot.fighter)
|
||||
}
|
||||
|
||||
pub fn all_players_ready(&self) -> bool {
|
||||
let has_unselected_fighter = self.players.iter().any(|player_slot| player_slot.fighter.is_none());
|
||||
let has_unselected_fighter = self
|
||||
.players
|
||||
.iter()
|
||||
.any(|player_slot| player_slot.fighter.is_none());
|
||||
!has_unselected_fighter
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_pending_match(&self) -> Result<PendingMatch, MenuMatchConfigErrors> {
|
||||
let mut match_players = vec![];
|
||||
|
||||
let stage = self.stage.ok_or(MenuMatchConfigErrors::StageNotSelected)?;
|
||||
|
||||
for player in self.players.iter() {
|
||||
let fighter_id =
|
||||
player
|
||||
.fighter
|
||||
.ok_or(MenuMatchConfigErrors::FighterNotSelectedForSlot(
|
||||
player.slot,
|
||||
))?;
|
||||
match_players.push(MatchPlayer {
|
||||
fighter: fighter_id,
|
||||
handle: player.slot,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(PendingMatch {
|
||||
players: match_players,
|
||||
stage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+66
-20
@@ -17,14 +17,14 @@ use crate::{
|
||||
pub mod button;
|
||||
pub mod character_select;
|
||||
pub mod cursor;
|
||||
pub mod errors;
|
||||
pub mod input;
|
||||
pub mod main_menu;
|
||||
pub mod match_config;
|
||||
pub mod navigation;
|
||||
pub mod scaling;
|
||||
pub mod style;
|
||||
pub mod match_config;
|
||||
pub mod errors;
|
||||
pub mod stage_select;
|
||||
pub mod style;
|
||||
|
||||
#[derive(Component, Clone, Default)]
|
||||
pub struct MenuMarker {}
|
||||
@@ -66,6 +66,19 @@ pub fn wrap_menu(menu: impl Scene) -> impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn despawn_menu(
|
||||
existing_menu: Option<Single<Entity, With<MenuMarker>>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Some(entity) = existing_menu {
|
||||
commands
|
||||
.entity(entity.into_inner())
|
||||
.remove::<Camera2d>()
|
||||
.remove::<Camera>()
|
||||
.despawn();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_menu(
|
||||
existing_menu: Option<Single<Entity, With<MenuMarker>>>,
|
||||
mut commands: Commands,
|
||||
@@ -116,10 +129,7 @@ impl Plugin for MenuPlugin {
|
||||
));
|
||||
}),
|
||||
)
|
||||
.add_systems(
|
||||
OnEnter(AppState::CharacterSelect),
|
||||
character_select::setup_character_select,
|
||||
)
|
||||
.add_message::<character_select::PlayerSlotAssigned>()
|
||||
.init_resource::<navigation::UINavigationActionState>()
|
||||
.init_resource::<navigation::UINavigationRepeat>()
|
||||
.init_resource::<input::MenuInputState>()
|
||||
@@ -150,35 +160,71 @@ impl Plugin for MenuPlugin {
|
||||
.run_if(resource_exists::<CommonAssets>),
|
||||
)
|
||||
.add_systems(Startup, scaling::ui_update_scale_on_startup)
|
||||
// CURSOR
|
||||
.add_systems(
|
||||
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(
|
||||
PreUpdate,
|
||||
cursor::cursor_input
|
||||
.before(bevy::picking::PickingSystems::ProcessInput)
|
||||
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(
|
||||
PostUpdate,
|
||||
(
|
||||
character_select::css_auto_assign_slots,
|
||||
cursor::copy_mouse_input,
|
||||
cursor::update_cursor_transform,
|
||||
character_select::copy_cursor_transform_to_token,
|
||||
)
|
||||
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)
|
||||
.add_observer(character_select::handle_fighter_slot_addition)
|
||||
.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)))
|
||||
.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_plugins(RonAssetPlugin::<FGUiStyle>::create("stylesheet.ron"))
|
||||
.add_systems(
|
||||
Update,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
AppState, game_settings::CommonAssets, match_loading::initiate_match, menus::{
|
||||
MenuMarker, button::FGUiButton, cursor, match_config::MenuMatchConfig, spawn_menu, stage_select::stage_box::StageBox, style::{FGUiButtonType, FGUiStyle},
|
||||
}, stage::manifest::{StageManifest, StageManifestRegistry},
|
||||
};
|
||||
|
||||
pub mod stage_box;
|
||||
|
||||
#[derive(Component, FromTemplate)]
|
||||
pub struct StageSelectScreen {
|
||||
pub stage_icons_container: Entity,
|
||||
}
|
||||
|
||||
pub fn setup_stage_select(
|
||||
existing_menu: Option<Single<Entity, With<MenuMarker>>>,
|
||||
mut commands: Commands,
|
||||
manifest_registry: Res<StageManifestRegistry>,
|
||||
manifests: Res<Assets<StageManifest>>,
|
||||
common_assets: Res<CommonAssets>,
|
||||
styles: Res<Assets<FGUiStyle>>,
|
||||
) {
|
||||
let style = styles
|
||||
.get(&common_assets.ui_style)
|
||||
.expect("UI style should be loaded");
|
||||
|
||||
let stage_boxes = manifest_registry
|
||||
.iter()
|
||||
.map(|(_, v)| stage_box::StageBox::scene(v.clone(), &manifests))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let menu = bsn! {
|
||||
// Characters
|
||||
StageSelectScreen {
|
||||
stage_icons_container: #PortraitContainer
|
||||
}
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column
|
||||
}
|
||||
Children [
|
||||
(
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column,
|
||||
width: percent(100),
|
||||
height: percent(100),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center
|
||||
}
|
||||
Children [
|
||||
{stage_boxes},
|
||||
]
|
||||
)
|
||||
]
|
||||
};
|
||||
|
||||
commands.spawn(cursor::FGMenuCursor::create_shared_cursor(style));
|
||||
|
||||
spawn_menu(existing_menu, commands, menu);
|
||||
}
|
||||
|
||||
pub fn stage_selected(
|
||||
ev: On<Pointer<Press>>,
|
||||
query: Query<&StageBox>,
|
||||
mut commands: Commands,
|
||||
mut next_state: ResMut<NextState<AppState>>,
|
||||
mut match_config: ResMut<MenuMatchConfig>
|
||||
) -> Result {
|
||||
let stage_box = query.get(ev.event_target());
|
||||
if let Ok(stage_box) = stage_box {
|
||||
match_config.stage = Some(stage_box.stage_id);
|
||||
initiate_match(&mut commands, &mut next_state, match_config.to_pending_match()?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use bevy::{picking::Pickable, prelude::*, text::FontSourceTemplate, ui::widget::ImageNodeSize};
|
||||
|
||||
use crate::{
|
||||
fighter::{FighterId, manifest::FighterManifest}, menus::{button::FGUiButton, style::FGUiButtonType}, stage::manifest::{StageId, StageManifest},
|
||||
};
|
||||
|
||||
#[derive(Component, Clone, FromTemplate)]
|
||||
pub struct StageBox {
|
||||
pub stage_manifest: Handle<StageManifest>,
|
||||
pub stage_id: StageId
|
||||
}
|
||||
|
||||
impl StageBox {
|
||||
pub fn scene(
|
||||
manifest_handle: Handle<StageManifest>,
|
||||
manifests: &Res<Assets<StageManifest>>,
|
||||
) -> impl Scene {
|
||||
let manifest = manifests
|
||||
.get(&manifest_handle)
|
||||
.expect("Stage manifest should be valid");
|
||||
let fighter_icon = manifest.icon.handle().clone();
|
||||
let stage_id = manifest.id;
|
||||
bsn! {
|
||||
Node {
|
||||
width: px(256),
|
||||
height: px(256),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::FlexEnd
|
||||
}
|
||||
ImageNode {
|
||||
image: fighter_icon
|
||||
}
|
||||
FGUiButton {
|
||||
button_type: FGUiButtonType::StageSelectIcon
|
||||
}
|
||||
StageBox {
|
||||
stage_manifest: manifest_handle,
|
||||
stage_id: stage_id
|
||||
}
|
||||
BackgroundColor(Color::srgb(1.0, 1.0, 1.0))
|
||||
Children [
|
||||
Pickable::IGNORE
|
||||
Node {
|
||||
width: percent(100)
|
||||
}
|
||||
TextFont {
|
||||
font: FontSourceTemplate::Handle("fonts/roboto.ttf"),
|
||||
font_size: px(crate::menus::style::TEXT_SIZE),
|
||||
}
|
||||
TextColor(Color::srgb(0.9, 0.9, 0.9))
|
||||
BackgroundColor(Color::srgb(0.0, 0.0, 1.0))
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -26,6 +26,7 @@ pub struct BoxShadowStyle {
|
||||
pub struct ButtonStyle {
|
||||
background_color: Color,
|
||||
border_color: Color,
|
||||
border_radius: f32,
|
||||
text_color: Color,
|
||||
box_shadow: BoxShadowStyle,
|
||||
border_size: i32,
|
||||
@@ -44,9 +45,10 @@ pub struct ButtonStyles {
|
||||
impl ButtonStyle {
|
||||
pub fn apply(&self, commands: &mut Commands, entity: Entity, node: &mut Node) {
|
||||
node.border = UiRect::all(px(self.border_size));
|
||||
node.border_radius = BorderRadius::all(px(self.border_radius));
|
||||
commands.entity(entity).insert((
|
||||
BackgroundColor(self.background_color),
|
||||
BorderColor::all(self.background_color),
|
||||
BorderColor::all(self.border_color),
|
||||
BoxShadow::new(
|
||||
self.box_shadow.color,
|
||||
px(self.box_shadow.offset_px_x),
|
||||
@@ -64,6 +66,7 @@ pub enum FGUiButtonType {
|
||||
MainMenu,
|
||||
CharacterSelectCharacter,
|
||||
CharacterSelectStartBanner,
|
||||
StageSelectIcon,
|
||||
}
|
||||
|
||||
#[derive(Asset, Resource, Reflect, Deserialize, RonAsset)]
|
||||
@@ -77,4 +80,5 @@ pub struct FGUiStyle {
|
||||
#[dependency]
|
||||
#[reflect(ignore)]
|
||||
pub css_token: TextureReference,
|
||||
pub player_colors: Vec<Color>,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
use rhai::EvalAltResult;
|
||||
|
||||
use crate::{
|
||||
fighter::attack::{self, AttackAngle, FighterDamage, Knockback, KnockbackType}, math::{int::FGi32, vec::FGVec2, vec3::FGVec3}, scripting::FighterAttackScript,
|
||||
fighter::attack::{self, AttackAngle, FighterDamage, Knockback, KnockbackType},
|
||||
math::{int::FGi32, vec::FGVec2, vec3::FGVec3},
|
||||
scripting::FighterAttackScript,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -61,9 +63,17 @@ impl MoveCompiler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_wont_autocancel_window(&mut self, start: u32, end: u32) -> Result<(), Box<EvalAltResult>> {
|
||||
fn set_wont_autocancel_window(
|
||||
&mut self,
|
||||
start: u32,
|
||||
end: u32,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
if end < start {
|
||||
return Err(format!("Couldn't set won't autocancel window, end frame {} is smaller than start frame {}", end, start).into());
|
||||
return Err(format!(
|
||||
"Couldn't set won't autocancel window, end frame {} is smaller than start frame {}",
|
||||
end, start
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
self.wont_autocancel_window = Some((start, end));
|
||||
@@ -77,13 +87,17 @@ impl MoveCompiler {
|
||||
|
||||
fn into_attack_script(mut self) -> Result<FighterAttackScript, String> {
|
||||
self.remove_all_hitboxes().map_err(|err| err.to_string())?;
|
||||
let hitboxes = self.hitboxes.into_iter().map(|(_, hitbox)| hitbox).collect();
|
||||
let hitboxes = self
|
||||
.hitboxes
|
||||
.into_iter()
|
||||
.map(|(_, hitbox)| hitbox)
|
||||
.collect();
|
||||
let iasa_frame = self.iasa_frame.unwrap_or(100000);
|
||||
let wont_autocancel_window = self.wont_autocancel_window.unwrap_or((0, 100000));
|
||||
Ok(FighterAttackScript {
|
||||
hitboxes,
|
||||
iasa_frame,
|
||||
wont_autocancel_window
|
||||
wont_autocancel_window,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -166,13 +180,10 @@ pub fn compile_script(text: &str) -> Result<FighterAttackScript, String> {
|
||||
|
||||
{
|
||||
let mc = mc.clone();
|
||||
engine.register_fn(
|
||||
"set_iasa_frame",
|
||||
move |frame: i64| {
|
||||
let mut b = mc.borrow_mut();
|
||||
b.set_iasa_frame(frame as u32);
|
||||
},
|
||||
);
|
||||
engine.register_fn("set_iasa_frame", move |frame: i64| {
|
||||
let mut b = mc.borrow_mut();
|
||||
b.set_iasa_frame(frame as u32);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@@ -180,15 +191,15 @@ pub fn compile_script(text: &str) -> Result<FighterAttackScript, String> {
|
||||
engine.register_fn(
|
||||
"hitbox",
|
||||
move |id: i64,
|
||||
bone: &str,
|
||||
damage: FighterDamage,
|
||||
offset: FGVec3,
|
||||
radius: FGi32,
|
||||
angle: AttackAngle,
|
||||
knockback_type: KnockbackType,
|
||||
knockback: Knockback,
|
||||
knockback_growth: FGi32|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
bone: &str,
|
||||
damage: FighterDamage,
|
||||
offset: FGVec3,
|
||||
radius: FGi32,
|
||||
angle: AttackAngle,
|
||||
knockback_type: KnockbackType,
|
||||
knockback: Knockback,
|
||||
knockback_growth: FGi32|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
let mut b = mc.borrow_mut();
|
||||
|
||||
// Rhai does not have first class u32 support, so we have to do this.
|
||||
@@ -222,7 +233,12 @@ pub fn compile_script(text: &str) -> Result<FighterAttackScript, String> {
|
||||
engine.run(&text)
|
||||
};
|
||||
|
||||
|
||||
result.map(|_| Rc::into_inner(mc).unwrap().into_inner().into_attack_script())
|
||||
result
|
||||
.map(|_| {
|
||||
Rc::into_inner(mc)
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.into_attack_script()
|
||||
})
|
||||
.map_err(|err| err.to_string())?
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Stage collision geometry.
|
||||
|
||||
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
+11
-8
@@ -1,18 +1,16 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bevy_asset_loader::asset_collection::AssetCollection;
|
||||
use ron_asset_manager::prelude::RonAsset;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
game_settings::GameSettings,
|
||||
math::vec::FGVec2,
|
||||
stage::{
|
||||
AppState, game_settings::GameSettings, math::vec::FGVec2, stage::{
|
||||
StagePoly,
|
||||
line::{StageCollision, StagePolyLineSegmentType, StagePolyType},
|
||||
},
|
||||
}, texture_reference::TextureReference,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -60,8 +58,9 @@ pub struct StageCameraProfile {
|
||||
pub max_downward_pan_degrees: f32,
|
||||
}
|
||||
|
||||
#[derive(Reflect, Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug, EnumIter)]
|
||||
#[derive(Reflect, Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug, EnumIter, Default)]
|
||||
pub enum StageId {
|
||||
#[default]
|
||||
TestStage,
|
||||
}
|
||||
|
||||
@@ -72,10 +71,14 @@ pub struct StagePolyDefinition {
|
||||
lines: Vec<(FGVec2, StagePolyLineSegmentType)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Asset, Reflect)]
|
||||
#[derive(Deserialize, Clone, Asset, Reflect, RonAsset)]
|
||||
pub struct StageManifest {
|
||||
pub id: StageId,
|
||||
pub model_path: String,
|
||||
#[asset]
|
||||
#[dependency]
|
||||
#[reflect(ignore)]
|
||||
pub icon: TextureReference,
|
||||
pub camera: StageCameraProfile,
|
||||
pub polygons: Vec<StagePolyDefinition>,
|
||||
}
|
||||
@@ -112,7 +115,7 @@ impl AssetCollection for StageManifestAssets {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
#[derive(Resource, Deref)]
|
||||
pub struct StageManifestRegistry(HashMap<StageId, Handle<StageManifest>>);
|
||||
|
||||
impl StageManifestRegistry {
|
||||
|
||||
+2
-2
@@ -5,12 +5,12 @@ use bevy::prelude::*;
|
||||
|
||||
pub mod debug;
|
||||
pub mod line;
|
||||
pub mod scene;
|
||||
pub mod manifest;
|
||||
pub mod scene;
|
||||
|
||||
pub use line::{StagePlaneIntersectResult, StagePoly};
|
||||
|
||||
use crate::{stage::{line::StageCollision, manifest::StageCameraProfile}};
|
||||
use crate::stage::{line::StageCollision, manifest::StageCameraProfile};
|
||||
|
||||
/// Owns the stage geometry: which scene gets spawned and how it is drawn.
|
||||
#[derive(Default)]
|
||||
|
||||
+1
-3
@@ -4,9 +4,7 @@ use std::f32::consts::PI;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
camera::MatchCamera, stage::line::StageCollision,
|
||||
};
|
||||
use crate::{camera::MatchCamera, stage::line::StageCollision};
|
||||
|
||||
pub fn spawn_stage_support(
|
||||
commands: &mut Commands,
|
||||
|
||||
Reference in New Issue
Block a user