mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-07-12 07:46:48 +00:00
initial implementation of weapons and bullets
This commit is contained in:
parent
3df8781c25
commit
e143185f5d
251
src/bullet.rs
251
src/bullet.rs
|
@ -1,4 +1,43 @@
|
||||||
use crate::common::{Condition, Direction, Rect, Flag};
|
use crate::caret::CaretType;
|
||||||
|
use crate::common::{Condition, Direction, Flag, Rect};
|
||||||
|
use crate::engine_constants::{BulletData, EngineConstants};
|
||||||
|
use crate::physics::PhysicalEntity;
|
||||||
|
use crate::SharedGameState;
|
||||||
|
use crate::stage::Stage;
|
||||||
|
|
||||||
|
pub struct BulletManager {
|
||||||
|
pub bullets: Vec<Bullet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BulletManager {
|
||||||
|
pub fn new() -> BulletManager {
|
||||||
|
BulletManager {
|
||||||
|
bullets: Vec::with_capacity(32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_bullet(&mut self, x: isize, y: isize, btype: u16, direction: Direction, constants: &EngineConstants) {
|
||||||
|
self.bullets.push(Bullet::new(x, y, btype, direction, constants));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_bullets(&mut self, state: &mut SharedGameState, stage: &Stage) {
|
||||||
|
for bullet in self.bullets.iter_mut() {
|
||||||
|
bullet.tick(state);
|
||||||
|
bullet.flags.0 = 0;
|
||||||
|
bullet.tick_map_collisions(state, stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bullets.retain(|b| !b.is_dead());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count_bullets(&self, btype: u16) -> usize {
|
||||||
|
self.bullets.iter().filter(|b| b.btype == btype).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count_bullets_multi(&self, btypes: [u16; 3]) -> usize {
|
||||||
|
self.bullets.iter().filter(|b| btypes.contains(&b.btype)).count()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Bullet {
|
pub struct Bullet {
|
||||||
pub btype: u16,
|
pub btype: u16,
|
||||||
|
@ -17,15 +56,221 @@ pub struct Bullet {
|
||||||
pub anim_rect: Rect<usize>,
|
pub anim_rect: Rect<usize>,
|
||||||
pub enemy_hit_width: u32,
|
pub enemy_hit_width: u32,
|
||||||
pub enemy_hit_height: u32,
|
pub enemy_hit_height: u32,
|
||||||
pub block_hit_width: u32,
|
|
||||||
pub block_hit_height: u32,
|
|
||||||
pub anim_num: u16,
|
pub anim_num: u16,
|
||||||
pub anim_counter: u16,
|
pub anim_counter: u16,
|
||||||
pub action_num: u16,
|
pub action_num: u16,
|
||||||
pub action_counter: u16,
|
pub action_counter: u16,
|
||||||
|
pub hit_bounds: Rect<usize>,
|
||||||
pub display_bounds: Rect<usize>,
|
pub display_bounds: Rect<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bullet {
|
impl Bullet {
|
||||||
|
pub fn new(x: isize, y: isize, btype: u16, direction: Direction, constants: &EngineConstants) -> Bullet {
|
||||||
|
let bullet = constants.weapon.bullet_table
|
||||||
|
.get(btype as usize)
|
||||||
|
.unwrap_or_else(|| &BulletData {
|
||||||
|
damage: 0,
|
||||||
|
life: 0,
|
||||||
|
lifetime: 0,
|
||||||
|
flags: Flag(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 },
|
||||||
|
});
|
||||||
|
|
||||||
|
Bullet {
|
||||||
|
btype,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
vel_x: 0,
|
||||||
|
vel_y: 0,
|
||||||
|
target_x: 0,
|
||||||
|
target_y: 0,
|
||||||
|
life: bullet.life as u16,
|
||||||
|
lifetime: bullet.lifetime,
|
||||||
|
damage: bullet.damage as u16,
|
||||||
|
cond: Condition(0x80),
|
||||||
|
flags: bullet.flags,
|
||||||
|
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(
|
||||||
|
bullet.display_bounds.left as usize * 0x200,
|
||||||
|
bullet.display_bounds.top as usize * 0x200,
|
||||||
|
bullet.display_bounds.right as usize * 0x200,
|
||||||
|
bullet.display_bounds.bottom as usize * 0x200,
|
||||||
|
),
|
||||||
|
hit_bounds: Rect::new(
|
||||||
|
bullet.block_hit_width as usize * 0x200,
|
||||||
|
bullet.block_hit_height as usize * 0x200,
|
||||||
|
bullet.block_hit_width as usize * 0x200,
|
||||||
|
bullet.block_hit_height as usize * 0x200,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_dead(&self) -> bool {
|
||||||
|
!self.cond.alive()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Direction::Left => { self.vel_x = -0x1000 }
|
||||||
|
Direction::Up => { self.vel_y = -0x1000 }
|
||||||
|
Direction::Right => { self.vel_x = 0x1000 }
|
||||||
|
Direction::Bottom => { self.vel_y = 0x1000 }
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.btype {
|
||||||
|
4 => {
|
||||||
|
match self.direction {
|
||||||
|
Direction::Left | Direction::Right => {
|
||||||
|
self.enemy_hit_height = 0x400;
|
||||||
|
}
|
||||||
|
Direction::Up | Direction::Bottom => {
|
||||||
|
self.enemy_hit_width = 0x400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
match self.direction {
|
||||||
|
Direction::Left | Direction::Right => {
|
||||||
|
self.enemy_hit_height = 0x800;
|
||||||
|
}
|
||||||
|
Direction::Up | Direction::Bottom => {
|
||||||
|
self.enemy_hit_width = 0x800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
6 => {
|
||||||
|
// level 3 uses default values
|
||||||
|
}
|
||||||
|
_ => { unreachable!() }
|
||||||
|
}
|
||||||
|
} 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { unreachable!() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self, state: &mut SharedGameState) {
|
||||||
|
if self.lifetime == 0 {
|
||||||
|
self.cond.set_alive(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.btype {
|
||||||
|
4 | 5 | 6 => {
|
||||||
|
self.tick_polar_star(state);
|
||||||
|
}
|
||||||
|
_ => { self.cond.set_alive(false); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhysicalEntity for Bullet {
|
||||||
|
fn x(&self) -> isize {
|
||||||
|
self.x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn y(&self) -> isize {
|
||||||
|
self.y
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vel_x(&self) -> isize {
|
||||||
|
self.vel_x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vel_y(&self) -> isize {
|
||||||
|
self.vel_y
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> u8 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hit_bounds(&self) -> &Rect<usize> {
|
||||||
|
&self.hit_bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_x(&mut self, x: isize) {
|
||||||
|
self.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_y(&mut self, y: isize) {
|
||||||
|
self.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vel_x(&mut self, vel_x: isize) {
|
||||||
|
self.vel_x = vel_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vel_y(&mut self, vel_y: isize) {
|
||||||
|
self.vel_y = vel_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cond(&mut self) -> &mut Condition {
|
||||||
|
&mut self.cond
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flags(&mut self) -> &mut Flag {
|
||||||
|
&mut self.flags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_player(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*fn judge_hit_block(&mut self, state: &SharedGameState, x: isize, y: isize) {
|
||||||
|
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
100
src/caret.rs
100
src/caret.rs
|
@ -2,6 +2,7 @@ use crate::bitfield;
|
||||||
use crate::common::{Condition, Direction, Rect};
|
use crate::common::{Condition, Direction, Rect};
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
use crate::rng::RNG;
|
use crate::rng::RNG;
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||||
pub enum CaretType {
|
pub enum CaretType {
|
||||||
|
@ -36,14 +37,15 @@ pub struct Caret {
|
||||||
pub cond: Condition,
|
pub cond: Condition,
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
pub anim_rect: Rect<usize>,
|
pub anim_rect: Rect<usize>,
|
||||||
anim_num: usize,
|
anim_num: u16,
|
||||||
anim_counter: isize,
|
anim_counter: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Caret {
|
impl Caret {
|
||||||
pub fn new(x: isize, y: isize, ctype: CaretType, direct: Direction, constants: &EngineConstants) -> Self {
|
pub fn new(x: isize, y: isize, ctype: CaretType, direct: Direction, constants: &EngineConstants) -> Caret {
|
||||||
let (offset_x, offset_y) = constants.caret.offsets[ctype as usize];
|
let (offset_x, offset_y) = constants.caret.offsets[ctype as usize];
|
||||||
Self {
|
|
||||||
|
Caret {
|
||||||
ctype,
|
ctype,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
@ -53,7 +55,7 @@ impl Caret {
|
||||||
offset_y,
|
offset_y,
|
||||||
cond: Condition(0x80),
|
cond: Condition(0x80),
|
||||||
direction: direct,
|
direction: direct,
|
||||||
anim_rect: Rect::<usize>::new(0, 0, 0, 0),
|
anim_rect: Rect::new(0, 0, 0, 0),
|
||||||
anim_num: 0,
|
anim_num: 0,
|
||||||
anim_counter: 0,
|
anim_counter: 0,
|
||||||
}
|
}
|
||||||
|
@ -63,13 +65,73 @@ impl Caret {
|
||||||
match self.ctype {
|
match self.ctype {
|
||||||
CaretType::None => {}
|
CaretType::None => {}
|
||||||
CaretType::Bubble => {}
|
CaretType::Bubble => {}
|
||||||
CaretType::ProjectileDissipation => {}
|
CaretType::ProjectileDissipation => {
|
||||||
CaretType::Shoot => {}
|
match self.direction {
|
||||||
CaretType::SnakeAfterimage | CaretType::SnakeAfterimage2 => { // dupe, unused
|
Direction::Left => {
|
||||||
|
self.vel_y -= 0x10;
|
||||||
|
self.y += self.vel_y;
|
||||||
|
|
||||||
|
self.anim_counter += 1;
|
||||||
|
if self.anim_counter > 5 {
|
||||||
|
self.anim_counter = 0;
|
||||||
|
self.anim_num += 1;
|
||||||
|
|
||||||
|
if self.anim_num > constants.caret.projectile_dissipation_left_rects.len() as u16 {
|
||||||
|
self.cond.set_alive(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.anim_rect = constants.caret.projectile_dissipation_left_rects[self.anim_num as usize];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Direction::Up => {
|
||||||
|
self.anim_counter += 1;
|
||||||
|
|
||||||
|
if self.anim_counter > 24 {
|
||||||
|
self.cond.set_alive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = constants.caret.projectile_dissipation_up_rects.len();
|
||||||
|
self.anim_rect = constants.caret.projectile_dissipation_up_rects[(self.anim_num as usize / 2) % len];
|
||||||
|
},
|
||||||
|
Direction::Right => {
|
||||||
|
self.anim_counter += 1;
|
||||||
|
if self.anim_counter > 2 {
|
||||||
|
self.anim_counter = 0;
|
||||||
|
self.anim_num += 1;
|
||||||
|
|
||||||
|
if self.anim_num > constants.caret.projectile_dissipation_right_rects.len() as u16 {
|
||||||
|
self.cond.set_alive(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.anim_rect = constants.caret.projectile_dissipation_right_rects[self.anim_num as usize];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Direction::Bottom => {
|
||||||
|
self.cond.set_alive(false);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
CaretType::Shoot => {
|
||||||
|
if self.anim_counter == 0 {
|
||||||
|
self.anim_rect = constants.caret.shoot_rects[self.anim_num as usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.anim_counter += 1;
|
||||||
|
if self.anim_counter > 3 {
|
||||||
|
self.anim_counter = 0;
|
||||||
|
self.anim_num += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.anim_num == constants.caret.shoot_rects.len() as u16 {
|
||||||
|
self.cond.set_alive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CaretType::SnakeAfterimage | CaretType::SnakeAfterimage2 => {} // dupe, unused
|
||||||
CaretType::Zzz => {
|
CaretType::Zzz => {
|
||||||
if self.anim_counter == 0 {
|
if self.anim_counter == 0 {
|
||||||
self.anim_rect = constants.caret.zzz_rects[self.anim_num];
|
self.anim_rect = constants.caret.zzz_rects[self.anim_num as usize];
|
||||||
}
|
}
|
||||||
|
|
||||||
self.anim_counter += 1;
|
self.anim_counter += 1;
|
||||||
|
@ -78,27 +140,30 @@ impl Caret {
|
||||||
self.anim_num += 1;
|
self.anim_num += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.anim_num == constants.caret.zzz_rects.len() {
|
if self.anim_num == constants.caret.zzz_rects.len() as u16 {
|
||||||
self.cond.set_alive(false);
|
self.cond.set_alive(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.x += 0x80; // 0.4fix9
|
self.x += 0x80; // 0.4fix9
|
||||||
self.y -= 0x80;
|
self.y -= 0x80;
|
||||||
}
|
}
|
||||||
CaretType::Exhaust => {
|
CaretType::Exhaust => {
|
||||||
|
if self.anim_counter == 0 {
|
||||||
|
self.anim_rect = constants.caret.exhaust_rects[self.anim_num as usize];
|
||||||
|
}
|
||||||
|
|
||||||
self.anim_counter += 1;
|
self.anim_counter += 1;
|
||||||
if self.anim_counter > 1 {
|
if self.anim_counter > 1 {
|
||||||
self.anim_counter = 0;
|
self.anim_counter = 0;
|
||||||
self.anim_num += 1;
|
self.anim_num += 1;
|
||||||
}
|
|
||||||
|
|
||||||
if self.anim_num >= constants.caret.exhaust_rects.len() {
|
if self.anim_num >= constants.caret.exhaust_rects.len() as u16 {
|
||||||
self.cond.set_alive(false);
|
self.cond.set_alive(false);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.anim_rect = constants.caret.exhaust_rects[self.anim_num];
|
|
||||||
|
|
||||||
match self.direction {
|
match self.direction {
|
||||||
Direction::Left => { self.x -= 0x400; } // 2.0fix9
|
Direction::Left => { self.x -= 0x400; } // 2.0fix9
|
||||||
Direction::Up => { self.y -= 0x400; }
|
Direction::Up => { self.y -= 0x400; }
|
||||||
|
@ -165,7 +230,8 @@ impl Caret {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.anim_rect = constants.caret.little_particles_rects[self.anim_num / 2 % constants.caret.little_particles_rects.len()];
|
let len = constants.caret.little_particles_rects.len();
|
||||||
|
self.anim_rect = constants.caret.little_particles_rects[self.anim_num as usize / 2 % len];
|
||||||
|
|
||||||
if self.direction == Direction::Right {
|
if self.direction == Direction::Right {
|
||||||
self.x -= 4 * 0x200;
|
self.x -= 4 * 0x200;
|
||||||
|
|
|
@ -13,10 +13,10 @@ bitfield! {
|
||||||
pub hit_bottom_wall, set_hit_bottom_wall: 3; // 0x08
|
pub hit_bottom_wall, set_hit_bottom_wall: 3; // 0x08
|
||||||
pub hit_right_slope, set_hit_right_slope: 4; // 0x10
|
pub hit_right_slope, set_hit_right_slope: 4; // 0x10
|
||||||
pub hit_left_slope, set_hit_left_slope: 5; // 0x20
|
pub hit_left_slope, set_hit_left_slope: 5; // 0x20
|
||||||
pub flag_x40, set_flag_x40: 6; // 0x40
|
pub snack_destroy, set_snack_destroy: 6; // 0x40
|
||||||
pub flag_x80, set_flag_x80: 7; // 0x80
|
pub flag_x80, set_flag_x80: 7; // 0x80
|
||||||
pub in_water, set_in_water: 8; // 0x100
|
pub in_water, set_in_water: 8; // 0x100
|
||||||
pub flag_x200, set_flag_x200: 9; // 0x200
|
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
|
||||||
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
|
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
|
||||||
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
|
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
|
||||||
pub force_left, set_force_left: 12; // 0x1000
|
pub force_left, set_force_left: 12; // 0x1000
|
||||||
|
@ -84,7 +84,7 @@ bitfield! {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct ControlFlags(u16);
|
pub struct ControlFlags(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
|
||||||
pub flag_x01, set_flag_x01: 0;
|
pub flag_x01, set_flag_x01: 0;
|
||||||
pub control_enabled, set_control_enabled: 1;
|
pub control_enabled, set_control_enabled: 1;
|
||||||
pub interactions_disabled, set_interactions_disabled: 2;
|
pub interactions_disabled, set_interactions_disabled: 2;
|
||||||
|
|
|
@ -48,6 +48,10 @@ pub struct CaretConsts {
|
||||||
pub offsets: [(isize, isize); 18],
|
pub offsets: [(isize, isize); 18],
|
||||||
pub bubble_left_rects: Vec<Rect<usize>>,
|
pub bubble_left_rects: Vec<Rect<usize>>,
|
||||||
pub bubble_right_rects: Vec<Rect<usize>>,
|
pub bubble_right_rects: Vec<Rect<usize>>,
|
||||||
|
pub projectile_dissipation_left_rects: Vec<Rect<usize>>,
|
||||||
|
pub projectile_dissipation_right_rects: Vec<Rect<usize>>,
|
||||||
|
pub projectile_dissipation_up_rects: Vec<Rect<usize>>,
|
||||||
|
pub shoot_rects: Vec<Rect<usize>>,
|
||||||
pub zzz_rects: Vec<Rect<usize>>,
|
pub zzz_rects: Vec<Rect<usize>>,
|
||||||
pub drowned_quote_left_rect: Rect<usize>,
|
pub drowned_quote_left_rect: Rect<usize>,
|
||||||
pub drowned_quote_right_rect: Rect<usize>,
|
pub drowned_quote_right_rect: Rect<usize>,
|
||||||
|
@ -65,6 +69,10 @@ impl Clone for CaretConsts {
|
||||||
offsets: self.offsets,
|
offsets: self.offsets,
|
||||||
bubble_left_rects: self.bubble_left_rects.clone(),
|
bubble_left_rects: self.bubble_left_rects.clone(),
|
||||||
bubble_right_rects: self.bubble_right_rects.clone(),
|
bubble_right_rects: self.bubble_right_rects.clone(),
|
||||||
|
projectile_dissipation_left_rects: self.projectile_dissipation_left_rects.clone(),
|
||||||
|
projectile_dissipation_right_rects: self.projectile_dissipation_right_rects.clone(),
|
||||||
|
projectile_dissipation_up_rects: self.projectile_dissipation_up_rects.clone(),
|
||||||
|
shoot_rects: self.shoot_rects.clone(),
|
||||||
zzz_rects: self.zzz_rects.clone(),
|
zzz_rects: self.zzz_rects.clone(),
|
||||||
drowned_quote_left_rect: self.drowned_quote_left_rect,
|
drowned_quote_left_rect: self.drowned_quote_left_rect,
|
||||||
drowned_quote_right_rect: self.drowned_quote_right_rect,
|
drowned_quote_right_rect: self.drowned_quote_right_rect,
|
||||||
|
@ -92,15 +100,24 @@ pub struct BulletData {
|
||||||
pub display_bounds: Rect<u8>,
|
pub display_bounds: Rect<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct BulletRects {
|
||||||
|
pub b004_polar_star_l1: [Rect<usize>; 2],
|
||||||
|
pub b005_polar_star_l2: [Rect<usize>; 2],
|
||||||
|
pub b006_polar_star_l3: [Rect<usize>; 2],
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WeaponConsts {
|
pub struct WeaponConsts {
|
||||||
pub bullet_table: Vec<BulletData>,
|
pub bullet_table: Vec<BulletData>,
|
||||||
|
pub bullet_rects: BulletRects,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for WeaponConsts {
|
impl Clone for WeaponConsts {
|
||||||
fn clone(&self) -> WeaponConsts {
|
fn clone(&self) -> WeaponConsts {
|
||||||
WeaponConsts {
|
WeaponConsts {
|
||||||
bullet_table: self.bullet_table.clone(),
|
bullet_table: self.bullet_table.clone(),
|
||||||
|
bullet_rects: self.bullet_rects,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,6 +333,29 @@ impl EngineConstants {
|
||||||
Rect { left: 80, top: 24, right: 88, bottom: 32 },
|
Rect { left: 80, top: 24, right: 88, bottom: 32 },
|
||||||
Rect { left: 88, top: 24, right: 96, bottom: 32 },
|
Rect { left: 88, top: 24, right: 96, bottom: 32 },
|
||||||
],
|
],
|
||||||
|
projectile_dissipation_left_rects: vec![
|
||||||
|
Rect { left: 0, top: 32, right: 16, bottom: 48 },
|
||||||
|
Rect { left: 16, top: 32, right: 32, bottom: 48 },
|
||||||
|
Rect { left: 32, top: 32, right: 48, bottom: 48 },
|
||||||
|
Rect { left: 48, top: 32, right: 64, bottom: 48 },
|
||||||
|
],
|
||||||
|
projectile_dissipation_right_rects: vec![
|
||||||
|
Rect { left: 176, top: 0, right: 192, bottom: 16 },
|
||||||
|
Rect { left: 192, top: 0, right: 208, bottom: 16 },
|
||||||
|
Rect { left: 208, top: 0, right: 224, bottom: 16 },
|
||||||
|
Rect { left: 224, top: 0, right: 240, bottom: 16 },
|
||||||
|
],
|
||||||
|
projectile_dissipation_up_rects: vec![
|
||||||
|
Rect { left: 0, top: 32, right: 16, bottom: 48 },
|
||||||
|
Rect { left: 32, top: 32, right: 48, bottom: 48 },
|
||||||
|
Rect { left: 16, top: 32, right: 32, bottom: 48 },
|
||||||
|
],
|
||||||
|
shoot_rects: vec![
|
||||||
|
Rect { left: 0, top: 48, right: 16, bottom: 64 },
|
||||||
|
Rect { left: 16, top: 48, right: 32, bottom: 64 },
|
||||||
|
Rect { left: 32, top: 48, right: 48, bottom: 64 },
|
||||||
|
Rect { left: 48, top: 48, right: 64, bottom: 64 },
|
||||||
|
],
|
||||||
zzz_rects: vec![
|
zzz_rects: vec![
|
||||||
Rect { left: 32, top: 64, right: 40, bottom: 72 },
|
Rect { left: 32, top: 64, right: 40, bottom: 72 },
|
||||||
Rect { left: 32, top: 72, right: 40, bottom: 80 },
|
Rect { left: 32, top: 72, right: 40, bottom: 80 },
|
||||||
|
@ -824,6 +864,20 @@ impl EngineConstants {
|
||||||
// Whimsical Star
|
// Whimsical Star
|
||||||
BulletData { damage: 1, life: 1, lifetime: 1, flags: Flag(36), enemy_hit_width: 1, enemy_hit_height: 1, block_hit_width: 1, block_hit_height: 1, display_bounds: Rect { left: 1, top: 1, right: 1, bottom: 1 } },
|
BulletData { damage: 1, life: 1, lifetime: 1, flags: Flag(36), enemy_hit_width: 1, enemy_hit_height: 1, block_hit_width: 1, block_hit_height: 1, display_bounds: Rect { left: 1, top: 1, right: 1, bottom: 1 } },
|
||||||
],
|
],
|
||||||
|
bullet_rects: BulletRects {
|
||||||
|
b004_polar_star_l1: [
|
||||||
|
Rect { left: 128, top: 32, right: 144, bottom: 48 },
|
||||||
|
Rect { left: 144, top: 32, right: 160, bottom: 48 },
|
||||||
|
],
|
||||||
|
b005_polar_star_l2: [
|
||||||
|
Rect { left: 160, top: 32, right: 176, bottom: 48 },
|
||||||
|
Rect { left: 176, top: 32, right: 192, bottom: 48 },
|
||||||
|
],
|
||||||
|
b006_polar_star_l3: [
|
||||||
|
Rect { left: 128, top: 48, right: 144, bottom: 64 },
|
||||||
|
Rect { left: 144, top: 48, right: 160, bottom: 64 },
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tex_sizes: case_insensitive_hashmap! {
|
tex_sizes: case_insensitive_hashmap! {
|
||||||
"ArmsImage" => (256, 16),
|
"ArmsImage" => (256, 16),
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
#[derive(Clone)]
|
use crate::weapon::{Weapon, WeaponLevel, WeaponType};
|
||||||
pub struct Weapon {
|
|
||||||
pub id: u16,
|
|
||||||
pub level: u16,
|
|
||||||
pub experience: u16,
|
|
||||||
pub ammo: u16,
|
|
||||||
pub max_ammo: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Item(u16);
|
pub struct Item(u16);
|
||||||
|
@ -43,20 +36,20 @@ impl Inventory {
|
||||||
self.items.iter().any(|item| item.0 == item_id)
|
self.items.iter().any(|item| item.0 == item_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_weapon(&mut self, weapon_id: u16, max_ammo: u16) {
|
pub fn add_weapon(&mut self, weapon_id: WeaponType, max_ammo: u16) {
|
||||||
if !self.has_weapon(weapon_id) {
|
if !self.has_weapon(weapon_id) {
|
||||||
self.weapons.push(Weapon {
|
self.weapons.push(Weapon::new(
|
||||||
id: weapon_id,
|
weapon_id,
|
||||||
level: 1,
|
WeaponLevel::Level1,
|
||||||
experience: 0,
|
0,
|
||||||
ammo: max_ammo,
|
|
||||||
max_ammo,
|
max_ammo,
|
||||||
});
|
max_ammo,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_weapon(&mut self, weapon_id: u16) {
|
pub fn remove_weapon(&mut self, wtype: WeaponType) {
|
||||||
self.weapons.retain(|weapon| weapon.id != weapon_id);
|
self.weapons.retain(|weapon| weapon.wtype != wtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_weapon(&self, idx: usize) -> Option<&Weapon> {
|
pub fn get_weapon(&self, idx: usize) -> Option<&Weapon> {
|
||||||
|
@ -75,7 +68,7 @@ impl Inventory {
|
||||||
|
|
||||||
pub fn reset_all_weapon_xp(&mut self) {
|
pub fn reset_all_weapon_xp(&mut self) {
|
||||||
for weapon in self.weapons.iter_mut() {
|
for weapon in self.weapons.iter_mut() {
|
||||||
weapon.level = 1;
|
weapon.level = WeaponLevel::Level1;
|
||||||
weapon.experience = 0;
|
weapon.experience = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,11 +81,11 @@ impl Inventory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_level(&self) -> u16 {
|
pub fn get_current_level(&self) -> WeaponLevel {
|
||||||
if let Some(weapon) = self.weapons.get(self.current_weapon as usize) {
|
if let Some(weapon) = self.weapons.get(self.current_weapon as usize) {
|
||||||
weapon.level
|
weapon.level
|
||||||
} else {
|
} else {
|
||||||
0
|
WeaponLevel::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +97,7 @@ impl Inventory {
|
||||||
self.weapons.len()
|
self.weapons.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_weapon(&self, weapon_id: u16) -> bool {
|
pub fn has_weapon(&self, wtype: WeaponType) -> bool {
|
||||||
self.weapons.iter().any(|weapon| weapon.id == weapon_id)
|
self.weapons.iter().any(|weapon| weapon.wtype == wtype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ use winit::{ElementState, Event, KeyboardInput, WindowEvent};
|
||||||
use crate::bmfont_renderer::BMFontRenderer;
|
use crate::bmfont_renderer::BMFontRenderer;
|
||||||
use crate::builtin_fs::BuiltinFS;
|
use crate::builtin_fs::BuiltinFS;
|
||||||
use crate::caret::{Caret, CaretType};
|
use crate::caret::{Caret, CaretType};
|
||||||
use crate::common::{Direction, FadeState, KeyState, ControlFlags};
|
use crate::common::{ControlFlags, Direction, FadeState, KeyState};
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
use crate::ggez::{Context, ContextBuilder, event, filesystem, GameResult};
|
use crate::ggez::{Context, ContextBuilder, event, filesystem, GameResult};
|
||||||
use crate::ggez::conf::{WindowMode, WindowSetup};
|
use crate::ggez::conf::{WindowMode, WindowSetup};
|
||||||
|
@ -34,6 +34,7 @@ use crate::ggez::graphics::DrawParam;
|
||||||
use crate::ggez::input::keyboard;
|
use crate::ggez::input::keyboard;
|
||||||
use crate::ggez::mint::ColumnMatrix4;
|
use crate::ggez::mint::ColumnMatrix4;
|
||||||
use crate::ggez::nalgebra::Vector2;
|
use crate::ggez::nalgebra::Vector2;
|
||||||
|
use crate::npc::NPCTable;
|
||||||
use crate::rng::RNG;
|
use crate::rng::RNG;
|
||||||
use crate::scene::loading_scene::LoadingScene;
|
use crate::scene::loading_scene::LoadingScene;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
|
@ -42,7 +43,6 @@ use crate::stage::StageData;
|
||||||
use crate::text_script::TextScriptVM;
|
use crate::text_script::TextScriptVM;
|
||||||
use crate::texture_set::TextureSet;
|
use crate::texture_set::TextureSet;
|
||||||
use crate::ui::UI;
|
use crate::ui::UI;
|
||||||
use crate::npc::NPCTable;
|
|
||||||
|
|
||||||
mod bmfont;
|
mod bmfont;
|
||||||
mod bmfont_renderer;
|
mod bmfont_renderer;
|
||||||
|
@ -53,6 +53,7 @@ mod common;
|
||||||
mod engine_constants;
|
mod engine_constants;
|
||||||
mod entity;
|
mod entity;
|
||||||
mod frame;
|
mod frame;
|
||||||
|
mod inventory;
|
||||||
mod ggez;
|
mod ggez;
|
||||||
mod live_debugger;
|
mod live_debugger;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
@ -68,7 +69,7 @@ mod sound;
|
||||||
mod text_script;
|
mod text_script;
|
||||||
mod texture_set;
|
mod texture_set;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod inventory;
|
mod weapon;
|
||||||
|
|
||||||
struct Game {
|
struct Game {
|
||||||
scene: Option<Box<dyn Scene>>,
|
scene: Option<Box<dyn Scene>>,
|
||||||
|
|
|
@ -327,6 +327,7 @@ pub struct NPCTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NPCTable {
|
impl NPCTable {
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> NPCTable {
|
pub fn new() -> NPCTable {
|
||||||
NPCTable {
|
NPCTable {
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
|
|
|
@ -32,7 +32,6 @@ pub struct Player {
|
||||||
pub cond: Condition,
|
pub cond: Condition,
|
||||||
pub flags: Flag,
|
pub flags: Flag,
|
||||||
pub equip: Equipment,
|
pub equip: Equipment,
|
||||||
pub inventory: Inventory,
|
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
pub display_bounds: Rect<usize>,
|
pub display_bounds: Rect<usize>,
|
||||||
pub hit_bounds: Rect<usize>,
|
pub hit_bounds: Rect<usize>,
|
||||||
|
@ -72,7 +71,6 @@ impl Player {
|
||||||
cond: Condition(0x80),
|
cond: Condition(0x80),
|
||||||
flags: Flag(0),
|
flags: Flag(0),
|
||||||
equip: Equipment(0),
|
equip: Equipment(0),
|
||||||
inventory: Inventory::new(),
|
|
||||||
direction: Direction::Right,
|
direction: Direction::Right,
|
||||||
display_bounds: constants.my_char.display_bounds,
|
display_bounds: constants.my_char.display_bounds,
|
||||||
hit_bounds: constants.my_char.hit_bounds,
|
hit_bounds: constants.my_char.hit_bounds,
|
||||||
|
|
|
@ -1,28 +1,32 @@
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::common::{FadeDirection, FadeState, Rect};
|
use crate::bullet::BulletManager;
|
||||||
|
use crate::common::{Direction, FadeDirection, FadeState, Rect};
|
||||||
use crate::entity::GameEntity;
|
use crate::entity::GameEntity;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::ggez::{Context, GameResult, graphics, timer};
|
use crate::ggez::{Context, GameResult, graphics, timer};
|
||||||
use crate::ggez::graphics::{Color, Drawable, DrawParam, Text, TextFragment};
|
use crate::ggez::graphics::Color;
|
||||||
use crate::ggez::nalgebra::clamp;
|
use crate::ggez::nalgebra::clamp;
|
||||||
|
use crate::inventory::Inventory;
|
||||||
use crate::npc::NPCMap;
|
use crate::npc::NPCMap;
|
||||||
use crate::physics::PhysicalEntity;
|
use crate::physics::PhysicalEntity;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use crate::SharedGameState;
|
use crate::SharedGameState;
|
||||||
use crate::stage::{BackgroundType, Stage};
|
use crate::stage::{BackgroundType, Stage};
|
||||||
use crate::str;
|
|
||||||
use crate::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptVM};
|
use crate::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptVM};
|
||||||
use crate::ui::Components;
|
use crate::ui::Components;
|
||||||
|
use crate::weapon::WeaponType;
|
||||||
|
|
||||||
pub struct GameScene {
|
pub struct GameScene {
|
||||||
pub tick: usize,
|
pub tick: usize,
|
||||||
pub stage: Stage,
|
pub stage: Stage,
|
||||||
pub frame: Frame,
|
pub frame: Frame,
|
||||||
pub player: Player,
|
pub player: Player,
|
||||||
|
pub inventory: Inventory,
|
||||||
pub stage_id: usize,
|
pub stage_id: usize,
|
||||||
pub npc_map: NPCMap,
|
pub npc_map: NPCMap,
|
||||||
|
pub bullet_manager: BulletManager,
|
||||||
tex_background_name: String,
|
tex_background_name: String,
|
||||||
tex_tileset_name: String,
|
tex_tileset_name: String,
|
||||||
life_bar: u16,
|
life_bar: u16,
|
||||||
|
@ -58,6 +62,7 @@ impl GameScene {
|
||||||
tick: 0,
|
tick: 0,
|
||||||
stage,
|
stage,
|
||||||
player: Player::new(state),
|
player: Player::new(state),
|
||||||
|
inventory: Inventory::new(),
|
||||||
frame: Frame {
|
frame: Frame {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -65,6 +70,7 @@ impl GameScene {
|
||||||
},
|
},
|
||||||
stage_id: id,
|
stage_id: id,
|
||||||
npc_map: NPCMap::new(),
|
npc_map: NPCMap::new(),
|
||||||
|
bullet_manager: BulletManager::new(),
|
||||||
tex_background_name,
|
tex_background_name,
|
||||||
tex_tileset_name,
|
tex_tileset_name,
|
||||||
life_bar: 0,
|
life_bar: 0,
|
||||||
|
@ -95,7 +101,7 @@ impl GameScene {
|
||||||
fn draw_hud(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
fn draw_hud(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||||
// none
|
// none
|
||||||
let weap_x = self.weapon_x_pos as f32;
|
let weap_x = self.weapon_x_pos as f32;
|
||||||
let (ammo, max_ammo) = self.player.inventory.get_current_ammo();
|
let (ammo, max_ammo) = self.inventory.get_current_ammo();
|
||||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||||
|
|
||||||
if max_ammo == 0 {
|
if max_ammo == 0 {
|
||||||
|
@ -133,9 +139,9 @@ impl GameScene {
|
||||||
batch.draw(ctx)?;
|
batch.draw(ctx)?;
|
||||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
||||||
|
|
||||||
let weapon_count = self.player.inventory.get_weapon_count();
|
let weapon_count = self.inventory.get_weapon_count();
|
||||||
if weapon_count != 0 {
|
if weapon_count != 0 {
|
||||||
let current_weapon = self.player.inventory.get_current_weapon_idx() as usize;
|
let current_weapon = self.inventory.get_current_weapon_idx() as usize;
|
||||||
let mut rect = Rect::new(0, 0, 0, 16);
|
let mut rect = Rect::new(0, 0, 0, 16);
|
||||||
|
|
||||||
for a in 0..weapon_count {
|
for a in 0..weapon_count {
|
||||||
|
@ -153,8 +159,8 @@ impl GameScene {
|
||||||
pos_x -= 48.0;
|
pos_x -= 48.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(weapon) = self.player.inventory.get_weapon(a) {
|
if let Some(weapon) = self.inventory.get_weapon(a) {
|
||||||
rect.left = weapon.id as usize * 16;
|
rect.left = weapon.wtype as usize * 16;
|
||||||
rect.right = rect.left + 16;
|
rect.right = rect.left + 16;
|
||||||
batch.add_rect(pos_x, 16.0, &rect);
|
batch.add_rect(pos_x, 16.0, &rect);
|
||||||
}
|
}
|
||||||
|
@ -167,7 +173,7 @@ impl GameScene {
|
||||||
self.draw_number(weap_x + 64.0, 16.0, ammo as usize, Alignment::Right, state, ctx)?;
|
self.draw_number(weap_x + 64.0, 16.0, ammo as usize, Alignment::Right, state, ctx)?;
|
||||||
self.draw_number(weap_x + 64.0, 24.0, max_ammo as usize, Alignment::Right, state, ctx)?;
|
self.draw_number(weap_x + 64.0, 24.0, max_ammo as usize, Alignment::Right, state, ctx)?;
|
||||||
}
|
}
|
||||||
self.draw_number(40.0, 32.0, self.player.inventory.get_current_level() as usize, Alignment::Right, state, ctx)?;
|
self.draw_number(40.0, 32.0, self.inventory.get_current_level() as usize, Alignment::Right, state, ctx)?;
|
||||||
self.draw_number(40.0, 40.0, self.life_bar as usize, Alignment::Right, state, ctx)?;
|
self.draw_number(40.0, 40.0, self.life_bar as usize, Alignment::Right, state, ctx)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -187,23 +193,18 @@ impl GameScene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BackgroundType::MoveDistant => {
|
BackgroundType::MoveDistant | BackgroundType::MoveNear => {
|
||||||
let off_x = self.frame.x as usize / 2 % (batch.width() * 0x200);
|
let (off_x, off_y) = if self.stage.data.background_type == BackgroundType::MoveDistant {
|
||||||
let off_y = self.frame.y as usize / 2 % (batch.height() * 0x200);
|
(
|
||||||
|
self.frame.x as usize % (batch.width() * 0x200),
|
||||||
let count_x = state.canvas_size.0 as usize / batch.width() + 2;
|
self.frame.y as usize % (batch.height() * 0x200)
|
||||||
let count_y = state.canvas_size.1 as usize / batch.height() + 2;
|
)
|
||||||
|
} else {
|
||||||
for y in 0..count_y {
|
(
|
||||||
for x in 0..count_x {
|
self.frame.x as usize / 2 % (batch.width() * 0x200),
|
||||||
batch.add((x * batch.width()) as f32 - (off_x / 0x200) as f32,
|
self.frame.y as usize / 2 % (batch.height() * 0x200)
|
||||||
(y * batch.height()) as f32 - (off_y / 0x200) as f32);
|
)
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
BackgroundType::MoveNear => {
|
|
||||||
let off_x = self.frame.x as usize % (batch.width() * 0x200);
|
|
||||||
let off_y = self.frame.y as usize % (batch.height() * 0x200);
|
|
||||||
|
|
||||||
let count_x = state.canvas_size.0 as usize / batch.width() + 2;
|
let count_x = state.canvas_size.0 as usize / batch.width() + 2;
|
||||||
let count_y = state.canvas_size.1 as usize / batch.height() + 2;
|
let count_y = state.canvas_size.1 as usize / batch.height() + 2;
|
||||||
|
@ -253,6 +254,40 @@ impl GameScene {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_bullets(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||||
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Bullet")?;
|
||||||
|
let mut x: isize;
|
||||||
|
let mut y: isize;
|
||||||
|
|
||||||
|
for bullet in self.bullet_manager.bullets.iter() {
|
||||||
|
match bullet.direction {
|
||||||
|
Direction::Left => {
|
||||||
|
x = bullet.x - bullet.display_bounds.left as isize;
|
||||||
|
y = bullet.y - bullet.display_bounds.top as isize;
|
||||||
|
}
|
||||||
|
Direction::Up => {
|
||||||
|
x = bullet.x - bullet.display_bounds.top as isize;
|
||||||
|
y = bullet.y - bullet.display_bounds.left as isize;
|
||||||
|
}
|
||||||
|
Direction::Right => {
|
||||||
|
x = bullet.x - bullet.display_bounds.right as isize;
|
||||||
|
y = bullet.y - bullet.display_bounds.top as isize;
|
||||||
|
}
|
||||||
|
Direction::Bottom => {
|
||||||
|
x = bullet.x - bullet.display_bounds.top as isize;
|
||||||
|
y = bullet.y - bullet.display_bounds.right as isize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.add_rect(((x / 0x200) - (self.frame.x / 0x200)) as f32,
|
||||||
|
((y / 0x200) - (self.frame.y / 0x200)) as f32,
|
||||||
|
&bullet.anim_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.draw(ctx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_carets(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
fn draw_carets(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?;
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?;
|
||||||
|
|
||||||
|
@ -560,6 +595,8 @@ impl Scene for GameScene {
|
||||||
self.player.target_x = self.player.x;
|
self.player.target_x = self.player.x;
|
||||||
self.player.target_y = self.player.y;
|
self.player.target_y = self.player.y;
|
||||||
self.frame.immediate_update(state, &self.player, &self.stage);
|
self.frame.immediate_update(state, &self.player, &self.stage);
|
||||||
|
|
||||||
|
self.inventory.add_weapon(WeaponType::PolarStar, 0);
|
||||||
//self.player.equip.set_booster_2_0(true);
|
//self.player.equip.set_booster_2_0(true);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -572,6 +609,7 @@ impl Scene for GameScene {
|
||||||
|
|
||||||
self.player.flags.0 = 0;
|
self.player.flags.0 = 0;
|
||||||
state.tick_carets();
|
state.tick_carets();
|
||||||
|
self.bullet_manager.tick_bullets(state, &self.stage);
|
||||||
|
|
||||||
self.player.tick_map_collisions(state, &self.stage);
|
self.player.tick_map_collisions(state, &self.stage);
|
||||||
self.player.tick_npc_collisions(state, &mut self.npc_map);
|
self.player.tick_npc_collisions(state, &mut self.npc_map);
|
||||||
|
@ -591,6 +629,10 @@ impl Scene for GameScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.control_flags.control_enabled() {
|
if state.control_flags.control_enabled() {
|
||||||
|
if let Some(weapon) = self.inventory.get_current_weapon_mut() {
|
||||||
|
weapon.shoot_bullet(&self.player, &mut self.bullet_manager, state);
|
||||||
|
}
|
||||||
|
|
||||||
// update health bar
|
// update health bar
|
||||||
if self.life_bar < self.player.life as u16 {
|
if self.life_bar < self.player.life as u16 {
|
||||||
self.life_bar = self.player.life as u16;
|
self.life_bar = self.player.life as u16;
|
||||||
|
@ -646,6 +688,7 @@ impl Scene for GameScene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.player.draw(state, ctx, &self.frame)?;
|
self.player.draw(state, ctx, &self.frame)?;
|
||||||
|
self.draw_bullets(state, ctx)?;
|
||||||
self.draw_tiles(state, ctx, TileLayer::Foreground)?;
|
self.draw_tiles(state, ctx, TileLayer::Foreground)?;
|
||||||
self.draw_tiles(state, ctx, TileLayer::Snack)?;
|
self.draw_tiles(state, ctx, TileLayer::Snack)?;
|
||||||
self.draw_carets(state, ctx)?;
|
self.draw_carets(state, ctx)?;
|
||||||
|
|
|
@ -20,6 +20,7 @@ use crate::ggez::{Context, GameResult};
|
||||||
use crate::ggez::GameError::ParseError;
|
use crate::ggez::GameError::ParseError;
|
||||||
use crate::player::ControlMode;
|
use crate::player::ControlMode;
|
||||||
use crate::scene::game_scene::GameScene;
|
use crate::scene::game_scene::GameScene;
|
||||||
|
use crate::weapon::WeaponType;
|
||||||
|
|
||||||
/// Engine's text script VM operation codes.
|
/// Engine's text script VM operation codes.
|
||||||
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]
|
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]
|
||||||
|
@ -639,17 +640,18 @@ impl TextScriptVM {
|
||||||
let item_id = read_cur_varint(&mut cursor)? as u16;
|
let item_id = read_cur_varint(&mut cursor)? as u16;
|
||||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||||
|
|
||||||
if game_scene.player.inventory.has_item(item_id) {
|
if game_scene.inventory.has_item(item_id) {
|
||||||
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
||||||
} else {
|
} else {
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OpCode::AMJ => {
|
OpCode::AMJ => {
|
||||||
let weapon = read_cur_varint(&mut cursor)? as u16;
|
let weapon = read_cur_varint(&mut cursor)? as u8;
|
||||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||||
|
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon);
|
||||||
|
|
||||||
if game_scene.player.inventory.has_weapon(weapon) {
|
if weapon_type.is_some() && game_scene.inventory.has_weapon(weapon_type.unwrap()) {
|
||||||
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
||||||
} else {
|
} else {
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
|
@ -743,7 +745,7 @@ impl TextScriptVM {
|
||||||
OpCode::YNJ => {
|
OpCode::YNJ => {
|
||||||
let event_no = read_cur_varint(&mut cursor)? as u16;
|
let event_no = read_cur_varint(&mut cursor)? as u16;
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::WaitConfirmation(event, cursor.position() as u32, event_no, 16, ConfirmSelection::No);
|
exec_state = TextScriptExecutionState::WaitConfirmation(event, cursor.position() as u32, event_no, 16, ConfirmSelection::Yes);
|
||||||
}
|
}
|
||||||
OpCode::GIT => {
|
OpCode::GIT => {
|
||||||
let item = read_cur_varint(&mut cursor)? as u16;
|
let item = read_cur_varint(&mut cursor)? as u16;
|
||||||
|
@ -758,6 +760,7 @@ impl TextScriptVM {
|
||||||
let pos_y = read_cur_varint(&mut cursor)? as isize * 16 * 0x200;
|
let pos_y = read_cur_varint(&mut cursor)? as isize * 16 * 0x200;
|
||||||
|
|
||||||
let mut new_scene = GameScene::new(state, ctx, map_id)?;
|
let mut new_scene = GameScene::new(state, ctx, map_id)?;
|
||||||
|
new_scene.inventory = game_scene.inventory.clone();
|
||||||
new_scene.player = game_scene.player.clone();
|
new_scene.player = game_scene.player.clone();
|
||||||
new_scene.player.vel_x = 0;
|
new_scene.player.vel_x = 0;
|
||||||
new_scene.player.vel_y = 0;
|
new_scene.player.vel_y = 0;
|
||||||
|
@ -998,39 +1001,45 @@ impl TextScriptVM {
|
||||||
OpCode::ITp => {
|
OpCode::ITp => {
|
||||||
let item_id = read_cur_varint(&mut cursor)? as u16;
|
let item_id = read_cur_varint(&mut cursor)? as u16;
|
||||||
|
|
||||||
game_scene.player.inventory.add_item(item_id);
|
game_scene.inventory.add_item(item_id);
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
OpCode::ITm => {
|
OpCode::ITm => {
|
||||||
let item_id = read_cur_varint(&mut cursor)? as u16;
|
let item_id = read_cur_varint(&mut cursor)? as u16;
|
||||||
|
|
||||||
game_scene.player.inventory.remove_item(item_id);
|
game_scene.inventory.remove_item(item_id);
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
OpCode::AMp => {
|
OpCode::AMp => {
|
||||||
let weapon_id = read_cur_varint(&mut cursor)? as u16;
|
let weapon_id = read_cur_varint(&mut cursor)? as u8;
|
||||||
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);
|
||||||
|
|
||||||
game_scene.player.inventory.add_weapon(weapon_id, max_ammo);
|
if let Some(wtype) = weapon_type {
|
||||||
|
game_scene.inventory.add_weapon(wtype, max_ammo);
|
||||||
|
}
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
OpCode::AMm => {
|
OpCode::AMm => {
|
||||||
let weapon_id = read_cur_varint(&mut cursor)? as u16;
|
let weapon_id = read_cur_varint(&mut cursor)? as u8;
|
||||||
|
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
|
||||||
|
|
||||||
game_scene.player.inventory.remove_weapon(weapon_id);
|
if let Some(wtype) = weapon_type {
|
||||||
|
game_scene.inventory.remove_weapon(wtype);
|
||||||
|
}
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
OpCode::AEp => {
|
OpCode::AEp => {
|
||||||
game_scene.player.inventory.refill_all_ammo();
|
game_scene.inventory.refill_all_ammo();
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
OpCode::ZAM => {
|
OpCode::ZAM => {
|
||||||
game_scene.player.inventory.reset_all_weapon_xp();
|
game_scene.inventory.reset_all_weapon_xp();
|
||||||
|
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
|
|
146
src/weapon.rs
Normal file
146
src/weapon.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
use num_derive::FromPrimitive;
|
||||||
|
|
||||||
|
use crate::bullet::BulletManager;
|
||||||
|
use crate::caret::CaretType;
|
||||||
|
use crate::common::Direction;
|
||||||
|
use crate::player::Player;
|
||||||
|
use crate::SharedGameState;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Copy, Clone, FromPrimitive)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum WeaponType {
|
||||||
|
None = 0,
|
||||||
|
Snake = 1,
|
||||||
|
PolarStar = 2,
|
||||||
|
Fireball = 3,
|
||||||
|
MachineGun = 4,
|
||||||
|
MissileLauncher = 5,
|
||||||
|
Bubbler = 7,
|
||||||
|
Sword = 9,
|
||||||
|
SuperMissileLauncher = 10,
|
||||||
|
Nemesis = 12,
|
||||||
|
Spur = 13,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Weapon {
|
||||||
|
pub wtype: WeaponType,
|
||||||
|
pub level: WeaponLevel,
|
||||||
|
pub experience: u16,
|
||||||
|
pub ammo: u16,
|
||||||
|
pub max_ammo: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum WeaponLevel {
|
||||||
|
None = 0,
|
||||||
|
Level1 = 1,
|
||||||
|
Level2 = 2,
|
||||||
|
Level3 = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Weapon {
|
||||||
|
pub fn new(wtype: WeaponType, level: WeaponLevel, experience: u16, ammo: u16, max_ammo: u16) -> Weapon {
|
||||||
|
Weapon {
|
||||||
|
wtype,
|
||||||
|
level,
|
||||||
|
experience,
|
||||||
|
ammo,
|
||||||
|
max_ammo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_ammo(&mut self, ammo: u16) -> bool {
|
||||||
|
if self.max_ammo == 0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.ammo >= ammo {
|
||||||
|
self.ammo -= ammo;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shoot_bullet_polar_star(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||||
|
if state.key_trigger.fire() && bullet_manager.count_bullets_multi([4, 5, 6]) < 2 {
|
||||||
|
let btype = match self.level {
|
||||||
|
WeaponLevel::Level1 => { 4 }
|
||||||
|
WeaponLevel::Level2 => { 5 }
|
||||||
|
WeaponLevel::Level3 => { 6 }
|
||||||
|
WeaponLevel::None => { unreachable!() }
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.consume_ammo(1) {
|
||||||
|
// todo: play sound 37
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.up {
|
||||||
|
match player.direction {
|
||||||
|
Direction::Left => {
|
||||||
|
bullet_manager.create_bullet(player.x - 0x200, player.y - 8 * 0x200, btype, 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, Direction::Up, &state.constants);
|
||||||
|
state.create_caret(player.x + 0x200, player.y - 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
|
}
|
||||||
|
_ => { unreachable!() }
|
||||||
|
}
|
||||||
|
} else if player.down {
|
||||||
|
match player.direction {
|
||||||
|
Direction::Left => {
|
||||||
|
bullet_manager.create_bullet(player.x - 0x200, player.y + 8 * 0x200, btype, 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, Direction::Bottom, &state.constants);
|
||||||
|
state.create_caret(player.x + 0x200, player.y + 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||||
|
}
|
||||||
|
_ => { unreachable!() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match player.direction {
|
||||||
|
Direction::Left => {
|
||||||
|
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 3 * 0x200, btype, 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, Direction::Right, &state.constants);
|
||||||
|
state.create_caret(player.x + 6 * 0x200, player.y + 3 * 0x200, CaretType::Shoot, Direction::Right);
|
||||||
|
}
|
||||||
|
_ => { unreachable!() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.level == WeaponLevel::Level3 {
|
||||||
|
// todo play sound 49
|
||||||
|
} else {
|
||||||
|
// todo play sound 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shoot_bullet(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||||
|
if player.cond.hidden() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.wtype {
|
||||||
|
WeaponType::None => {}
|
||||||
|
WeaponType::Snake => {}
|
||||||
|
WeaponType::PolarStar => { self.shoot_bullet_polar_star(player, bullet_manager, state) }
|
||||||
|
WeaponType::Fireball => {}
|
||||||
|
WeaponType::MachineGun => {}
|
||||||
|
WeaponType::MissileLauncher => {}
|
||||||
|
WeaponType::Bubbler => {}
|
||||||
|
WeaponType::Sword => {}
|
||||||
|
WeaponType::SuperMissileLauncher => {}
|
||||||
|
WeaponType::Nemesis => {}
|
||||||
|
WeaponType::Spur => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue