Skip to content

Commit f9ee040

Browse files
bors[bot]Zac8668odecay
authored
Merge #272
272: Boss Bomb Throwing r=odecay a=Zac8668 Implements boss bomb throwing Closes #269 Co-authored-by: Isaac <78173025+Zac8668@users.noreply.github.com> Co-authored-by: odecay <electricbuck@gmail.com>
2 parents 0dcf9c9 + 9712852 commit f9ee040

10 files changed

Lines changed: 416 additions & 10 deletions

File tree

assets/fighters/big_bass/big_bass.fighter.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ spritesheet:
4040
repeat: false
4141
attacking:
4242
frames: [30, 44]
43+
bomb_throw:
44+
frames: [45, 51]
4345

4446
attacks:
4547
- name: "ground_slam"
@@ -52,6 +54,17 @@ attacks:
5254
size: [96, 32]
5355
offset: [0, -69]
5456
hitstun_duration: 0.2
57+
- name: "bomb_throw"
58+
damage: 100
59+
frames:
60+
startup: 5
61+
active: 6
62+
recovery: 6
63+
hitbox:
64+
size: [96, 32]
65+
offset: [0, 0]
66+
hitstun_duration: 0.2
67+
item: /items/bomb/bomb.item.yaml
5568

5669
audio:
5770
effects:

assets/items/bomb/bomb.item.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Bomb
2+
3+
image:
4+
image: bomb_icon.png
5+
image_size: [32, 32]
6+
7+
kind: !Bomb
8+
spritesheet:
9+
image: [bomb_144_112.png]
10+
tile_size: [144, 112]
11+
columns: 11
12+
rows: 3
13+
14+
animation_fps: 0.12
15+
animations:
16+
explosion:
17+
frames: [0, 10]
18+
bomb:
19+
frames: [11, 11]
20+
bomb_fuse:
21+
frames: [22, 24]
22+
attack_frames:
23+
startup: 0
24+
active: 3
25+
recovery: 3

assets/items/bomb/bomb_144_112.png

21.7 KB
Loading

assets/items/bomb/bomb_icon.png

620 Bytes
Loading

src/assets.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,17 @@ impl AssetLoader for FighterLoader {
267267
let self_path = load_context.path();
268268
let mut dependencies = Vec::new();
269269

270+
for attack in &mut meta.attacks {
271+
if let Some(item) = &attack.item {
272+
let (item_path, item_handle) =
273+
get_relative_asset(load_context, self_path, item);
274+
275+
dependencies.push(item_path);
276+
277+
attack.item_handle = item_handle;
278+
}
279+
}
280+
270281
let (portrait_path, portrait_handle) =
271282
get_relative_asset(load_context, self_path, &meta.hud.portrait.image);
272283
dependencies.push(portrait_path);
@@ -424,6 +435,24 @@ impl AssetLoader for ItemLoader {
424435
dependencies.push(script_path);
425436
*script_handle = loaded_script_handle;
426437
}
438+
ItemKind::Bomb { spritesheet, .. } => {
439+
for (index, image) in spritesheet.image.iter().enumerate() {
440+
let (texture_path, texture_handle) =
441+
get_relative_asset(load_context, load_context.path(), image);
442+
443+
let atlas_handle = load_context.set_labeled_asset(
444+
format!("atlas_{}", index).as_str(),
445+
LoadedAsset::new(TextureAtlas::from_grid(
446+
texture_handle,
447+
spritesheet.tile_size.as_vec2(),
448+
spritesheet.columns,
449+
spritesheet.rows,
450+
))
451+
.with_dependency(texture_path),
452+
);
453+
spritesheet.atlas_handle.push(atlas_handle);
454+
}
455+
}
427456
_ => {}
428457
}
429458

src/attack.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
damage::{DamageEvent, Damageable, Health},
1515
enemy::Enemy,
1616
fighter_state::MeleeWeapon,
17-
item::Drop,
17+
item::{Drop, Explodable},
1818
metadata::ColliderMeta,
1919
player::Player,
2020
GameState,
@@ -83,6 +83,7 @@ impl Breakable {
8383
pub struct BrokeEvent {
8484
pub drop: Option<Drop>,
8585
pub transform: Option<Transform>,
86+
pub explodable: Option<Explodable>,
8687
}
8788

8889
/// A component identifying the attacks active collision frames.
@@ -102,7 +103,15 @@ pub struct AttackFrames {
102103
//TODO: is there a way we can move the adding of collision layers here as well?
103104
fn activate_hitbox(
104105
attack_query: Query<(Entity, &Attack, &AttackFrames, &Parent), Without<Collider>>,
105-
parent_query: Query<&Animation, Or<(With<Player>, With<Enemy>, With<MeleeWeapon>)>>,
106+
parent_query: Query<
107+
&Animation,
108+
Or<(
109+
With<Player>,
110+
With<Enemy>,
111+
With<MeleeWeapon>,
112+
With<Explodable>,
113+
)>,
114+
>,
106115
mut commands: Commands,
107116
) {
108117
for (entity, attack, attack_frames, parent) in attack_query.iter() {
@@ -191,20 +200,24 @@ fn breakable_system(
191200
Option<&Drop>,
192201
Option<&Transform>,
193202
Option<&Parent>,
203+
Option<&Explodable>,
194204
)>,
195205
mut commands: Commands,
196206
mut event_writer: EventWriter<BrokeEvent>,
197207
) {
198208
for ev in events.iter() {
199209
if let CollisionEvent::Started(e1, e2, _flags) = ev {
200210
for e in [e1, e2].iter() {
201-
if let Ok((mut breakable, drop, transform, parent)) = despawn_query.get_mut(**e) {
211+
if let Ok((mut breakable, drop, transform, parent, explodable)) =
212+
despawn_query.get_mut(**e)
213+
{
202214
if breakable.hit_count < breakable.hit_tolerance {
203215
breakable.hit_count += 1;
204216
} else {
205217
event_writer.send(BrokeEvent {
206218
drop: drop.cloned(),
207219
transform: transform.cloned(),
220+
explodable: explodable.cloned(),
208221
});
209222
commands.entity(**e).despawn_recursive();
210223

src/enemy_ai.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
consts::{self, ENEMY_MAX_ATTACK_DISTANCE, ENEMY_MIN_ATTACK_DISTANCE, ENEMY_TARGET_MAX_OFFSET},
99
enemy::{Boss, Enemy, TripPointX},
1010
fighter_state::{
11-
GroundSlam, Idling, Moving, Punching, StateTransition, StateTransitionIntents,
11+
BossBombThrow, Idling, Moving, Punching, StateTransition, StateTransitionIntents,
1212
},
1313
player::Player,
1414
Stats,
@@ -142,9 +142,10 @@ pub fn emit_enemy_intents(
142142
// make them attack with their first available attack??
143143
// And attack!
144144
if maybe_boss.is_some() {
145+
// TODO Add some proper ai for the bomb throw
145146
intents.push_back(StateTransition::new(
146-
GroundSlam::default(),
147-
GroundSlam::PRIORITY,
147+
BossBombThrow::default(),
148+
BossBombThrow::PRIORITY,
148149
false,
149150
))
150151
} else {

src/fighter_state.rs

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use crate::{
1717
enemy_ai,
1818
fighter::{Attached, AvailableAttacks, Inventory},
1919
input::PlayerAction,
20-
item::{Drop, Item, ItemBundle, Projectile, ScriptItemGrabEvent, ScriptItemThrowEvent},
20+
item::{
21+
AnimatedProjectile, Drop, Explodable, Item, ItemBundle, Projectile, ScriptItemGrabEvent,
22+
ScriptItemThrowEvent,
23+
},
2124
lifetime::Lifetime,
2225
metadata::{AttackMeta, AudioMeta, FighterMeta, ItemKind, ItemMeta, ItemSpawnMeta},
2326
movement::LinearVelocity,
@@ -63,6 +66,7 @@ impl Plugin for FighterStatePlugin {
6366
.with_system(transition_from_hitstun)
6467
.with_system(transition_from_melee_attacking)
6568
.with_system(transition_from_shooting)
69+
.with_system(transition_from_bomb_throw)
6670
.into(),
6771
)
6872
// State handler systems
@@ -82,6 +86,7 @@ impl Plugin for FighterStatePlugin {
8286
.with_system(holding)
8387
.with_system(melee_attacking)
8488
.with_system(shooting)
89+
.with_system(bomb_throw)
8590
.into(),
8691
);
8792
}
@@ -248,6 +253,18 @@ impl GroundSlam {
248253
pub const ANIMATION: &'static str = "attacking";
249254
}
250255

256+
#[derive(Component, Reflect, Default, Debug)]
257+
#[component(storage = "SparseSet")]
258+
pub struct BossBombThrow {
259+
pub has_started: bool,
260+
pub is_finished: bool,
261+
pub thrown: bool,
262+
}
263+
impl BossBombThrow {
264+
pub const PRIORITY: i32 = 30;
265+
pub const ANIMATION: &'static str = "bomb_throw";
266+
}
267+
251268
#[derive(Component, Reflect, Default, Debug)]
252269
#[component(storage = "SparseSet")]
253270
pub struct Punching {
@@ -542,6 +559,35 @@ fn transition_from_ground_slam(
542559
}
543560
}
544561

562+
fn transition_from_bomb_throw(
563+
mut commands: Commands,
564+
mut fighters: Query<(Entity, &mut StateTransitionIntents, &BossBombThrow)>,
565+
) {
566+
'entity: for (entity, mut transition_intents, bomb_throw) in &mut fighters {
567+
// Transition to any higher priority states
568+
let current_state_removed = transition_intents
569+
.transition_to_higher_priority_states::<BossBombThrow>(
570+
entity,
571+
BossBombThrow::PRIORITY,
572+
&mut commands,
573+
);
574+
575+
// If our current state was removed, don't continue processing this fighter
576+
if current_state_removed {
577+
continue 'entity;
578+
}
579+
580+
// If we're done flopping
581+
if bomb_throw.is_finished {
582+
// Go back to idle
583+
commands
584+
.entity(entity)
585+
.remove::<BossBombThrow>()
586+
.insert(Idling);
587+
}
588+
}
589+
}
590+
545591
// Initiate any transitions from the hit stun state
546592
fn transition_from_hitstun(
547593
mut commands: Commands,
@@ -999,6 +1045,112 @@ fn ground_slam(
9991045
}
10001046
}
10011047

1048+
fn bomb_throw(
1049+
mut commands: Commands,
1050+
mut fighters: Query<
1051+
(
1052+
&mut Animation,
1053+
&mut LinearVelocity,
1054+
&Facing,
1055+
&Transform,
1056+
&Handle<FighterMeta>,
1057+
&mut BossBombThrow,
1058+
&AvailableAttacks,
1059+
),
1060+
With<Boss>,
1061+
>,
1062+
fighter_assets: Res<Assets<FighterMeta>>,
1063+
item_assets: Res<Assets<ItemMeta>>,
1064+
) {
1065+
for (
1066+
mut animation,
1067+
mut velocity,
1068+
facing,
1069+
transform,
1070+
meta_handle,
1071+
mut bomb_throw,
1072+
available_attacks,
1073+
) in &mut fighters
1074+
{
1075+
// Start the attack
1076+
if let Some(fighter) = fighter_assets.get(meta_handle) {
1077+
let attack = available_attacks.current_attack();
1078+
let item = item_assets
1079+
.get(&attack.item_handle)
1080+
.expect("Fighter has no item");
1081+
1082+
let (mut sprite, mut frames) = (None, None);
1083+
if let ItemKind::Bomb {
1084+
attack_frames,
1085+
spritesheet,
1086+
} = &item.kind
1087+
{
1088+
sprite = Some(spritesheet);
1089+
frames = Some(attack_frames);
1090+
}
1091+
let (spritesheet, attack_frames) = (
1092+
sprite.expect("No bomb item found."),
1093+
frames.expect("No bomb item found;."),
1094+
);
1095+
1096+
let mut translation = transform.translation;
1097+
translation.z += 0.2; // Get above boss
1098+
translation.x +=
1099+
(spritesheet.tile_size.x / 3) as f32 * if facing.is_left() { -1. } else { 1. };
1100+
translation.y += (spritesheet.tile_size.y / 2) as f32;
1101+
let mut animated_sprite = AnimatedSpriteSheetBundle {
1102+
sprite_sheet: SpriteSheetBundle {
1103+
texture_atlas: spritesheet.atlas_handle[0].clone(),
1104+
transform: Transform::from_translation(translation),
1105+
..Default::default()
1106+
},
1107+
animation: Animation::new(
1108+
spritesheet.animation_fps,
1109+
spritesheet.animations.clone(),
1110+
),
1111+
};
1112+
animated_sprite.animation.current_animation = Some("bomb".to_string());
1113+
1114+
let mut offset = attack.hitbox.offset;
1115+
if facing.is_left() {
1116+
offset.x *= -1.0
1117+
}
1118+
offset.y += fighter.collision_offset;
1119+
1120+
if !bomb_throw.has_started {
1121+
bomb_throw.has_started = true;
1122+
1123+
// Start the attack from the beginning
1124+
animation.play(BossBombThrow::ANIMATION, false);
1125+
}
1126+
1127+
if !animation.is_finished() {
1128+
// Frames that each bomb is thrown
1129+
if (animation.current_frame == attack.frames.startup && !bomb_throw.thrown)
1130+
|| (animation.current_frame == attack.frames.active && bomb_throw.thrown)
1131+
{
1132+
// Spawn bomb
1133+
commands
1134+
.spawn_bundle(AnimatedProjectile::new(0, facing, animated_sprite.clone()))
1135+
.insert(Explodable {
1136+
attack: attack.clone(),
1137+
timer: Timer::from_seconds(consts::THROW_ITEM_LIFETIME, false),
1138+
fusing: false,
1139+
animated_sprite,
1140+
explosion_frames: *attack_frames,
1141+
});
1142+
bomb_throw.thrown = !bomb_throw.thrown;
1143+
}
1144+
} else if animation.is_finished() {
1145+
bomb_throw.is_finished = true;
1146+
}
1147+
1148+
// Stop boss
1149+
**velocity = Vec2::ZERO;
1150+
}
1151+
}
1152+
}
1153+
10021154
/// Handle fighter moving state
10031155
fn moving(
10041156
mut commands: Commands,
@@ -1206,6 +1358,9 @@ fn throwing(
12061358
}
12071359
}
12081360
}
1361+
ItemKind::Bomb { .. } => {
1362+
panic!("Can't throw bomb")
1363+
}
12091364
}
12101365
}
12111366

@@ -1403,6 +1558,9 @@ fn grabbing(
14031558
.id();
14041559
commands.entity(fighter_ent).add_child(weapon);
14051560
}
1561+
ItemKind::Bomb { .. } => {
1562+
panic!("Can't pick up bomb")
1563+
}
14061564
}
14071565
}
14081566
break;

0 commit comments

Comments
 (0)