From ef9ca2e89a070db623450516765e1989c13c1e57 Mon Sep 17 00:00:00 2001 From: Alula Date: Thu, 20 Aug 2020 20:31:47 +0200 Subject: [PATCH] add carets and fix booster --- README.md | 4 +- src/caret.rs | 132 ++++++++++++++++++ src/common.rs | 9 ++ src/engine_constants.rs | 83 +++++++++++- src/ggez/mod.rs | 1 + src/live_debugger.rs | 89 +++++++----- src/main.rs | 19 +++ src/player.rs | 290 +++++++++++++++++++++------------------- src/player_hit.rs | 84 ++++++------ src/rng.rs | 22 +++ src/scene/game_scene.rs | 27 +++- src/texture_set.rs | 4 + src/weapon.rs | 3 + 13 files changed, 544 insertions(+), 223 deletions(-) create mode 100644 src/caret.rs create mode 100644 src/rng.rs create mode 100644 src/weapon.rs diff --git a/README.md b/README.md index 3477560..ebdf2bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # doukutsu-rs -A re-implementation of Cave Story (Doukutsu Monogatari) engine written in [Rust](https://www.rust-lang.org/), aiming for accuracy and cleaner code. +A re-implementation of Cave Story (Doukutsu Monogatari) engine written in [Rust](https://www.rust-lang.org/), aiming for behavior accuracy and cleaner code. Later plans might involve turning it into a fully-featured modding tool with live debugging and stuff. **The project is still in a very early state and nowhere near being playable. Expect lots of breaking changes and bugs** @@ -26,7 +26,7 @@ The engine should work fine with [CSE2-Enhanced](https://github.com/Clownacy/CSE The project is a result of me wanting to build something in a new programming language for memes. -I had an idea of writing my own CS engine long time before and I would've very likely picked C++17/20+SDL2, but after +I had an idea of writing my own CS engine long time before and I would've very likely picked C++17/20 and SDL2, but after all I've picked Rust instead because it seemed quite interesting for me. Would 90% of end-users running this thing care about the programming language software was written in? After all who tf cares if the performance is the same (and maybe a slightly better), but you also get a lot of various benefits? diff --git a/src/caret.rs b/src/caret.rs new file mode 100644 index 0000000..47264ae --- /dev/null +++ b/src/caret.rs @@ -0,0 +1,132 @@ +use crate::bitfield; +use crate::common::{Direction, Rect}; +use crate::engine_constants::EngineConstants; + +bitfield! { + pub struct Cond(u16); + impl Debug; + + pub visible, set_visible: 7; +} + +#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)] +pub enum CaretType { + None, + Bubble, + ProjectileDissipation, + Shoot, + SnakeAfterimage, + Zzz, + SnakeAfterimage2, + Exhaust, + DrownedQuote, + QuestionMark, + LevelUp, + HurtParticles, + Explosion, + SmallParticles, + Unknown, + SmallProjectileDissipation, + Empty, + PushJumpKey, +} + +pub struct Caret { + pub ctype: CaretType, + pub x: isize, + pub y: isize, + pub vel_x: isize, + pub vel_y: isize, + pub offset_x: isize, + pub offset_y: isize, + pub cond: Cond, + pub direct: Direction, + pub anim_rect: Rect, + anim_num: usize, + anim_wait: isize, +} + +impl Caret { + pub fn new(x: isize, y: isize, ctype: CaretType, direct: Direction, constants: &EngineConstants) -> Self { + let (offset_x, offset_y) = constants.caret.offsets[ctype as usize]; + Self { + ctype, + x, + y, + vel_x: 0, + vel_y: 0, + offset_x, + offset_y, + cond: Cond(0x80), + direct, + anim_rect: Rect::::new(0, 0, 0, 0), + anim_num: 0, + anim_wait: 0, + } + } + + pub fn tick(&mut self, constants: &EngineConstants) { + match self.ctype { + CaretType::None => {} + CaretType::Bubble => {} + CaretType::ProjectileDissipation => {} + CaretType::Shoot => {} + CaretType::SnakeAfterimage | CaretType::SnakeAfterimage2 => { // dupe, unused + } + CaretType::Zzz => {} + CaretType::Exhaust => { + self.anim_wait += 1; + if self.anim_wait > 1 { + self.anim_wait = 0; + self.anim_num += 1; + } + + if self.anim_num >= constants.caret.exhaust_rects.len() { + self.cond.set_visible(false); + return; + } + + self.anim_rect = constants.caret.exhaust_rects[self.anim_num]; + + match self.direct { + Direction::Left => { self.x -= 0x400; } + Direction::Up => { self.y -= 0x400; } + Direction::Right => { self.x += 0x400; } + Direction::Bottom => { self.y += 0x400; } + } + } + CaretType::DrownedQuote => {} + CaretType::QuestionMark => { + self.anim_wait += 1; + if self.anim_wait < 5 { + self.y -= 0x800; + } + + if self.anim_wait == 32 { + self.cond.set_visible(false); + } + + self.anim_rect = match self.direct { + Direction::Left => { constants.caret.question_left_rect } + Direction::Right => { constants.caret.question_right_rect } + _ => { self.anim_rect } + } + } + CaretType::LevelUp => {} + CaretType::HurtParticles => {} + CaretType::Explosion => {} + CaretType::SmallParticles => {} + CaretType::Unknown => { + // not implemented because it was apparently broken in og game? + self.cond.set_visible(false); + } + CaretType::SmallProjectileDissipation => {} + CaretType::Empty => {} + CaretType::PushJumpKey => {} + } + } + + pub fn is_dead(&self) -> bool { + !self.cond.visible() + } +} diff --git a/src/common.rs b/src/common.rs index e5b70e6..cf7e56e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -25,6 +25,15 @@ impl Direction { _ => { None } } } + + pub fn opposite(&self) -> Direction { + match self { + Direction::Left => { Direction::Right } + Direction::Up => { Direction::Bottom } + Direction::Right => { Direction::Left } + Direction::Bottom => { Direction::Up } + } + } } #[derive(Debug, Clone, Copy)] diff --git a/src/engine_constants.rs b/src/engine_constants.rs index aca4d05..198320a 100644 --- a/src/engine_constants.rs +++ b/src/engine_constants.rs @@ -1,6 +1,6 @@ -use log::info; use std::collections::HashMap; +use log::info; use maplit::hashmap; use crate::common::{Direction, Rect}; @@ -21,6 +21,7 @@ pub struct PhysicsConsts { #[derive(Debug, Copy, Clone)] pub struct BoosterConsts { + pub fuel: usize, pub b2_0_up: isize, pub b2_0_up_nokey: isize, pub b2_0_down: isize, @@ -36,8 +37,8 @@ pub struct MyCharConsts { pub direction: Direction, pub view: Rect, pub hit: Rect, - pub life: u16, - pub max_life: u16, + pub life: usize, + pub max_life: usize, pub unit: u8, pub air_physics: PhysicsConsts, pub water_physics: PhysicsConsts, @@ -45,20 +46,45 @@ pub struct MyCharConsts { pub animations_right: [Rect; 12], } +#[derive(Debug)] +pub struct CaretConsts { + pub offsets: [(isize, isize); 18], + pub bubble_left_rects: Vec>, + pub bubble_right_rects: Vec>, + pub exhaust_rects: Vec>, + pub question_left_rect: Rect, + pub question_right_rect: Rect, +} + +impl Clone for CaretConsts { + fn clone(&self) -> Self { + Self { + offsets: self.offsets, + bubble_left_rects: self.bubble_left_rects.clone(), + bubble_right_rects: self.bubble_right_rects.clone(), + exhaust_rects: self.exhaust_rects.clone(), + question_left_rect: self.question_left_rect, + question_right_rect: self.question_right_rect, + } + } +} + #[derive(Debug)] pub struct EngineConstants { pub is_cs_plus: bool, pub my_char: MyCharConsts, pub booster: BoosterConsts, + pub caret: CaretConsts, pub tex_sizes: HashMap, } impl Clone for EngineConstants { fn clone(&self) -> Self { - EngineConstants { + Self { is_cs_plus: self.is_cs_plus, my_char: self.my_char, booster: self.booster, + caret: self.caret.clone(), tex_sizes: self.tex_sizes.clone(), } } @@ -128,11 +154,57 @@ impl EngineConstants { ], }, booster: BoosterConsts { + fuel: 50, b2_0_up: -0x5ff, b2_0_up_nokey: -0x5ff, b2_0_down: 0x5ff, b2_0_left: -0x5ff, - b2_0_right: 0x5ff + b2_0_right: 0x5ff, + }, + caret: CaretConsts { + offsets: [ + (0, 0), + (4 * 0x200, 4 * 0x200), + (8 * 0x200, 8 * 0x200), + (8 * 0x200, 8 * 0x200), + (8 * 0x200, 8 * 0x200), + (4 * 0x200, 4 * 0x200), + (8 * 0x200, 8 * 0x200), + (4 * 0x200, 4 * 0x200), + (8 * 0x200, 8 * 0x200), + (8 * 0x200, 8 * 0x200), + (28 * 0x200, 8 * 0x200), + (4 * 0x200, 4 * 0x200), + (16 * 0x200, 16 * 0x200), + (4 * 0x200, 4 * 0x200), + (20 * 0x200, 20 * 0x200), + (4 * 0x200, 4 * 0x200), + (20 * 0x200, 4 * 0x200), + (52 * 0x200, 4 * 0x200), + ], + bubble_left_rects: vec![ + Rect { left: 0, top: 64, right: 8, bottom: 72 }, + Rect { left: 8, top: 64, right: 16, bottom: 72 }, + Rect { left: 16, top: 64, right: 24, bottom: 72 }, + Rect { left: 24, top: 64, right: 32, bottom: 72 }, + ], + bubble_right_rects: vec![ + Rect { left: 64, top: 24, right: 72, bottom: 32 }, + Rect { left: 72, top: 24, right: 80, bottom: 32 }, + Rect { left: 80, top: 24, right: 88, bottom: 32 }, + Rect { left: 88, top: 24, right: 96, bottom: 32 }, + ], + exhaust_rects: vec![ + Rect { left: 56, top: 0, right: 64, bottom: 8 }, + Rect { left: 64, top: 0, right: 72, bottom: 8 }, + Rect { left: 72, top: 0, right: 80, bottom: 8 }, + Rect { left: 80, top: 0, right: 88, bottom: 8 }, + Rect { left: 88, top: 0, right: 96, bottom: 8 }, + Rect { left: 96, top: 0, right: 104, bottom: 8 }, + Rect { left: 104, top: 0, right: 112, bottom: 8 }, + ], + question_left_rect: Rect { left: 0, top: 80, right: 16, bottom: 96 }, + question_right_rect: Rect { left: 48, top: 64, right: 64, bottom: 80 }, }, tex_sizes: hashmap! { str!("ArmsImage") => (256, 16), @@ -257,6 +329,7 @@ impl EngineConstants { info!("Applying Cave Story+ constants patches..."); self.is_cs_plus = true; + self.tex_sizes.insert(str!("Caret"), (320, 320)); self.tex_sizes.insert(str!("MyChar"), (200, 384)); } } diff --git a/src/ggez/mod.rs b/src/ggez/mod.rs index 7206317..81d8ec8 100644 --- a/src/ggez/mod.rs +++ b/src/ggez/mod.rs @@ -79,6 +79,7 @@ //! //! ``` +#![allow(dead_code)] #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] diff --git a/src/live_debugger.rs b/src/live_debugger.rs index 2f62127..8f5a686 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -1,12 +1,13 @@ -use crate::ggez::{Context, GameResult}; use imgui::{Condition, im_str, ImStr, ImString, Window}; use itertools::Itertools; +use crate::ggez::{Context, GameResult}; use crate::scene::game_scene::GameScene; use crate::SharedGameState; pub struct LiveDebugger { selected_item: i32, + map_selector_visible: bool, stages: Vec, error: Option, } @@ -15,21 +16,37 @@ impl LiveDebugger { pub fn new() -> Self { Self { selected_item: -1, + map_selector_visible: false, stages: vec![], error: None, } } pub fn run_ingame(&mut self, game_scene: &mut GameScene, state: &mut SharedGameState, ctx: &mut Context, ui: &mut imgui::Ui) -> GameResult { - /*Window::new(im_str!("Live Debugger")) + Window::new(im_str!("Live Debugger")) + .position([5.0, 5.0], Condition::FirstUseEver) .size([300.0, 100.0], Condition::FirstUseEver) .build(ui, || { ui.text(format!( "Player position: ({:.1},{:.1})", - state.player.x as f32 / 512.0, - state.player.y as f32 / 512.0, + game_scene.player.x as f32 / 512.0, + game_scene.player.y as f32 / 512.0, )); - });*/ + + ui.text(format!( + "Player velocity: ({:.1},{:.1})", + game_scene.player.vel_x as f32 / 512.0, + game_scene.player.vel_y as f32 / 512.0, + )); + + ui.text(format!( + "Booster fuel: ({})", game_scene.player.booster_fuel + )); + + if ui.button(im_str!("Map Selector"), [0.0, 0.0]) { + self.map_selector_visible = true; + } + }); if self.error.is_some() { Window::new(im_str!("Error!")) @@ -47,39 +64,41 @@ impl LiveDebugger { }); } - Window::new(im_str!("Map selector")) - .resizable(false) - .collapsed(true, Condition::FirstUseEver) - .size([240.0, 280.0], Condition::FirstUseEver) - .build(ui, || { - if self.stages.is_empty() { - for s in state.stages.iter() { - self.stages.push(ImString::new(s.name.to_owned())); - } - - self.selected_item = match state.stages.iter().find_position(|s| s.name == game_scene.stage.data.name) { - Some((pos, _)) => { pos as i32 } - _ => { -1 } - }; - } - let stages: Vec<&ImStr> = self.stages.iter().map(|e| e.as_ref()).collect(); - - ui.push_item_width(-1.0); - ui.list_box(im_str!(""), &mut self.selected_item, &stages, 10); - - if ui.button(im_str!("Load"), [0.0, 0.0]) { - match GameScene::new(state, ctx, self.selected_item as usize) { - Ok(mut scene) => { - scene.player.x = (scene.stage.map.width / 2 * 16 * 0x200) as isize; - scene.player.y = (scene.stage.map.height / 2 * 16 * 0x200) as isize; - state.next_scene = Some(Box::new(scene)); + if self.map_selector_visible { + Window::new(im_str!("Map selector")) + .resizable(false) + .position([5.0, 35.0], Condition::FirstUseEver) + .size([240.0, 280.0], Condition::FirstUseEver) + .build(ui, || { + if self.stages.is_empty() { + for s in state.stages.iter() { + self.stages.push(ImString::new(s.name.to_owned())); } - Err(e) => { - self.error = Some(ImString::new(e.to_string())); + + self.selected_item = match state.stages.iter().find_position(|s| s.name == game_scene.stage.data.name) { + Some((pos, _)) => { pos as i32 } + _ => { -1 } + }; + } + let stages: Vec<&ImStr> = self.stages.iter().map(|e| e.as_ref()).collect(); + + ui.push_item_width(-1.0); + ui.list_box(im_str!(""), &mut self.selected_item, &stages, 10); + + if ui.button(im_str!("Load"), [0.0, 0.0]) { + match GameScene::new(state, ctx, self.selected_item as usize) { + Ok(mut scene) => { + scene.player.x = (scene.stage.map.width / 2 * 16 * 0x200) as isize; + scene.player.y = (scene.stage.map.height / 2 * 16 * 0x200) as isize; + state.next_scene = Some(Box::new(scene)); + } + Err(e) => { + self.error = Some(ImString::new(e.to_string())); + } } } - } - }); + }); + } Ok(()) } diff --git a/src/main.rs b/src/main.rs index 399b4fc..c88a69e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,10 @@ use crate::sound::SoundManager; use crate::stage::StageData; use crate::texture_set::TextureSet; use crate::ui::UI; +use crate::caret::{Caret, CaretType}; +use crate::common::Direction; +mod caret; mod common; mod engine_constants; mod entity; @@ -46,12 +49,14 @@ mod live_debugger; mod map; mod player; mod player_hit; +mod rng; mod scene; mod stage; mod sound; mod text_script; mod texture_set; mod ui; +mod weapon; bitfield! { pub struct KeyState(u16); @@ -86,6 +91,7 @@ struct Game { pub struct SharedGameState { pub flags: GameFlags, + pub carets: Vec, pub key_state: KeyState, pub key_trigger: KeyState, pub texture_set: TextureSet, @@ -107,6 +113,18 @@ impl SharedGameState { self.key_old = self.key_state.0; self.key_trigger = KeyState(trigger); } + + pub fn tick_carets(&mut self) { + for caret in self.carets.iter_mut() { + caret.tick(&self.constants); + } + + self.carets.retain(|c| !c.is_dead()); + } + + pub fn create_caret(&mut self, x: isize, y: isize, ctype: CaretType, direct: Direction) { + self.carets.push(Caret::new(x, y, ctype, direct, &self.constants)); + } } impl Game { @@ -137,6 +155,7 @@ impl Game { def_matrix: DrawParam::new().to_matrix(), state: SharedGameState { flags: GameFlags(0), + carets: Vec::new(), key_state: KeyState(0), key_trigger: KeyState(0), texture_set: TextureSet::new(base_path), diff --git a/src/player.rs b/src/player.rs index 15fc223..51d6256 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,11 +1,11 @@ -use crate::ggez::{Context, GameResult}; use num_traits::clamp; use crate::bitfield; +use crate::caret::CaretType; use crate::common::{Direction, Rect}; -use crate::engine_constants::PhysicsConsts; use crate::entity::GameEntity; use crate::frame::Frame; +use crate::ggez::{Context, GameResult}; use crate::SharedGameState; use crate::str; @@ -69,31 +69,30 @@ bitfield! { pub struct Player { pub x: isize, pub y: isize, - pub xm: isize, - pub ym: isize, + pub vel_x: isize, + pub vel_y: isize, pub target_x: isize, pub target_y: isize, + pub life: usize, + pub max_life: usize, pub cond: Cond, pub flags: Flags, pub equip: Equip, pub direction: Direction, pub view: Rect, pub hit: Rect, - pub life: u16, - pub max_life: u16, pub unit: u8, - pub air_physics: PhysicsConsts, - pub water_physics: PhysicsConsts, + pub question: bool, + pub booster_fuel: usize, index_x: isize, index_y: isize, sprash: bool, - ques: bool, up: bool, down: bool, - shock: u8, + shock_counter: u8, + booster_switch: u8, + star: u8, bubble: u8, - boost_sw: u8, - boost_cnt: isize, exp_wait: isize, exp_count: isize, anim_num: usize, @@ -111,31 +110,30 @@ impl Player { Ok(Player { x: 0, y: 0, - xm: 0, - ym: 0, + vel_x: 0, + vel_y: 0, target_x: 0, target_y: 0, + life: constants.my_char.life, + max_life: constants.my_char.max_life, cond: Cond(constants.my_char.cond), flags: Flags(constants.my_char.flags), equip: Equip(constants.my_char.equip), direction: constants.my_char.direction.clone(), view: constants.my_char.view, hit: constants.my_char.hit, - life: constants.my_char.life, - max_life: constants.my_char.max_life, unit: constants.my_char.unit, - air_physics: constants.my_char.air_physics, - water_physics: constants.my_char.water_physics, + question: false, + booster_fuel: 0, index_x: 0, index_y: 0, sprash: false, - ques: false, up: false, down: false, - shock: 0, + shock_counter: 0, + booster_switch: 0, + star: 0, bubble: 0, - boost_sw: 0, - boost_cnt: 0, exp_wait: 0, exp_count: 0, anim_num: 0, @@ -145,42 +143,42 @@ impl Player { }) } - fn tick_normal(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<()> { + fn tick_normal(&mut self, state: &mut SharedGameState) -> GameResult { if self.cond.cond_x02() { return Ok(()); } - let physics = if self.flags.underwater() { &self.water_physics } else { &self.air_physics }; + let physics = if self.flags.underwater() { state.constants.my_char.water_physics } else { state.constants.my_char.air_physics }; - self.ques = false; + self.question = false; if !state.flags.control_enabled() { - self.boost_sw = 0; + self.booster_switch = 0; } // todo: split those into separate procedures and refactor (try to not break the logic!) // ground movement if self.flags.flag_x08() || self.flags.flag_x10() || self.flags.flag_x20() { - self.boost_sw = 0; + self.booster_switch = 0; if self.equip.has_booster_0_8() || self.equip.has_booster_2_0() { - self.boost_cnt = 50; + self.booster_fuel = state.constants.booster.fuel; } else { - self.boost_cnt = 0; + self.booster_fuel = 0; } if state.flags.control_enabled() { - if state.key_trigger.only_down() && state.key_state.only_down() && !self.cond.cond_x01() && state.flags.flag_x04() { + if state.key_trigger.only_down() && state.key_state.only_down() && !self.cond.cond_x01() && !state.flags.flag_x04() { self.cond.set_cond_x01(true); - self.ques = true; + self.question = true; } else { - if state.key_state.left() && self.xm > -physics.max_dash { - self.xm -= physics.dash_ground; + if state.key_state.left() && self.vel_x > -physics.max_dash { + self.vel_x -= physics.dash_ground; } - if state.key_state.right() && self.xm < physics.max_dash { - self.xm += physics.dash_ground; + if state.key_state.right() && self.vel_x < physics.max_dash { + self.vel_x += physics.dash_ground; } if state.key_state.left() { @@ -194,62 +192,61 @@ impl Player { } if !self.cond.cond_x20() { - if self.xm < 0 { - if self.xm > -physics.resist { - self.xm = 0; + if self.vel_x < 0 { + if self.vel_x > -physics.resist { + self.vel_x = 0; } else { - self.xm += physics.resist; + self.vel_x += physics.resist; } - } else if self.xm > 0 { - if self.xm < physics.resist { - self.xm = 0; + } + if self.vel_x > 0 { + if self.vel_x < physics.resist { + self.vel_x = 0; } else { - self.xm -= physics.resist; + self.vel_x -= physics.resist; } } } } else { // air movement if state.flags.control_enabled() { - if (self.equip.has_booster_0_8() || self.equip.has_booster_2_0()) && state.key_trigger.jump() && self.boost_cnt != 0 { + if state.key_trigger.jump() && self.booster_fuel != 0 { if self.equip.has_booster_0_8() { - self.boost_sw = 1; + self.booster_switch = 1; - if self.ym > 0x100 { // 0.5fix9 - self.ym /= 2; + if self.vel_y > 0x100 { // 0.5fix9 + self.vel_y /= 2; } - } - - if self.equip.has_booster_2_0() { + } else if self.equip.has_booster_2_0() { if state.key_state.up() { - self.boost_sw = 2; - self.xm = 0; - self.ym = state.constants.booster.b2_0_up; + self.booster_switch = 2; + self.vel_x = 0; + self.vel_y = state.constants.booster.b2_0_up; } else if state.key_state.left() { - self.boost_sw = 2; - self.xm = 0; - self.ym = state.constants.booster.b2_0_left; + self.booster_switch = 1; + self.vel_x = state.constants.booster.b2_0_left; + self.vel_y = 0; } else if state.key_state.right() { - self.boost_sw = 2; - self.xm = 0; - self.ym = state.constants.booster.b2_0_right; + self.booster_switch = 1; + self.vel_x = state.constants.booster.b2_0_right; + self.vel_y = 0; } else if state.key_state.down() { - self.boost_sw = 2; - self.xm = 0; - self.ym = state.constants.booster.b2_0_down; + self.booster_switch = 3; + self.vel_x = 0; + self.vel_y = state.constants.booster.b2_0_down; } else { - self.boost_sw = 2; - self.xm = 0; - self.ym = state.constants.booster.b2_0_up_nokey; + self.booster_switch = 2; + self.vel_x = 0; + self.vel_y = state.constants.booster.b2_0_up_nokey; } } } - if state.key_state.left() && self.xm > -physics.max_dash { - self.xm -= physics.dash_air; + if state.key_state.left() && self.vel_x > -physics.max_dash { + self.vel_x -= physics.dash_air; } - if state.key_state.right() && self.xm < physics.max_dash { - self.xm += physics.dash_air; + if state.key_state.right() && self.vel_x < physics.max_dash { + self.vel_x += physics.dash_air; } if state.key_state.left() { @@ -261,16 +258,16 @@ impl Player { } } - if self.equip.has_booster_2_0() && self.boost_sw != 0 && !state.key_state.jump() || self.boost_cnt == 0 { - match self.boost_sw { - 1 => { self.xm /= 2 } - 2 => { self.ym /= 2 } + if self.equip.has_booster_2_0() && self.booster_switch != 0 && (!state.key_state.jump() || self.booster_fuel == 0) { + match self.booster_switch { + 1 => { self.vel_x /= 2 } + 2 => { self.vel_y /= 2 } _ => {} } } - if self.boost_cnt == 0 || !state.key_state.jump() { - self.boost_cnt = 0; + if self.booster_fuel == 0 || !state.key_state.jump() { + self.booster_switch = 0; } } @@ -281,7 +278,7 @@ impl Player { if state.key_trigger.jump() && (self.flags.flag_x08() || self.flags.flag_x10() || self.flags.flag_x20()) { if !self.flags.force_up() { - self.ym = -physics.jump; + self.vel_y = -physics.jump; // todo: PlaySoundObject(15, SOUND_MODE_PLAY); } } @@ -293,110 +290,107 @@ impl Player { } // booster losing fuel - if self.boost_sw != 0 && self.boost_cnt != 0 { - self.boost_cnt -= 1; + if self.booster_switch != 0 && self.booster_fuel != 0 { + self.booster_fuel -= 1; } // wind / current forces if self.flags.force_left() { - self.xm -= 0x88; + self.vel_x -= 0x88; } if self.flags.force_up() { - self.ym -= 0x80; + self.vel_y -= 0x80; } if self.flags.force_right() { - self.xm += 0x80; + self.vel_x += 0x80; } if self.flags.force_down() { - self.ym += 0x55; + self.vel_y += 0x55; } - if self.equip.has_booster_2_0() && self.boost_sw != 0 { - match self.boost_sw { + if self.equip.has_booster_2_0() && self.booster_switch != 0 { + match self.booster_switch { 1 => { if self.flags.flag_x01() || self.flags.flag_x04() { - self.ym = -0x100; // -0.5fix9 + self.vel_y = -0x100; // -0.5fix9 } if self.direction == Direction::Left { - self.xm -= 0x20; // 0.1fix9 + self.vel_x -= 0x20; // 0.1fix9 } if self.direction == Direction::Right { - self.xm += 0x20; // 0.1fix9 + self.vel_x += 0x20; // 0.1fix9 } - // todo: particles and sound - if state.key_trigger.jump() || self.boost_cnt % 3 == 1 { - if self.direction == Direction::Left { - // SetCaret(self.x + 2 * 0x200, self.y + 2 * 0x200, 7, 2); - } - if self.direction == Direction::Right { - // SetCaret(self.x + 2 * 0x200, self.y + 2 * 0x200, 7, 0); + // todo: sound + if state.key_trigger.jump() || self.booster_fuel % 3 == 1 { + if self.direction == Direction::Left || self.direction == Direction::Right { + state.create_caret(self.x + 0x400, self.y + 0x400, CaretType::Exhaust, self.direction.opposite()); } // PlaySoundObject(113, SOUND_MODE_PLAY); } } 2 => { - self.ym -= 0x20; + self.vel_y -= 0x20; - // todo: particles and sound - if state.key_trigger.jump() || self.boost_cnt % 3 == 1 { - // SetCaret(self.x, self.y + 6 * 0x200, 7, 3); + // todo: sound + if state.key_trigger.jump() || self.booster_fuel % 3 == 1 { + state.create_caret(self.x, self.y + 6 * 0x200, CaretType::Exhaust, Direction::Bottom); // PlaySoundObject(113, SOUND_MODE_PLAY); } } - // todo: particles and sound - 3 if state.key_trigger.jump() || self.boost_cnt % 3 == 1 => { - // SetCaret(self.x, self.y + 6 * 0x200, 7, 1); + // todo: sound + 3 if state.key_trigger.jump() || self.booster_fuel % 3 == 1 => { + state.create_caret(self.x, self.y + 6 * 0x200, CaretType::Exhaust, Direction::Up); // PlaySoundObject(113, SOUND_MODE_PLAY); } _ => {} } } else if self.flags.force_up() { - self.ym += physics.gravity_air; - } else if self.equip.has_booster_0_8() && self.boost_sw != 0 && self.ym > -0x400 { - self.ym -= 0x20; + self.vel_y += physics.gravity_air; + } else if self.equip.has_booster_0_8() && self.booster_switch != 0 && self.vel_y > -0x400 { + self.vel_y -= 0x20; - if self.boost_cnt % 3 == 0 { - // SetCaret(self.x, self.y + self.hit.bottom as isize / 2, 7, 3); + if self.booster_fuel % 3 == 0 { + state.create_caret(self.x, self.y + self.hit.bottom as isize / 2, CaretType::Exhaust, Direction::Bottom); // PlaySoundObject(113, SOUND_MODE_PLAY); } // bounce off of ceiling if self.flags.flag_x02() { - self.ym = 0x200; // 1.0fix9 + self.vel_y = 0x200; // 1.0fix9 } - } else if self.ym < 0 && state.flags.control_enabled() && state.key_state.jump() { - self.ym += physics.gravity_air; + } else if self.vel_y < 0 && state.flags.control_enabled() && state.key_state.jump() { + self.vel_y += physics.gravity_air; } else { - self.ym += physics.gravity_ground; + self.vel_y += physics.gravity_ground; } if !state.flags.control_enabled() || !state.key_trigger.jump() { - if self.flags.flag_x10() && self.xm < 0 { - self.ym = -self.xm; + if self.flags.flag_x10() && self.vel_x < 0 { + self.vel_y = -self.vel_x; } - if self.flags.flag_x20() && self.xm > 0 { - self.ym = self.xm; + if self.flags.flag_x20() && self.vel_x > 0 { + self.vel_y = self.vel_x; } - if (self.flags.flag_x08() && self.flags.flag_x80000() && self.xm < 0) - || (self.flags.flag_x08() && self.flags.flag_x10000() && self.xm > 0) + if (self.flags.flag_x08() && self.flags.flag_x80000() && self.vel_x < 0) + || (self.flags.flag_x08() && self.flags.flag_x10000() && self.vel_x > 0) || (self.flags.flag_x08() && self.flags.flag_x20000() && self.flags.flag_x40000()) { - self.ym = 0x400; // 2.0fix9 + self.vel_y = 0x400; // 2.0fix9 } } let max_move = if self.flags.underwater() && !(self.flags.force_left() || self.flags.force_up() || self.flags.force_right() || self.flags.force_down()) { - self.water_physics.max_move + physics.max_move } else { - self.air_physics.max_move + physics.max_move }; - self.xm = clamp(self.xm, -max_move, max_move); - self.ym = clamp(self.ym, -max_move, max_move); + self.vel_x = clamp(self.vel_x, -max_move, max_move); + self.vel_y = clamp(self.vel_y, -max_move, max_move); // todo: water splashing @@ -406,7 +400,7 @@ impl Player { // spike damage if self.flags.flag_x400() { - //self.damage(10); // todo: borrow checker yells at me + self.damage(10); } // camera @@ -423,14 +417,14 @@ impl Player { } if state.flags.control_enabled() && state.key_state.up() { - self.index_x -= 0x200; // 1.0fix9 - if self.index_x < -0x8000 { // -64.0fix9 - self.index_x = -0x8000; + self.index_y -= 0x200; // 1.0fix9 + if self.index_y < -0x8000 { // -64.0fix9 + self.index_y = -0x8000; } } else if state.flags.control_enabled() && state.key_state.down() { - self.index_x += 0x200; // 1.0fix9 - if self.index_x > 0x8000 { // -64.0fix9 - self.index_x = 0x8000; + self.index_y += 0x200; // 1.0fix9 + if self.index_y > 0x8000 { // -64.0fix9 + self.index_y = 0x8000; } } else { if self.index_y > 0x200 { // 1.0fix9 @@ -445,16 +439,16 @@ impl Player { self.target_x = self.x + self.index_x; self.target_y = self.y + self.index_y; - if self.xm > physics.resist || self.xm < -physics.resist { - self.x += self.xm; + if self.vel_x > physics.resist || self.vel_x < -physics.resist { + self.x += self.vel_x; } - self.y += self.ym; + self.y += self.vel_y; Ok(()) } - fn tick_stream(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<()> { + fn tick_stream(&mut self, state: &mut SharedGameState) -> GameResult { Ok(()) } @@ -518,7 +512,7 @@ impl Player { } else if state.key_state.down() { self.anim_num = 10; } else { - self.anim_num = if self.ym > 0 { 1 } else { 3 }; + self.anim_num = if self.vel_y > 0 { 1 } else { 3 }; } match self.direction { @@ -532,11 +526,29 @@ impl Player { } } - pub fn damage(&mut self, hp: isize) {} + pub fn damage(&mut self, hp: isize) { + if self.shock_counter > 0 { + return; + } + + // PlaySoundObject(16, SOUND_MODE_PLAY); // todo: damage sound + self.shock_counter = 128; + self.cond.set_cond_x01(false); + + if self.unit != 1 { + self.vel_y = -0x400; // -2.0fix9 + } + + self.life = if hp >= self.life as isize { 0 } else { (self.life as isize - hp) as usize }; + + if self.equip.has_whimsical_star() && self.star > 0 { + self.star -= 1; + } + } } impl GameEntity for Player { - fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<()> { + fn tick(&mut self, state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { if !self.cond.visible() { return Ok(()); } @@ -545,8 +557,8 @@ impl GameEntity for Player { self.exp_wait -= 1; } - if self.shock != 0 { - self.shock -= 1; + if self.shock_counter != 0 { + self.shock_counter -= 1; } else if self.exp_count != 0 { // SetValueView(&self.x, &self.y, self.exp_count); // todo: damage popup self.exp_count = 0; @@ -558,10 +570,10 @@ impl GameEntity for Player { // AirProcess(); // todo } - self.tick_normal(state, ctx)?; + self.tick_normal(state)?; } 1 => { - self.tick_stream(state, ctx)?; + self.tick_stream(state)?; } _ => {} } diff --git a/src/player_hit.rs b/src/player_hit.rs index 522765c..3fde700 100644 --- a/src/player_hit.rs +++ b/src/player_hit.rs @@ -3,6 +3,8 @@ use num_traits::clamp; use crate::player::Player; use crate::stage::Stage; use crate::SharedGameState; +use crate::caret::CaretType; +use crate::common::Direction; const OFF_X: &[isize; 4] = &[0, 1, 0, 1]; const OFF_Y: &[isize; 4] = &[0, 0, 1, 1]; @@ -16,12 +18,12 @@ impl Player { && (self.x - self.hit.right as isize) > x * 0x10 * 0x200 { self.x = ((x * 0x10 + 8) * 0x200) + self.hit.right as isize; - if self.xm < -0x180 { - self.xm = -0x180; + if self.vel_x < -0x180 { + self.vel_x = -0x180; } - if !state.key_state.left() && self.xm < 0 { - self.xm = 0; + if !state.key_state.left() && self.vel_x < 0 { + self.vel_x = 0; } self.flags.set_flag_x01(true); @@ -34,12 +36,12 @@ impl Player { && (self.x + self.hit.right as isize) < x * 0x10 * 0x200 { self.x = ((x * 0x10 - 8) * 0x200) - self.hit.right as isize; - if self.xm > 0x180 { - self.xm = 0x180; + if self.vel_x > 0x180 { + self.vel_x = 0x180; } - if !state.key_state.right() && self.xm > 0 { - self.xm = 0; + if !state.key_state.right() && self.vel_x > 0 { + self.vel_x = 0; } self.flags.set_flag_x04(true); @@ -52,12 +54,12 @@ impl Player { && (self.y - self.hit.top as isize) > y * 0x10 * 0x200 { self.y = ((y * 0x10 + 8) * 0x200) + self.hit.top as isize; - if !self.cond.cond_x02() && self.ym < -0x200 { + if !self.cond.cond_x02() && self.vel_y < -0x200 { // PutLittleStar(); todo } - if self.ym < 0 { - self.ym = 0; + if self.vel_y < 0 { + self.vel_y = 0; } self.flags.set_flag_x02(true); @@ -70,12 +72,12 @@ impl Player { && ((self.y + self.hit.bottom as isize) < y * 0x10 * 0x200) { self.y = ((y * 0x10 - 8) * 0x200) - self.hit.bottom as isize; - if self.ym > 0x400 { + if self.vel_y > 0x400 { // PlaySoundObject(23, SOUND_MODE_PLAY); todo } - if self.ym > 0 { - self.ym = 0; + if self.vel_y > 0 { + self.vel_y = 0; } self.flags.set_flag_x08(true); @@ -89,12 +91,12 @@ impl Player { && (self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200 { self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) + 0x800 + self.hit.top as isize; - if !self.cond.cond_x02() && self.ym < -0x200 { + if !self.cond.cond_x02() && self.vel_y < -0x200 { // PutLittleStar(); todo } - if self.ym < 0 { - self.ym = 0; + if self.vel_y < 0 { + self.vel_y = 0; } self.flags.set_flag_x02(true); @@ -108,12 +110,12 @@ impl Player { && (self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200 { self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) - 0x800 + self.hit.top as isize; - if !self.cond.cond_x02() && self.ym < -0x200 { + if !self.cond.cond_x02() && self.vel_y < -0x200 { // PutLittleStar(); todo } - if self.ym < 0 { - self.ym = 0; + if self.vel_y < 0 { + self.vel_y = 0; } self.flags.set_flag_x02(true); @@ -127,12 +129,12 @@ impl Player { && (self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200 { self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) - 0x800 + self.hit.top as isize; - if !self.cond.cond_x02() && self.ym < -0x200 { + if !self.cond.cond_x02() && self.vel_y < -0x200 { // PutLittleStar(); todo } - if self.ym < 0 { - self.ym = 0; + if self.vel_y < 0 { + self.vel_y = 0; } self.flags.set_flag_x02(true); @@ -146,12 +148,12 @@ impl Player { && (self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200 { self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) + 0x800 + self.hit.top as isize; - if !self.cond.cond_x02() && self.ym < -0x200 { + if !self.cond.cond_x02() && self.vel_y < -0x200 { // PutLittleStar(); todo } - if self.ym < 0 { - self.ym = 0; + if self.vel_y < 0 { + self.vel_y = 0; } self.flags.set_flag_x02(true); @@ -167,12 +169,12 @@ impl Player { && (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200 { self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) - 0x800 - self.hit.bottom as isize; - if self.ym > 0x400 { + if self.vel_y > 0x400 { // PlaySoundObject(23, SOUND_MODE_PLAY); todo } - if self.ym > 0 { - self.ym = 0; + if self.vel_y > 0 { + self.vel_y = 0; } self.flags.set_flag_x20(true); @@ -189,12 +191,12 @@ impl Player { && (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200 { self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) + 0x800 - self.hit.bottom as isize; - if self.ym > 0x400 { + if self.vel_y > 0x400 { // PlaySoundObject(23, SOUND_MODE_PLAY); todo } - if self.ym > 0 { - self.ym = 0; + if self.vel_y > 0 { + self.vel_y = 0; } self.flags.set_flag_x20(true); @@ -211,12 +213,12 @@ impl Player { && (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200 { self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) + 0x800 - self.hit.bottom as isize; - if self.ym > 0x400 { + if self.vel_y > 0x400 { // PlaySoundObject(23, SOUND_MODE_PLAY); todo } - if self.ym > 0 { - self.ym = 0; + if self.vel_y > 0 { + self.vel_y = 0; } self.flags.set_flag_x10(true); @@ -233,12 +235,12 @@ impl Player { && (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200 { self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) - 0x800 - self.hit.bottom as isize; - if self.ym > 0x400 { + if self.vel_y > 0x400 { // PlaySoundObject(23, SOUND_MODE_PLAY); todo } - if self.ym > 0 { - self.ym = 0; + if self.vel_y > 0 { + self.vel_y = 0; } self.flags.set_flag_x10(true); @@ -309,4 +311,10 @@ impl Player { } } } + + pub fn tick_npc_collisions(&mut self, state: &mut SharedGameState, stage: &Stage) { + if self.question { + state.create_caret(self.x, self.y, CaretType::QuestionMark, Direction::Left); + } + } } diff --git a/src/rng.rs b/src/rng.rs new file mode 100644 index 0000000..2886fef --- /dev/null +++ b/src/rng.rs @@ -0,0 +1,22 @@ +/// Stateful RNG +pub struct RNG { + pub seed: u32, +} + +impl RNG { + pub fn new() -> Self { + Self { + seed: 0, + } + } + + pub fn next(&mut self) -> u32 { + // MSVC LCG values + self.seed = self.seed.wrapping_mul(214013).wrapping_add(2531011); + self.seed + } + + pub fn range(&mut self, start: i32, end: i32) -> i32 { + start + (self.next() % (end - start) as u32) as i32 + } +} diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 8aa3277..3277cd7 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -17,9 +17,10 @@ pub struct GameScene { pub stage: Stage, pub frame: Frame, pub player: Player, - tex_tileset_name: String, tex_background_name: String, + tex_caret_name: String, tex_hud_name: String, + tex_tileset_name: String, life_bar: usize, life_bar_count: usize, } @@ -43,9 +44,10 @@ impl GameScene { info!("Loaded stage: {}", stage.data.name); info!("Map size: {}x{}", stage.map.width, stage.map.height); - let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join(""); let tex_background_name = stage.data.background.filename(); + let tex_caret_name = str!("Caret"); let tex_hud_name = str!("TextBox"); + let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join(""); Ok(Self { tick: 0, @@ -56,9 +58,10 @@ impl GameScene { y: 0, wait: 16, }, - tex_tileset_name, tex_background_name, + tex_caret_name, tex_hud_name, + tex_tileset_name, life_bar: 3, life_bar_count: 0, }) @@ -176,6 +179,19 @@ impl GameScene { Ok(()) } + fn draw_carets(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &self.tex_caret_name)?; + + for caret in state.carets.iter() { + batch.add_rect((((caret.x - caret.offset_x) / 0x200) - (self.frame.x / 0x200)) as f32, + (((caret.y - caret.offset_y) / 0x200) - (self.frame.y / 0x200)) as f32, + &caret.anim_rect); + } + + batch.draw(ctx)?; + Ok(()) + } + 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 mut rect = Rect::::new(0, 0, 16, 16); @@ -226,7 +242,7 @@ impl Scene for GameScene { state.sound_manager.play_song(ctx)?; //self.player.x = 700 * 0x200; //self.player.y = 1000 * 0x200; - //self.player.equip.set_booster_2_0(true); + self.player.equip.set_booster_2_0(true); state.flags.set_flag_x01(true); state.flags.set_control_enabled(true); Ok(()) @@ -239,7 +255,9 @@ impl Scene for GameScene { self.player.tick(state, ctx)?; self.player.flags.0 = 0; + state.tick_carets(); self.player.tick_map_collisions(state, &self.stage); + self.player.tick_npc_collisions(state, &self.stage); self.frame.update(state, &self.player, &self.stage); } @@ -269,6 +287,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_carets(state, ctx)?; self.draw_hud(state, ctx)?; self.draw_number(state.canvas_size.0 - 8.0, 8.0, timer::fps(ctx) as usize, Alignment::Right, state, ctx)?; diff --git a/src/texture_set.rs b/src/texture_set.rs index 99ed5b4..460dd18 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -57,6 +57,10 @@ impl SizedBatch { } pub fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect) { + if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { + return; + } + let param = DrawParam::new() .src(Rect::new(rect.left as f32 / self.width as f32, rect.top as f32 / self.height as f32, diff --git a/src/weapon.rs b/src/weapon.rs new file mode 100644 index 0000000..b1789d8 --- /dev/null +++ b/src/weapon.rs @@ -0,0 +1,3 @@ +pub struct Weapon { + +}