From 62cb96d4b3eadc930bb7727b0da8b7961824225a Mon Sep 17 00:00:00 2001 From: Alula Date: Fri, 11 Sep 2020 18:30:18 +0200 Subject: [PATCH] bullet stuff and many cs+ workarounds --- src/bullet.rs | 31 ++++ src/common.rs | 10 +- src/engine_constants.rs | 344 ++++++++++++++++++++++++++-------------- src/main.rs | 2 + src/map.rs | 6 +- src/scene/game_scene.rs | 2 +- src/sound/mod.rs | 99 +++++------- src/stage.rs | 32 ++-- src/text_script.rs | 14 +- 9 files changed, 335 insertions(+), 205 deletions(-) create mode 100644 src/bullet.rs diff --git a/src/bullet.rs b/src/bullet.rs new file mode 100644 index 0000000..128a883 --- /dev/null +++ b/src/bullet.rs @@ -0,0 +1,31 @@ +use crate::common::{Condition, Direction, Rect, Flag}; + +pub struct Bullet { + pub btype: u16, + pub x: isize, + pub y: isize, + pub vel_x: isize, + pub vel_y: isize, + pub target_x: isize, + pub target_y: isize, + pub life: u16, + pub lifetime: u16, + pub damage: u16, + pub cond: Condition, + pub flags: Flag, + pub direction: Direction, + pub anim_rect: Rect, + 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 display_bounds: Rect, +} + +impl Bullet { + +} diff --git a/src/common.rs b/src/common.rs index 2102720..8b5e830 100644 --- a/src/common.rs +++ b/src/common.rs @@ -3,7 +3,7 @@ use num_traits::{AsPrimitive, Num}; use crate::bitfield; bitfield! { - #[derive(Clone)] + #[derive(Clone, Copy)] pub struct Flag(u32); impl Debug; @@ -33,7 +33,7 @@ bitfield! { } bitfield! { - #[derive(Clone)] + #[derive(Clone, Copy)] pub struct Equipment(u16); impl Debug; @@ -50,7 +50,7 @@ bitfield! { } bitfield! { - #[derive(Clone)] + #[derive(Clone, Copy)] pub struct Condition(u16); impl Debug; @@ -65,8 +65,10 @@ bitfield! { } bitfield! { + #[derive(Clone, Copy)] pub struct KeyState(u16); impl Debug; + pub left, set_left: 0; pub right, set_right: 1; pub up, set_up: 2; @@ -79,8 +81,10 @@ bitfield! { } 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; diff --git a/src/engine_constants.rs b/src/engine_constants.rs index 5b4ead7..04081b0 100644 --- a/src/engine_constants.rs +++ b/src/engine_constants.rs @@ -1,9 +1,9 @@ -use std::collections::HashMap; - use log::info; -use maplit::hashmap; -use crate::common::{Direction, Rect}; +use case_insensitive_hashmap::CaseInsensitiveHashMap; + +use crate::case_insensitive_hashmap; +use crate::common::{Rect, Flag}; use crate::player::ControlMode; use crate::str; use crate::text_script::TextScriptEncoding; @@ -20,7 +20,6 @@ pub struct PhysicsConsts { pub jump: isize, } - #[derive(Debug, Copy, Clone)] pub struct BoosterConsts { pub fuel: usize, @@ -79,6 +78,33 @@ impl Clone for CaretConsts { } } + +#[derive(Debug, Copy, Clone)] +pub struct BulletData { + pub damage: u8, + pub life: u8, + pub lifetime: u16, + pub flags: Flag, + pub enemy_hit_width: u16, + pub enemy_hit_height: u16, + pub block_hit_width: u16, + pub block_hit_height: u16, + pub display_bounds: Rect, +} + +#[derive(Debug)] +pub struct WeaponConsts { + pub bullet_table: Vec, +} + +impl Clone for WeaponConsts { + fn clone(&self) -> WeaponConsts { + WeaponConsts { + bullet_table: self.bullet_table.clone(), + } + } +} + #[derive(Debug, Copy, Clone)] pub struct WorldConsts { pub snack_rect: Rect, @@ -161,7 +187,8 @@ pub struct EngineConstants { pub caret: CaretConsts, pub world: WorldConsts, pub npc: NPCConsts, - pub tex_sizes: HashMap, + pub weapon: WeaponConsts, + pub tex_sizes: CaseInsensitiveHashMap<(usize, usize)>, pub textscript: TextScriptConsts, pub font_path: String, pub font_scale: f32, @@ -170,14 +197,15 @@ pub struct EngineConstants { } impl Clone for EngineConstants { - fn clone(&self) -> Self { - Self { + fn clone(&self) -> EngineConstants { + EngineConstants { is_cs_plus: self.is_cs_plus, my_char: self.my_char, booster: self.booster, caret: self.caret.clone(), world: self.world.clone(), npc: self.npc.clone(), + weapon: self.weapon.clone(), tex_sizes: self.tex_sizes.clone(), textscript: self.textscript.clone(), font_path: self.font_path.clone(), @@ -727,121 +755,191 @@ impl EngineConstants { Rect { left: 304, top: 200, right: 320, bottom: 216 }, ], }, - tex_sizes: hashmap! { - str!("ArmsImage") => (256, 16), - str!("Arms") => (320, 200), - str!("bk0") => (64, 64), - str!("bkBlack") => (64, 64), - str!("bkBlue") => (64, 64), - str!("bkFall") => (64, 64), - str!("bkFog") => (320, 240), - str!("bkFog480fix") => (480, 272), // nxengine - str!("bkGard") => (48, 64), - str!("bkGray") => (64, 64), - str!("bkGreen") => (64, 64), - str!("bkHellish") => (320, 240), // nxengine - str!("bkHellish480fix") => (480, 272), // nxengine - str!("bkLight") => (320, 240), // nxengine - str!("bkLight480fix") => (480, 272), // nxengine - str!("bkMaze") => (64, 64), - str!("bkMoon") => (320, 240), - str!("bkMoon480fix") => (480, 272), // nxengine - str!("bkRed") => (32, 32), - str!("bkSunset") => (320, 240), // nxengine - str!("bkSunset480fix") => (480, 272), // nxengine - str!("bkWater") => (32, 48), - str!("Bullet") => (320, 176), - str!("Caret") => (320, 240), - str!("casts") => (320, 240), - str!("Face") => (288, 240), - str!("Face_0") => (288, 240), // nxengine - str!("Face_1") => (288, 240), // nxengine - str!("Face_2") => (288, 240), // nxengine - str!("Fade") => (256, 32), - str!("ItemImage") => (256, 128), - str!("Loading") => (64, 8), - str!("MyChar") => (200, 64), - str!("Npc/Npc0") => (32, 32), - str!("Npc/NpcAlmo1") => (320, 240), - str!("Npc/NpcAlmo2") => (320, 240), - str!("Npc/NpcBallos") => (320, 240), - str!("Npc/NpcBllg") => (320, 96), - str!("Npc/NpcCemet") => (320, 112), - str!("Npc/NpcCent") => (320, 192), - str!("Npc/NpcCurly") => (256, 80), - str!("Npc/NpcDark") => (160, 64), - str!("Npc/NpcDr") => (320, 240), - str!("Npc/NpcEggs1") => (320, 112), - str!("Npc/NpcEggs2") => (320, 128), - str!("Npc/NpcFrog") => (320, 240), - str!("Npc/NpcGuest") => (320, 184), - str!("Npc/NpcHell") => (320, 160), - str!("Npc/NpcHeri") => (320, 128), - str!("Npc/NpcIronH") => (320, 72), - str!("Npc/NpcIsland") => (320, 80), - str!("Npc/NpcKings") => (96, 48), - str!("Npc/NpcMaze") => (320, 192), - str!("Npc/NpcMiza") => (320, 240), - str!("Npc/NpcMoon") => (320, 128), - str!("Npc/NpcOmg") => (320, 120), - str!("Npc/NpcPlant") => (320, 48), - str!("Npc/NpcPress") => (320, 240), - str!("Npc/NpcPriest") => (320, 240), - str!("Npc/NpcRavil") => (320, 168), - str!("Npc/NpcRed") => (320, 144), - str!("Npc/NpcRegu") => (320, 240), - str!("Npc/NpcSand") => (320, 176), - str!("Npc/NpcStream") => (64, 32), - str!("Npc/NpcSym") => (320, 240), - str!("Npc/NpcToro") => (320, 144), - str!("Npc/NpcTwinD") => (320, 144), - str!("Npc/NpcWeed") => (320, 240), - str!("Npc/NpcX") => (320, 240), - str!("Resource/BITMAP/Credit01") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit02") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit03") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit04") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit05") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit06") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit07") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit08") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit09") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit10") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit11") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit12") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit14") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit15") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit16") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit17") => (160, 240), // cse2 - str!("Resource/BITMAP/Credit18") => (160, 240), // cse2 - str!("Resource/BITMAP/pixel") => (160, 16), // cse2 - str!("Resource/CURSOR/CURSOR_IKA") => (32, 32), // cse2 - str!("Resource/CURSOR/CURSOR_NORMAL") => (32, 32), // cse2 - str!("StageImage") => (256, 16), - str!("Stage/Prt0") => (32, 32), - str!("Stage/PrtAlmond") => (256, 96), - str!("Stage/PrtBarr") => (256, 88), - str!("Stage/PrtCave") => (256, 80), - str!("Stage/PrtCent") => (256, 128), - str!("Stage/PrtEggIn") => (256, 80), - str!("Stage/PrtEggs") => (256, 240), - str!("Stage/PrtEggX") => (256, 240), - str!("Stage/PrtFall") => (256, 128), - str!("Stage/PrtGard") => (256, 97), - str!("Stage/PrtHell") => (256, 240), - str!("Stage/PrtJail") => (256, 128), - str!("Stage/PrtLabo") => (128, 64), - str!("Stage/PrtMaze") => (256, 160), - str!("Stage/PrtMimi") => (256, 160), - str!("Stage/PrtOside") => (256, 64), - str!("Stage/PrtPens") => (256, 64), - str!("Stage/PrtRiver") => (256, 96), - str!("Stage/PrtSand") => (256, 112), - str!("Stage/PrtStore") => (256, 112), - str!("Stage/PrtWeed") => (256, 128), - str!("Stage/PrtWhite") => (256, 240), - str!("TextBox") => (244, 144), - str!("Title") => (320, 48), + weapon: WeaponConsts { + bullet_table: vec![ + // Null + 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 } }, + // Snake + BulletData { damage: 4, life: 1, lifetime: 20, flags: Flag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 6, life: 1, lifetime: 23, flags: Flag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 8, life: 1, lifetime: 30, flags: Flag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + // Polar Star + BulletData { damage: 1, life: 1, lifetime: 8, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 2, life: 1, lifetime: 12, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 4, life: 1, lifetime: 16, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + // Fireball + BulletData { damage: 2, life: 2, lifetime: 100, flags: Flag(8), enemy_hit_width: 8, enemy_hit_height: 16, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 3, life: 2, lifetime: 100, flags: Flag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 3, life: 2, lifetime: 100, flags: Flag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + // Machine Gun + BulletData { damage: 2, life: 1, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 4, life: 1, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 6, life: 1, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + // Missile Launcher + BulletData { damage: 0, life: 10, lifetime: 50, flags: Flag(40), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 0, life: 10, lifetime: 70, flags: Flag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 0, life: 10, lifetime: 90, flags: Flag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + // Missile Launcher explosion + BulletData { damage: 1, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } }, + BulletData { damage: 1, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } }, + BulletData { damage: 1, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } }, + // Bubbler + BulletData { damage: 1, life: 1, lifetime: 20, flags: Flag(8), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } }, + BulletData { damage: 2, life: 1, lifetime: 20, flags: Flag(8), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } }, + BulletData { damage: 2, life: 1, lifetime: 20, flags: Flag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } }, + // Bubbler level 3 thorns + BulletData { damage: 3, life: 1, lifetime: 32, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } }, + // Blade slashes + BulletData { damage: 0, life: 100, lifetime: 0, flags: Flag(36), enemy_hit_width: 8, enemy_hit_height: 8, block_hit_width: 8, block_hit_height: 8, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } }, + // Falling spike + BulletData { damage: 127, life: 1, lifetime: 2, flags: Flag(4), enemy_hit_width: 8, enemy_hit_height: 4, block_hit_width: 8, block_hit_height: 4, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } }, + // Blade + BulletData { damage: 15, life: 1, lifetime: 30, flags: Flag(36), enemy_hit_width: 8, enemy_hit_height: 8, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 6, life: 3, lifetime: 18, flags: Flag(36), enemy_hit_width: 10, enemy_hit_height: 10, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } }, + BulletData { damage: 1, life: 100, lifetime: 30, flags: Flag(36), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } }, + // Super Missile Launcher + BulletData { damage: 0, life: 10, lifetime: 30, flags: Flag(40), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 0, life: 10, lifetime: 40, flags: Flag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 0, life: 10, lifetime: 40, flags: Flag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + // Super Missile Launcher explosion + BulletData { damage: 2, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } }, + BulletData { damage: 2, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } }, + BulletData { damage: 2, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } }, + // Nemesis + BulletData { damage: 4, life: 4, lifetime: 20, flags: Flag(32), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } }, + BulletData { damage: 4, life: 2, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } }, + BulletData { damage: 1, life: 1, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } }, + // Spur + BulletData { damage: 4, life: 4, lifetime: 30, flags: Flag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 8, life: 8, lifetime: 30, flags: Flag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + BulletData { damage: 12, life: 12, lifetime: 30, flags: Flag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } }, + // Spur trail + BulletData { damage: 3, life: 100, lifetime: 30, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } }, + BulletData { damage: 6, life: 100, lifetime: 30, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } }, + BulletData { damage: 11, life: 100, lifetime: 30, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } }, + // Curly's Nemesis + BulletData { damage: 4, life: 4, lifetime: 20, flags: Flag(32), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } }, + // EnemyClear? + BulletData { damage: 0, life: 4, lifetime: 4, flags: Flag(4), 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 } }, + // 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 } }, + ], + }, + tex_sizes: case_insensitive_hashmap! { + "ArmsImage" => (256, 16), + "Arms" => (320, 200), + "bk0" => (64, 64), + "bkBlack" => (64, 64), + "bkBlue" => (64, 64), + "bkFall" => (64, 64), + "bkFog" => (320, 240), + "bkFog480fix" => (480, 272), // nxengine + "bkGard" => (48, 64), + "bkGray" => (64, 64), + "bkGreen" => (64, 64), + "bkHellish" => (320, 240), // nxengine + "bkHellish480fix" => (480, 272), // nxengine + "bkLight" => (320, 240), // nxengine + "bkLight480fix" => (480, 272), // nxengine + "bkMaze" => (64, 64), + "bkMoon" => (320, 240), + "bkMoon480fix" => (480, 272), // nxengine + "bkRed" => (32, 32), + "bkSunset" => (320, 240), // nxengine + "bkSunset480fix" => (480, 272), // nxengine + "bkWater" => (32, 48), + "Bullet" => (320, 176), + "Caret" => (320, 240), + "casts" => (320, 240), + "Face" => (288, 240), + "Face_0" => (288, 240), // nxengine + "Face_1" => (288, 240), // nxengine + "Face_2" => (288, 240), // nxengine + "Fade" => (256, 32), + "ItemImage" => (256, 128), + "Loading" => (64, 8), + "MyChar" => (200, 64), + "Npc/Npc0" => (32, 32), + "Npc/NpcAlmo1" => (320, 240), + "Npc/NpcAlmo2" => (320, 240), + "Npc/NpcBallos" => (320, 240), + "Npc/NpcBllg" => (320, 96), + "Npc/NpcCemet" => (320, 112), + "Npc/NpcCent" => (320, 192), + "Npc/NpcCurly" => (256, 80), + "Npc/NpcDark" => (160, 64), + "Npc/NpcDr" => (320, 240), + "Npc/NpcEggs1" => (320, 112), + "Npc/NpcEggs2" => (320, 128), + "Npc/NpcFrog" => (320, 240), + "Npc/NpcGuest" => (320, 184), + "Npc/NpcHell" => (320, 160), + "Npc/NpcHeri" => (320, 128), + "Npc/NpcIronH" => (320, 72), + "Npc/NpcIsland" => (320, 80), + "Npc/NpcKings" => (96, 48), + "Npc/NpcMaze" => (320, 192), + "Npc/NpcMiza" => (320, 240), + "Npc/NpcMoon" => (320, 128), + "Npc/NpcOmg" => (320, 120), + "Npc/NpcPlant" => (320, 48), + "Npc/NpcPress" => (320, 240), + "Npc/NpcPriest" => (320, 240), + "Npc/NpcRavil" => (320, 168), + "Npc/NpcRed" => (320, 144), + "Npc/NpcRegu" => (320, 240), + "Npc/NpcSand" => (320, 176), + "Npc/NpcStream" => (64, 32), + "Npc/NpcSym" => (320, 240), + "Npc/NpcToro" => (320, 144), + "Npc/NpcTwinD" => (320, 144), + "Npc/NpcWeed" => (320, 240), + "Npc/NpcX" => (320, 240), + "Resource/BITMAP/Credit01" => (160, 240), // cse2 + "Resource/BITMAP/Credit02" => (160, 240), // cse2 + "Resource/BITMAP/Credit03" => (160, 240), // cse2 + "Resource/BITMAP/Credit04" => (160, 240), // cse2 + "Resource/BITMAP/Credit05" => (160, 240), // cse2 + "Resource/BITMAP/Credit06" => (160, 240), // cse2 + "Resource/BITMAP/Credit07" => (160, 240), // cse2 + "Resource/BITMAP/Credit08" => (160, 240), // cse2 + "Resource/BITMAP/Credit09" => (160, 240), // cse2 + "Resource/BITMAP/Credit10" => (160, 240), // cse2 + "Resource/BITMAP/Credit11" => (160, 240), // cse2 + "Resource/BITMAP/Credit12" => (160, 240), // cse2 + "Resource/BITMAP/Credit14" => (160, 240), // cse2 + "Resource/BITMAP/Credit15" => (160, 240), // cse2 + "Resource/BITMAP/Credit16" => (160, 240), // cse2 + "Resource/BITMAP/Credit17" => (160, 240), // cse2 + "Resource/BITMAP/Credit18" => (160, 240), // cse2 + "Resource/BITMAP/pixel" => (160, 16), // cse2 + "Resource/CURSOR/CURSOR_IKA" => (32, 32), // cse2 + "Resource/CURSOR/CURSOR_NORMAL" => (32, 32), // cse2 + "StageImage" => (256, 16), + "Stage/Prt0" => (32, 32), + "Stage/PrtAlmond" => (256, 96), + "Stage/PrtBarr" => (256, 88), + "Stage/PrtCave" => (256, 80), + "Stage/PrtCent" => (256, 128), + "Stage/PrtEggIn" => (256, 80), + "Stage/PrtEggs" => (256, 240), + "Stage/PrtEggX" => (256, 240), + "Stage/PrtFall" => (256, 128), + "Stage/PrtGard" => (256, 97), + "Stage/PrtHell" => (256, 240), + "Stage/PrtJail" => (256, 128), + "Stage/PrtLabo" => (128, 64), + "Stage/PrtMaze" => (256, 160), + "Stage/PrtMimi" => (256, 160), + "Stage/PrtOside" => (256, 64), + "Stage/PrtPens" => (256, 64), + "Stage/PrtRiver" => (256, 96), + "Stage/PrtSand" => (256, 112), + "Stage/PrtStore" => (256, 112), + "Stage/PrtWeed" => (256, 128), + "Stage/PrtWhite" => (256, 240), + "TextBox" => (244, 144), + "Title" => (320, 48), }, textscript: TextScriptConsts { encoding: TextScriptEncoding::UTF8, diff --git a/src/main.rs b/src/main.rs index 1c2e921..0498541 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,7 @@ use crate::npc::NPCTable; mod bmfont; mod bmfont_renderer; mod builtin_fs; +mod bullet; mod caret; mod common; mod engine_constants; @@ -331,5 +332,6 @@ pub fn main() -> GameResult { game.scene.as_mut().unwrap().init(&mut game.state, ctx)?; } } + Ok(()) } diff --git a/src/map.rs b/src/map.rs index 330892d..cb4f6c1 100644 --- a/src/map.rs +++ b/src/map.rs @@ -39,8 +39,12 @@ impl Map { let mut tiles = vec![0u8; width * height]; let mut attrib = [0u8; 0x100]; + log::info!("Map size: {}x{}", width, height); + map_data.read_exact(&mut tiles)?; - attrib_data.read_exact(&mut attrib)?; + if attrib_data.read_exact(&mut attrib).is_err() { + log::warn!("Map attribute data is shorter than 256 bytes!"); + } Ok(Map { width, diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index f5a7fc5..f75fea0 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -47,9 +47,9 @@ pub enum Alignment { impl GameScene { pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult { + info!("Loading stage {} ({})", id, &state.stages[id].map); let stage = Stage::load(&state.base_path, &state.stages[id], ctx)?; info!("Loaded stage: {}", stage.data.name); - info!("Map size: {}x{}", stage.map.width, stage.map.height); let tex_background_name = stage.data.background.filename(); let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join(""); diff --git a/src/sound/mod.rs b/src/sound/mod.rs index 86f00f3..8098c5e 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -26,49 +26,50 @@ pub struct SoundManager { current_song_id: usize, } -static SONGS: [&str; 42] = [ - "XXXX", - "WANPAKU", - "ANZEN", - "GAMEOVER", - "GRAVITY", - "WEED", - "MDOWN2", - "FIREEYE", - "VIVI", - "MURA", - "FANFALE1", - "GINSUKE", - "CEMETERY", - "PLANT", - "KODOU", - "FANFALE3", - "FANFALE2", - "DR", - "ESCAPE", - "JENKA", - "MAZE", - "ACCESS", - "IRONH", - "GRAND", - "Curly", - "OSIDE", - "REQUIEM", - "WANPAK2", - "QUIET", - "LASTCAVE", - "BALCONY", - "LASTBTL", - "LASTBT3", - "ENDING", - "ZONBIE", - "BDOWN", - "HELL", - "JENKA2", - "MARINE", - "BALLOS", - "TOROKO", - "WHITE" +static SONGS: [&str; 43] = [ + "xxxx", + "wanpaku", + "anzen", + "gameover", + "gravity", + "weed", + "mdown2", + "fireeye", + "vivi", + "mura", + "fanfale1", + "ginsuke", + "cemetery", + "plant", + "kodou", + "fanfale3", + "fanfale2", + "dr", + "escape", + "jenka", + "maze", + "access", + "ironh", + "grand", + "curly", + "oside", + "requiem", + "wanpak2", + "quiet", + "lastcave", + "balcony", + "lastbtl", + "lastbt3", + "ending", + "zonbie", + "bdown", + "hell", + "jenka2", + "marine", + "ballos", + "toroko", + "white", + "kaze" ]; impl SoundManager { @@ -260,15 +261,3 @@ fn run(rx: Receiver, bank: SoundBank, std::thread::sleep(Duration::from_millis(10)); } } - -fn write_data(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> u16) - where - T: cpal::Sample, -{ - for frame in output.chunks_mut(channels) { - let value: T = cpal::Sample::from::(&next_sample()); - for sample in frame.iter_mut() { - *sample = value; - } - } -} diff --git a/src/stage.rs b/src/stage.rs index f3545ae..0160c73 100644 --- a/src/stage.rs +++ b/src/stage.rs @@ -173,6 +173,10 @@ const NXENGINE_NPCS: [&str; 34] = [ "Press", "Priest", "Ballos", "Island" ]; +fn zero_index(s: &[u8]) -> usize { + s.iter().position(|&c| c == b'\0').unwrap_or(s.len()) +} + impl StageData { // todo: refactor to make it less repetitive. pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult> { @@ -210,24 +214,24 @@ impl StageData { f.read_exact(&mut name_jap_buf)?; f.read_exact(&mut name_buf)?; - let tileset = from_utf8(&ts_buf) + let tileset = from_utf8(&ts_buf[0..zero_index(&ts_buf)]) .map_err(|_| ResourceLoadError("UTF-8 error in tileset field".to_string()))? - .trim_matches('\0').to_owned(); - let map = from_utf8(&map_buf) + .to_owned(); + let map = from_utf8(&map_buf[0..zero_index(&map_buf)]) .map_err(|_| ResourceLoadError("UTF-8 error in map field".to_string()))? - .trim_matches('\0').to_owned(); - let background = from_utf8(&back_buf) + .to_owned(); + let background = from_utf8(&back_buf[0..zero_index(&back_buf)]) .map_err(|_| ResourceLoadError("UTF-8 error in background field".to_string()))? - .trim_matches('\0').to_owned(); - let npc1 = from_utf8(&npc1_buf) + .to_owned(); + let npc1 = from_utf8(&npc1_buf[0..zero_index(&npc1_buf)]) .map_err(|_| ResourceLoadError("UTF-8 error in npc1 field".to_string()))? - .trim_matches('\0').to_owned(); - let npc2 = from_utf8(&npc2_buf) + .to_owned(); + let npc2 = from_utf8(&npc2_buf[0..zero_index(&npc2_buf)]) .map_err(|_| ResourceLoadError("UTF-8 error in npc2 field".to_string()))? - .trim_matches('\0').to_owned(); - let name = from_utf8(&name_buf) + .to_owned(); + let name = from_utf8(&name_buf[0..zero_index(&name_buf)]) .map_err(|_| ResourceLoadError("UTF-8 error in name field".to_string()))? - .trim_matches('\0').to_owned(); + .to_owned(); let stage = Self { name: name.clone(), @@ -277,9 +281,9 @@ impl StageData { let boss_no = f.read_u8()? as usize; f.read_exact(&mut name_buf)?; - let tileset = from_utf8(&ts_buf) + let tileset = from_utf8(&ts_buf[0..zero_index(&ts_buf)]) .map_err(|_| ResourceLoadError("UTF-8 error in tileset field".to_string()))? - .trim_matches('\0').to_owned(); + .to_owned(); let map = from_utf8(&map_buf) .map_err(|_| ResourceLoadError("UTF-8 error in map field".to_string()))? .trim_matches('\0').to_owned(); diff --git a/src/text_script.rs b/src/text_script.rs index 84850c1..679e245 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -1205,6 +1205,12 @@ impl TextScript { iter.next(); } n => { + // CS+ boss rush is the buggiest shit ever. + if !strict && last_event == 0 { + iter.next(); + continue; + } + return Err(ParseError(format!("Unexpected token in event {}: {}", last_event, n as char))); } } @@ -1380,14 +1386,6 @@ impl TextScript { Ok(()) } - fn expect_newline>(iter: &mut Peekable) -> GameResult { - if let Some(b'\r') = iter.peek() { - iter.next(); - } - - TextScript::expect_char(b'\n', iter) - } - fn expect_char>(expect: u8, iter: &mut I) -> GameResult { let res = iter.next();