From a41cf4879e94e0b8960457df695cb1f662373f63 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Tue, 21 Apr 2026 17:08:33 +0300 Subject: [PATCH] feat: Dummy enemy and attack hit detection --- src/ai/dummy.rs | 32 +++++++++++++++++++++++++++ src/ai/mod.rs | 6 ++++++ src/combat/attack.rs | 51 ++++++++++++++++++++++++++++++++++++++++---- src/combat/mod.rs | 46 +++++++++++++++++++++++++++++++++++++-- src/lib.rs | 14 ++++++------ src/player/mod.rs | 3 ++- src/plugin.rs | 2 +- 7 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 src/ai/dummy.rs create mode 100644 src/ai/mod.rs diff --git a/src/ai/dummy.rs b/src/ai/dummy.rs new file mode 100644 index 0000000..dd172a5 --- /dev/null +++ b/src/ai/dummy.rs @@ -0,0 +1,32 @@ +//! Dummy enemy module that does nothing + +use super::*; + +use crate::{GROUP_ATTACK, GROUP_ENEMY, GROUP_FRIENDLY, GROUP_INTERACTIVE, GROUP_STATIC, combat::Health, meters}; + +/// Dummy enemy. Has [Health] component and no AI logic +#[derive(Component, Clone, Copy, PartialEq, Eq, Debug, Default, Reflect)] +#[reflect(Component, Clone, PartialEq, Debug, Default)] +#[require(Health)] +pub struct Dummy; + +impl Dummy { + /// Constructs [Dummy] bundle without sprite + pub fn bundle(position: Vec2, health: f32) -> impl Bundle { + ( + // Basic + Dummy, + Health::new(health), + + // World + Transform::from_xyz(position.x, position.y, 0.), + + // Collision + Collider::cuboid(meters(0.5), meters(0.5)), + CollisionGroups::new( + GROUP_ENEMY, + GROUP_STATIC | GROUP_INTERACTIVE | GROUP_FRIENDLY | GROUP_ATTACK, + ), + ) + } +} diff --git a/src/ai/mod.rs b/src/ai/mod.rs new file mode 100644 index 0000000..535002d --- /dev/null +++ b/src/ai/mod.rs @@ -0,0 +1,6 @@ +//! Enemy AI module + +use bevy::prelude::*; +use bevy_rapier2d::prelude::*; + +pub mod dummy; diff --git a/src/combat/attack.rs b/src/combat/attack.rs index 95ead23..9146905 100644 --- a/src/combat/attack.rs +++ b/src/combat/attack.rs @@ -5,7 +5,7 @@ use std::time::Duration; use bevy::prelude::*; use bevy_rapier2d::prelude::*; -use crate::{GROUP_ATTACK, GROUP_INTERACTIVE, GROUP_STATIC}; +use crate::{GROUP_ATTACK, GROUP_INTERACTIVE, GROUP_STATIC, combat::Health}; /// Contains logic for attack-related calculations #[derive(Component, Clone, Debug, Reflect)] @@ -27,6 +27,7 @@ pub struct AttackAreaOrigin; impl AttackArea { /// Constructs new attack area + #[inline(always)] pub fn new(accuracy: f32, damage: f32, duration: Duration, half_area: Vec2) -> Self { let max_distance = ((half_area.x * 2.).powi(2) + half_area.y.powi(2)).sqrt(); Self { @@ -58,6 +59,9 @@ impl AttackArea { GROUP_STATIC | GROUP_INTERACTIVE | affinity, ), Collider::cuboid(half_area.x, half_area.y), + ActiveCollisionTypes::all(), + Sleeping::disabled(), + ActiveEvents::COLLISION_EVENTS, // AttackAreaOrigin Children::spawn(( @@ -70,11 +74,19 @@ impl AttackArea { } /// Updates timer and returns true if attack has ended + #[inline(always)] pub fn tick(&mut self, dt: Duration) -> bool { self.timer.tick(dt).is_finished() } + /// Calls [Timer::almost_finish] + #[inline(always)] + pub fn almost_finish(&mut self) { + self.timer.almost_finish(); + } + /// Returns damage for given distance to origin + #[inline(always)] pub fn damage(&self, distance_to_origin: f32) -> f32 { let distance_ratio = 1. - distance_to_origin.min(self.max_distance) / self.max_distance; let total_multiplier = self.accuracy * distance_ratio; @@ -82,11 +94,42 @@ impl AttackArea { } } -/// System that updates AttackArea timers and despawns those who timed out -pub fn update_attack_areas(mut commands: Commands, time: Res