diff --git a/src/caret.rs b/src/caret.rs index 47264ae..45da3fb 100644 --- a/src/caret.rs +++ b/src/caret.rs @@ -1,6 +1,7 @@ use crate::bitfield; use crate::common::{Direction, Rect}; use crate::engine_constants::EngineConstants; +use crate::rng::RNG; bitfield! { pub struct Cond(u16); @@ -24,7 +25,7 @@ pub enum CaretType { LevelUp, HurtParticles, Explosion, - SmallParticles, + LittleParticles, Unknown, SmallProjectileDissipation, Empty, @@ -65,7 +66,7 @@ impl Caret { } } - pub fn tick(&mut self, constants: &EngineConstants) { + pub fn tick(&mut self, rng: &RNG, constants: &EngineConstants) { match self.ctype { CaretType::None => {} CaretType::Bubble => {} @@ -115,7 +116,41 @@ impl Caret { CaretType::LevelUp => {} CaretType::HurtParticles => {} CaretType::Explosion => {} - CaretType::SmallParticles => {} + CaretType::LittleParticles => { + if self.anim_num == 0 { + match self.direct { + Direction::Left => { + self.vel_x = rng.range(-0x300..0x300) as isize; + self.vel_y = rng.range(-0x100..0x100) as isize; + } + Direction::Up => { + self.vel_y = rng.range(1..3) as isize * 0x100; + } + _ => {} + } + } + + self.anim_num += 1; + + if self.direct == Direction::Left { + self.vel_x = (self.vel_x * 4) / 5; + self.vel_y = (self.vel_y * 4) / 5; + } + + self.x += self.vel_x; + self.y += self.vel_y; + + if self.anim_num == 21 { + self.cond.set_visible(false); + return; + } + + self.anim_rect = constants.caret.little_particles_rects[self.anim_num / 2 % constants.caret.little_particles_rects.len()]; + + if self.direct == Direction::Right { + self.x -= 4 * 0x200; + } + } CaretType::Unknown => { // not implemented because it was apparently broken in og game? self.cond.set_visible(false); diff --git a/src/engine_constants.rs b/src/engine_constants.rs index 198320a..d97a9dc 100644 --- a/src/engine_constants.rs +++ b/src/engine_constants.rs @@ -51,6 +51,7 @@ pub struct CaretConsts { pub offsets: [(isize, isize); 18], pub bubble_left_rects: Vec>, pub bubble_right_rects: Vec>, + pub little_particles_rects: Vec>, pub exhaust_rects: Vec>, pub question_left_rect: Rect, pub question_right_rect: Rect, @@ -62,6 +63,7 @@ impl Clone for CaretConsts { offsets: self.offsets, bubble_left_rects: self.bubble_left_rects.clone(), bubble_right_rects: self.bubble_right_rects.clone(), + little_particles_rects: self.little_particles_rects.clone(), exhaust_rects: self.exhaust_rects.clone(), question_left_rect: self.question_left_rect, question_right_rect: self.question_right_rect, @@ -69,12 +71,26 @@ impl Clone for CaretConsts { } } +#[derive(Debug)] +pub struct WorldConsts { + pub snack_rect: Rect, +} + +impl Clone for WorldConsts { + fn clone(&self) -> Self { + Self { + snack_rect: self.snack_rect, + } + } +} + #[derive(Debug)] pub struct EngineConstants { pub is_cs_plus: bool, pub my_char: MyCharConsts, pub booster: BoosterConsts, pub caret: CaretConsts, + pub world: WorldConsts, pub tex_sizes: HashMap, } @@ -85,6 +101,7 @@ impl Clone for EngineConstants { my_char: self.my_char, booster: self.booster, caret: self.caret.clone(), + world: self.world.clone(), tex_sizes: self.tex_sizes.clone(), } } @@ -194,6 +211,10 @@ impl EngineConstants { Rect { left: 80, top: 24, right: 88, bottom: 32 }, Rect { left: 88, top: 24, right: 96, bottom: 32 }, ], + little_particles_rects: vec![ + Rect { left: 56, top: 24, right: 64, bottom: 32 }, + Rect { left: 0, top: 0, right: 0, bottom: 0 }, + ], exhaust_rects: vec![ Rect { left: 56, top: 0, right: 64, bottom: 8 }, Rect { left: 64, top: 0, right: 72, bottom: 8 }, @@ -206,6 +227,9 @@ impl EngineConstants { question_left_rect: Rect { left: 0, top: 80, right: 16, bottom: 96 }, question_right_rect: Rect { left: 48, top: 64, right: 64, bottom: 80 }, }, + world: WorldConsts { + snack_rect: Rect { left: 256, top: 48, right: 272, bottom: 64 }, + }, tex_sizes: hashmap! { str!("ArmsImage") => (256, 16), str!("Arms") => (320, 200), diff --git a/src/main.rs b/src/main.rs index c88a69e..abf8010 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,8 @@ use crate::texture_set::TextureSet; use crate::ui::UI; use crate::caret::{Caret, CaretType}; use crate::common::Direction; +use crate::rng::RNG; +use std::time::Instant; mod caret; mod common; @@ -91,6 +93,8 @@ struct Game { pub struct SharedGameState { pub flags: GameFlags, + pub game_rng: RNG, + pub effect_rng: RNG, pub carets: Vec, pub key_state: KeyState, pub key_trigger: KeyState, @@ -116,7 +120,7 @@ impl SharedGameState { pub fn tick_carets(&mut self) { for caret in self.carets.iter_mut() { - caret.tick(&self.constants); + caret.tick(&self.effect_rng, &self.constants); } self.carets.retain(|c| !c.is_dead()); @@ -155,6 +159,8 @@ impl Game { def_matrix: DrawParam::new().to_matrix(), state: SharedGameState { flags: GameFlags(0), + game_rng: RNG::new(0), + effect_rng: RNG::new(Instant::now().elapsed().as_nanos() as i32), carets: Vec::new(), key_state: KeyState(0), key_trigger: KeyState(0), diff --git a/src/player.rs b/src/player.rs index 51d6256..dc191d0 100644 --- a/src/player.rs +++ b/src/player.rs @@ -33,6 +33,9 @@ bitfield! { pub flag_x20000, set_flag_x20000: 17; // 0x20000 pub flag_x40000, set_flag_x40000: 18; // 0x40000 pub flag_x80000, set_flag_x80000: 19; // 0x80000 + + // engine specific flags + pub head_bounced, set_head_bounced: 31; } bitfield! { @@ -151,6 +154,12 @@ impl Player { let physics = if self.flags.underwater() { state.constants.my_char.water_physics } else { state.constants.my_char.air_physics }; self.question = false; + if self.flags.head_bounced() { + self.flags.set_head_bounced(false); + // todo: PlaySoundObject(3, SOUND_MODE_PLAY); + state.create_caret(self.x, self.y - self.hit.top as isize, CaretType::LittleParticles, Direction::Left); + state.create_caret(self.x, self.y - self.hit.top as isize, CaretType::LittleParticles, Direction::Left); + } if !state.flags.control_enabled() { self.booster_switch = 0; diff --git a/src/player_hit.rs b/src/player_hit.rs index 3fde700..d6e2d9a 100644 --- a/src/player_hit.rs +++ b/src/player_hit.rs @@ -55,7 +55,7 @@ impl Player { self.y = ((y * 0x10 + 8) * 0x200) + self.hit.top as isize; if !self.cond.cond_x02() && self.vel_y < -0x200 { - // PutLittleStar(); todo + self.flags.set_head_bounced(true); } if self.vel_y < 0 { @@ -92,7 +92,7 @@ impl Player { self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) + 0x800 + self.hit.top as isize; if !self.cond.cond_x02() && self.vel_y < -0x200 { - // PutLittleStar(); todo + self.flags.set_head_bounced(true); } if self.vel_y < 0 { @@ -111,7 +111,7 @@ impl Player { self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) - 0x800 + self.hit.top as isize; if !self.cond.cond_x02() && self.vel_y < -0x200 { - // PutLittleStar(); todo + self.flags.set_head_bounced(true); } if self.vel_y < 0 { @@ -130,7 +130,7 @@ impl Player { self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) - 0x800 + self.hit.top as isize; if !self.cond.cond_x02() && self.vel_y < -0x200 { - // PutLittleStar(); todo + self.flags.set_head_bounced(true); } if self.vel_y < 0 { @@ -149,7 +149,7 @@ impl Player { self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) + 0x800 + self.hit.top as isize; if !self.cond.cond_x02() && self.vel_y < -0x200 { - // PutLittleStar(); todo + self.flags.set_head_bounced(true); } if self.vel_y < 0 { diff --git a/src/rng.rs b/src/rng.rs index 2886fef..952f8b0 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -1,22 +1,23 @@ -/// Stateful RNG -pub struct RNG { - pub seed: u32, -} +use std::cell::Cell; + +pub struct RNG(Cell); impl RNG { - pub fn new() -> Self { - Self { - seed: 0, - } + pub fn new(seed: i32) -> Self { + Self(Cell::new(seed)) } - pub fn next(&mut self) -> u32 { + pub fn next(&self) -> i32 { // MSVC LCG values - self.seed = self.seed.wrapping_mul(214013).wrapping_add(2531011); - self.seed + self.0.replace(self.0.get().wrapping_mul(214013).wrapping_add(2531011)); + self.0.get() } - pub fn range(&mut self, start: i32, end: i32) -> i32 { - start + (self.next() % (end - start) as u32) as i32 + pub fn next_u32(&self) -> u32 { + self.next() as u32 + } + + pub fn range(&self, range: std::ops::Range) -> i32 { + range.start.wrapping_add(self.next() % (range.end.wrapping_sub(range.start).wrapping_add(1))) } } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 3277cd7..9c61bfc 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -20,6 +20,7 @@ pub struct GameScene { tex_background_name: String, tex_caret_name: String, tex_hud_name: String, + tex_npcsym_name: String, tex_tileset_name: String, life_bar: usize, life_bar_count: usize, @@ -30,6 +31,7 @@ pub enum TileLayer { All, Background, Foreground, + Snack, } #[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)] @@ -47,6 +49,7 @@ impl GameScene { let tex_background_name = stage.data.background.filename(); let tex_caret_name = str!("Caret"); let tex_hud_name = str!("TextBox"); + let tex_npcsym_name = str!("Npc/NpcSym"); let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join(""); Ok(Self { @@ -61,6 +64,7 @@ impl GameScene { tex_background_name, tex_caret_name, tex_hud_name, + tex_npcsym_name, tex_tileset_name, life_bar: 3, life_bar_count: 0, @@ -193,7 +197,11 @@ impl GameScene { } fn draw_tiles(&self, state: &mut SharedGameState, ctx: &mut Context, layer: TileLayer) -> GameResult { - let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &self.tex_tileset_name)?; + let tex = match layer { + TileLayer::Snack => &self.tex_npcsym_name, + _ => &self.tex_tileset_name, + }; + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex)?; let mut rect = Rect::::new(0, 0, 16, 16); let tile_start_x = clamp(self.frame.x / 0x200 / 16, 0, self.stage.map.width as isize) as usize; @@ -201,6 +209,10 @@ impl GameScene { let tile_end_x = clamp((self.frame.x / 0x200 + 8 + state.canvas_size.0 as isize) / 16 + 1, 0, self.stage.map.width as isize) as usize; let tile_end_y = clamp((self.frame.y / 0x200 + 8 + state.canvas_size.1 as isize) / 16 + 1, 0, self.stage.map.height as isize) as usize; + if layer == TileLayer::Snack { + rect = state.constants.world.snack_rect; + } + for y in tile_start_y..tile_end_y { for x in tile_start_x..tile_end_x { let tile = *self.stage.map.tiles @@ -212,22 +224,34 @@ impl GameScene { if self.stage.map.attrib[tile as usize] >= 0x20 { continue; } + + rect.left = (tile as usize % 16) * 16; + rect.top = (tile as usize / 16) * 16; + rect.right = rect.left + 16; + rect.bottom = rect.top + 16; } TileLayer::Foreground => { let attr = self.stage.map.attrib[tile as usize]; - if attr < 0x40 || attr >= 0x80 { + + if attr < 0x40 || attr >= 0x80 || attr == 0x43 { + continue; + } + + rect.left = (tile as usize % 16) * 16; + rect.top = (tile as usize / 16) * 16; + rect.right = rect.left + 16; + rect.bottom = rect.top + 16; + } + TileLayer::Snack => { + if self.stage.map.attrib[tile as usize] != 0x43 { continue; } } _ => {} } - rect.left = (tile as usize % 16) * 16; - rect.top = (tile as usize / 16) * 16; - rect.right = rect.left + 16; - rect.bottom = rect.top + 16; - - batch.add_rect((x as f32 * 16.0 - 8.0) - (self.frame.x / 0x200) as f32, (y as f32 * 16.0 - 8.0) - (self.frame.y / 0x200) as f32, &rect); + batch.add_rect((x as f32 * 16.0 - 8.0) - (self.frame.x / 0x200) as f32, + (y as f32 * 16.0 - 8.0) - (self.frame.y / 0x200) as f32, &rect); } } @@ -287,6 +311,7 @@ impl Scene for GameScene { self.draw_tiles(state, ctx, TileLayer::Background)?; self.player.draw(state, ctx, &self.frame)?; self.draw_tiles(state, ctx, TileLayer::Foreground)?; + self.draw_tiles(state, ctx, TileLayer::Snack)?; self.draw_carets(state, ctx)?; self.draw_hud(state, ctx)?;