use crate::prelude::*;
#[derive(HasSchema, Default, Debug, Clone)]
#[type_data(metadata_asset("kick_bomb"))]
#[repr(C)]
pub struct KickBombMeta {
pub body_diameter: f32,
pub fin_anim: Ustr,
pub grab_offset: Vec2,
pub damage_region_size: Vec2,
pub damage_region_lifetime: f32,
pub kick_velocity: Vec2,
pub kickable: bool,
pub throw_velocity: f32,
pub explosion_lifetime: f32,
pub explosion_frames: u32,
pub explosion_fps: f32,
pub explosion_sound: Handle<AudioSource>,
pub explosion_volume: f64,
pub lit_frames_start: u32,
pub lit_frames_end: u32,
pub lit_fps: f32,
pub fuse_sound: Handle<AudioSource>,
pub fuse_sound_volume: f64,
pub fuse_time: Duration,
pub can_rotate: bool,
pub atlas: Handle<Atlas>,
pub explosion_atlas: Handle<Atlas>,
pub bounciness: f32,
pub angular_velocity: f32,
pub arm_delay: Duration,
pub explode_on_contact: bool,
}
pub fn game_plugin(_game: &mut Game) {
KickBombMeta::register_schema();
}
pub fn session_plugin(session: &mut Session) {
session
.stages
.add_system_to_stage(CoreStage::PreUpdate, hydrate)
.add_system_to_stage(CoreStage::PostUpdate, update_lit_kick_bombs)
.add_system_to_stage(CoreStage::PostUpdate, update_idle_kick_bombs);
}
#[derive(Clone, HasSchema, Default, Debug, Copy)]
pub struct IdleKickBomb;
#[derive(Clone, HasSchema, Default, Debug)]
pub struct LitKickBomb {
arm_delay: Timer,
fuse_time: Timer,
kicking: bool,
kicks: u32,
}
#[derive(Deref, DerefMut, HasSchema, Default, Clone)]
#[repr(C)]
pub struct KickBombHandle(pub Handle<KickBombMeta>);
#[derive(Clone, Debug)]
pub struct KickBombCommand;
impl KickBombCommand {
#[must_use]
pub fn spawn_kick_bomb(
entity: Option<Entity>,
transform: Transform,
kick_bomb_meta_handle: UntypedHandle,
lit: bool,
player_flip_f: Option<bool>,
) -> StaticSystem<(), ()> {
(move |game_meta: Root<GameMeta>,
assets: Res<AssetServer>,
mut animated_sprites: CompMut<AnimatedSprite>,
mut atlas_sprites: CompMut<AtlasSprite>,
mut bodies: CompMut<KinematicBody>,
mut entities: ResMutInit<Entities>,
mut idle_bombs: CompMut<IdleKickBomb>,
mut lit_bombs: CompMut<LitKickBomb>,
mut items: CompMut<Item>,
mut item_throws: CompMut<ItemThrow>,
mut item_grabs: CompMut<ItemGrab>,
mut kick_bomb_handles: CompMut<KickBombHandle>,
mut transforms: CompMut<Transform>| {
let entity = entity.unwrap_or_else(|| entities.create());
let kick_bomb_meta_handle =
match try_cast_meta_handle::<KickBombMeta>(kick_bomb_meta_handle, &assets) {
Ok(handle) => handle,
Err(err) => {
error!("KickBombCommand::spawn_kick_bomb() failed: {err}");
return;
}
};
let KickBombMeta {
atlas,
fin_anim,
grab_offset,
body_diameter,
can_rotate,
bounciness,
throw_velocity,
angular_velocity,
arm_delay,
fuse_time,
..
} = *assets.get(kick_bomb_meta_handle);
kick_bomb_handles.insert(entity, KickBombHandle(kick_bomb_meta_handle));
items.insert(entity, Item);
item_throws.insert(
entity,
ItemThrow::strength(throw_velocity).with_spin(angular_velocity),
);
item_grabs.insert(
entity,
ItemGrab {
fin_anim,
sync_animation: false,
grab_offset,
},
);
atlas_sprites.insert(entity, AtlasSprite::new(atlas));
transforms.insert(entity, transform);
animated_sprites.insert(entity, default());
bodies.insert(
entity,
KinematicBody {
shape: ColliderShape::Circle {
diameter: body_diameter,
},
gravity: game_meta.core.physics.gravity,
has_mass: true,
has_friction: true,
can_rotate,
bounciness,
..default()
},
);
if lit {
lit_bombs.insert(
entity,
LitKickBomb {
arm_delay: Timer::new(arm_delay, TimerMode::Once),
fuse_time: Timer::new(fuse_time, TimerMode::Once),
kicking: false,
kicks: 0,
},
);
if let Some(body) = bodies.get_mut(entity) {
let horizontal_flip_factor = if player_flip_f.unwrap() {
Vec2::new(-1.0, 1.0)
} else {
Vec2::ONE
};
body.velocity = Vec2::new(
horizontal_flip_factor.x * throw_velocity,
horizontal_flip_factor.y * throw_velocity / 2.5,
);
body.angular_velocity = angular_velocity;
}
} else {
idle_bombs.insert(entity, IdleKickBomb);
}
})
.system()
}
}
fn hydrate(
assets: Res<AssetServer>,
mut entities: ResMutInit<Entities>,
transforms: Comp<Transform>,
mut hydrated: CompMut<MapElementHydrated>,
mut element_handles: CompMut<ElementHandle>,
mut respawn_points: CompMut<DehydrateOutOfBounds>,
mut spawner_manager: SpawnerManager,
mut commands: Commands,
) {
let mut not_hydrated_bitset = hydrated.bitset().clone();
not_hydrated_bitset.bit_not();
not_hydrated_bitset.bit_and(element_handles.bitset());
let spawner_entities = entities
.iter_with_bitset(¬_hydrated_bitset)
.collect::<Vec<_>>();
for spawner_ent in spawner_entities {
let transform = *transforms.get(spawner_ent).unwrap();
let element_handle = *element_handles.get(spawner_ent).unwrap();
let element_meta = assets.get(element_handle.0);
if assets
.get(element_meta.data)
.try_cast_ref::<KickBombMeta>()
.is_ok()
{
hydrated.insert(spawner_ent, MapElementHydrated);
let entity = entities.create();
hydrated.insert(entity, MapElementHydrated);
element_handles.insert(entity, element_handle);
respawn_points.insert(entity, DehydrateOutOfBounds(spawner_ent));
spawner_manager.create_spawner(spawner_ent, vec![entity]);
commands.add(KickBombCommand::spawn_kick_bomb(
Some(entity),
transform,
element_meta.data.untyped(),
false,
None,
));
}
}
}
fn update_idle_kick_bombs(
entities: Res<Entities>,
mut commands: Commands,
mut items_used: CompMut<ItemUsed>,
mut audio_center: ResMut<AudioCenter>,
kick_bomb_handles: Comp<KickBombHandle>,
mut idle_bombs: CompMut<IdleKickBomb>,
assets: Res<AssetServer>,
mut animated_sprites: CompMut<AnimatedSprite>,
) {
for (entity, (_kick_bomb, kick_bomb_handle)) in
entities.iter_with((&mut idle_bombs, &kick_bomb_handles))
{
let kick_bomb_meta = assets.get(kick_bomb_handle.0);
let KickBombMeta {
fuse_sound,
fuse_sound_volume,
arm_delay,
fuse_time,
lit_frames_start,
lit_frames_end,
lit_fps,
..
} = *kick_bomb_meta;
if items_used.remove(entity).is_some() {
audio_center.play_sound(fuse_sound, fuse_sound_volume);
let animated_sprite = animated_sprites.get_mut(entity).unwrap();
animated_sprite.frames = (lit_frames_start..lit_frames_end).collect();
animated_sprite.repeat = true;
animated_sprite.fps = lit_fps;
commands.add(
move |mut idle: CompMut<IdleKickBomb>, mut lit: CompMut<LitKickBomb>| {
idle.remove(entity);
lit.insert(
entity,
LitKickBomb {
arm_delay: Timer::new(arm_delay, TimerMode::Once),
fuse_time: Timer::new(fuse_time, TimerMode::Once),
kicking: false,
kicks: 0,
},
);
},
);
}
}
}
fn update_lit_kick_bombs(
entities: Res<Entities>,
kick_bomb_handles: Comp<KickBombHandle>,
assets: Res<AssetServer>,
collision_world: CollisionWorld,
player_indexes: Comp<PlayerIdx>,
mut audio_center: ResMut<AudioCenter>,
mut trauma_events: ResMutInit<CameraTraumaEvents>,
mut lit_grenades: CompMut<LitKickBomb>,
mut sprites: CompMut<AtlasSprite>,
mut bodies: CompMut<KinematicBody>,
mut hydrated: CompMut<MapElementHydrated>,
player_inventories: PlayerInventories,
mut transforms: CompMut<Transform>,
mut commands: Commands,
time: Res<Time>,
spawners: Comp<DehydrateOutOfBounds>,
invincibles: CompMut<Invincibility>,
) {
for (entity, (kick_bomb, kick_bomb_handle, spawner)) in
entities.iter_with((&mut lit_grenades, &kick_bomb_handles, &Optional(&spawners)))
{
let kick_bomb_meta = assets.get(kick_bomb_handle.0);
let KickBombMeta {
explosion_sound,
explosion_volume,
explode_on_contact,
kick_velocity,
kickable,
damage_region_lifetime,
damage_region_size,
explosion_lifetime,
explosion_atlas,
explosion_fps,
explosion_frames,
..
} = *kick_bomb_meta;
kick_bomb.fuse_time.tick(time.delta());
kick_bomb.arm_delay.tick(time.delta());
let should_explode = 'should_explode: {
if kick_bomb.fuse_time.finished() {
break 'should_explode true;
}
if explode_on_contact {
let players = entities
.iter_with(&player_indexes)
.map(|x| x.0)
.collect::<Vec<_>>();
let colliding_with_players = collision_world
.actor_collisions_filtered(entity, |e| {
players.contains(&e) && invincibles.get(e).is_none()
})
.into_iter()
.collect::<Vec<_>>();
if !colliding_with_players.is_empty() && kick_bomb.arm_delay.finished() {
break 'should_explode true;
}
}
if player_inventories.find_item(entity).is_some() {
kick_bomb.kicking = false;
break 'should_explode false;
}
if kickable {
if let Some(player_entity) = collision_world
.actor_collisions_filtered(entity, |e| !invincibles.contains(e))
.into_iter()
.find(|&x| player_indexes.contains(x))
{
if !std::mem::replace(&mut kick_bomb.kicking, true) {
kick_bomb.kicks += 1;
}
if kick_bomb.kicks > 3 {
break 'should_explode true;
}
let body = bodies.get_mut(entity).unwrap();
let translation = transforms.get_mut(entity).unwrap().translation;
let player_sprite = sprites.get_mut(player_entity).unwrap();
let player_translation = transforms.get(player_entity).unwrap().translation;
let player_standing_left = player_translation.x <= translation.x;
if body.velocity.x == 0.0 {
body.velocity = kick_velocity;
if player_sprite.flip_x {
body.velocity.x *= -1.0;
}
} else if player_standing_left && !player_sprite.flip_x {
body.velocity.x = kick_velocity.x;
body.velocity.y = kick_velocity.y;
} else if !player_standing_left && player_sprite.flip_x {
body.velocity.x = -kick_velocity.x;
body.velocity.y = kick_velocity.y;
} else if kick_bomb.arm_delay.finished() {
break 'should_explode true;
}
} else {
kick_bomb.kicking = false;
}
}
false
};
if should_explode {
audio_center.play_sound(explosion_sound, explosion_volume);
trauma_events.send(7.5);
if let Some(spawner) = spawner {
hydrated.remove(**spawner);
}
let mut explosion_transform = *transforms.get(entity).unwrap();
explosion_transform.translation.z = -10.0; explosion_transform.rotation = Quat::IDENTITY;
commands.add(
move |mut entities: ResMutInit<Entities>,
mut transforms: CompMut<Transform>,
mut damage_regions: CompMut<DamageRegion>,
mut lifetimes: CompMut<Lifetime>,
mut sprites: CompMut<AtlasSprite>,
mut animated_sprites: CompMut<AnimatedSprite>| {
entities.kill(entity);
let ent = entities.create();
transforms.insert(ent, explosion_transform);
damage_regions.insert(
ent,
DamageRegion {
size: damage_region_size,
},
);
lifetimes.insert(ent, Lifetime::new(damage_region_lifetime));
let ent = entities.create();
transforms.insert(ent, explosion_transform);
sprites.insert(
ent,
AtlasSprite {
atlas: explosion_atlas,
..default()
},
);
animated_sprites.insert(
ent,
AnimatedSprite {
frames: (0..explosion_frames).collect(),
fps: explosion_fps,
repeat: false,
..default()
},
);
lifetimes.insert(ent, Lifetime::new(explosion_lifetime));
},
);
}
}
}