2020-09-12 04:43:29 +00:00
|
|
|
use num_traits::clamp;
|
|
|
|
|
2020-09-12 00:42:44 +00:00
|
|
|
use crate::caret::CaretType;
|
2020-12-04 18:58:59 +00:00
|
|
|
use crate::common::{BulletFlag, Condition, Direction, Flag, Rect};
|
2020-09-12 00:42:44 +00:00
|
|
|
use crate::engine_constants::{BulletData, EngineConstants};
|
2020-12-25 22:39:41 +00:00
|
|
|
use crate::npc::list::NPCList;
|
|
|
|
use crate::npc::NPC;
|
2021-06-27 01:06:56 +00:00
|
|
|
use crate::physics::{PhysicalEntity, OFFSETS};
|
2021-03-09 14:05:38 +00:00
|
|
|
use crate::player::{Player, TargetPlayer};
|
2021-02-26 20:03:35 +00:00
|
|
|
use crate::rng::{XorShift, Xoroshiro32PlusPlus, RNG};
|
2021-06-27 01:06:56 +00:00
|
|
|
use crate::shared_game_state::{SharedGameState, TileSize};
|
2020-09-12 00:42:44 +00:00
|
|
|
use crate::stage::Stage;
|
|
|
|
|
|
|
|
pub struct BulletManager {
|
|
|
|
pub bullets: Vec<Bullet>,
|
2021-01-16 13:51:52 +00:00
|
|
|
pub new_bullets: Vec<Bullet>,
|
2021-01-18 19:30:40 +00:00
|
|
|
pub seeder: XorShift,
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl BulletManager {
|
2020-09-12 21:43:09 +00:00
|
|
|
#[allow(clippy::new_without_default)]
|
2020-09-12 00:42:44 +00:00
|
|
|
pub fn new() -> BulletManager {
|
2021-08-16 16:38:06 +00:00
|
|
|
BulletManager {
|
|
|
|
bullets: Vec::with_capacity(64),
|
|
|
|
new_bullets: Vec::with_capacity(8),
|
|
|
|
seeder: XorShift::new(0x359c482f),
|
|
|
|
}
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
pub fn create_bullet(
|
|
|
|
&mut self,
|
|
|
|
x: i32,
|
|
|
|
y: i32,
|
|
|
|
btype: u16,
|
|
|
|
owner: TargetPlayer,
|
|
|
|
direction: Direction,
|
|
|
|
constants: &EngineConstants,
|
|
|
|
) {
|
2021-01-18 19:30:40 +00:00
|
|
|
let mut bullet = Bullet::new(x, y, btype, owner, direction, constants);
|
|
|
|
bullet.rng = Xoroshiro32PlusPlus::new(self.seeder.next_u32());
|
|
|
|
|
|
|
|
self.bullets.push(bullet);
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
2021-01-18 19:30:40 +00:00
|
|
|
pub fn push_bullet(&mut self, mut bullet: Bullet) {
|
|
|
|
bullet.rng = Xoroshiro32PlusPlus::new(self.seeder.next_u32());
|
2021-01-16 13:51:52 +00:00
|
|
|
self.bullets.push(bullet);
|
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
pub fn tick_bullets(
|
|
|
|
&mut self,
|
|
|
|
state: &mut SharedGameState,
|
|
|
|
players: [&Player; 2],
|
|
|
|
npc_list: &NPCList,
|
|
|
|
stage: &mut Stage,
|
|
|
|
) {
|
2021-01-16 13:51:52 +00:00
|
|
|
let mut i = 0;
|
|
|
|
while i < self.bullets.len() {
|
|
|
|
{
|
|
|
|
let bullet = unsafe { self.bullets.get_unchecked_mut(i) };
|
|
|
|
i += 1;
|
|
|
|
|
|
|
|
if bullet.life < 1 {
|
|
|
|
bullet.cond.set_alive(false);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
bullet.tick(state, players, npc_list, &mut self.new_bullets);
|
|
|
|
bullet.tick_map_collisions(state, npc_list, stage);
|
2020-09-13 00:39:43 +00:00
|
|
|
}
|
2020-09-13 03:30:56 +00:00
|
|
|
|
2022-01-17 22:29:30 +00:00
|
|
|
for bullet in &mut self.new_bullets {
|
2021-01-18 19:30:40 +00:00
|
|
|
bullet.rng = Xoroshiro32PlusPlus::new(self.seeder.next_u32());
|
|
|
|
}
|
|
|
|
|
2021-01-16 13:51:52 +00:00
|
|
|
self.bullets.append(&mut self.new_bullets);
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.bullets.retain(|b| !b.is_dead());
|
|
|
|
}
|
|
|
|
|
2020-12-05 21:24:38 +00:00
|
|
|
pub fn count_bullets(&self, btype: u16, player_id: TargetPlayer) -> usize {
|
|
|
|
self.bullets.iter().filter(|b| b.owner == player_id && b.btype == btype).count()
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
2020-12-15 22:09:14 +00:00
|
|
|
pub fn count_bullets_type_idx_all(&self, type_idx: u16) -> usize {
|
|
|
|
self.bullets.iter().filter(|b| (b.btype.saturating_sub(2) / 3) == type_idx).count()
|
2020-12-07 19:20:19 +00:00
|
|
|
}
|
|
|
|
|
2021-01-16 13:51:52 +00:00
|
|
|
pub fn count_bullets_multi(&self, btypes: &[u16], player_id: TargetPlayer) -> usize {
|
2020-12-05 21:24:38 +00:00
|
|
|
self.bullets.iter().filter(|b| b.owner == player_id && btypes.contains(&b.btype)).count()
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-11 16:30:18 +00:00
|
|
|
|
2021-03-12 16:53:08 +00:00
|
|
|
#[derive(Clone)]
|
2020-09-11 16:30:18 +00:00
|
|
|
pub struct Bullet {
|
|
|
|
pub btype: u16,
|
2021-01-01 01:46:01 +00:00
|
|
|
pub x: i32,
|
|
|
|
pub y: i32,
|
|
|
|
pub vel_x: i32,
|
|
|
|
pub vel_y: i32,
|
|
|
|
pub target_x: i32,
|
|
|
|
pub target_y: i32,
|
|
|
|
pub prev_x: i32,
|
|
|
|
pub prev_y: i32,
|
2020-09-11 16:30:18 +00:00
|
|
|
pub life: u16,
|
|
|
|
pub lifetime: u16,
|
2021-01-18 19:30:40 +00:00
|
|
|
pub damage: i16,
|
|
|
|
pub counter1: u16,
|
2021-03-12 16:53:08 +00:00
|
|
|
pub counter2: u16,
|
2021-01-18 19:30:40 +00:00
|
|
|
pub rng: Xoroshiro32PlusPlus,
|
2020-12-05 21:24:38 +00:00
|
|
|
pub owner: TargetPlayer,
|
2020-09-11 16:30:18 +00:00
|
|
|
pub cond: Condition,
|
2020-12-04 18:58:59 +00:00
|
|
|
pub weapon_flags: BulletFlag,
|
2020-09-11 16:30:18 +00:00
|
|
|
pub flags: Flag,
|
|
|
|
pub direction: Direction,
|
2020-11-17 02:42:45 +00:00
|
|
|
pub anim_rect: Rect<u16>,
|
2020-09-11 16:30:18 +00:00
|
|
|
pub enemy_hit_width: u32,
|
|
|
|
pub enemy_hit_height: u32,
|
|
|
|
pub anim_num: u16,
|
|
|
|
pub anim_counter: u16,
|
|
|
|
pub action_num: u16,
|
|
|
|
pub action_counter: u16,
|
2021-03-29 21:19:07 +00:00
|
|
|
pub hit_bounds: Rect<u32>,
|
|
|
|
pub display_bounds: Rect<u32>,
|
2020-09-11 16:30:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Bullet {
|
2021-08-16 16:38:06 +00:00
|
|
|
pub fn new(
|
|
|
|
x: i32,
|
|
|
|
y: i32,
|
|
|
|
btype: u16,
|
|
|
|
owner: TargetPlayer,
|
|
|
|
direction: Direction,
|
|
|
|
constants: &EngineConstants,
|
|
|
|
) -> Bullet {
|
2021-02-26 20:03:35 +00:00
|
|
|
let bullet = constants.weapon.bullet_table.get(btype as usize).unwrap_or_else(|| &BulletData {
|
|
|
|
damage: 0,
|
|
|
|
life: 0,
|
|
|
|
lifetime: 0,
|
|
|
|
flags: BulletFlag(0),
|
|
|
|
enemy_hit_width: 0,
|
|
|
|
enemy_hit_height: 0,
|
|
|
|
block_hit_width: 0,
|
|
|
|
block_hit_height: 0,
|
|
|
|
display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 },
|
|
|
|
});
|
2020-09-12 00:42:44 +00:00
|
|
|
|
|
|
|
Bullet {
|
|
|
|
btype,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
vel_x: 0,
|
|
|
|
vel_y: 0,
|
|
|
|
target_x: 0,
|
|
|
|
target_y: 0,
|
2020-11-07 17:17:01 +00:00
|
|
|
prev_x: x,
|
|
|
|
prev_y: y,
|
2020-09-12 00:42:44 +00:00
|
|
|
life: bullet.life as u16,
|
|
|
|
lifetime: bullet.lifetime,
|
2021-01-18 19:30:40 +00:00
|
|
|
damage: bullet.damage as i16,
|
|
|
|
counter1: 0,
|
2021-03-12 16:53:08 +00:00
|
|
|
counter2: 0,
|
2021-01-18 19:30:40 +00:00
|
|
|
rng: Xoroshiro32PlusPlus::new(1),
|
2020-12-05 21:24:38 +00:00
|
|
|
owner,
|
2020-09-12 00:42:44 +00:00
|
|
|
cond: Condition(0x80),
|
2020-10-02 01:30:42 +00:00
|
|
|
weapon_flags: bullet.flags,
|
|
|
|
flags: Flag(0),
|
2020-09-12 00:42:44 +00:00
|
|
|
direction,
|
|
|
|
anim_rect: Rect::new(0, 0, 0, 0),
|
|
|
|
enemy_hit_width: bullet.enemy_hit_width as u32 * 0x200,
|
|
|
|
enemy_hit_height: bullet.enemy_hit_height as u32 * 0x200,
|
|
|
|
anim_num: 0,
|
|
|
|
anim_counter: 0,
|
|
|
|
action_num: 0,
|
|
|
|
action_counter: 0,
|
|
|
|
display_bounds: Rect::new(
|
2021-03-29 21:19:07 +00:00
|
|
|
bullet.display_bounds.left as u32 * 0x200,
|
|
|
|
bullet.display_bounds.top as u32 * 0x200,
|
|
|
|
bullet.display_bounds.right as u32 * 0x200,
|
|
|
|
bullet.display_bounds.bottom as u32 * 0x200,
|
2020-09-12 00:42:44 +00:00
|
|
|
),
|
|
|
|
hit_bounds: Rect::new(
|
2021-03-29 21:19:07 +00:00
|
|
|
bullet.block_hit_width as u32 * 0x200,
|
|
|
|
bullet.block_hit_height as u32 * 0x200,
|
|
|
|
bullet.block_hit_width as u32 * 0x200,
|
|
|
|
bullet.block_hit_height as u32 * 0x200,
|
2020-09-12 00:42:44 +00:00
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn is_dead(&self) -> bool {
|
|
|
|
!self.cond.alive()
|
|
|
|
}
|
|
|
|
|
2020-10-02 01:30:42 +00:00
|
|
|
fn tick_snake_1(&mut self, state: &mut SharedGameState) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
self.anim_num = state.game_rng.range(0..2) as u16;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x = -0x600,
|
|
|
|
Direction::Up => self.vel_y = -0x600,
|
|
|
|
Direction::Right => self.vel_x = 0x600,
|
|
|
|
Direction::Bottom => self.vel_y = 0x600,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
2021-01-16 13:51:52 +00:00
|
|
|
self.anim_num = (self.anim_num + 1) % 4;
|
2020-10-02 01:30:42 +00:00
|
|
|
|
|
|
|
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b001_snake_l1[self.anim_num as usize + dir_offset];
|
|
|
|
}
|
|
|
|
|
2021-01-16 13:51:52 +00:00
|
|
|
fn tick_snake_2(&mut self, state: &mut SharedGameState, npc_list: &NPCList) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
self.anim_num = state.game_rng.range(0..2) as u16;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
self.vel_x = -0x200;
|
|
|
|
self.vel_y = if self.target_x & 1 == 0 { -0x400 } else { 0x400 };
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
self.vel_y = -0x200;
|
|
|
|
self.vel_x = if self.target_x & 1 == 0 { -0x400 } else { 0x400 };
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
self.vel_x = 0x200;
|
|
|
|
self.vel_y = if self.target_x & 1 == 0 { -0x400 } else { 0x400 };
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
self.vel_y = 0x200;
|
|
|
|
self.vel_x = if self.target_x & 1 == 0 { -0x400 } else { 0x400 };
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x += -0x80,
|
|
|
|
Direction::Up => self.vel_y += -0x80,
|
|
|
|
Direction::Right => self.vel_x += 0x80,
|
|
|
|
Direction::Bottom => self.vel_y += 0x80,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_counter % 5 == 2 {
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.vel_y = if self.vel_y < 0 { 0x400 } else { -0x400 };
|
|
|
|
}
|
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.vel_x = if self.vel_x < 0 { 0x400 } else { -0x400 };
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_num = (self.anim_num + 1) % 3;
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b002_003_snake_l2_3[self.anim_num as usize];
|
|
|
|
|
|
|
|
let mut npc = NPC::create(129, &state.npc_table);
|
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.x = self.x;
|
|
|
|
npc.y = self.y;
|
|
|
|
npc.vel_y = -0x200;
|
|
|
|
npc.action_counter2 = if self.btype == 3 { self.anim_num + 3 } else { self.anim_num };
|
|
|
|
|
|
|
|
let _ = npc_list.spawn(0x100, npc);
|
|
|
|
}
|
|
|
|
|
2020-09-12 00:42:44 +00:00
|
|
|
fn tick_polar_star(&mut self, state: &mut SharedGameState) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
2020-09-30 03:11:25 +00:00
|
|
|
Direction::Left => self.vel_x = -0x1000,
|
|
|
|
Direction::Up => self.vel_y = -0x1000,
|
|
|
|
Direction::Right => self.vel_x = 0x1000,
|
|
|
|
Direction::Bottom => self.vel_y = 0x1000,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
2021-02-26 20:03:35 +00:00
|
|
|
4 => match self.direction {
|
|
|
|
Direction::Left | Direction::Right => self.enemy_hit_height = 0x400,
|
|
|
|
Direction::Up | Direction::Bottom => self.enemy_hit_width = 0x400,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
},
|
|
|
|
5 => match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.enemy_hit_height = 0x800;
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.enemy_hit_width = 0x800;
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
},
|
2020-09-12 00:42:44 +00:00
|
|
|
6 => {
|
|
|
|
// level 3 uses default values
|
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
_ => {
|
|
|
|
unreachable!()
|
|
|
|
}
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
|
|
|
4 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_num = 1;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b004_polar_star_l1[1];
|
|
|
|
} else {
|
|
|
|
self.anim_num = 0;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b004_polar_star_l1[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
5 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_num = 1;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b005_polar_star_l2[1];
|
|
|
|
} else {
|
|
|
|
self.anim_num = 0;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b005_polar_star_l2[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
6 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_num = 1;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b006_polar_star_l3[1];
|
|
|
|
} else {
|
|
|
|
self.anim_num = 0;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b006_polar_star_l3[0];
|
|
|
|
}
|
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
_ => {
|
|
|
|
unreachable!()
|
|
|
|
}
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:05:38 +00:00
|
|
|
fn tick_fireball(&mut self, state: &mut SharedGameState, players: [&Player; 2], npc_list: &NPCList) {
|
2020-09-12 21:43:09 +00:00
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
if (self.flags.hit_left_wall() && self.flags.hit_right_wall())
|
|
|
|
|| (self.flags.hit_top_wall() && self.flags.hit_bottom_wall())
|
|
|
|
{
|
2020-09-12 21:43:09 +00:00
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
|
2020-10-02 01:30:42 +00:00
|
|
|
state.sound_manager.play_sfx(28);
|
2020-09-12 21:43:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// bounce off walls
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left if self.flags.hit_left_wall() => {
|
|
|
|
self.direction = Direction::Right;
|
|
|
|
}
|
|
|
|
Direction::Right if self.flags.hit_right_wall() => {
|
|
|
|
self.direction = Direction::Left;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
self.vel_x = -0x400;
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
self.vel_x = 0x400;
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
2020-12-05 21:24:38 +00:00
|
|
|
self.vel_x = players[self.owner.index()].vel_x();
|
2020-09-12 21:43:09 +00:00
|
|
|
|
2021-02-26 20:03:35 +00:00
|
|
|
self.direction = if self.vel_x < 0 { Direction::Left } else { Direction::Right };
|
2020-09-12 21:43:09 +00:00
|
|
|
|
2021-05-05 16:34:23 +00:00
|
|
|
self.vel_x += players[self.owner.index()].direction().vector_x() * 0x80;
|
2020-09-12 21:43:09 +00:00
|
|
|
self.vel_y = -0x5ff;
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
2020-12-05 21:24:38 +00:00
|
|
|
self.vel_x = players[self.owner.index()].vel_x();
|
2020-09-12 21:43:09 +00:00
|
|
|
|
2021-02-26 20:03:35 +00:00
|
|
|
self.direction = if self.vel_x < 0 { Direction::Left } else { Direction::Right };
|
2020-09-12 21:43:09 +00:00
|
|
|
|
|
|
|
self.vel_y = 0x5ff;
|
|
|
|
}
|
2020-09-30 03:11:25 +00:00
|
|
|
Direction::FacingPlayer => unreachable!(),
|
2020-09-12 21:43:09 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if self.flags.hit_bottom_wall() {
|
|
|
|
self.vel_y = -0x400;
|
|
|
|
} else if self.flags.hit_left_wall() {
|
|
|
|
self.vel_x = 0x400;
|
|
|
|
} else if self.flags.hit_right_wall() {
|
|
|
|
self.vel_x = -0x400;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.vel_y += 0x55;
|
|
|
|
if self.vel_y > 0x3ff {
|
|
|
|
self.vel_y = 0x3ff;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
|
|
|
|
if self.flags.hit_left_wall() || self.flags.hit_right_wall() || self.flags.hit_bottom_wall() {
|
2020-10-02 01:30:42 +00:00
|
|
|
state.sound_manager.play_sfx(34);
|
2020-09-12 21:43:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_num += 1;
|
|
|
|
|
2021-02-26 20:03:35 +00:00
|
|
|
if self.btype == 7 {
|
|
|
|
// level 1
|
2020-10-02 01:30:42 +00:00
|
|
|
if self.anim_num > 3 {
|
|
|
|
self.anim_num = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b007_fireball_l1[self.anim_num as usize + dir_offset];
|
|
|
|
} else {
|
|
|
|
if self.anim_num > 2 {
|
|
|
|
self.anim_num = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let dir_offset = if self.direction == Direction::Left { 0 } else { 3 };
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
self.anim_rect =
|
|
|
|
state.constants.weapon.bullet_rects.b008_009_fireball_l2_3[self.anim_num as usize + dir_offset];
|
2020-10-02 01:30:42 +00:00
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
let mut npc = NPC::create(129, &state.npc_table);
|
2020-10-02 01:30:42 +00:00
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.x = self.x;
|
|
|
|
npc.y = self.y;
|
|
|
|
npc.vel_y = -0x200;
|
|
|
|
npc.action_counter2 = if self.btype == 9 { self.anim_num + 3 } else { self.anim_num };
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
let _ = npc_list.spawn(0x100, npc);
|
2020-09-12 21:43:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-26 20:03:35 +00:00
|
|
|
fn tick_machine_gun(&mut self, state: &mut SharedGameState, npc_list: &NPCList) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
self.vel_x = -0x1000;
|
|
|
|
self.vel_y = self.rng.range(-0xaa..0xaa);
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
self.vel_y = -0x1000;
|
|
|
|
self.vel_x = self.rng.range(-0xaa..0xaa);
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
self.vel_x = 0x1000;
|
|
|
|
self.vel_y = self.rng.range(-0xaa..0xaa);
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
self.vel_y = 0x1000;
|
|
|
|
self.vel_x = self.rng.range(-0xaa..0xaa);
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
|
|
|
|
match self.btype {
|
|
|
|
11 => {
|
|
|
|
let mut npc = NPC::create(127, &state.npc_table);
|
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.x = self.x;
|
|
|
|
npc.y = self.y;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
npc.direction = Direction::Left;
|
|
|
|
}
|
|
|
|
Direction::Bottom | Direction::Up => {
|
|
|
|
npc.direction = Direction::Up;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
let _ = npc_list.spawn(256, npc);
|
|
|
|
}
|
|
|
|
12 => {
|
|
|
|
let mut npc = NPC::create(128, &state.npc_table);
|
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.x = self.x;
|
|
|
|
npc.y = self.y;
|
|
|
|
npc.direction = self.direction;
|
|
|
|
|
|
|
|
let _ = npc_list.spawn(256, npc);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
|
|
|
10 => {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b010_machine_gun_l1[self.direction as usize];
|
|
|
|
}
|
|
|
|
11 => {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b011_machine_gun_l2[self.direction as usize];
|
|
|
|
}
|
|
|
|
12 => {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b012_machine_gun_l3[self.direction as usize];
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
fn tick_missile(&mut self, state: &mut SharedGameState, players: [&Player; 2], new_bullets: &mut Vec<Bullet>) {
|
|
|
|
let player = players[self.owner.index()];
|
|
|
|
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if match self.direction {
|
|
|
|
_ if self.life != 10 => true,
|
|
|
|
Direction::Left if self.flags.hit_left_wall() => true,
|
|
|
|
Direction::Left if self.flags.hit_left_slope() => true,
|
2022-02-25 02:00:10 +00:00
|
|
|
Direction::Left if self.flags.hit_upper_left_slope() => true,
|
2021-08-16 16:38:06 +00:00
|
|
|
Direction::Right if self.flags.hit_right_wall() => true,
|
|
|
|
Direction::Right if self.flags.hit_right_slope() => true,
|
2022-02-25 02:00:10 +00:00
|
|
|
Direction::Right if self.flags.hit_upper_right_slope() => true,
|
2021-08-16 16:38:06 +00:00
|
|
|
Direction::Up if self.flags.hit_top_wall() => true,
|
|
|
|
Direction::Bottom if self.flags.hit_bottom_wall() => true,
|
|
|
|
_ => false,
|
|
|
|
} {
|
|
|
|
let bomb_bullet = match self.btype {
|
|
|
|
13 => 16,
|
|
|
|
14 => 17,
|
|
|
|
15 => 18,
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let bullet = Bullet::new(self.x, self.y, bomb_bullet, self.owner, self.direction, &state.constants);
|
|
|
|
new_bullets.push(bullet);
|
|
|
|
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.target_y = self.y;
|
|
|
|
}
|
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.target_x = self.x;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.btype == 15 {
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.vel_y = (self.y - player.y).signum() * 0x100;
|
|
|
|
self.vel_x = self.rng.range(-0x200..0x200);
|
|
|
|
}
|
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.vel_x = (self.x - player.x).signum() * 0x100;
|
|
|
|
self.vel_y = self.rng.range(-0x200..0x200);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.counter1 = match self.counter2 {
|
|
|
|
1 => 64,
|
|
|
|
2 => 51,
|
|
|
|
_ => 128,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
self.counter1 = 128;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 1 {
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x -= self.counter1 as i32,
|
|
|
|
Direction::Up => self.vel_y -= self.counter1 as i32,
|
|
|
|
Direction::Right => self.vel_x += self.counter1 as i32,
|
|
|
|
Direction::Bottom => self.vel_y += self.counter1 as i32,
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.btype == 15 {
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.vel_y = (player.y - self.y).signum() * 0x20;
|
|
|
|
}
|
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.vel_x = (player.x - self.x).signum() * 0x20;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.vel_x = clamp(self.vel_x, -0xa00, 0xa00);
|
|
|
|
self.vel_y = clamp(self.vel_y, -0xa00, 0xa00);
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
self.counter2 += 1;
|
|
|
|
|
|
|
|
if self.counter2 > 2 {
|
|
|
|
self.counter2 = 0;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
state.create_caret(self.x + 0x1000, self.y, CaretType::Exhaust, self.direction.opposite());
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
state.create_caret(self.x, self.y + 0x1000, CaretType::Exhaust, self.direction.opposite());
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
state.create_caret(self.x - 0x1000, self.y, CaretType::Exhaust, self.direction.opposite());
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
state.create_caret(self.x, self.y - 0x1000, CaretType::Exhaust, self.direction.opposite());
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
|
|
|
13 => self.anim_rect = state.constants.weapon.bullet_rects.b013_missile_l1[self.direction as usize],
|
|
|
|
14 => self.anim_rect = state.constants.weapon.bullet_rects.b014_missile_l2[self.direction as usize],
|
|
|
|
15 => self.anim_rect = state.constants.weapon.bullet_rects.b015_missile_l3[self.direction as usize],
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_missile_explosion(&mut self, state: &mut SharedGameState, npc_list: &NPCList) {
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
self.action_counter = match self.btype {
|
|
|
|
16 => 10,
|
|
|
|
17 => 15,
|
|
|
|
18 => 5,
|
|
|
|
_ => 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
state.sound_manager.play_sfx(44);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_counter % 3 == 0 {
|
|
|
|
let radius = match self.btype {
|
|
|
|
16 => 16,
|
|
|
|
17 => 32,
|
|
|
|
18 => 40,
|
|
|
|
_ => 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
npc_list.create_death_smoke_up(
|
|
|
|
self.x + self.rng.range(-radius..radius) * 0x200,
|
|
|
|
self.y + self.rng.range(-radius..radius) * 0x200,
|
|
|
|
self.enemy_hit_width as usize,
|
|
|
|
2,
|
|
|
|
state,
|
|
|
|
&self.rng,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.action_counter -= 1;
|
|
|
|
if self.action_counter == 0 {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:05:38 +00:00
|
|
|
fn tick_bubble_1(&mut self, state: &mut SharedGameState) {
|
|
|
|
if self.flags.hit_anything() {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x = -0x600,
|
|
|
|
Direction::Up => self.vel_y = -0x600,
|
|
|
|
Direction::Right => self.vel_x = 0x600,
|
|
|
|
Direction::Bottom => self.vel_y = 0x600,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x += 0x2a,
|
|
|
|
Direction::Up => self.vel_y += 0x2a,
|
|
|
|
Direction::Right => self.vel_x -= 0x2a,
|
|
|
|
Direction::Bottom => self.vel_y -= 0x2a,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > 40 {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::SmallProjectileDissipation, Direction::Left);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_counter += 1;
|
|
|
|
if self.anim_counter > 3 {
|
|
|
|
self.anim_counter = 0;
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num > 3 {
|
|
|
|
self.anim_num = 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b019_bubble_l1[self.anim_num as usize];
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_bubble_2(&mut self, state: &mut SharedGameState) {
|
|
|
|
if (self.direction == Direction::Left && self.flags.hit_left_wall())
|
|
|
|
|| (self.direction == Direction::Right && self.flags.hit_right_wall())
|
|
|
|
|| (self.direction == Direction::Up && self.flags.hit_top_wall())
|
|
|
|
|| (self.direction == Direction::Bottom && self.flags.hit_bottom_wall())
|
|
|
|
{
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
self.vel_x = -0x600;
|
|
|
|
self.vel_y = self.rng.range(-0x100..0x100);
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
self.vel_y = -0x600;
|
|
|
|
self.vel_x = self.rng.range(-0x100..0x100);
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
self.vel_x = 0x600;
|
|
|
|
self.vel_y = self.rng.range(-0x100..0x100);
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
self.vel_y = 0x600;
|
|
|
|
self.vel_x = self.rng.range(-0x100..0x100);
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x += 0x10,
|
|
|
|
Direction::Up => self.vel_y += 0x10,
|
|
|
|
Direction::Right => self.vel_x -= 0x10,
|
|
|
|
Direction::Bottom => self.vel_y -= 0x10,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > 60 {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::SmallProjectileDissipation, Direction::Left);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_counter += 1;
|
|
|
|
if self.anim_counter > 3 {
|
|
|
|
self.anim_counter = 0;
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num > 3 {
|
|
|
|
self.anim_num = 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b020_bubble_l2[self.anim_num as usize];
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_bubble_3(&mut self, state: &mut SharedGameState, players: [&Player; 2], new_bullets: &mut Vec<Bullet>) {
|
|
|
|
let player = players[self.owner.index()];
|
|
|
|
|
|
|
|
self.action_counter += 1;
|
|
|
|
|
|
|
|
if self.action_counter > 100 || !player.controller.shoot() {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
|
|
|
|
state.sound_manager.play_sfx(100);
|
|
|
|
|
|
|
|
match () {
|
2021-08-16 16:38:06 +00:00
|
|
|
_ if player.up => {
|
|
|
|
new_bullets.push(Bullet::new(self.x, self.y, 22, self.owner, Direction::Up, &state.constants))
|
|
|
|
}
|
|
|
|
_ if player.down => {
|
|
|
|
new_bullets.push(Bullet::new(self.x, self.y, 22, self.owner, Direction::Bottom, &state.constants))
|
|
|
|
}
|
2021-03-09 14:05:38 +00:00
|
|
|
_ => new_bullets.push(Bullet::new(self.x, self.y, 22, self.owner, player.direction, &state.constants)),
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-12 16:53:08 +00:00
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
2021-03-09 14:05:38 +00:00
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
self.vel_x = self.rng.range(-0x400..-0x200);
|
2021-03-12 16:53:08 +00:00
|
|
|
self.vel_y = (self.rng.range(-4..4) * 0x200) / 2;
|
2021-03-09 14:05:38 +00:00
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
self.vel_y = self.rng.range(-0x400..-0x200);
|
2021-03-12 16:53:08 +00:00
|
|
|
self.vel_x = (self.rng.range(-4..4) * 0x200) / 2;
|
2021-03-09 14:05:38 +00:00
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
self.vel_x = self.rng.range(0x200..0x400);
|
2021-03-12 16:53:08 +00:00
|
|
|
self.vel_y = (self.rng.range(-4..4) * 0x200) / 2;
|
2021-03-09 14:05:38 +00:00
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
self.vel_y = self.rng.range(0x80..0x100);
|
2021-03-12 16:53:08 +00:00
|
|
|
self.vel_x = (self.rng.range(-4..4) * 0x200) / 2;
|
2021-03-09 14:05:38 +00:00
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.vel_x += (player.x - self.x).signum() * 0x20;
|
|
|
|
self.vel_y += (player.y - self.y).signum() * 0x20;
|
|
|
|
|
|
|
|
if self.vel_x < 0 && self.flags.hit_left_wall() {
|
|
|
|
self.vel_x = 0x400;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.vel_x > 0 && self.flags.hit_right_wall() {
|
|
|
|
self.vel_x = -0x400;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.vel_y < 0 && self.flags.hit_top_wall() {
|
|
|
|
self.vel_y = 0x400;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.vel_y > 0 && self.flags.hit_bottom_wall() {
|
|
|
|
self.vel_y = -0x400;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
|
|
|
|
self.anim_counter += 1;
|
|
|
|
if self.anim_counter > 3 {
|
|
|
|
self.anim_counter = 0;
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num > 3 {
|
|
|
|
self.anim_num = 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b021_bubble_l3[self.anim_num as usize];
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_bubble_spines(&mut self, state: &mut SharedGameState) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime || self.flags.hit_bottom_wall() {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
let val = self.rng.range(10..16);
|
|
|
|
// what the fuck
|
|
|
|
self.vel_x = (((val * -0x200).rotate_left(1) & 1) - val * 0x200) / 2;
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
let val = self.rng.range(10..16);
|
|
|
|
// what the fuck
|
|
|
|
self.vel_y = (((val * -0x200).rotate_left(1) & 1) - val * 0x200) / 2;
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
self.vel_x = (self.rng.range(10..16) * 0x200) / 2;
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
self.vel_y = (self.rng.range(10..16) * 0x200) / 2;
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_counter += 1;
|
|
|
|
if self.anim_counter > 1 {
|
|
|
|
self.anim_counter = 0;
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num > 1 {
|
|
|
|
self.anim_num = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b022_bubble_spines[self.anim_num as usize];
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b022_bubble_spines[self.anim_num as usize + 2];
|
|
|
|
}
|
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b022_bubble_spines[self.anim_num as usize + 4];
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-18 19:30:40 +00:00
|
|
|
fn tick_blade_slash(&mut self, state: &mut SharedGameState) {
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
self.x += if self.direction == Direction::Left { 0x2000 } else { -0x2000 };
|
|
|
|
self.y -= 0x1800;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_counter += 1;
|
|
|
|
if self.anim_counter > 2 {
|
|
|
|
self.anim_counter = 0;
|
|
|
|
self.anim_num += 1;
|
|
|
|
|
|
|
|
self.damage = if self.anim_num == 1 { 2 } else { 1 };
|
|
|
|
if self.anim_num > 4 {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += if self.direction == Direction::Left { -0x400 } else { 0x400 };
|
|
|
|
self.y += 0x400;
|
|
|
|
|
|
|
|
let dir_offset = if self.direction == Direction::Left { 0 } else { 5 };
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b023_blade_slash[self.anim_num as usize + dir_offset];
|
|
|
|
}
|
|
|
|
|
2021-10-08 02:41:31 +00:00
|
|
|
fn tick_spike(&mut self) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > 2 {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_rect = Rect::new(0, 0, 0, 0);
|
|
|
|
}
|
|
|
|
|
2021-01-18 19:30:40 +00:00
|
|
|
fn tick_blade_1(&mut self, state: &mut SharedGameState) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_counter == 3 {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.weapon_flags.set_no_collision_checks(false);
|
2021-01-18 19:30:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_counter % 5 == 1 {
|
|
|
|
state.sound_manager.play_sfx(34);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x = -0x800,
|
|
|
|
Direction::Up => self.vel_y = -0x800,
|
|
|
|
Direction::Right => self.vel_x = 0x800,
|
|
|
|
Direction::Bottom => self.vel_y = 0x800,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_counter += 1;
|
|
|
|
if self.anim_counter > 1 {
|
|
|
|
self.anim_counter = 0;
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num > 3 {
|
|
|
|
self.anim_num = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b025_blade_l1[self.anim_num as usize + dir_offset];
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_blade_2(&mut self, state: &mut SharedGameState) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_counter == 3 {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.weapon_flags.set_no_collision_checks(false);
|
2021-01-18 19:30:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_counter % 7 == 1 {
|
|
|
|
state.sound_manager.play_sfx(106);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x = -0x800,
|
|
|
|
Direction::Up => self.vel_y = -0x800,
|
|
|
|
Direction::Right => self.vel_x = 0x800,
|
|
|
|
Direction::Bottom => self.vel_y = 0x800,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_counter += 1;
|
|
|
|
if self.anim_counter > 1 {
|
|
|
|
self.anim_counter = 0;
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num > 3 {
|
|
|
|
self.anim_num = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b026_blade_l2[self.anim_num as usize + dir_offset];
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_blade_3(&mut self, state: &mut SharedGameState, new_bullets: &mut Vec<Bullet>) {
|
|
|
|
match self.action_num {
|
|
|
|
0 | 1 => {
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
self.vel_x = 0;
|
|
|
|
self.vel_y = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x = -0x800,
|
|
|
|
Direction::Up => self.vel_y = -0x800,
|
|
|
|
Direction::Right => self.vel_x = 0x800,
|
|
|
|
Direction::Bottom => self.vel_y = 0x800,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.life != 100 {
|
|
|
|
self.action_num = 2;
|
|
|
|
self.anim_num = 1;
|
|
|
|
self.damage = -1;
|
|
|
|
self.action_counter = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter % 4 == 1 {
|
|
|
|
state.sound_manager.play_sfx(106);
|
|
|
|
|
|
|
|
self.counter1 += 1;
|
2021-02-26 20:03:35 +00:00
|
|
|
let direction = if self.counter1 % 2 != 0 { Direction::Left } else { Direction::Right };
|
2021-01-18 19:30:40 +00:00
|
|
|
|
|
|
|
new_bullets.push(Bullet::new(self.x, self.y, 23, self.owner, direction, &state.constants));
|
|
|
|
}
|
|
|
|
|
|
|
|
self.counter1 += 1;
|
|
|
|
if self.counter1 == 5 {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.weapon_flags.set_no_collision_checks(false);
|
2021-01-18 19:30:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if self.counter1 > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
2 => {
|
|
|
|
self.vel_x = 0;
|
|
|
|
self.vel_y = 0;
|
|
|
|
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.rng.range(-1..1) == 0 {
|
|
|
|
state.sound_manager.play_sfx(106);
|
|
|
|
|
|
|
|
let x = self.rng.range(-64..64) * 0x200 + self.x;
|
|
|
|
let y = self.rng.range(-64..64) * 0x200 + self.y;
|
2021-02-26 20:03:35 +00:00
|
|
|
let direction = if self.rng.range(0..1) != 0 { Direction::Left } else { Direction::Right };
|
2021-01-18 19:30:40 +00:00
|
|
|
|
|
|
|
new_bullets.push(Bullet::new(x, y, 23, self.owner, direction, &state.constants));
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_counter > 50 {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
|
|
|
|
let dir_offset = match self.direction {
|
|
|
|
Direction::Left => 0,
|
|
|
|
Direction::Up => 2,
|
|
|
|
Direction::Right => 4,
|
|
|
|
Direction::Bottom => 6,
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b027_blade_l3[self.anim_num as usize + dir_offset];
|
|
|
|
|
|
|
|
if self.action_counter % 2 != 0 {
|
|
|
|
self.anim_rect = Rect { left: 0, top: 0, right: 0, bottom: 0 };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
fn tick_super_missile(
|
|
|
|
&mut self,
|
|
|
|
state: &mut SharedGameState,
|
|
|
|
players: [&Player; 2],
|
|
|
|
new_bullets: &mut Vec<Bullet>,
|
|
|
|
) {
|
2021-03-12 16:53:08 +00:00
|
|
|
let player = players[self.owner.index()];
|
|
|
|
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if match self.direction {
|
2021-08-16 16:38:06 +00:00
|
|
|
_ if self.life != 10 => true,
|
2021-03-12 16:53:08 +00:00
|
|
|
Direction::Left if self.flags.hit_left_wall() => true,
|
|
|
|
Direction::Left if self.flags.hit_left_slope() => true,
|
2022-02-25 02:00:10 +00:00
|
|
|
Direction::Left if self.flags.hit_upper_left_slope() => true,
|
2021-03-12 16:53:08 +00:00
|
|
|
Direction::Right if self.flags.hit_right_wall() => true,
|
|
|
|
Direction::Right if self.flags.hit_right_slope() => true,
|
2022-02-25 02:00:10 +00:00
|
|
|
Direction::Right if self.flags.hit_upper_right_slope() => true,
|
2021-03-12 16:53:08 +00:00
|
|
|
Direction::Up if self.flags.hit_top_wall() => true,
|
|
|
|
Direction::Bottom if self.flags.hit_bottom_wall() => true,
|
|
|
|
_ => false,
|
|
|
|
} {
|
|
|
|
let bomb_bullet = match self.btype {
|
|
|
|
28 => 31,
|
|
|
|
29 => 32,
|
|
|
|
30 => 33,
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let bullet = Bullet::new(self.x, self.y, bomb_bullet, self.owner, self.direction, &state.constants);
|
|
|
|
new_bullets.push(bullet);
|
|
|
|
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.target_y = self.y;
|
|
|
|
self.enemy_hit_height = 0x1000;
|
2022-02-25 02:00:10 +00:00
|
|
|
self.hit_bounds.left = 0x1000;
|
|
|
|
self.hit_bounds.right = 0x1000;
|
2021-03-12 16:53:08 +00:00
|
|
|
}
|
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.target_x = self.x;
|
|
|
|
self.enemy_hit_width = 0x1000;
|
2022-02-25 02:00:10 +00:00
|
|
|
self.hit_bounds.top = 0x1000;
|
|
|
|
self.hit_bounds.bottom = 0x1000;
|
2021-03-12 16:53:08 +00:00
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.btype == 30 {
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.vel_y = (self.y - player.y).signum() * 0x100;
|
|
|
|
self.vel_x = self.rng.range(-0x200..0x200);
|
|
|
|
}
|
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.vel_x = (self.x - player.x).signum() * 0x100;
|
|
|
|
self.vel_y = self.rng.range(-0x200..0x200);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
self.counter1 = match self.counter2 {
|
|
|
|
1 => 256,
|
|
|
|
2 => 170,
|
|
|
|
_ => 512,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
self.counter1 = 512;
|
|
|
|
}
|
2021-03-12 16:53:08 +00:00
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
if self.action_num == 1 {
|
2021-03-12 16:53:08 +00:00
|
|
|
match self.direction {
|
2021-08-16 16:38:06 +00:00
|
|
|
Direction::Left => self.vel_x -= self.counter1 as i32,
|
|
|
|
Direction::Up => self.vel_y -= self.counter1 as i32,
|
|
|
|
Direction::Right => self.vel_x += self.counter1 as i32,
|
|
|
|
Direction::Bottom => self.vel_y += self.counter1 as i32,
|
2021-03-12 16:53:08 +00:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
if self.btype == 30 {
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.vel_y = (player.y - self.y).signum() * 0x40;
|
|
|
|
}
|
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.vel_x = (player.x - self.x).signum() * 0x40;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
2021-03-12 16:53:08 +00:00
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
self.vel_x = clamp(self.vel_x, -0x1400, 0x1400);
|
|
|
|
self.vel_y = clamp(self.vel_y, -0x1400, 0x1400);
|
2021-03-12 16:53:08 +00:00
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
2021-03-12 16:53:08 +00:00
|
|
|
self.counter2 += 1;
|
|
|
|
|
|
|
|
if self.counter2 > 2 {
|
|
|
|
self.counter2 = 0;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
state.create_caret(self.x + 0x1000, self.y, CaretType::Exhaust, self.direction.opposite());
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
state.create_caret(self.x, self.y + 0x1000, CaretType::Exhaust, self.direction.opposite());
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
state.create_caret(self.x - 0x1000, self.y, CaretType::Exhaust, self.direction.opposite());
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
state.create_caret(self.x, self.y - 0x1000, CaretType::Exhaust, self.direction.opposite());
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
|
|
|
28 | 30 => {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b028_super_missile_l1[self.direction as usize];
|
|
|
|
}
|
|
|
|
29 => {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b029_super_missile_l2[self.direction as usize];
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_super_missile_explosion(&mut self, state: &mut SharedGameState, npc_list: &NPCList) {
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
self.action_counter = match self.btype {
|
|
|
|
31 => 10,
|
|
|
|
32 => 14,
|
|
|
|
33 => 6,
|
|
|
|
_ => 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
state.sound_manager.play_sfx(44);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_counter % 3 == 0 {
|
|
|
|
let radius = match self.btype {
|
|
|
|
31 => 16,
|
|
|
|
32 => 32,
|
|
|
|
33 => 40,
|
|
|
|
_ => 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
npc_list.create_death_smoke_up(
|
|
|
|
self.x + self.rng.range(-radius..radius) * 0x200,
|
|
|
|
self.y + self.rng.range(-radius..radius) * 0x200,
|
|
|
|
self.enemy_hit_width as usize,
|
|
|
|
2,
|
|
|
|
state,
|
|
|
|
&self.rng,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.action_counter -= 1;
|
|
|
|
if self.action_counter == 0 {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:05:38 +00:00
|
|
|
fn tick_nemesis(&mut self, state: &mut SharedGameState, npc_list: &NPCList) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x = -0x1000,
|
|
|
|
Direction::Up => self.vel_y = -0x1000,
|
|
|
|
Direction::Right => self.vel_x = 0x1000,
|
|
|
|
Direction::Bottom => self.vel_y = 0x1000,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.btype == 36 {
|
|
|
|
self.vel_x /= 3;
|
|
|
|
self.vel_y /= 3;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if self.btype == 34 && self.action_counter % 4 == 1 {
|
|
|
|
let mut npc = NPC::create(4, &state.npc_table);
|
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.x = self.x;
|
|
|
|
npc.y = self.y;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
npc.vel_y = self.rng.range(-0x200..0x200);
|
|
|
|
npc.vel_x = -0x200;
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
npc.vel_x = self.rng.range(-0x200..0x200);
|
|
|
|
npc.vel_y = -0x200;
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
npc.vel_y = self.rng.range(-0x200..0x200);
|
|
|
|
npc.vel_x = 0x200;
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
npc.vel_x = self.rng.range(-0x200..0x200);
|
|
|
|
npc.vel_y = 0x200;
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
let _ = npc_list.spawn(256, npc);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num > 1 {
|
|
|
|
self.anim_num = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let dir_offset = match self.direction {
|
|
|
|
Direction::Left => 0,
|
|
|
|
Direction::Up => 2,
|
|
|
|
Direction::Right => 4,
|
|
|
|
Direction::Bottom => 6,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
} + self.anim_num as usize;
|
|
|
|
|
|
|
|
self.anim_rect = match self.btype {
|
|
|
|
34 => state.constants.weapon.bullet_rects.b034_nemesis_l1[dir_offset],
|
|
|
|
35 => state.constants.weapon.bullet_rects.b035_nemesis_l2[dir_offset],
|
|
|
|
36 => state.constants.weapon.bullet_rects.b036_nemesis_l3[dir_offset],
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-08 04:52:26 +00:00
|
|
|
fn tick_nemesis_curly(&mut self, state: &mut SharedGameState, npc_list: &NPCList) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x = -0x1000,
|
|
|
|
Direction::Up => self.vel_y = -0x1000,
|
|
|
|
Direction::Right => self.vel_x = 0x1000,
|
|
|
|
Direction::Bottom => self.vel_y = 0x1000,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if self.action_counter % 4 == 1 {
|
|
|
|
let mut npc = NPC::create(4, &state.npc_table);
|
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.x = self.x;
|
|
|
|
npc.y = self.y;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => {
|
|
|
|
npc.vel_y = self.rng.range(-0x200..0x200);
|
|
|
|
npc.vel_x = -0x200;
|
|
|
|
}
|
|
|
|
Direction::Up => {
|
|
|
|
npc.vel_x = self.rng.range(-0x200..0x200);
|
|
|
|
npc.vel_y = -0x200;
|
|
|
|
}
|
|
|
|
Direction::Right => {
|
|
|
|
npc.vel_y = self.rng.range(-0x200..0x200);
|
|
|
|
npc.vel_x = 0x200;
|
|
|
|
}
|
|
|
|
Direction::Bottom => {
|
|
|
|
npc.vel_x = self.rng.range(-0x200..0x200);
|
|
|
|
npc.vel_y = 0x200;
|
|
|
|
}
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
let _ = npc_list.spawn(256, npc);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num > 1 {
|
|
|
|
self.anim_num = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let dir_offset = match self.direction {
|
|
|
|
Direction::Left => 0,
|
|
|
|
Direction::Up => 2,
|
|
|
|
Direction::Right => 4,
|
|
|
|
Direction::Bottom => 6,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
} + self.anim_num as usize;
|
|
|
|
|
|
|
|
self.anim_rect = match self.btype {
|
|
|
|
43 => state.constants.weapon.bullet_rects.b034_nemesis_l1[dir_offset],
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-01-16 13:51:52 +00:00
|
|
|
fn tick_spur(&mut self, state: &mut SharedGameState, new_bullets: &mut Vec<Bullet>) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > self.lifetime {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.action_num == 0 {
|
|
|
|
self.action_num = 1;
|
|
|
|
|
|
|
|
match self.direction {
|
|
|
|
Direction::Left => self.vel_x = -0x1000,
|
|
|
|
Direction::Up => self.vel_y = -0x1000,
|
|
|
|
Direction::Right => self.vel_x = 0x1000,
|
|
|
|
Direction::Bottom => self.vel_y = 0x1000,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
2021-02-26 20:03:35 +00:00
|
|
|
37 => match self.direction {
|
|
|
|
Direction::Left | Direction::Right => self.enemy_hit_height = 0x400,
|
|
|
|
Direction::Up | Direction::Bottom => self.enemy_hit_width = 0x400,
|
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
},
|
|
|
|
38 => match self.direction {
|
|
|
|
Direction::Left | Direction::Right => {
|
|
|
|
self.enemy_hit_height = 0x800;
|
2021-01-16 13:51:52 +00:00
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
Direction::Up | Direction::Bottom => {
|
|
|
|
self.enemy_hit_width = 0x800;
|
2021-01-16 13:51:52 +00:00
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
Direction::FacingPlayer => unreachable!(),
|
|
|
|
},
|
2021-01-16 13:51:52 +00:00
|
|
|
39 => {
|
|
|
|
// level 3 uses default values
|
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
_ => {
|
|
|
|
unreachable!()
|
|
|
|
}
|
2021-01-16 13:51:52 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.x += self.vel_x;
|
|
|
|
self.y += self.vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
|
|
|
37 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_num = 1;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b037_spur_l1[1];
|
|
|
|
} else {
|
|
|
|
self.anim_num = 0;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b037_spur_l1[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
new_bullets.push(Bullet::new(self.x, self.y, 40, self.owner, self.direction, &state.constants));
|
|
|
|
}
|
|
|
|
38 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_num = 1;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b038_spur_l2[1];
|
|
|
|
} else {
|
|
|
|
self.anim_num = 0;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b038_spur_l2[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
new_bullets.push(Bullet::new(self.x, self.y, 41, self.owner, self.direction, &state.constants));
|
|
|
|
}
|
|
|
|
39 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_num = 1;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b039_spur_l3[1];
|
|
|
|
} else {
|
|
|
|
self.anim_num = 0;
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b039_spur_l3[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
new_bullets.push(Bullet::new(self.x, self.y, 42, self.owner, self.direction, &state.constants));
|
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
_ => {
|
|
|
|
unreachable!()
|
|
|
|
}
|
2021-01-16 13:51:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_spur_trail(&mut self, state: &mut SharedGameState) {
|
|
|
|
self.action_counter += 1;
|
|
|
|
if self.action_counter > 20 {
|
|
|
|
self.anim_num = self.action_counter.wrapping_sub(20);
|
|
|
|
|
|
|
|
if self.anim_num > 2 {
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.damage != 0 && self.life != 100 {
|
|
|
|
self.damage = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
|
|
|
40 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b040_spur_trail_l1[self.anim_num as usize + 3];
|
|
|
|
} else {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b040_spur_trail_l1[self.anim_num as usize];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
41 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b041_spur_trail_l2[self.anim_num as usize + 3];
|
|
|
|
} else {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b041_spur_trail_l2[self.anim_num as usize];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
42 => {
|
|
|
|
if self.direction == Direction::Up || self.direction == Direction::Bottom {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b042_spur_trail_l3[self.anim_num as usize + 3];
|
|
|
|
} else {
|
|
|
|
self.anim_rect = state.constants.weapon.bullet_rects.b042_spur_trail_l3[self.anim_num as usize];
|
|
|
|
}
|
|
|
|
}
|
2021-02-26 20:03:35 +00:00
|
|
|
_ => {
|
|
|
|
unreachable!()
|
|
|
|
}
|
2021-01-16 13:51:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
pub fn tick(
|
|
|
|
&mut self,
|
|
|
|
state: &mut SharedGameState,
|
|
|
|
players: [&Player; 2],
|
|
|
|
npc_list: &NPCList,
|
|
|
|
new_bullets: &mut Vec<Bullet>,
|
|
|
|
) {
|
2021-01-18 19:30:40 +00:00
|
|
|
if self.life == 0 {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.cond.set_alive(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.btype {
|
2020-10-02 01:30:42 +00:00
|
|
|
1 => self.tick_snake_1(state),
|
2021-01-16 13:51:52 +00:00
|
|
|
2 | 3 => self.tick_snake_2(state, npc_list),
|
2020-10-02 01:30:42 +00:00
|
|
|
4 | 5 | 6 => self.tick_polar_star(state),
|
2020-12-25 22:39:41 +00:00
|
|
|
7 | 8 | 9 => self.tick_fireball(state, players, npc_list),
|
2021-02-26 20:03:35 +00:00
|
|
|
10 | 11 | 12 => self.tick_machine_gun(state, npc_list),
|
2021-08-16 16:38:06 +00:00
|
|
|
13 | 14 | 15 => self.tick_missile(state, players, new_bullets),
|
|
|
|
16 | 17 | 18 => self.tick_missile_explosion(state, npc_list),
|
2021-03-09 14:05:38 +00:00
|
|
|
19 => self.tick_bubble_1(state),
|
|
|
|
20 => self.tick_bubble_2(state),
|
|
|
|
21 => self.tick_bubble_3(state, players, new_bullets),
|
|
|
|
22 => self.tick_bubble_spines(state),
|
2021-01-18 19:30:40 +00:00
|
|
|
23 => self.tick_blade_slash(state),
|
2021-10-08 02:41:31 +00:00
|
|
|
24 => self.tick_spike(),
|
2021-01-18 19:30:40 +00:00
|
|
|
25 => self.tick_blade_1(state),
|
|
|
|
26 => self.tick_blade_2(state),
|
|
|
|
27 => self.tick_blade_3(state, new_bullets),
|
2021-03-12 16:53:08 +00:00
|
|
|
28 | 29 | 30 => self.tick_super_missile(state, players, new_bullets),
|
|
|
|
31 | 32 | 33 => self.tick_super_missile_explosion(state, npc_list),
|
2021-03-09 14:05:38 +00:00
|
|
|
34 | 35 | 36 => self.tick_nemesis(state, npc_list),
|
2021-01-16 13:51:52 +00:00
|
|
|
37 | 38 | 39 => self.tick_spur(state, new_bullets),
|
|
|
|
40 | 41 | 42 => self.tick_spur_trail(state),
|
2022-01-08 04:52:26 +00:00
|
|
|
43 => self.tick_nemesis_curly(state, npc_list),
|
2020-10-02 01:30:42 +00:00
|
|
|
_ => self.cond.set_alive(false),
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-12 04:43:29 +00:00
|
|
|
|
|
|
|
pub fn vanish(&mut self, state: &mut SharedGameState) {
|
2020-12-05 21:24:38 +00:00
|
|
|
match self.btype {
|
2021-05-05 16:34:23 +00:00
|
|
|
// spur is a special case
|
2020-12-05 21:24:38 +00:00
|
|
|
37 | 38 | 39 => state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Up),
|
|
|
|
_ => state.sound_manager.play_sfx(28),
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.cond.set_alive(false);
|
|
|
|
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Right);
|
|
|
|
}
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
fn test_hit_block_destructible(&mut self, x: i32, y: i32, attributes: &[u8; 16], state: &mut SharedGameState) {
|
|
|
|
let mut hits = [false; 16];
|
2022-02-25 02:00:10 +00:00
|
|
|
let mut temp_flags = Flag(0);
|
2021-06-27 01:06:56 +00:00
|
|
|
|
|
|
|
let (block_x, block_y, max_attr) = match state.tile_size {
|
|
|
|
TileSize::Tile8x8 => ((x * 2 + 1) * 0x800, (y * 2 + 1) * 0x800, 4),
|
|
|
|
TileSize::Tile16x16 => ((x * 2 + 1) * 0x1000, (y * 2 + 1) * 0x1000, 4),
|
|
|
|
};
|
2020-09-12 04:43:29 +00:00
|
|
|
|
2021-05-05 16:34:23 +00:00
|
|
|
for (i, &attr) in attributes.iter().enumerate() {
|
2021-06-27 01:06:56 +00:00
|
|
|
if i == max_attr {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-05-05 16:34:23 +00:00
|
|
|
if self.weapon_flags.can_destroy_snack() {
|
2020-09-12 04:43:29 +00:00
|
|
|
hits[i] = attr == 0x41 || attr == 0x61;
|
|
|
|
} else {
|
|
|
|
hits[i] = attr == 0x41 || attr == 0x43 || attr == 0x61;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// left wall
|
|
|
|
if hits[0] && hits[2] {
|
2021-01-01 01:46:01 +00:00
|
|
|
if (self.x - self.hit_bounds.left as i32) < block_x {
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_left_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
} else if hits[0] && !hits[2] {
|
2021-08-16 16:38:06 +00:00
|
|
|
if (self.x - self.hit_bounds.left as i32) < block_x
|
|
|
|
&& (self.y - self.hit_bounds.top as i32) < block_y - (0x600)
|
|
|
|
{
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_left_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
2021-08-16 16:38:06 +00:00
|
|
|
} else if !hits[0]
|
|
|
|
&& hits[2]
|
|
|
|
&& (self.x - self.hit_bounds.left as i32) < block_x
|
|
|
|
&& (self.y + self.hit_bounds.top as i32) > block_y + (0x600)
|
|
|
|
{
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_left_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// right wall
|
|
|
|
if hits[1] && hits[3] {
|
2021-01-01 01:46:01 +00:00
|
|
|
if (self.x + self.hit_bounds.right as i32) > block_x {
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_right_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
} else if hits[1] && !hits[3] {
|
2021-08-16 16:38:06 +00:00
|
|
|
if (self.x + self.hit_bounds.right as i32) > block_x
|
|
|
|
&& (self.y - self.hit_bounds.top as i32) < block_y - (0x600)
|
|
|
|
{
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_right_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
2021-08-16 16:38:06 +00:00
|
|
|
} else if !hits[1]
|
|
|
|
&& hits[3]
|
|
|
|
&& (self.x + self.hit_bounds.right as i32) > block_x
|
|
|
|
&& (self.y + self.hit_bounds.top as i32) > block_y + (0x600)
|
|
|
|
{
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_right_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ceiling
|
|
|
|
if hits[0] && hits[1] {
|
2021-01-01 01:46:01 +00:00
|
|
|
if (self.y - self.hit_bounds.top as i32) < block_y {
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_top_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
} else if hits[0] && !hits[1] {
|
2021-08-16 16:38:06 +00:00
|
|
|
if (self.x - self.hit_bounds.left as i32) < block_x - (0x600)
|
|
|
|
&& (self.y - self.hit_bounds.top as i32) < block_y
|
|
|
|
{
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_top_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
2021-08-16 16:38:06 +00:00
|
|
|
} else if !hits[0]
|
|
|
|
&& hits[1]
|
|
|
|
&& (self.x + self.hit_bounds.right as i32) > block_x + (0x600)
|
|
|
|
&& (self.y - self.hit_bounds.top as i32) < block_y
|
|
|
|
{
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_top_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ground
|
|
|
|
if hits[2] && hits[3] {
|
2021-01-01 01:46:01 +00:00
|
|
|
if (self.y + self.hit_bounds.bottom as i32) > block_y {
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_bottom_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
} else if hits[2] && !hits[3] {
|
2021-08-16 16:38:06 +00:00
|
|
|
if (self.x - self.hit_bounds.left as i32) < block_x - (0x600)
|
|
|
|
&& (self.y + self.hit_bounds.bottom as i32) > block_y
|
|
|
|
{
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_bottom_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
2021-08-16 16:38:06 +00:00
|
|
|
} else if !hits[2]
|
|
|
|
&& hits[3]
|
|
|
|
&& (self.x + self.hit_bounds.right as i32) > block_x + (0x600)
|
|
|
|
&& (self.y + self.hit_bounds.bottom as i32) > block_y
|
|
|
|
{
|
2022-02-25 02:00:10 +00:00
|
|
|
temp_flags.set_hit_bottom_wall(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
2022-02-25 02:00:10 +00:00
|
|
|
self.flags.0 |= temp_flags.0;
|
2020-09-12 04:43:29 +00:00
|
|
|
|
2021-05-05 16:34:23 +00:00
|
|
|
if self.weapon_flags.bounce_from_walls() {
|
2022-02-25 02:00:10 +00:00
|
|
|
if temp_flags.hit_left_wall() {
|
2021-01-01 01:46:01 +00:00
|
|
|
self.x = block_x + self.hit_bounds.right as i32;
|
2022-02-25 02:00:10 +00:00
|
|
|
} else if temp_flags.hit_right_wall() {
|
2021-01-01 01:46:01 +00:00
|
|
|
self.x = block_x - self.hit_bounds.left as i32;
|
2022-02-25 02:00:10 +00:00
|
|
|
} else if temp_flags.hit_top_wall() {
|
2021-01-01 01:46:01 +00:00
|
|
|
self.y = block_y + self.hit_bounds.bottom as i32;
|
2022-02-25 02:00:10 +00:00
|
|
|
} else if temp_flags.hit_bottom_wall() {
|
2021-01-01 01:46:01 +00:00
|
|
|
self.y = block_y - self.hit_bounds.top as i32;
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
2021-08-16 16:38:06 +00:00
|
|
|
} else if self.flags.hit_left_wall()
|
|
|
|
|| self.flags.hit_top_wall()
|
|
|
|
|| self.flags.hit_right_wall()
|
|
|
|
|| self.flags.hit_bottom_wall()
|
|
|
|
{
|
2020-09-12 04:43:29 +00:00
|
|
|
self.vanish(state);
|
|
|
|
}
|
|
|
|
}
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PhysicalEntity for Bullet {
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-01-01 01:46:01 +00:00
|
|
|
fn x(&self) -> i32 {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.x
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-01-01 01:46:01 +00:00
|
|
|
fn y(&self) -> i32 {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.y
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-01-01 01:46:01 +00:00
|
|
|
fn vel_x(&self) -> i32 {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.vel_x
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-01-01 01:46:01 +00:00
|
|
|
fn vel_y(&self) -> i32 {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.vel_y
|
|
|
|
}
|
|
|
|
|
2020-10-01 03:06:18 +00:00
|
|
|
fn hit_rect_size(&self) -> usize {
|
|
|
|
2
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-03-29 21:19:07 +00:00
|
|
|
fn hit_bounds(&self) -> &Rect<u32> {
|
2020-09-12 00:42:44 +00:00
|
|
|
&self.hit_bounds
|
|
|
|
}
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
#[inline(always)]
|
|
|
|
fn display_bounds(&self) -> &Rect<u32> {
|
|
|
|
&self.display_bounds
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-01-01 01:46:01 +00:00
|
|
|
fn set_x(&mut self, x: i32) {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.x = x;
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-01-01 01:46:01 +00:00
|
|
|
fn set_y(&mut self, y: i32) {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.y = y;
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-01-01 01:46:01 +00:00
|
|
|
fn set_vel_x(&mut self, vel_x: i32) {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.vel_x = vel_x;
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2021-01-01 01:46:01 +00:00
|
|
|
fn set_vel_y(&mut self, vel_y: i32) {
|
2020-09-12 00:42:44 +00:00
|
|
|
self.vel_y = vel_y;
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2020-09-12 00:42:44 +00:00
|
|
|
fn cond(&mut self) -> &mut Condition {
|
|
|
|
&mut self.cond
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
2020-09-12 00:42:44 +00:00
|
|
|
fn flags(&mut self) -> &mut Flag {
|
2020-10-02 01:30:42 +00:00
|
|
|
&mut self.flags
|
2020-09-12 00:42:44 +00:00
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:09 +00:00
|
|
|
#[inline(always)]
|
|
|
|
fn direction(&self) -> Direction {
|
|
|
|
self.direction
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
2020-09-12 00:42:44 +00:00
|
|
|
fn is_player(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
fn test_block_hit(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
|
|
|
let ti = state.tile_size.as_int() * 0x100;
|
|
|
|
|
|
|
|
if (self.x - self.hit_bounds.left as i32) < (x * 2 + 1) * ti
|
|
|
|
&& (self.x + self.hit_bounds.right as i32) > (x * 2 - 1) * ti
|
|
|
|
&& (self.y - self.hit_bounds.top as i32) < (y * 2 + 1) * ti
|
|
|
|
&& (self.y + self.hit_bounds.bottom as i32) > (y * 2 - 1) * ti
|
2020-09-12 04:43:29 +00:00
|
|
|
{
|
2020-10-02 01:30:42 +00:00
|
|
|
self.flags.set_weapon_hit_block(true);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
fn tick_map_collisions(&mut self, state: &mut SharedGameState, npc_list: &NPCList, stage: &mut Stage) {
|
2020-10-01 03:06:18 +00:00
|
|
|
self.flags().0 = 0;
|
2021-05-05 16:34:23 +00:00
|
|
|
if self.weapon_flags.no_collision_checks() {
|
2021-02-26 20:03:35 +00:00
|
|
|
// ???
|
2020-09-12 04:43:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
let tile_size = state.tile_size.as_int() * 0x200;
|
|
|
|
let max_hits = match state.tile_size {
|
|
|
|
TileSize::Tile8x8 => 16,
|
|
|
|
TileSize::Tile16x16 => 4,
|
|
|
|
};
|
|
|
|
|
|
|
|
let x = clamp(self.x() / tile_size, 0, stage.map.width as i32);
|
|
|
|
let y = clamp(self.y() / tile_size, 0, stage.map.height as i32);
|
|
|
|
let mut hit_attribs = [0u8; 16];
|
2020-12-04 18:58:59 +00:00
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
for (idx, &(ox, oy)) in OFFSETS.iter().enumerate() {
|
|
|
|
if idx == max_hits || !self.cond.alive() {
|
2020-09-12 04:43:29 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let attrib = stage.map.get_attribute((x + ox) as usize, (y + oy) as usize);
|
|
|
|
hit_attribs[idx] = attrib;
|
|
|
|
|
|
|
|
match attrib {
|
|
|
|
// Blocks
|
|
|
|
0x41 | 0x44 | 0x61 | 0x64 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_block_hit(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
0x43 => {
|
2020-12-04 18:58:59 +00:00
|
|
|
let old_hit = self.flags;
|
|
|
|
self.flags.0 = 0;
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_block_hit(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
if self.flags.weapon_hit_block()
|
2022-01-08 14:20:14 +00:00
|
|
|
&& (self.weapon_flags.check_block_hit() || self.weapon_flags.can_destroy_snack())
|
2021-08-16 16:38:06 +00:00
|
|
|
{
|
2021-05-05 16:34:23 +00:00
|
|
|
if !self.weapon_flags.can_destroy_snack() {
|
2020-09-12 04:43:29 +00:00
|
|
|
self.cond.set_alive(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
|
2020-09-16 19:53:53 +00:00
|
|
|
state.sound_manager.play_sfx(12);
|
2020-09-13 05:18:24 +00:00
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
let mut npc = NPC::create(4, &state.npc_table);
|
2020-11-02 10:54:17 +00:00
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.direction = Direction::Left;
|
2020-12-06 23:48:12 +00:00
|
|
|
npc.x = (x * 16 + 8) * 0x200;
|
2020-12-06 23:41:57 +00:00
|
|
|
npc.y = (y * 16 + 8) * 0x200;
|
2020-09-13 05:18:24 +00:00
|
|
|
|
2020-11-02 10:54:17 +00:00
|
|
|
for _ in 0..4 {
|
2021-01-01 01:46:01 +00:00
|
|
|
npc.vel_x = state.game_rng.range(-0x200..0x200) as i32;
|
|
|
|
npc.vel_y = state.game_rng.range(-0x200..0x200) as i32;
|
2020-09-13 05:18:24 +00:00
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
let _ = npc_list.spawn(0x100, npc.clone());
|
2020-09-13 05:18:24 +00:00
|
|
|
}
|
2020-09-11 16:30:18 +00:00
|
|
|
|
2021-08-16 16:38:06 +00:00
|
|
|
if let Some(tile) =
|
|
|
|
stage.map.tiles.get_mut(stage.map.width as usize * (y + oy) as usize + (x + ox) as usize)
|
|
|
|
{
|
2020-09-12 04:43:29 +00:00
|
|
|
*tile = tile.wrapping_sub(1);
|
|
|
|
}
|
|
|
|
}
|
2020-12-04 18:58:59 +00:00
|
|
|
|
|
|
|
self.flags.0 |= old_hit.0;
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
// Slopes
|
|
|
|
0x50 | 0x70 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_upper_left_slope_high(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
0x51 | 0x71 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_upper_left_slope_low(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
0x52 | 0x72 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_upper_right_slope_low(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
0x53 | 0x73 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_upper_right_slope_high(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
0x54 | 0x74 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_lower_left_slope_high(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
0x55 | 0x75 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_lower_left_slope_low(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
0x56 | 0x76 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_lower_right_slope_low(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
0x57 | 0x77 => {
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_lower_right_slope_high(state, x + ox, y + oy);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 16:34:23 +00:00
|
|
|
self.test_hit_block_destructible(x, y, &hit_attribs, state);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
2020-09-11 16:30:18 +00:00
|
|
|
}
|