1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-03-24 10:59:20 +00:00

initial implementation of weapons and bullets

This commit is contained in:
Alula 2020-09-12 02:42:44 +02:00
parent 3df8781c25
commit e143185f5d
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
11 changed files with 644 additions and 88 deletions

View file

@ -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 btype: u16,
@ -17,15 +56,221 @@ pub struct Bullet {
pub anim_rect: Rect<usize>,
pub enemy_hit_width: u32,
pub enemy_hit_height: u32,
pub block_hit_width: u32,
pub block_hit_height: u32,
pub anim_num: u16,
pub anim_counter: u16,
pub action_num: u16,
pub action_counter: u16,
pub hit_bounds: Rect<usize>,
pub display_bounds: Rect<usize>,
}
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) {
}*/
}

View file

@ -2,6 +2,7 @@ use crate::bitfield;
use crate::common::{Condition, Direction, Rect};
use crate::engine_constants::EngineConstants;
use crate::rng::RNG;
use std::fs::read_to_string;
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
pub enum CaretType {
@ -36,14 +37,15 @@ pub struct Caret {
pub cond: Condition,
pub direction: Direction,
pub anim_rect: Rect<usize>,
anim_num: usize,
anim_counter: isize,
anim_num: u16,
anim_counter: u16,
}
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];
Self {
Caret {
ctype,
x,
y,
@ -53,7 +55,7 @@ impl Caret {
offset_y,
cond: Condition(0x80),
direction: direct,
anim_rect: Rect::<usize>::new(0, 0, 0, 0),
anim_rect: Rect::new(0, 0, 0, 0),
anim_num: 0,
anim_counter: 0,
}
@ -63,13 +65,73 @@ impl Caret {
match self.ctype {
CaretType::None => {}
CaretType::Bubble => {}
CaretType::ProjectileDissipation => {}
CaretType::Shoot => {}
CaretType::SnakeAfterimage | CaretType::SnakeAfterimage2 => { // dupe, unused
CaretType::ProjectileDissipation => {
match self.direction {
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 => {
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;
@ -78,27 +140,30 @@ impl Caret {
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);
return;
}
self.x += 0x80; // 0.4fix9
self.y -= 0x80;
}
CaretType::Exhaust => {
if self.anim_counter == 0 {
self.anim_rect = constants.caret.exhaust_rects[self.anim_num as usize];
}
self.anim_counter += 1;
if self.anim_counter > 1 {
self.anim_counter = 0;
self.anim_num += 1;
}
if self.anim_num >= constants.caret.exhaust_rects.len() {
self.cond.set_alive(false);
return;
if self.anim_num >= constants.caret.exhaust_rects.len() as u16 {
self.cond.set_alive(false);
return;
}
}
self.anim_rect = constants.caret.exhaust_rects[self.anim_num];
match self.direction {
Direction::Left => { self.x -= 0x400; } // 2.0fix9
Direction::Up => { self.y -= 0x400; }
@ -165,7 +230,8 @@ impl Caret {
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 {
self.x -= 4 * 0x200;

View file

@ -13,10 +13,10 @@ bitfield! {
pub hit_bottom_wall, set_hit_bottom_wall: 3; // 0x08
pub hit_right_slope, set_hit_right_slope: 4; // 0x10
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 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 water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
pub force_left, set_force_left: 12; // 0x1000
@ -84,7 +84,7 @@ bitfield! {
#[derive(Clone, Copy)]
pub struct ControlFlags(u16);
impl Debug;
pub flag_x01, set_flag_x01: 0;
pub control_enabled, set_control_enabled: 1;
pub interactions_disabled, set_interactions_disabled: 2;

View file

@ -48,6 +48,10 @@ pub struct CaretConsts {
pub offsets: [(isize, isize); 18],
pub bubble_left_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 drowned_quote_left_rect: Rect<usize>,
pub drowned_quote_right_rect: Rect<usize>,
@ -65,6 +69,10 @@ impl Clone for CaretConsts {
offsets: self.offsets,
bubble_left_rects: self.bubble_left_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(),
drowned_quote_left_rect: self.drowned_quote_left_rect,
drowned_quote_right_rect: self.drowned_quote_right_rect,
@ -92,15 +100,24 @@ pub struct BulletData {
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)]
pub struct WeaponConsts {
pub bullet_table: Vec<BulletData>,
pub bullet_rects: BulletRects,
}
impl Clone for WeaponConsts {
fn clone(&self) -> WeaponConsts {
WeaponConsts {
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: 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![
Rect { left: 32, top: 64, right: 40, bottom: 72 },
Rect { left: 32, top: 72, right: 40, bottom: 80 },
@ -824,6 +864,20 @@ impl EngineConstants {
// 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 } },
],
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! {
"ArmsImage" => (256, 16),

View file

@ -1,11 +1,4 @@
#[derive(Clone)]
pub struct Weapon {
pub id: u16,
pub level: u16,
pub experience: u16,
pub ammo: u16,
pub max_ammo: u16,
}
use crate::weapon::{Weapon, WeaponLevel, WeaponType};
#[derive(Clone, Copy)]
pub struct Item(u16);
@ -43,20 +36,20 @@ impl Inventory {
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) {
self.weapons.push(Weapon {
id: weapon_id,
level: 1,
experience: 0,
ammo: max_ammo,
self.weapons.push(Weapon::new(
weapon_id,
WeaponLevel::Level1,
0,
max_ammo,
});
max_ammo,
));
}
}
pub fn remove_weapon(&mut self, weapon_id: u16) {
self.weapons.retain(|weapon| weapon.id != weapon_id);
pub fn remove_weapon(&mut self, wtype: WeaponType) {
self.weapons.retain(|weapon| weapon.wtype != wtype);
}
pub fn get_weapon(&self, idx: usize) -> Option<&Weapon> {
@ -75,7 +68,7 @@ impl Inventory {
pub fn reset_all_weapon_xp(&mut self) {
for weapon in self.weapons.iter_mut() {
weapon.level = 1;
weapon.level = WeaponLevel::Level1;
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) {
weapon.level
} else {
0
WeaponLevel::None
}
}
@ -104,7 +97,7 @@ impl Inventory {
self.weapons.len()
}
pub fn has_weapon(&self, weapon_id: u16) -> bool {
self.weapons.iter().any(|weapon| weapon.id == weapon_id)
pub fn has_weapon(&self, wtype: WeaponType) -> bool {
self.weapons.iter().any(|weapon| weapon.wtype == wtype)
}
}

View file

@ -24,7 +24,7 @@ use winit::{ElementState, Event, KeyboardInput, WindowEvent};
use crate::bmfont_renderer::BMFontRenderer;
use crate::builtin_fs::BuiltinFS;
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::ggez::{Context, ContextBuilder, event, filesystem, GameResult};
use crate::ggez::conf::{WindowMode, WindowSetup};
@ -34,6 +34,7 @@ use crate::ggez::graphics::DrawParam;
use crate::ggez::input::keyboard;
use crate::ggez::mint::ColumnMatrix4;
use crate::ggez::nalgebra::Vector2;
use crate::npc::NPCTable;
use crate::rng::RNG;
use crate::scene::loading_scene::LoadingScene;
use crate::scene::Scene;
@ -42,7 +43,6 @@ use crate::stage::StageData;
use crate::text_script::TextScriptVM;
use crate::texture_set::TextureSet;
use crate::ui::UI;
use crate::npc::NPCTable;
mod bmfont;
mod bmfont_renderer;
@ -53,6 +53,7 @@ mod common;
mod engine_constants;
mod entity;
mod frame;
mod inventory;
mod ggez;
mod live_debugger;
mod macros;
@ -68,7 +69,7 @@ mod sound;
mod text_script;
mod texture_set;
mod ui;
mod inventory;
mod weapon;
struct Game {
scene: Option<Box<dyn Scene>>,

View file

@ -327,6 +327,7 @@ pub struct NPCTable {
}
impl NPCTable {
#[allow(clippy::new_without_default)]
pub fn new() -> NPCTable {
NPCTable {
entries: Vec::new(),

View file

@ -32,7 +32,6 @@ pub struct Player {
pub cond: Condition,
pub flags: Flag,
pub equip: Equipment,
pub inventory: Inventory,
pub direction: Direction,
pub display_bounds: Rect<usize>,
pub hit_bounds: Rect<usize>,
@ -72,7 +71,6 @@ impl Player {
cond: Condition(0x80),
flags: Flag(0),
equip: Equipment(0),
inventory: Inventory::new(),
direction: Direction::Right,
display_bounds: constants.my_char.display_bounds,
hit_bounds: constants.my_char.hit_bounds,

View file

@ -1,28 +1,32 @@
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::frame::Frame;
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::inventory::Inventory;
use crate::npc::NPCMap;
use crate::physics::PhysicalEntity;
use crate::player::Player;
use crate::scene::Scene;
use crate::SharedGameState;
use crate::stage::{BackgroundType, Stage};
use crate::str;
use crate::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptVM};
use crate::ui::Components;
use crate::weapon::WeaponType;
pub struct GameScene {
pub tick: usize,
pub stage: Stage,
pub frame: Frame,
pub player: Player,
pub inventory: Inventory,
pub stage_id: usize,
pub npc_map: NPCMap,
pub bullet_manager: BulletManager,
tex_background_name: String,
tex_tileset_name: String,
life_bar: u16,
@ -58,6 +62,7 @@ impl GameScene {
tick: 0,
stage,
player: Player::new(state),
inventory: Inventory::new(),
frame: Frame {
x: 0,
y: 0,
@ -65,6 +70,7 @@ impl GameScene {
},
stage_id: id,
npc_map: NPCMap::new(),
bullet_manager: BulletManager::new(),
tex_background_name,
tex_tileset_name,
life_bar: 0,
@ -95,7 +101,7 @@ impl GameScene {
fn draw_hud(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
// none
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")?;
if max_ammo == 0 {
@ -133,9 +139,9 @@ impl GameScene {
batch.draw(ctx)?;
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 {
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);
for a in 0..weapon_count {
@ -153,8 +159,8 @@ impl GameScene {
pos_x -= 48.0;
}
if let Some(weapon) = self.player.inventory.get_weapon(a) {
rect.left = weapon.id as usize * 16;
if let Some(weapon) = self.inventory.get_weapon(a) {
rect.left = weapon.wtype as usize * 16;
rect.right = rect.left + 16;
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, 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)?;
Ok(())
@ -187,23 +193,18 @@ impl GameScene {
}
}
}
BackgroundType::MoveDistant => {
let off_x = self.frame.x as usize / 2 % (batch.width() * 0x200);
let off_y = self.frame.y as usize / 2 % (batch.height() * 0x200);
let count_x = state.canvas_size.0 as usize / batch.width() + 2;
let count_y = state.canvas_size.1 as usize / batch.height() + 2;
for y in 0..count_y {
for x in 0..count_x {
batch.add((x * batch.width()) as f32 - (off_x / 0x200) as f32,
(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);
BackgroundType::MoveDistant | BackgroundType::MoveNear => {
let (off_x, off_y) = if self.stage.data.background_type == BackgroundType::MoveDistant {
(
self.frame.x as usize % (batch.width() * 0x200),
self.frame.y as usize % (batch.height() * 0x200)
)
} else {
(
self.frame.x as usize / 2 % (batch.width() * 0x200),
self.frame.y as usize / 2 % (batch.height() * 0x200)
)
};
let count_x = state.canvas_size.0 as usize / batch.width() + 2;
let count_y = state.canvas_size.1 as usize / batch.height() + 2;
@ -253,6 +254,40 @@ impl GameScene {
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 {
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_y = self.player.y;
self.frame.immediate_update(state, &self.player, &self.stage);
self.inventory.add_weapon(WeaponType::PolarStar, 0);
//self.player.equip.set_booster_2_0(true);
Ok(())
}
@ -572,6 +609,7 @@ impl Scene for GameScene {
self.player.flags.0 = 0;
state.tick_carets();
self.bullet_manager.tick_bullets(state, &self.stage);
self.player.tick_map_collisions(state, &self.stage);
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 let Some(weapon) = self.inventory.get_current_weapon_mut() {
weapon.shoot_bullet(&self.player, &mut self.bullet_manager, state);
}
// update health bar
if 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.draw_bullets(state, ctx)?;
self.draw_tiles(state, ctx, TileLayer::Foreground)?;
self.draw_tiles(state, ctx, TileLayer::Snack)?;
self.draw_carets(state, ctx)?;

View file

@ -20,6 +20,7 @@ use crate::ggez::{Context, GameResult};
use crate::ggez::GameError::ParseError;
use crate::player::ControlMode;
use crate::scene::game_scene::GameScene;
use crate::weapon::WeaponType;
/// Engine's text script VM operation codes.
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]
@ -639,17 +640,18 @@ impl TextScriptVM {
let item_id = 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);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
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 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);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -743,7 +745,7 @@ impl TextScriptVM {
OpCode::YNJ => {
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 => {
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 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.vel_x = 0;
new_scene.player.vel_y = 0;
@ -998,39 +1001,45 @@ impl TextScriptVM {
OpCode::ITp => {
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);
}
OpCode::ITm => {
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);
}
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 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);
}
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);
}
OpCode::AEp => {
game_scene.player.inventory.refill_all_ammo();
game_scene.inventory.refill_all_ammo();
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
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);
}

146
src/weapon.rs Normal file
View 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 => {}
}
}
}