#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub mod components;
pub mod systems;
pub use components::*;
use bevy::prelude::*;
pub mod prelude {
pub use crate::components::*;
pub use crate::{spawn_fighter, start_attack, AiDuelPlugin, AiDuelSet};
}
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct AiDuelSet;
pub struct AiDuelPlugin;
impl Plugin for AiDuelPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<AiDuelConfig>().add_systems(
Update,
(
systems::tick_cooldowns,
systems::ai_behavior,
systems::animate_fighters,
systems::update_hitboxes,
systems::resolve_hits,
systems::tick_health_and_flash,
systems::check_death,
)
.chain()
.in_set(AiDuelSet),
);
}
}
pub fn start_attack(
state: &mut CurrentState,
anim: &mut AnimationTimer,
sprite: &mut Sprite,
assets: &FighterAssets,
config: &AiDuelConfig,
) {
if state.0 == FighterState::Attacking {
return;
}
state.0 = FighterState::Attacking;
anim.frame_index = 0;
anim.max_frames = config.attack_frames.max(1);
anim.timer = Timer::from_seconds(config.attack_frame_time, TimerMode::Repeating);
if let Some(atlas) = &mut sprite.texture_atlas {
atlas.layout = assets.attack_layout.clone();
atlas.index = 0;
}
sprite.image = assets.attack_sheet.clone();
}
pub fn spawn_fighter(
commands: &mut Commands,
assets: &FighterAssets,
config: &AiDuelConfig,
pos: Vec3,
faction: Faction,
ai: Option<AiController>,
) -> Entity {
let mut fighter = commands.spawn((
Sprite::from_atlas_image(
assets.run_sheet.clone(),
TextureAtlas {
layout: assets.run_layout.clone(),
index: 0,
},
),
Transform::from_translation(pos).with_scale(Vec3::splat(config.sprite_scale)),
CurrentState(FighterState::Running),
AnimationTimer {
timer: Timer::from_seconds(config.run_frame_time, TimerMode::Repeating),
frame_index: 0,
max_frames: config.run_frames.max(1),
},
faction,
Health::full(config.default_max_health),
Hurtbox { half_size: config.hurtbox_half_size },
AttackCooldown(0.0),
WantsToMove(false),
));
if faction == Faction::Player {
fighter.insert(Player);
}
if let Some(ai) = ai {
fighter.insert(ai);
}
let parent = fighter.id();
let local_offset = config.hitbox_offset / config.sprite_scale.max(f32::EPSILON);
let mut hitbox = commands.spawn((
Transform::from_xyz(local_offset.x, local_offset.y, 0.5),
Hitbox {
owner: parent,
faction,
half_size: config.hitbox_half_size,
damage: config.attack_damage,
active: false,
},
));
if config.debug_hitboxes {
hitbox.insert(Sprite {
color: Color::srgba(1.0, 0.2, 0.2, 0.35),
custom_size: Some(config.hitbox_half_size * 2.0 / config.sprite_scale),
..default()
});
}
let hitbox = hitbox.id();
commands.entity(parent).add_child(hitbox);
parent
}