diff --git a/src/lib.rs b/src/lib.rs index f8a75dc..828d133 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,6 +109,12 @@ impl Game { self.loops += 1; } + if self.loops == 10 { + log::warn!("Frame skip is way too high, a long system lag occurred?"); + self.next_tick = self.start_time.elapsed().as_nanos(); + self.loops = 0; + } + for _ in 0..self.loops { scene.tick(&mut self.state, ctx)?; } diff --git a/src/npc/grasstown.rs b/src/npc/grasstown.rs index 7624f96..ff82fe4 100644 --- a/src/npc/grasstown.rs +++ b/src/npc/grasstown.rs @@ -347,7 +347,205 @@ impl NPC { Ok(()) } - pub fn tick_n104_frog(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult { + pub(crate) fn tick_n094_kulala(&mut self, state: &SharedGameState, player: &Player) -> GameResult { + match self.action_num { + 0 => { + self.anim_num = 4; + if self.shock > 0 { + self.anim_num = 0; + self.action_num = 10; + self.action_counter = 0; + } + } + 10 => { + self.npc_flags.set_shootable(true); + self.npc_flags.set_invulnerable(false); + + self.action_counter += 1; + if self.action_counter > 40 { + self.action_num = 11; + self.action_counter = 0; + self.anim_counter = 0; + } + } + 11 => { + self.anim_counter += 1; + if self.anim_counter > 5 { + self.anim_counter = 0; + self.anim_num += 1; + if self.anim_num > 2 { + self.action_num = 12; + self.anim_num = 3; + } + } + } + 12 => { + self.vel_y = -0x155; + + self.action_counter += 1; + if self.action_counter > 20 { + self.action_num = 10; + self.action_counter = 0; + self.anim_num = 0; + } + } + 20 => { + self.vel_x /= 2; + self.vel_y += 0x10; + + if self.shock == 0 { + self.action_num = 10; + self.action_counter = 30; + self.anim_num = 0; + } + } + _ => {} + } + + if self.shock > 0 { + self.action_counter2 += 1; + if self.action_counter2 > 12 { + self.action_num = 20; + self.anim_num = 4; + self.npc_flags.set_shootable(false); + self.npc_flags.set_invulnerable(true); + } + } else { + self.action_counter2 = 0; + } + + if self.action_num >= 10 { + if self.flags.hit_left_wall() { + self.vel_x2 = 50; + self.direction = Direction::Right; + } + + if self.flags.hit_right_wall() { + self.vel_x2 = 50; + self.direction = Direction::Left; + } + + if self.vel_x2 > 0 { + self.vel_x2 -= 1; + + self.vel_x += self.direction.vector_x() * 0x80; + } else { + self.vel_x2 = 50; + self.direction = if self.x > player.x { + Direction::Left + } else { + Direction::Right + }; + } + + self.vel_y += 0x10; + + if self.flags.hit_bottom_wall() { + self.vel_y = -2 * 0x200; + } + } + + self.vel_x = clamp(self.vel_x, -0x100, 0x100); + self.vel_y = clamp(self.vel_y, -0x300, 0x300); + + self.x += self.vel_x; + self.y += self.vel_y; + + self.anim_rect = state.constants.npc.n094_kulala[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n095_jelly(&mut self, state: &SharedGameState) -> GameResult { + match self.action_num { + 0 | 1 | 10 => { + if self.action_num == 0 { + self.action_num = 1; + self.action_counter = state.game_rng.range(0..50) as u16; + self.target_x = self.x; + self.target_y = self.y; + + self.vel_x = -self.direction.vector_x() * 0x200; + } + + if self.action_num == 1 { + if self.action_counter > 0 { + self.action_counter -= 1; + } else { + self.action_num = 10; + } + } + + if self.action_num == 10 { + self.action_counter += 1; + if self.action_counter > 10 { + self.action_num = 11; + self.action_counter = 0; + self.anim_counter = 0; + } + } + } + 11 => { + self.anim_counter += 1; + if self.anim_counter > 5 { + self.anim_counter = 0; + self.anim_num += 1; + if self.anim_num == 2 { + self.vel_x += self.direction.vector_x() * 0x100; + self.vel_y -= 0x200; + } else if self.anim_num > 2 { + self.action_num = 12; + self.anim_num = 3; + } + } + } + 12 => { + self.action_counter += 1; + if self.action_counter > 10 && self.y > self.target_y { + self.action_num = 10; + self.action_counter = 0; + self.anim_num = 0; + } + } + _ => {} + } + + self.direction = if self.x <= self.target_x { Direction::Right } else { Direction::Left }; + + if self.flags.hit_left_wall() { + self.action_counter2 = 50; + self.direction = Direction::Right; + } + + if self.flags.hit_right_wall() { + self.action_counter2 = 50; + self.direction = Direction::Left; + } + + self.vel_y += 0x20; + if self.flags.hit_bottom_wall() { + self.vel_y = -2 * 0x200; + } + + self.vel_x = clamp(self.vel_x, -0x100, 0x100); + self.vel_y = clamp(self.vel_y, -0x200, 0x200); + + if self.shock > 0 { + self.x += self.vel_x / 2; + self.y += self.vel_y / 2; + } else { + self.x += self.vel_x; + self.y += self.vel_y; + } + + + let dir_offset = if self.direction == Direction::Left { 0 } else { 4 }; + self.anim_rect = state.constants.npc.n095_jelly[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n104_frog(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult { match self.action_num { 0 => { self.action_num = 1; @@ -467,7 +665,7 @@ impl NPC { Ok(()) } - pub fn tick_n110_puchi(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult { + pub(crate) fn tick_n110_puchi(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult { match self.action_num { 0 => { self.action_num = 1; diff --git a/src/npc/mod.rs b/src/npc/mod.rs index fa78d8c..45761a0 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -238,6 +238,8 @@ impl GameEntity<(&mut Player, &HashMap>, &mut Stage)> for NPC 86 => self.tick_n086_missile_pickup(state), 87 => self.tick_n087_heart_pickup(state), 91 => self.tick_n091_mimiga_cage(state), + 94 => self.tick_n094_kulala(state, player), + 95 => self.tick_n095_jelly(state), 96 => self.tick_n096_fan_left(state, player), 97 => self.tick_n097_fan_up(state, player), 98 => self.tick_n098_fan_right(state, player), @@ -685,14 +687,19 @@ impl NPCMap { } } - pub fn is_alive(&self, npc_id: u16) -> bool { - if let Some(npc_cell) = self.npcs.get(&npc_id) { - return npc_cell.borrow().cond.alive(); + /// Returns true if at least one NPC with specified type is alive. + pub fn is_alive_by_type(&self, npc_type: u16) -> bool { + for npc_cell in self.npcs.values() { + let npc = npc_cell.borrow(); + if npc.cond.alive() && npc.npc_type == npc_type { + return true; + } } false } + /// Returns true if at least one NPC with specified event is alive. pub fn is_alive_by_event(&self, event_num: u16) -> bool { for npc_cell in self.npcs.values() { let npc = npc_cell.borrow(); diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 5c62199..97e9bf6 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -586,7 +586,7 @@ impl GameScene { if !self.player.cond.hidden() && self.inventory.get_current_weapon().is_some() { self.draw_light(fix9_scale(self.player.x - self.frame.x, scale), fix9_scale(self.player.y - self.frame.y, scale), - 2.5, (225, 225, 225), batch); + 4.0, (140, 140, 140), batch); } for bullet in self.bullet_manager.bullets.iter() { diff --git a/src/text_script.rs b/src/text_script.rs index 0002ef0..4c20cb5 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -185,7 +185,7 @@ pub enum OpCode { NCJ, /// { - let npc_id = read_cur_varint(&mut cursor)? as u16; + let npc_type = read_cur_varint(&mut cursor)? as u16; let event_num = read_cur_varint(&mut cursor)? as u16; - if game_scene.npc_map.is_alive(npc_id) { + if game_scene.npc_map.is_alive_by_type(npc_type) { exec_state = TextScriptExecutionState::Running(event_num, 0); } else { exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);