mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-09-20 19:44:00 +00:00
Compare commits
5 commits
8111e01caf
...
41307ccbc7
Author | SHA1 | Date | |
---|---|---|---|
|
41307ccbc7 | ||
|
49d990bc38 | ||
|
8e810bf026 | ||
|
2563c09b69 | ||
|
9f70bf6fd0 |
|
@ -8,7 +8,7 @@ version = "0.1.0"
|
||||||
crate-type = ["lib", "cdylib"]
|
crate-type = ["lib", "cdylib"]
|
||||||
|
|
||||||
[package.metadata.android]
|
[package.metadata.android]
|
||||||
android_version = 28
|
android_version = 29
|
||||||
target_sdk_version = 28
|
target_sdk_version = 28
|
||||||
min_sdk_version = 26
|
min_sdk_version = 26
|
||||||
build_targets = ["aarch64-linux-android"]
|
build_targets = ["aarch64-linux-android"]
|
||||||
|
@ -18,7 +18,6 @@ opengles_version = [3, 1]
|
||||||
fullscreen = true
|
fullscreen = true
|
||||||
orientation = "sensorLandscape"
|
orientation = "sensorLandscape"
|
||||||
permission = [
|
permission = [
|
||||||
{name = "android.permission.MANAGE_EXTERNAL_STORAGE"},
|
|
||||||
{name = "android.permission.READ_EXTERNAL_STORAGE"},
|
{name = "android.permission.READ_EXTERNAL_STORAGE"},
|
||||||
{name = "android.permission.WRITE_EXTERNAL_STORAGE"}
|
{name = "android.permission.WRITE_EXTERNAL_STORAGE"}
|
||||||
]
|
]
|
||||||
|
|
226
src/bullet.rs
226
src/bullet.rs
|
@ -13,13 +13,15 @@ use crate::stage::Stage;
|
||||||
|
|
||||||
pub struct BulletManager {
|
pub struct BulletManager {
|
||||||
pub bullets: Vec<Bullet>,
|
pub bullets: Vec<Bullet>,
|
||||||
|
pub new_bullets: Vec<Bullet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BulletManager {
|
impl BulletManager {
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> BulletManager {
|
pub fn new() -> BulletManager {
|
||||||
BulletManager {
|
BulletManager {
|
||||||
bullets: Vec::with_capacity(32),
|
bullets: Vec::with_capacity(64),
|
||||||
|
new_bullets: Vec::with_capacity(8),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,17 +29,29 @@ impl BulletManager {
|
||||||
self.bullets.push(Bullet::new(x, y, btype, owner, direction, constants));
|
self.bullets.push(Bullet::new(x, y, btype, owner, direction, constants));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn push_bullet(&mut self, bullet: Bullet) {
|
||||||
|
self.bullets.push(bullet);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tick_bullets(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], npc_list: &NPCList, stage: &mut Stage) {
|
pub fn tick_bullets(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], npc_list: &NPCList, stage: &mut Stage) {
|
||||||
for bullet in self.bullets.iter_mut() {
|
let mut i = 0;
|
||||||
|
while i < self.bullets.len() {
|
||||||
|
{
|
||||||
|
let bullet = unsafe { self.bullets.get_unchecked_mut(i) };
|
||||||
|
i += 1;
|
||||||
|
|
||||||
if bullet.life < 1 {
|
if bullet.life < 1 {
|
||||||
bullet.cond.set_alive(false);
|
bullet.cond.set_alive(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bullet.tick(state, players, npc_list);
|
bullet.tick(state, players, npc_list, &mut self.new_bullets);
|
||||||
bullet.tick_map_collisions(state, npc_list, stage);
|
bullet.tick_map_collisions(state, npc_list, stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.bullets.append(&mut self.new_bullets);
|
||||||
|
}
|
||||||
|
|
||||||
self.bullets.retain(|b| !b.is_dead());
|
self.bullets.retain(|b| !b.is_dead());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +63,7 @@ impl BulletManager {
|
||||||
self.bullets.iter().filter(|b| (b.btype.saturating_sub(2) / 3) == type_idx).count()
|
self.bullets.iter().filter(|b| (b.btype.saturating_sub(2) / 3) == type_idx).count()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_bullets_multi(&self, btypes: [u16; 3], player_id: TargetPlayer) -> usize {
|
pub fn count_bullets_multi(&self, btypes: &[u16], player_id: TargetPlayer) -> usize {
|
||||||
self.bullets.iter().filter(|b| b.owner == player_id && btypes.contains(&b.btype)).count()
|
self.bullets.iter().filter(|b| b.owner == player_id && btypes.contains(&b.btype)).count()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,13 +182,83 @@ impl Bullet {
|
||||||
self.y += self.vel_y;
|
self.y += self.vel_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.anim_num = (self.anim_num + 1) % 3;
|
self.anim_num = (self.anim_num + 1) % 4;
|
||||||
|
|
||||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
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];
|
self.anim_rect = state.constants.weapon.bullet_rects.b001_snake_l1[self.anim_num as usize + dir_offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
fn tick_polar_star(&mut self, state: &mut SharedGameState) {
|
fn tick_polar_star(&mut self, state: &mut SharedGameState) {
|
||||||
self.action_counter += 1;
|
self.action_counter += 1;
|
||||||
if self.action_counter > self.lifetime {
|
if self.action_counter > self.lifetime {
|
||||||
|
@ -374,7 +458,134 @@ impl Bullet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], npc_list: &NPCList) {
|
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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
Direction::Up | Direction::Bottom => {
|
||||||
|
self.enemy_hit_width = 0x800;
|
||||||
|
}
|
||||||
|
Direction::FacingPlayer => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
39 => {
|
||||||
|
// level 3 uses default values
|
||||||
|
}
|
||||||
|
_ => { unreachable!() }
|
||||||
|
}
|
||||||
|
} 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));
|
||||||
|
}
|
||||||
|
_ => { unreachable!() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { unreachable!() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], npc_list: &NPCList, new_bullets: &mut Vec<Bullet>) {
|
||||||
if self.lifetime == 0 {
|
if self.lifetime == 0 {
|
||||||
self.cond.set_alive(false);
|
self.cond.set_alive(false);
|
||||||
return;
|
return;
|
||||||
|
@ -382,8 +593,11 @@ impl Bullet {
|
||||||
|
|
||||||
match self.btype {
|
match self.btype {
|
||||||
1 => self.tick_snake_1(state),
|
1 => self.tick_snake_1(state),
|
||||||
|
2 | 3 => self.tick_snake_2(state, npc_list),
|
||||||
4 | 5 | 6 => self.tick_polar_star(state),
|
4 | 5 | 6 => self.tick_polar_star(state),
|
||||||
7 | 8 | 9 => self.tick_fireball(state, players, npc_list),
|
7 | 8 | 9 => self.tick_fireball(state, players, npc_list),
|
||||||
|
37 | 38 | 39 => self.tick_spur(state, new_bullets),
|
||||||
|
40 | 41 | 42 => self.tick_spur_trail(state),
|
||||||
_ => self.cond.set_alive(false),
|
_ => self.cond.set_alive(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ use std::cmp::Ordering;
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::weapon::{Weapon, WeaponLevel, WeaponType};
|
use crate::weapon::{Weapon, WeaponLevel, WeaponType};
|
||||||
|
use crate::player::Player;
|
||||||
|
use crate::caret::CaretType;
|
||||||
|
use crate::common::Direction;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
/// (id, amount)
|
/// (id, amount)
|
||||||
|
@ -16,13 +19,6 @@ pub struct Inventory {
|
||||||
weapons: Vec<Weapon>,
|
weapons: Vec<Weapon>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum AddExperienceResult {
|
|
||||||
None,
|
|
||||||
LevelUp,
|
|
||||||
AddStar,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum TakeExperienceResult {
|
pub enum TakeExperienceResult {
|
||||||
None,
|
None,
|
||||||
|
@ -102,6 +98,18 @@ impl Inventory {
|
||||||
self.weapons.iter_mut().find(|w| w.wtype == weapon_id).unwrap()
|
self.weapons.iter_mut().find(|w| w.wtype == weapon_id).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn trade_weapon(&mut self, old: Option<WeaponType>, new: WeaponType, max_ammo: u16) {
|
||||||
|
if let Some(wtype) = old {
|
||||||
|
if let Some(weapon) = self.get_weapon_by_type_mut(wtype) {
|
||||||
|
*weapon = Weapon::new(new, WeaponLevel::Level1, 0, max_ammo, max_ammo);
|
||||||
|
} else {
|
||||||
|
self.add_weapon(new, max_ammo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.add_weapon(new, max_ammo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_weapon(&mut self, wtype: WeaponType) {
|
pub fn remove_weapon(&mut self, wtype: WeaponType) {
|
||||||
self.weapons.retain(|weapon| weapon.wtype != wtype);
|
self.weapons.retain(|weapon| weapon.wtype != wtype);
|
||||||
}
|
}
|
||||||
|
@ -151,6 +159,13 @@ impl Inventory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_current_weapon_xp(&mut self) {
|
||||||
|
if let Some(weapon) = self.get_current_weapon_mut() {
|
||||||
|
weapon.level = WeaponLevel::Level1;
|
||||||
|
weapon.experience = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn take_xp(&mut self, exp: u16, state: &mut SharedGameState) -> TakeExperienceResult {
|
pub fn take_xp(&mut self, exp: u16, state: &mut SharedGameState) -> TakeExperienceResult {
|
||||||
let mut result = TakeExperienceResult::None;
|
let mut result = TakeExperienceResult::None;
|
||||||
|
|
||||||
|
@ -182,9 +197,7 @@ impl Inventory {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_xp(&mut self, exp: u16, state: &mut SharedGameState) -> AddExperienceResult {
|
pub fn add_xp(&mut self, exp: u16, player: &mut Player, state: &mut SharedGameState) {
|
||||||
let mut result = AddExperienceResult::None;
|
|
||||||
|
|
||||||
if let Some(weapon) = self.get_current_weapon_mut() {
|
if let Some(weapon) = self.get_current_weapon_mut() {
|
||||||
let curr_level_idx = weapon.level as usize - 1;
|
let curr_level_idx = weapon.level as usize - 1;
|
||||||
let lvl_table = state.constants.weapon.level_table[weapon.wtype as usize];
|
let lvl_table = state.constants.weapon.level_table[weapon.wtype as usize];
|
||||||
|
@ -194,19 +207,21 @@ impl Inventory {
|
||||||
if weapon.level == WeaponLevel::Level3 {
|
if weapon.level == WeaponLevel::Level3 {
|
||||||
if weapon.experience > lvl_table[2] {
|
if weapon.experience > lvl_table[2] {
|
||||||
weapon.experience = lvl_table[2];
|
weapon.experience = lvl_table[2];
|
||||||
result = AddExperienceResult::AddStar;
|
|
||||||
|
if player.equip.has_whimsical_star() && player.stars < 3 {
|
||||||
|
player.stars += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if weapon.experience > lvl_table[curr_level_idx] {
|
} else if weapon.experience > lvl_table[curr_level_idx] {
|
||||||
weapon.level = weapon.level.next();
|
weapon.level = weapon.level.next();
|
||||||
weapon.experience = 0;
|
weapon.experience = 0;
|
||||||
|
|
||||||
if weapon.wtype != WeaponType::Spur {
|
if weapon.wtype != WeaponType::Spur {
|
||||||
result = AddExperienceResult::LevelUp;
|
state.sound_manager.play_sfx(27);
|
||||||
|
state.create_caret(player.x, player.y, CaretType::LevelUp, Direction::Left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current experience state. Returns a (exp, max exp, max level/exp) tuple.
|
/// Get current experience state. Returns a (exp, max exp, max level/exp) tuple.
|
||||||
|
|
|
@ -257,10 +257,10 @@ pub fn android_main() {
|
||||||
|
|
||||||
request_perms().expect("Failed to attach to the JVM and request storage permissions.");
|
request_perms().expect("Failed to attach to the JVM and request storage permissions.");
|
||||||
|
|
||||||
env::set_var("CAVESTORY_DATA_DIR", "/storage/emulated/0/doukutsu");
|
env::set_var("CAVESTORY_DATA_DIR", "/sdcard/doukutsu");
|
||||||
|
|
||||||
let _ = std::fs::create_dir("/storage/emulated/0/doukutsu/");
|
let _ = std::fs::create_dir("/sdcard/doukutsu/");
|
||||||
let _ = std::fs::write("/storage/emulated/0/doukutsu/.nomedia", b"");
|
let _ = std::fs::write("/sdcard/doukutsu/.nomedia", b"");
|
||||||
|
|
||||||
init().unwrap();
|
init().unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,6 @@ impl NPC {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub(crate) fn tick_n064_first_cave_critter(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
pub(crate) fn tick_n064_first_cave_critter(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||||
match self.action_num {
|
match self.action_num {
|
||||||
0 | 1 => {
|
0 | 1 => {
|
||||||
|
|
105
src/npc/ai/last_cave.rs
Normal file
105
src/npc/ai/last_cave.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use ggez::GameResult;
|
||||||
|
|
||||||
|
use crate::common::Direction;
|
||||||
|
use crate::npc::NPC;
|
||||||
|
use crate::player::Player;
|
||||||
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
|
||||||
|
impl NPC {
|
||||||
|
pub(crate) fn tick_n241_critter_red(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||||
|
match self.action_num {
|
||||||
|
0 | 1 => {
|
||||||
|
if self.action_num == 0 {
|
||||||
|
self.y += 3 * 0x200;
|
||||||
|
self.action_num = 1;
|
||||||
|
self.anim_num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = self.get_closest_player_mut(players);
|
||||||
|
if self.x > player.x {
|
||||||
|
self.direction = Direction::Left;
|
||||||
|
} else {
|
||||||
|
self.direction = Direction::Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.target_x < 100 {
|
||||||
|
self.target_x += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.action_counter >= 8
|
||||||
|
&& self.x - (144 * 0x200) < player.x
|
||||||
|
&& self.x + (144 * 0x200) > player.x
|
||||||
|
&& self.y - (96 * 0x200) < player.y
|
||||||
|
&& self.y + (96 * 0x200) > player.y {
|
||||||
|
self.anim_num = 1;
|
||||||
|
} else {
|
||||||
|
if self.action_counter < 8 {
|
||||||
|
self.action_counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.anim_num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.shock > 0 {
|
||||||
|
self.action_num = 2;
|
||||||
|
self.action_counter = 0;
|
||||||
|
|
||||||
|
self.anim_num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.action_counter >= 8
|
||||||
|
&& self.target_x >= 100
|
||||||
|
&& self.x - (96 * 0x200) < player.x
|
||||||
|
&& self.x + (96 * 0x200) > player.x
|
||||||
|
&& self.y - (80 * 0x200) < player.y
|
||||||
|
&& self.y + (80 * 0x200) > player.y {
|
||||||
|
self.action_num = 2;
|
||||||
|
self.action_counter = 0;
|
||||||
|
self.anim_num = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
self.action_counter += 1;
|
||||||
|
if self.action_counter > 8 {
|
||||||
|
self.action_num = 3;
|
||||||
|
self.anim_num = 2;
|
||||||
|
|
||||||
|
self.vel_y = -0x5ff;
|
||||||
|
state.sound_manager.play_sfx(30);
|
||||||
|
|
||||||
|
if self.direction == Direction::Left {
|
||||||
|
self.vel_x = -0x200;
|
||||||
|
} else {
|
||||||
|
self.vel_x = 0x200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
if self.flags.hit_bottom_wall() {
|
||||||
|
self.vel_x = 0;
|
||||||
|
self.action_counter = 0;
|
||||||
|
self.action_num = 1;
|
||||||
|
self.anim_num = 0;
|
||||||
|
|
||||||
|
state.sound_manager.play_sfx(23);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.vel_y += 0x55;
|
||||||
|
if self.vel_y > 0x5ff {
|
||||||
|
self.vel_y = 0x5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.x += self.vel_x;
|
||||||
|
self.y += self.vel_y;
|
||||||
|
|
||||||
|
|
||||||
|
let dir_offset = if self.direction == Direction::Left { 0 } else { 3 };
|
||||||
|
|
||||||
|
self.anim_rect = state.constants.npc.n241_critter_red[self.anim_num as usize + dir_offset];
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ pub mod first_cave;
|
||||||
pub mod grasstown;
|
pub mod grasstown;
|
||||||
pub mod igor;
|
pub mod igor;
|
||||||
pub mod intro;
|
pub mod intro;
|
||||||
|
pub mod last_cave;
|
||||||
pub mod maze;
|
pub mod maze;
|
||||||
pub mod mimiga_village;
|
pub mod mimiga_village;
|
||||||
pub mod misc;
|
pub mod misc;
|
||||||
|
|
|
@ -275,6 +275,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)> for NP
|
||||||
199 => self.tick_n199_wind_particles(state),
|
199 => self.tick_n199_wind_particles(state),
|
||||||
211 => self.tick_n211_small_spikes(state),
|
211 => self.tick_n211_small_spikes(state),
|
||||||
234 => self.tick_n234_red_flowers_picked(state),
|
234 => self.tick_n234_red_flowers_picked(state),
|
||||||
|
241 => self.tick_n241_critter_red(state, players),
|
||||||
298 => self.tick_n298_intro_doctor(state),
|
298 => self.tick_n298_intro_doctor(state),
|
||||||
299 => self.tick_n299_intro_balrog_misery(state),
|
299 => self.tick_n299_intro_balrog_misery(state),
|
||||||
300 => self.tick_n300_intro_demon_crown(state),
|
300 => self.tick_n300_intro_demon_crown(state),
|
||||||
|
|
|
@ -4,7 +4,7 @@ use num_traits::abs;
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
use crate::common::{Condition, Direction, Flag, Rect};
|
use crate::common::{Condition, Direction, Flag, Rect};
|
||||||
use crate::inventory::{AddExperienceResult, Inventory};
|
use crate::inventory::Inventory;
|
||||||
use crate::npc::boss::BossNPC;
|
use crate::npc::boss::BossNPC;
|
||||||
use crate::npc::list::NPCList;
|
use crate::npc::list::NPCList;
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
|
@ -256,18 +256,7 @@ impl Player {
|
||||||
// experience pickup
|
// experience pickup
|
||||||
1 => {
|
1 => {
|
||||||
state.sound_manager.play_sfx(14);
|
state.sound_manager.play_sfx(14);
|
||||||
match inventory.add_xp(npc.exp, state) {
|
inventory.add_xp(npc.exp, self, state);
|
||||||
AddExperienceResult::None => {}
|
|
||||||
AddExperienceResult::LevelUp => {
|
|
||||||
state.sound_manager.play_sfx(27);
|
|
||||||
state.create_caret(self.x, self.y, CaretType::LevelUp, Direction::Left);
|
|
||||||
}
|
|
||||||
AddExperienceResult::AddStar => {
|
|
||||||
if self.equip.has_whimsical_star() && self.stars < 3 {
|
|
||||||
self.stars += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
npc.cond.set_alive(false);
|
npc.cond.set_alive(false);
|
||||||
}
|
}
|
||||||
// missile pickup
|
// missile pickup
|
||||||
|
|
|
@ -5,7 +5,7 @@ pub trait RNG {
|
||||||
fn next(&self) -> i32;
|
fn next(&self) -> i32;
|
||||||
|
|
||||||
fn range(&self, range: Range<i32>) -> i32 {
|
fn range(&self, range: Range<i32>) -> i32 {
|
||||||
range.start.saturating_add((self.next() >> 2) % range.len() as i32)
|
range.start + ((self.next() & 0x7fffffff) % (range.len() as i32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +100,6 @@ impl Xoroshiro32PlusPlus {
|
||||||
|
|
||||||
impl RNG for Xoroshiro32PlusPlus {
|
impl RNG for Xoroshiro32PlusPlus {
|
||||||
fn next(&self) -> i32 {
|
fn next(&self) -> i32 {
|
||||||
(((self.next_u16() as u32) << 16 | self.next_u16() as u32) >> 2) as i32
|
((self.next_u16() as u32) << 16 | self.next_u16() as u32) as i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -519,7 +519,7 @@ impl GameScene {
|
||||||
for bullet in self.bullet_manager.bullets.iter() {
|
for bullet in self.bullet_manager.bullets.iter() {
|
||||||
self.draw_light(fix9_scale(bullet.x - self.frame.x, scale),
|
self.draw_light(fix9_scale(bullet.x - self.frame.x, scale),
|
||||||
fix9_scale(bullet.y - self.frame.y, scale),
|
fix9_scale(bullet.y - self.frame.y, scale),
|
||||||
0.7, (200, 200, 200), batch);
|
0.3, (200, 200, 200), batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
for caret in state.carets.iter() {
|
for caret in state.carets.iter() {
|
||||||
|
@ -1055,12 +1055,24 @@ impl GameScene {
|
||||||
self.frame.update(state, &self.stage);
|
self.frame.update(state, &self.stage);
|
||||||
|
|
||||||
if state.control_flags.control_enabled() {
|
if state.control_flags.control_enabled() {
|
||||||
|
#[allow(clippy::cast_ref_to_mut)]
|
||||||
|
let inventory = unsafe { &mut *(&self.inventory_player1 as *const Inventory as *mut Inventory)}; // fuck off
|
||||||
if let Some(weapon) = self.inventory_player1.get_current_weapon_mut() {
|
if let Some(weapon) = self.inventory_player1.get_current_weapon_mut() {
|
||||||
weapon.shoot_bullet(&self.player1, TargetPlayer::Player1, &mut self.bullet_manager, state);
|
weapon.shoot_bullet(&mut self.player1,
|
||||||
|
TargetPlayer::Player1,
|
||||||
|
inventory,
|
||||||
|
&mut self.bullet_manager,
|
||||||
|
state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_ref_to_mut)]
|
||||||
|
let inventory = unsafe { &mut *(&self.inventory_player2 as *const Inventory as *mut Inventory)};
|
||||||
if let Some(weapon) = self.inventory_player2.get_current_weapon_mut() {
|
if let Some(weapon) = self.inventory_player2.get_current_weapon_mut() {
|
||||||
weapon.shoot_bullet(&self.player2, TargetPlayer::Player2, &mut self.bullet_manager, state);
|
weapon.shoot_bullet(&mut self.player2,
|
||||||
|
TargetPlayer::Player2,
|
||||||
|
inventory,
|
||||||
|
&mut self.bullet_manager,
|
||||||
|
state);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.hud_player1.tick(state, (&self.player1, &mut self.inventory_player1))?;
|
self.hud_player1.tick(state, (&self.player1, &mut self.inventory_player1))?;
|
||||||
|
|
37
src/scripting/doukutsu.d.ts
vendored
Normal file
37
src/scripting/doukutsu.d.ts
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
declare type EventHandler<T> = (this: void, param: T) => void;
|
||||||
|
|
||||||
|
declare interface DoukutsuPlayer {
|
||||||
|
x(): number;
|
||||||
|
y(): number;
|
||||||
|
velX(): number;
|
||||||
|
velY(): number;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare interface DoukutsuScene {
|
||||||
|
/**
|
||||||
|
* Returns the tick of current scene.
|
||||||
|
*/
|
||||||
|
tick(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns player at specified index.
|
||||||
|
*/
|
||||||
|
player(index: number): DoukutsuPlayer | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare namespace doukutsu {
|
||||||
|
/**
|
||||||
|
* Plays a PixTone sound effect with specified ID.
|
||||||
|
*/
|
||||||
|
function playSfx(id: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes current music to one with specified ID.
|
||||||
|
* If ID equals 0, the music is stopped.
|
||||||
|
*/
|
||||||
|
function playMusic(id: number): void;
|
||||||
|
|
||||||
|
function on(event: "tick", handler: EventHandler<DoukutsuScene>): EventHandler<DoukutsuScene>;
|
||||||
|
|
||||||
|
function on<T>(event: string, handler: EventHandler<T>): EventHandler<T>;
|
||||||
|
};
|
|
@ -40,45 +40,45 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
||||||
Channel::disabled(),
|
Channel::disabled(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
PixToneParameters { // fx2 (CS+)
|
// PixToneParameters { // fx2 (CS+)
|
||||||
channels: [
|
// channels: [
|
||||||
Channel {
|
// Channel {
|
||||||
enabled: true,
|
// enabled: true,
|
||||||
length: 2000,
|
// length: 2000,
|
||||||
carrier: Waveform {
|
// carrier: Waveform {
|
||||||
waveform_type: 0,
|
// waveform_type: 0,
|
||||||
pitch: 92.000000,
|
// pitch: 92.000000,
|
||||||
level: 32,
|
// level: 32,
|
||||||
offset: 0,
|
// offset: 0,
|
||||||
},
|
// },
|
||||||
frequency: Waveform {
|
// frequency: Waveform {
|
||||||
waveform_type: 0,
|
// waveform_type: 0,
|
||||||
pitch: 3.000000,
|
// pitch: 3.000000,
|
||||||
level: 44,
|
// level: 44,
|
||||||
offset: 0,
|
// offset: 0,
|
||||||
},
|
// },
|
||||||
amplitude: Waveform {
|
// amplitude: Waveform {
|
||||||
waveform_type: 0,
|
// waveform_type: 0,
|
||||||
pitch: 0.000000,
|
// pitch: 0.000000,
|
||||||
level: 32,
|
// level: 32,
|
||||||
offset: 0,
|
// offset: 0,
|
||||||
},
|
// },
|
||||||
envelope: Envelope {
|
// envelope: Envelope {
|
||||||
initial: 7,
|
// initial: 7,
|
||||||
time_a: 2,
|
// time_a: 2,
|
||||||
value_a: 18,
|
// value_a: 18,
|
||||||
time_b: 128,
|
// time_b: 128,
|
||||||
value_b: 0,
|
// value_b: 0,
|
||||||
time_c: 255,
|
// time_c: 255,
|
||||||
value_c: 0,
|
// value_c: 0,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
Channel::disabled(),
|
// Channel::disabled(),
|
||||||
Channel::disabled(),
|
// Channel::disabled(),
|
||||||
Channel::disabled(),
|
// Channel::disabled(),
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
/*PixToneParameters { // fx2
|
PixToneParameters { // fx2
|
||||||
channels: [
|
channels: [
|
||||||
Channel {
|
Channel {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -115,7 +115,7 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
||||||
Channel::disabled(),
|
Channel::disabled(),
|
||||||
Channel::disabled(),
|
Channel::disabled(),
|
||||||
],
|
],
|
||||||
},*/
|
},
|
||||||
PixToneParameters { // fx3
|
PixToneParameters { // fx3
|
||||||
channels: [
|
channels: [
|
||||||
Channel {
|
Channel {
|
||||||
|
|
|
@ -373,6 +373,7 @@ pub struct TextScriptVM {
|
||||||
/// while parsing no one noticed them.
|
/// while parsing no one noticed them.
|
||||||
pub strict_mode: bool,
|
pub strict_mode: bool,
|
||||||
pub suspend: bool,
|
pub suspend: bool,
|
||||||
|
pub numbers: [u16; 4],
|
||||||
pub face: u16,
|
pub face: u16,
|
||||||
pub item: u16,
|
pub item: u16,
|
||||||
pub current_line: TextScriptLine,
|
pub current_line: TextScriptLine,
|
||||||
|
@ -455,6 +456,7 @@ impl TextScriptVM {
|
||||||
executor_player: TargetPlayer::Player1,
|
executor_player: TargetPlayer::Player1,
|
||||||
strict_mode: false,
|
strict_mode: false,
|
||||||
suspend: true,
|
suspend: true,
|
||||||
|
numbers: [0; 4],
|
||||||
face: 0,
|
face: 0,
|
||||||
item: 0,
|
item: 0,
|
||||||
current_line: TextScriptLine::Line1,
|
current_line: TextScriptLine::Line1,
|
||||||
|
@ -715,7 +717,6 @@ impl TextScriptVM {
|
||||||
|
|
||||||
game_scene.player1.cond.set_interacted(false);
|
game_scene.player1.cond.set_interacted(false);
|
||||||
game_scene.player2.cond.set_interacted(false);
|
game_scene.player2.cond.set_interacted(false);
|
||||||
game_scene.frame.update_target = UpdateTarget::Player;
|
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Ended;
|
exec_state = TextScriptExecutionState::Ended;
|
||||||
}
|
}
|
||||||
|
@ -1026,6 +1027,21 @@ impl TextScriptVM {
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::WaitConfirmation(event, cursor.position() as u32, event_no, 16, ConfirmSelection::Yes);
|
exec_state = TextScriptExecutionState::WaitConfirmation(event, cursor.position() as u32, event_no, 16, ConfirmSelection::Yes);
|
||||||
}
|
}
|
||||||
|
OpCode::NUM => {
|
||||||
|
let index = read_cur_varint(&mut cursor)? as usize;
|
||||||
|
|
||||||
|
if let Some(num) = state.textscript_vm.numbers.get(index) {
|
||||||
|
let mut str = num.to_string().chars().collect_vec();
|
||||||
|
|
||||||
|
match state.textscript_vm.current_line {
|
||||||
|
TextScriptLine::Line1 => state.textscript_vm.line_1.append(&mut str),
|
||||||
|
TextScriptLine::Line2 => state.textscript_vm.line_2.append(&mut str),
|
||||||
|
TextScriptLine::Line3 => state.textscript_vm.line_3.append(&mut str),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
|
}
|
||||||
OpCode::GIT => {
|
OpCode::GIT => {
|
||||||
let item = read_cur_varint(&mut cursor)? as u16;
|
let item = read_cur_varint(&mut cursor)? as u16;
|
||||||
state.textscript_vm.item = item;
|
state.textscript_vm.item = item;
|
||||||
|
@ -1418,6 +1434,8 @@ impl TextScriptVM {
|
||||||
let max_ammo = read_cur_varint(&mut cursor)? as u16;
|
let max_ammo = read_cur_varint(&mut cursor)? as u16;
|
||||||
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
|
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
|
||||||
|
|
||||||
|
state.textscript_vm.numbers[0] = max_ammo;
|
||||||
|
|
||||||
if let Some(wtype) = weapon_type {
|
if let Some(wtype) = weapon_type {
|
||||||
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
|
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
|
||||||
game_scene.inventory_player2.add_weapon(wtype, max_ammo);
|
game_scene.inventory_player2.add_weapon(wtype, max_ammo);
|
||||||
|
@ -1442,6 +1460,20 @@ impl TextScriptVM {
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
|
OpCode::TAM => {
|
||||||
|
let old_weapon_id = read_cur_varint(&mut cursor)? as u8;
|
||||||
|
let new_weapon_id = read_cur_varint(&mut cursor)? as u8;
|
||||||
|
let max_ammo = read_cur_varint(&mut cursor)? as u16;
|
||||||
|
let old_weapon_type: Option<WeaponType> = FromPrimitive::from_u8(old_weapon_id);
|
||||||
|
let new_weapon_type: Option<WeaponType> = FromPrimitive::from_u8(new_weapon_id);
|
||||||
|
|
||||||
|
if let Some(wtype) = new_weapon_type {
|
||||||
|
game_scene.inventory_player1.trade_weapon(old_weapon_type, wtype, max_ammo);
|
||||||
|
game_scene.inventory_player2.trade_weapon(old_weapon_type, wtype, max_ammo);
|
||||||
|
}
|
||||||
|
|
||||||
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
|
}
|
||||||
OpCode::ZAM => {
|
OpCode::ZAM => {
|
||||||
game_scene.inventory_player1.reset_all_weapon_xp();
|
game_scene.inventory_player1.reset_all_weapon_xp();
|
||||||
game_scene.inventory_player2.reset_all_weapon_xp();
|
game_scene.inventory_player2.reset_all_weapon_xp();
|
||||||
|
@ -1494,7 +1526,7 @@ impl TextScriptVM {
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
// One operand codes
|
// One operand codes
|
||||||
OpCode::NUM | OpCode::MPp | OpCode::SKm | OpCode::SKp |
|
OpCode::MPp | OpCode::SKm | OpCode::SKp |
|
||||||
OpCode::UNJ | OpCode::MPJ | OpCode::XX1 | OpCode::SIL |
|
OpCode::UNJ | OpCode::MPJ | OpCode::XX1 | OpCode::SIL |
|
||||||
OpCode::SSS | OpCode::ACH => {
|
OpCode::SSS | OpCode::ACH => {
|
||||||
let par_a = read_cur_varint(&mut cursor)?;
|
let par_a = read_cur_varint(&mut cursor)?;
|
||||||
|
@ -1510,16 +1542,6 @@ impl TextScriptVM {
|
||||||
|
|
||||||
log::warn!("unimplemented opcode: {:?} {} {}", op, par_a, par_b);
|
log::warn!("unimplemented opcode: {:?} {} {}", op, par_a, par_b);
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
|
||||||
}
|
|
||||||
// Three operand codes
|
|
||||||
OpCode::TAM => {
|
|
||||||
let par_a = read_cur_varint(&mut cursor)?;
|
|
||||||
let par_b = read_cur_varint(&mut cursor)?;
|
|
||||||
let par_c = read_cur_varint(&mut cursor)?;
|
|
||||||
|
|
||||||
log::warn!("unimplemented opcode: {:?} {} {} {}", op, par_a, par_b, par_c);
|
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
161
src/weapon.rs
161
src/weapon.rs
|
@ -1,8 +1,9 @@
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
|
|
||||||
use crate::bullet::BulletManager;
|
use crate::bullet::{Bullet, BulletManager};
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
use crate::inventory::Inventory;
|
||||||
use crate::player::{Player, TargetPlayer};
|
use crate::player::{Player, TargetPlayer};
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
|
||||||
|
@ -58,6 +59,8 @@ pub struct Weapon {
|
||||||
pub experience: u16,
|
pub experience: u16,
|
||||||
pub ammo: u16,
|
pub ammo: u16,
|
||||||
pub max_ammo: u16,
|
pub max_ammo: u16,
|
||||||
|
counter1: u16,
|
||||||
|
counter2: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Weapon {
|
impl Weapon {
|
||||||
|
@ -68,6 +71,8 @@ impl Weapon {
|
||||||
experience,
|
experience,
|
||||||
ammo,
|
ammo,
|
||||||
max_ammo,
|
max_ammo,
|
||||||
|
counter1: 0,
|
||||||
|
counter2: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +90,7 @@ impl Weapon {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shoot_bullet_snake(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
fn shoot_bullet_snake(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([1, 2, 3], player_id) < 4 {
|
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi(&[1, 2, 3], player_id) < 4 {
|
||||||
let btype = match self.level {
|
let btype = match self.level {
|
||||||
WeaponLevel::Level1 => { 1 }
|
WeaponLevel::Level1 => { 1 }
|
||||||
WeaponLevel::Level2 => { 2 }
|
WeaponLevel::Level2 => { 2 }
|
||||||
|
@ -98,14 +103,20 @@ impl Weapon {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.counter1 = self.counter1.wrapping_add(1);
|
||||||
|
|
||||||
if player.up {
|
if player.up {
|
||||||
match player.direction {
|
match player.direction {
|
||||||
Direction::Left => {
|
Direction::Left => {
|
||||||
bullet_manager.create_bullet(player.x - 3 * 0x200, player.y - 10 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
let mut bullet = Bullet::new(player.x - 3 * 0x200, player.y - 10 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||||
|
bullet.target_x = self.counter1 as i32;
|
||||||
|
bullet_manager.push_bullet(bullet);
|
||||||
state.create_caret(player.x - 3 * 0x200, player.y - 10 * 0x200, CaretType::Shoot, Direction::Left);
|
state.create_caret(player.x - 3 * 0x200, player.y - 10 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
}
|
}
|
||||||
Direction::Right => {
|
Direction::Right => {
|
||||||
bullet_manager.create_bullet(player.x + 3 * 0x200, player.y - 10 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
let mut bullet = Bullet::new(player.x + 3 * 0x200, player.y - 10 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||||
|
bullet.target_x = self.counter1 as i32;
|
||||||
|
bullet_manager.push_bullet(bullet);
|
||||||
state.create_caret(player.x + 3 * 0x200, player.y - 10 * 0x200, CaretType::Shoot, Direction::Left);
|
state.create_caret(player.x + 3 * 0x200, player.y - 10 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -113,11 +124,15 @@ impl Weapon {
|
||||||
} else if player.down {
|
} else if player.down {
|
||||||
match player.direction {
|
match player.direction {
|
||||||
Direction::Left => {
|
Direction::Left => {
|
||||||
bullet_manager.create_bullet(player.x - 3 * 0x200, player.y + 10 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
let mut bullet = Bullet::new(player.x - 3 * 0x200, player.y + 10 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||||
|
bullet.target_x = self.counter1 as i32;
|
||||||
|
bullet_manager.push_bullet(bullet);
|
||||||
state.create_caret(player.x - 3 * 0x200, player.y + 10 * 0x200, CaretType::Shoot, Direction::Left);
|
state.create_caret(player.x - 3 * 0x200, player.y + 10 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
}
|
}
|
||||||
Direction::Right => {
|
Direction::Right => {
|
||||||
bullet_manager.create_bullet(player.x + 3 * 0x200, player.y + 10 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
let mut bullet = Bullet::new(player.x + 3 * 0x200, player.y + 10 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||||
|
bullet.target_x = self.counter1 as i32;
|
||||||
|
bullet_manager.push_bullet(bullet);
|
||||||
state.create_caret(player.x + 3 * 0x200, player.y + 10 * 0x200, CaretType::Shoot, Direction::Left);
|
state.create_caret(player.x + 3 * 0x200, player.y + 10 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -125,11 +140,15 @@ impl Weapon {
|
||||||
} else {
|
} else {
|
||||||
match player.direction {
|
match player.direction {
|
||||||
Direction::Left => {
|
Direction::Left => {
|
||||||
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 2 * 0x200, btype, player_id, Direction::Left, &state.constants);
|
let mut bullet = Bullet::new(player.x - 6 * 0x200, player.y + 2 * 0x200, btype, player_id, Direction::Left, &state.constants);
|
||||||
|
bullet.target_x = self.counter1 as i32;
|
||||||
|
bullet_manager.push_bullet(bullet);
|
||||||
state.create_caret(player.x - 12 * 0x200, player.y + 2 * 0x200, CaretType::Shoot, Direction::Left);
|
state.create_caret(player.x - 12 * 0x200, player.y + 2 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
}
|
}
|
||||||
Direction::Right => {
|
Direction::Right => {
|
||||||
bullet_manager.create_bullet(player.x + 6 * 0x200, player.y + 2 * 0x200, btype, player_id, Direction::Right, &state.constants);
|
let mut bullet = Bullet::new(player.x + 6 * 0x200, player.y + 2 * 0x200, btype, player_id, Direction::Right, &state.constants);
|
||||||
|
bullet.target_x = self.counter1 as i32;
|
||||||
|
bullet_manager.push_bullet(bullet);
|
||||||
state.create_caret(player.x + 12 * 0x200, player.y + 2 * 0x200, CaretType::Shoot, Direction::Right);
|
state.create_caret(player.x + 12 * 0x200, player.y + 2 * 0x200, CaretType::Shoot, Direction::Right);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -141,7 +160,7 @@ impl Weapon {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shoot_bullet_polar_star(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
fn shoot_bullet_polar_star(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([4, 5, 6], player_id) < 2 {
|
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi(&[4, 5, 6], player_id) < 2 {
|
||||||
let btype = match self.level {
|
let btype = match self.level {
|
||||||
WeaponLevel::Level1 => { 4 }
|
WeaponLevel::Level1 => { 4 }
|
||||||
WeaponLevel::Level2 => { 5 }
|
WeaponLevel::Level2 => { 5 }
|
||||||
|
@ -203,7 +222,7 @@ impl Weapon {
|
||||||
|
|
||||||
fn shoot_bullet_fireball(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
fn shoot_bullet_fireball(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||||
let max_bullets = self.level as usize + 1;
|
let max_bullets = self.level as usize + 1;
|
||||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([7, 8, 9], player_id) < max_bullets {
|
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi(&[7, 8, 9], player_id) < max_bullets {
|
||||||
let btype = match self.level {
|
let btype = match self.level {
|
||||||
WeaponLevel::Level1 => { 7 }
|
WeaponLevel::Level1 => { 7 }
|
||||||
WeaponLevel::Level2 => { 8 }
|
WeaponLevel::Level2 => { 8 }
|
||||||
|
@ -258,7 +277,125 @@ impl Weapon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shoot_bullet(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
fn shoot_bullet_spur(&mut self, player: &mut Player, player_id: TargetPlayer, inventory: &mut Inventory, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||||
|
let mut shoot = false;
|
||||||
|
let mut btype = 0;
|
||||||
|
|
||||||
|
if player.controller.shoot() {
|
||||||
|
inventory.add_xp(if player.equip.has_turbocharge() { 3 } else { 2 }, player, state);
|
||||||
|
self.counter1 += 1;
|
||||||
|
|
||||||
|
if (self.counter1 / 2 % 2) != 0 {
|
||||||
|
match self.level {
|
||||||
|
WeaponLevel::Level1 => {
|
||||||
|
state.sound_manager.play_sfx(59);
|
||||||
|
}
|
||||||
|
WeaponLevel::Level2 => {
|
||||||
|
state.sound_manager.play_sfx(60);
|
||||||
|
}
|
||||||
|
WeaponLevel::Level3 => {
|
||||||
|
if let (_, _, false) = inventory.get_current_max_exp(&state.constants) {
|
||||||
|
state.sound_manager.play_sfx(61);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WeaponLevel::None => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.counter1 > 0 {
|
||||||
|
shoot = true;
|
||||||
|
self.counter1 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (_, _, true) = inventory.get_current_max_exp(&state.constants) {
|
||||||
|
if self.counter2 == 0 {
|
||||||
|
self.counter2 = 1;
|
||||||
|
state.sound_manager.play_sfx(65);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.counter2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let level = self.level;
|
||||||
|
if !player.controller.shoot() {
|
||||||
|
inventory.reset_current_weapon_xp();
|
||||||
|
}
|
||||||
|
|
||||||
|
match level {
|
||||||
|
WeaponLevel::Level1 => {
|
||||||
|
btype = 6;
|
||||||
|
shoot = false;
|
||||||
|
}
|
||||||
|
WeaponLevel::Level2 => btype = 37,
|
||||||
|
WeaponLevel::Level3 => {
|
||||||
|
if self.counter2 == 1 {
|
||||||
|
btype = 39;
|
||||||
|
} else {
|
||||||
|
btype = 38;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WeaponLevel::None => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const bullets: [u16; 6] = [44, 45, 46, 47, 48, 49];
|
||||||
|
if bullet_manager.count_bullets_multi(&bullets, player_id) == 0
|
||||||
|
&& (player.controller.trigger_shoot() || shoot) {
|
||||||
|
if !self.consume_ammo(1) {
|
||||||
|
state.sound_manager.play_sfx(37);
|
||||||
|
} else {
|
||||||
|
if player.up {
|
||||||
|
match player.direction {
|
||||||
|
Direction::Left => {
|
||||||
|
bullet_manager.create_bullet(player.x - 0x200, player.y - 8 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||||
|
state.create_caret(player.x - 0x200, player.y - 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
|
}
|
||||||
|
Direction::Right => {
|
||||||
|
bullet_manager.create_bullet(player.x + 0x200, player.y - 8 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||||
|
state.create_caret(player.x + 0x200, player.y - 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else if player.down {
|
||||||
|
match player.direction {
|
||||||
|
Direction::Left => {
|
||||||
|
bullet_manager.create_bullet(player.x - 0x200, player.y + 8 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||||
|
state.create_caret(player.x - 0x200, player.y + 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
|
}
|
||||||
|
Direction::Right => {
|
||||||
|
bullet_manager.create_bullet(player.x + 0x200, player.y + 8 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||||
|
state.create_caret(player.x + 0x200, player.y + 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match player.direction {
|
||||||
|
Direction::Left => {
|
||||||
|
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 3 * 0x200, btype, player_id, Direction::Left, &state.constants);
|
||||||
|
state.create_caret(player.x - 6 * 0x200, player.y + 3 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
|
}
|
||||||
|
Direction::Right => {
|
||||||
|
bullet_manager.create_bullet(player.x + 6 * 0x200, player.y + 3 * 0x200, btype, player_id, Direction::Right, &state.constants);
|
||||||
|
state.create_caret(player.x + 6 * 0x200, player.y + 3 * 0x200, CaretType::Shoot, Direction::Right);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sound = match btype {
|
||||||
|
6 => 49,
|
||||||
|
37 => 62,
|
||||||
|
38 => 63,
|
||||||
|
39 => 64,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.sound_manager.play_sfx(sound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shoot_bullet(&mut self, player: &mut Player, player_id: TargetPlayer, inventory: &mut Inventory, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||||
if !player.cond.alive() || player.cond.hidden() {
|
if !player.cond.alive() || player.cond.hidden() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -274,7 +411,7 @@ impl Weapon {
|
||||||
WeaponType::Blade => {}
|
WeaponType::Blade => {}
|
||||||
WeaponType::SuperMissileLauncher => {}
|
WeaponType::SuperMissileLauncher => {}
|
||||||
WeaponType::Nemesis => {}
|
WeaponType::Nemesis => {}
|
||||||
WeaponType::Spur => {}
|
WeaponType::Spur => self.shoot_bullet_spur(player, player_id, inventory, bullet_manager, state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue