Make CSS and SSS reach a match

This commit is contained in:
2026-09-11 22:01:16 +02:00
parent f3701bd8b0
commit 74a635785e
16 changed files with 487 additions and 531 deletions
+49 -14
View File
@@ -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>();
+54 -52
View File
@@ -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();
}
}
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+75
View File
@@ -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(())
}
+55
View File
@@ -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
View File
@@ -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>,
}
+39 -23
View File
@@ -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
View File
@@ -1,6 +1,5 @@
//! Stage collision geometry.
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
+11 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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,