track stats in a singleton, add exp scaling for player attack, spawn player attack with averaged values of absorbed attacks

sakurakat.systems f5ce8e1b cb90b22b

verified
+1 -1
.idea/runConfigurations/Run_Web_dev.xml
··· 1 1 <component name="ProjectRunConfigurationManager"> 2 2 <configuration default="false" name="Run Web dev" type="ShConfigurationType" folderName="Build"> 3 - <option name="SCRIPT_TEXT" value="bevy run --yes web" /> 3 + <option name="SCRIPT_TEXT" value="bevy run --yes web --open" /> 4 4 <option name="INDEPENDENT_SCRIPT_PATH" value="true" /> 5 5 <option name="SCRIPT_PATH" value="" /> 6 6 <option name="SCRIPT_OPTIONS" value="" />
+2 -2
.vscode/bevy.code-snippets
··· 5 5 "body": [ 6 6 "use bevy::prelude::*;", 7 7 "", 8 - "pub(super) fn plugin(app: &mut App) {", 8 + "pub fn plugin(app: &mut App) {", 9 9 "\t$0", 10 10 "}" 11 11 ] ··· 65 65 "}" 66 66 ] 67 67 } 68 - } 68 + }
+1 -1
src/asset_tracking.rs
··· 4 4 5 5 use bevy::prelude::*; 6 6 7 - pub(super) fn plugin(app: &mut App) { 7 + pub fn plugin(app: &mut App) { 8 8 app.init_resource::<ResourceHandles>(); 9 9 app.add_systems(PreUpdate, load_resource_assets); 10 10 }
+1 -1
src/audio.rs
··· 1 1 use bevy::prelude::*; 2 2 3 - pub(super) fn plugin(app: &mut App) { 3 + pub fn plugin(app: &mut App) { 4 4 app.register_type::<Music>(); 5 5 app.register_type::<SoundEffect>(); 6 6
+1 -1
src/menus/credits.rs
··· 6 6 7 7 use crate::{asset_tracking::LoadResource, audio::music, menus::Menu, theme::prelude::*}; 8 8 9 - pub(super) fn plugin(app: &mut App) { 9 + pub fn plugin(app: &mut App) { 10 10 app.add_systems(OnEnter(Menu::Credits), spawn_credits_menu); 11 11 app.add_systems( 12 12 Update,
+1 -1
src/menus/main.rs
··· 4 4 5 5 use crate::{asset_tracking::ResourceHandles, menus::Menu, screens::Screen, theme::widget}; 6 6 7 - pub(super) fn plugin(app: &mut App) { 7 + pub fn plugin(app: &mut App) { 8 8 app.add_systems(OnEnter(Menu::Main), spawn_main_menu); 9 9 } 10 10
+1 -1
src/menus/mod.rs
··· 7 7 8 8 use bevy::prelude::*; 9 9 10 - pub(super) fn plugin(app: &mut App) { 10 + pub fn plugin(app: &mut App) { 11 11 app.init_state::<Menu>(); 12 12 13 13 app.add_plugins((
+1 -1
src/menus/pause.rs
··· 4 4 5 5 use crate::{menus::Menu, screens::Screen, theme::widget}; 6 6 7 - pub(super) fn plugin(app: &mut App) { 7 + pub fn plugin(app: &mut App) { 8 8 app.add_systems(OnEnter(Menu::Pause), spawn_pause_menu); 9 9 app.add_systems( 10 10 Update,
+1 -1
src/menus/settings.rs
··· 6 6 7 7 use crate::{menus::Menu, screens::Screen, theme::prelude::*}; 8 8 9 - pub(super) fn plugin(app: &mut App) { 9 + pub fn plugin(app: &mut App) { 10 10 app.add_systems(OnEnter(Menu::Settings), spawn_settings_menu); 11 11 app.add_systems( 12 12 Update,
+1 -1
src/parylord/attack.rs
··· 6 6 7 7 #[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Default, Reflect)] 8 8 #[reflect(Component)] 9 - pub struct Attack(pub(crate) u8); 9 + pub struct Attack(pub u8);
+18 -10
src/parylord/enemy.rs
··· 3 3 use crate::parylord::health::{DisplayHealth, Health, ZeroHealth}; 4 4 use crate::parylord::player::Player; 5 5 use crate::parylord::ttl::Ttl; 6 - use crate::parylord::CollisionLayer; 6 + use crate::parylord::{CollisionLayer, ParrylordSingleton}; 7 7 use crate::screens::Screen; 8 8 use crate::{AppSystems, PausableSystems}; 9 9 use avian2d::prelude::{ ··· 54 54 mut events: EventReader<SpawnEnemy>, 55 55 mut commands: Commands, 56 56 enemy_assets: Res<EnemyAssets>, 57 + singleton: Res<ParrylordSingleton>, 57 58 ) { 58 59 for _ in events.read() { 59 - commands.spawn(Enemy::bundle(&enemy_assets, get_random_vec2_in_play_area())); 60 + commands.spawn(Enemy::bundle( 61 + &enemy_assets, 62 + get_random_vec2_in_play_area(), 63 + Enemy::BASE_HEALTH.pow(u32::from(singleton.level) - 1), 64 + )); 60 65 } 61 66 } 62 67 ··· 92 97 } 93 98 } 94 99 95 - const DEBUG_STATE_MACHINE: bool = true; 100 + const DEBUG_STATE_MACHINE: bool = false; 96 101 97 102 #[tracing::instrument(skip_all)] 98 103 pub fn write_enemy_intents( ··· 285 290 } 286 291 287 292 impl Enemy { 288 - const SPEED: f32 = 100.0; 293 + const SPEED: f32 = 300.0; 294 + const BASE_HEALTH: u8 = 2; 289 295 290 296 #[tracing::instrument()] 291 - pub fn bundle(enemy_assets: &EnemyAssets, position: Vec2) -> impl Bundle { 297 + pub fn bundle(enemy_assets: &EnemyAssets, position: Vec2, health: u8) -> impl Bundle { 292 298 let mut rng = thread_rng(); 293 299 let pick = rng.gen_range(0..=EnemyAssets::MAX_ASSETS); 294 300 ( 295 301 Self::default(), 296 - Health(15), 302 + Health(health), 297 303 DisplayHealth::bundle(), 298 304 EnemyStateTimer(Timer::from_seconds(2.0, TimerMode::Repeating)), 299 - Transform::from_translation(position.extend(1.0)), 305 + Transform::from_translation(position.extend(1.0)).with_scale(Vec3::splat(0.8)), 300 306 Sprite { 301 307 image: match pick % EnemyAssets::MAX_ASSETS { 302 308 0 => enemy_assets.beige.clone(), ··· 311 317 }, 312 318 RigidBody::Dynamic, 313 319 LinearVelocity::default(), 314 - CollisionEventsEnabled, 315 320 Collider::circle(64.0), 316 321 CollisionLayers::new( 317 322 [CollisionLayer::Enemy], 318 323 [ 324 + CollisionLayer::Enemy, 319 325 CollisionLayer::Walls, 320 - CollisionLayer::Player, 321 326 CollisionLayer::PlayerProjectile, 322 327 CollisionLayer::PlayerHurt, 323 328 ], ··· 330 335 pub fn handle_dead_enemies( 331 336 dead_enemies: Query<Entity, (With<ZeroHealth>, With<Enemy>)>, 332 337 mut commands: Commands, 338 + mut singleton: ResMut<ParrylordSingleton>, 333 339 ) { 334 340 for entity in dead_enemies { 335 341 let Ok(mut entity) = commands.get_entity(entity) else { 336 342 continue; 337 343 }; 338 - entity.despawn(); 344 + entity.try_despawn(); 345 + 346 + singleton.enemies_killed += 1; 339 347 } 340 348 } 341 349
+1 -1
src/parylord/health.rs
··· 24 24 25 25 #[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Default, Reflect)] 26 26 #[reflect(Component)] 27 - pub struct Health(pub i8); 27 + pub struct Health(pub u8); 28 28 29 29 #[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Default, Reflect)] 30 30 #[reflect(Component)]
+5 -5
src/parylord/level.rs
··· 1 1 use crate::parylord::assets::{LevelAssets, PlayerAssets}; 2 2 use crate::parylord::enemy::{Enemy, SpawnEnemy}; 3 3 use crate::parylord::player::Player; 4 - use crate::parylord::{CollisionLayer, ParrylordLevel}; 4 + use crate::parylord::{CollisionLayer, ParrylordSingleton}; 5 5 use crate::screens::Screen; 6 6 use crate::PausableSystems; 7 7 use avian2d::prelude::{Collider, CollisionLayers, RigidBody}; 8 8 use bevy::prelude::*; 9 9 10 - pub(super) fn plugin(app: &mut App) { 10 + pub fn plugin(app: &mut App) { 11 11 app.register_type::<Level>(); 12 12 app.register_type::<EnemySpawn>(); 13 13 app.register_type::<LevelAssets>(); ··· 60 60 fn new_level( 61 61 enemies: Query<(), With<Enemy>>, 62 62 mut spawn_enemy_event_writer: EventWriter<SpawnEnemy>, 63 - mut level: ResMut<ParrylordLevel>, 63 + mut singleton: ResMut<ParrylordSingleton>, 64 64 ) { 65 65 if !enemies.is_empty() { 66 66 return; 67 67 } 68 68 69 - for _ in 0..level.0 { 69 + for _ in 0..singleton.level { 70 70 spawn_enemy_event_writer.write(SpawnEnemy); 71 71 } 72 72 73 - level.0 += 1; 73 + singleton.level += 1; 74 74 } 75 75 76 76 // let root = context.entity;
+6 -2
src/parylord/mod.rs
··· 13 13 pub mod ttl; 14 14 15 15 pub fn plugin(app: &mut App) { 16 - app.init_resource::<ParrylordLevel>(); 16 + app.init_resource::<ParrylordSingleton>(); 17 17 18 18 app.add_plugins(( 19 19 assets::plugin, ··· 44 44 45 45 #[derive(Resource, Clone, Reflect, Debug, Default)] 46 46 #[reflect(Resource)] 47 - pub struct ParrylordLevel(u8); 47 + pub struct ParrylordSingleton { 48 + pub enemies_killed: u32, 49 + pub level: u32, 50 + pub max_parried: u32, 51 + }
+6 -5
src/parylord/player.rs
··· 71 71 fn flip_x(&self) -> bool { 72 72 match self { 73 73 Self::Front(_) => false, 74 - Self::Walk(dir, _) | Self::Stand(dir) => { 75 - !dir.is_sign_positive() 76 - } 74 + Self::Walk(dir, _) | Self::Stand(dir) => !dir.is_sign_positive(), 77 75 } 78 76 } 79 77 ··· 248 246 continue; 249 247 }; 250 248 251 - entity.despawn(); 249 + entity.try_despawn(); 252 250 } 253 251 } 254 252 } 255 253 256 254 #[tracing::instrument(skip_all)] 257 - fn handle_player_death(query: Option<Single<(), (With<Player>, With<ZeroHealth>)>>) { 255 + fn handle_player_death( 256 + query: Option<Single<(), (With<Player>, With<ZeroHealth>)>>, 257 + next: ResMut<NextState<Screen>>, 258 + ) { 258 259 if query.is_none() { 259 260 return; 260 261 }
+22 -19
src/parylord/player_attack.rs
··· 4 4 use crate::parylord::health::{Health, InvincibilityTimer}; 5 5 use crate::parylord::player::Player; 6 6 use crate::parylord::ttl::Ttl; 7 - use crate::parylord::CollisionLayer; 7 + use crate::parylord::{CollisionLayer, ParrylordSingleton}; 8 8 use crate::screens::Screen; 9 9 use crate::{exponential_decay, AppSystems, PausableSystems}; 10 10 use avian2d::prelude::{ ··· 210 210 attack_assets: Res<AttackAssets>, 211 211 window: Single<&Window>, 212 212 camera: Single<(&Camera, &GlobalTransform)>, 213 + mut singleton: ResMut<ParrylordSingleton>, 213 214 ) { 214 215 let Some(entities) = entities else { 215 216 return; 216 217 }; 217 218 218 - let number_of_entities = entities.len().clamp(0, AttackAssets::MAX as usize - 1); //remove clamp 219 219 let Some(&entity) = entities.first() else { 220 220 warn!("Some(&entity) = entities.get(0)"); 221 221 return; 222 222 }; 223 - let Ok((velocity, _transform, ttl)) = change_components.get(entity) else { 223 + let Ok(_) = change_components.get(entity) else { 224 224 warn!("Entity {entity} not in change_components"); 225 225 return; 226 226 }; 227 227 228 - let pos = entities 228 + let Some((sum_speed, sum_pos, sum_ttl, total)) = entities 229 229 .iter() 230 230 .flat_map(|&x| change_components.get(x)) 231 - .map(|(_, x, _)| x) 232 - .map(|x| x.translation) 233 - .map(|x| x.truncate()) 234 - .collect::<Vec<_>>(); 235 - 236 - let Some(sum) = pos.iter().copied().reduce(|acc, x| acc + x) else { 231 + .map(|(x, y, z)| (x.length(), y.translation, z.0.remaining_secs())) 232 + .map(|(x, y, z)| (x, y.truncate(), z, 1u32)) 233 + .reduce(|(a, b, c, d), (x, y, z, w)| (a + x, b + y, c + z, d + w)) 234 + else { 237 235 warn!("Some(&sum) = pos.iter().reduce(|acc, x| acc + x)"); 238 236 return; 239 237 }; 240 - let total = pos.len() as f32; 238 + 239 + singleton.max_parried = singleton.max_parried.max(total); 240 + 241 + #[allow(clippy::cast_precision_loss)] 242 + let total_f32 = total as f32; 241 243 242 244 let window = *window; 243 245 let Some(mouse) = window.cursor_position() else { ··· 253 255 camera_transform, 254 256 ); 255 257 let angle = Vec2::from_angle(angle); 256 - let speed = velocity.0.length(); 257 258 258 - let pos = sum / total; 259 - let velocity = LinearVelocity(angle * speed); // average the speed 260 - let ttl = Ttl::new(ttl.0.remaining_secs() + 1.0); 259 + let pos = sum_pos / total_f32; 260 + let velocity = LinearVelocity(angle * sum_speed / total_f32); 261 + let ttl = Ttl::new((sum_ttl / total_f32) + 1.0); 262 + 263 + let power = 2u8.checked_pow(total - 1).unwrap_or(u8::MAX); 261 264 262 265 commands.spawn(PlayerAttack::bundle( 263 - number_of_entities as u8, //remove clamp 266 + power, 264 267 &attack_assets, 265 268 pos, 266 269 velocity, ··· 271 274 let Ok(mut entity) = commands.get_entity(entity) else { 272 275 continue; 273 276 }; 274 - entity.despawn(); 277 + entity.try_despawn(); 275 278 } 276 279 } 277 280 ··· 286 289 continue 'outer; 287 290 }; 288 291 289 - health.0 -= attack.0 as i8; 292 + health.0 = health.0.saturating_sub(attack.0); 290 293 291 294 commands 292 295 .entity(entity) ··· 298 301 let Ok(mut attack_entity) = commands.get_entity(attack_entity) else { 299 302 continue; 300 303 }; 301 - attack_entity.despawn(); 304 + attack_entity.try_despawn(); 302 305 } 303 306 } 304 307 }
+2 -2
src/parylord/ttl.rs
··· 18 18 19 19 #[derive(Component, Debug, Clone, PartialEq, Eq, Default, Reflect)] 20 20 #[reflect(Component)] 21 - pub struct Ttl(pub(crate) Timer); 21 + pub struct Ttl(pub Timer); 22 22 23 23 impl Ttl { 24 24 pub fn new(secs: f32) -> Self { ··· 45 45 let Ok(mut entity) = commands.get_entity(timer) else { 46 46 continue; 47 47 }; 48 - entity.despawn(); 48 + entity.try_despawn(); 49 49 } 50 50 }
+2 -2
src/screens/gameplay.rs
··· 2 2 3 3 use bevy::{input::common_conditions::input_just_pressed, prelude::*, ui::Val::*}; 4 4 5 - use crate::{Pause, parylord::level::spawn_level, menus::Menu, screens::Screen}; 5 + use crate::{menus::Menu, parylord::level::spawn_level, screens::Screen, Pause}; 6 6 7 - pub(super) fn plugin(app: &mut App) { 7 + pub fn plugin(app: &mut App) { 8 8 app.add_systems(OnEnter(Screen::Gameplay), spawn_level); 9 9 10 10 // Toggle pause on key press.
+1 -1
src/screens/loading.rs
··· 5 5 6 6 use crate::{asset_tracking::ResourceHandles, screens::Screen, theme::prelude::*}; 7 7 8 - pub(super) fn plugin(app: &mut App) { 8 + pub fn plugin(app: &mut App) { 9 9 app.add_systems(OnEnter(Screen::Loading), spawn_loading_screen); 10 10 11 11 app.add_systems(
+1 -1
src/screens/mod.rs
··· 7 7 8 8 use bevy::prelude::*; 9 9 10 - pub(super) fn plugin(app: &mut App) { 10 + pub fn plugin(app: &mut App) { 11 11 app.init_state::<Screen>(); 12 12 13 13 app.add_plugins((
+2 -2
src/screens/splash.rs
··· 6 6 prelude::*, 7 7 }; 8 8 9 - use crate::{AppSystems, screens::Screen, theme::prelude::*}; 9 + use crate::{screens::Screen, theme::prelude::*, AppSystems}; 10 10 11 - pub(super) fn plugin(app: &mut App) { 11 + pub fn plugin(app: &mut App) { 12 12 // Spawn splash screen. 13 13 app.insert_resource(ClearColor(SPLASH_BACKGROUND_COLOR)); 14 14 app.add_systems(OnEnter(Screen::Splash), spawn_splash_screen);
+1 -1
src/screens/title.rs
··· 4 4 5 5 use crate::{menus::Menu, screens::Screen}; 6 6 7 - pub(super) fn plugin(app: &mut App) { 7 + pub fn plugin(app: &mut App) { 8 8 app.add_systems(OnEnter(Screen::Title), open_main_menu); 9 9 app.add_systems(OnExit(Screen::Title), close_menu); 10 10 }
+1 -1
src/theme/interaction.rs
··· 2 2 3 3 use crate::{asset_tracking::LoadResource, audio::sound_effect}; 4 4 5 - pub(super) fn plugin(app: &mut App) { 5 + pub fn plugin(app: &mut App) { 6 6 app.register_type::<InteractionPalette>(); 7 7 app.add_systems(Update, apply_interaction_palette); 8 8
+2 -2
src/theme/mod.rs
··· 9 9 10 10 #[allow(unused_imports)] 11 11 pub mod prelude { 12 - pub use super::{interaction::InteractionPalette, palette as ui_palette, widget}; 12 + pub use super::widget; 13 13 } 14 14 15 15 use bevy::prelude::*; 16 16 17 - pub(super) fn plugin(app: &mut App) { 17 + pub fn plugin(app: &mut App) { 18 18 app.add_plugins(interaction::plugin); 19 19 }