slopes, pxmap, camera fixes
This commit is contained in:
parent
0d47a0f401
commit
6f3beb6e28
|
@ -273,7 +273,6 @@ pub struct Rect<T: Num + PartialOrd + Copy = isize> {
|
|||
}
|
||||
|
||||
impl<T: Num + PartialOrd + Copy> Rect<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(left: T, top: T, right: T, bottom: T) -> Rect<T> {
|
||||
Rect {
|
||||
left,
|
||||
|
@ -283,7 +282,6 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn new_size(x: T, y: T, width: T, height: T) -> Rect<T> {
|
||||
Rect {
|
||||
left: x,
|
||||
|
|
28
src/frame.rs
28
src/frame.rs
|
@ -35,8 +35,10 @@ impl Frame {
|
|||
}
|
||||
|
||||
pub fn immediate_update(&mut self, state: &mut SharedGameState, stage: &Stage) {
|
||||
if (stage.map.width as usize).saturating_sub(1) * 16 < state.canvas_size.0 as usize {
|
||||
self.x = -(((state.canvas_size.0 as i32 - ((stage.map.width - 1) * 16) as i32) * 0x200) / 2);
|
||||
let tile_size = state.tile_size.as_int();
|
||||
|
||||
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.0 as usize {
|
||||
self.x = -(((state.canvas_size.0 as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} else {
|
||||
self.x = self.target_x - (state.canvas_size.0 as i32 * 0x200 / 2);
|
||||
|
||||
|
@ -44,14 +46,14 @@ impl Frame {
|
|||
self.x = 0;
|
||||
}
|
||||
|
||||
let max_x = (((stage.map.width as i32 - 1) * 16) - state.canvas_size.0 as i32) * 0x200;
|
||||
let max_x = (((stage.map.width as i32 - 1) * tile_size) - state.canvas_size.0 as i32) * 0x200;
|
||||
if self.x > max_x {
|
||||
self.x = max_x;
|
||||
}
|
||||
}
|
||||
|
||||
if (stage.map.height as usize).saturating_sub(1) * 16 < state.canvas_size.1 as usize {
|
||||
self.y = -(((state.canvas_size.1 as i32 - ((stage.map.height - 1) * 16) as i32) * 0x200) / 2);
|
||||
if (stage.map.height as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.1 as usize {
|
||||
self.y = -(((state.canvas_size.1 as i32 - (stage.map.height as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} else {
|
||||
self.y = self.target_y - (state.canvas_size.1 as i32 * 0x200 / 2);
|
||||
|
||||
|
@ -59,7 +61,7 @@ impl Frame {
|
|||
self.y = 0;
|
||||
}
|
||||
|
||||
let max_y = (((stage.map.height as i32 - 1) * 16) - state.canvas_size.1 as i32) * 0x200;
|
||||
let max_y = (((stage.map.height as i32 - 1) * tile_size) - state.canvas_size.1 as i32) * 0x200;
|
||||
if self.y > max_y {
|
||||
self.y = max_y;
|
||||
}
|
||||
|
@ -70,8 +72,10 @@ impl Frame {
|
|||
}
|
||||
|
||||
pub fn update(&mut self, state: &mut SharedGameState, stage: &Stage) {
|
||||
if (stage.map.width as usize).saturating_sub(1) * 16 < state.canvas_size.0 as usize {
|
||||
self.x = -(((state.canvas_size.0 as i32 - ((stage.map.width - 1) * 16) as i32) * 0x200) / 2);
|
||||
let tile_size = state.tile_size.as_int();
|
||||
|
||||
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.0 as usize {
|
||||
self.x = -(((state.canvas_size.0 as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} else {
|
||||
self.x += (self.target_x - (state.canvas_size.0 as i32 * 0x200 / 2) - self.x) / self.wait;
|
||||
|
||||
|
@ -79,14 +83,14 @@ impl Frame {
|
|||
self.x = 0;
|
||||
}
|
||||
|
||||
let max_x = (((stage.map.width as i32 - 1) * 16) - state.canvas_size.0 as i32) * 0x200;
|
||||
let max_x = (((stage.map.width as i32 - 1) * tile_size) - state.canvas_size.0 as i32) * 0x200;
|
||||
if self.x > max_x {
|
||||
self.x = max_x;
|
||||
}
|
||||
}
|
||||
|
||||
if (stage.map.height as usize).saturating_sub(1) * 16 < state.canvas_size.1 as usize {
|
||||
self.y = -(((state.canvas_size.1 as i32 - ((stage.map.height - 1) * 16) as i32) * 0x200) / 2);
|
||||
if (stage.map.height as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.1 as usize {
|
||||
self.y = -(((state.canvas_size.1 as i32 - (stage.map.height as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} else {
|
||||
self.y += (self.target_y - (state.canvas_size.1 as i32 * 0x200 / 2) - self.y) / self.wait;
|
||||
|
||||
|
@ -94,7 +98,7 @@ impl Frame {
|
|||
self.y = 0;
|
||||
}
|
||||
|
||||
let max_y = (((stage.map.height as i32 - 1) * 16) - state.canvas_size.1 as i32) * 0x200;
|
||||
let max_y = (((stage.map.height as i32 - 1) * tile_size) - state.canvas_size.1 as i32) * 0x200;
|
||||
if self.y > max_y {
|
||||
self.y = max_y;
|
||||
}
|
||||
|
|
|
@ -75,14 +75,20 @@ impl LiveDebugger {
|
|||
));
|
||||
|
||||
ui.text(format!(
|
||||
"NPC Count: {}/{}/{}",
|
||||
game_scene.npc_list.iter_alive().count(),
|
||||
game_scene.npc_list.current_capacity(),
|
||||
game_scene.npc_list.max_capacity(),
|
||||
"frame: ({:.1},{:.1} -> {:.1},{:.1} / {})",
|
||||
game_scene.frame.x as f32 / 512.0,
|
||||
game_scene.frame.y as f32 / 512.0,
|
||||
game_scene.frame.target_x as f32 / 512.0,
|
||||
game_scene.frame.target_y as f32 / 512.0,
|
||||
game_scene.frame.wait
|
||||
));
|
||||
|
||||
ui.text(format!(
|
||||
"Booster fuel: {}", game_scene.player1.booster_fuel
|
||||
"NPC Count: {}/{}/{} Booster fuel: {}",
|
||||
game_scene.npc_list.iter_alive().count(),
|
||||
game_scene.npc_list.current_capacity(),
|
||||
game_scene.npc_list.max_capacity(),
|
||||
game_scene.player1.booster_fuel
|
||||
));
|
||||
|
||||
|
||||
|
@ -164,20 +170,21 @@ impl LiveDebugger {
|
|||
if ui.button(im_str!("Load"), [0.0, 0.0]) {
|
||||
match GameScene::new(state, ctx, self.selected_stage as usize) {
|
||||
Ok(mut scene) => {
|
||||
let tile_size = scene.stage.map.tile_size.as_int() * 0x200;
|
||||
scene.inventory_player1 = game_scene.inventory_player1.clone();
|
||||
scene.inventory_player2 = game_scene.inventory_player2.clone();
|
||||
|
||||
scene.player1 = game_scene.player1.clone();
|
||||
scene.player1.x = scene.stage.map.width as i32 / 2 * 0x2000;
|
||||
scene.player1.y = scene.stage.map.height as i32 / 2 * 0x2000;
|
||||
scene.player1.x = scene.stage.map.width as i32 / 2 * tile_size;
|
||||
scene.player1.y = scene.stage.map.height as i32 / 2 * tile_size;
|
||||
|
||||
if scene.player1.life == 0 {
|
||||
scene.player1.life = scene.player1.max_life;
|
||||
}
|
||||
|
||||
scene.player2 = game_scene.player2.clone();
|
||||
scene.player2.x = scene.stage.map.width as i32 / 2 * 0x2000;
|
||||
scene.player2.y = scene.stage.map.height as i32 / 2 * 0x2000;
|
||||
scene.player2.x = scene.stage.map.width as i32 / 2 * tile_size;
|
||||
scene.player2.y = scene.stage.map.height as i32 / 2 * tile_size;
|
||||
|
||||
if scene.player2.life == 0 {
|
||||
scene.player2.life = scene.player1.max_life;
|
||||
|
|
236
src/map.rs
236
src/map.rs
|
@ -1,14 +1,19 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::io::{BufRead, BufReader, Error};
|
||||
use std::io::{BufRead, BufReader, Cursor, Read};
|
||||
use std::sync::Arc;
|
||||
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::encoding::read_cur_shift_jis;
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::shared_game_state::TileSize;
|
||||
use crate::stage::{StageData, PxPackScroll, PxPackStageData};
|
||||
use crate::str;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::context::Context;
|
||||
|
||||
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
||||
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
|
||||
|
@ -18,6 +23,7 @@ pub struct Map {
|
|||
pub height: u16,
|
||||
pub tiles: Vec<u8>,
|
||||
pub attrib: [u8; 0x100],
|
||||
pub tile_size: TileSize,
|
||||
}
|
||||
|
||||
static SOLID_TILES: [u8; 8] = [0x05, 0x41, 0x43, 0x46, 0x54, 0x55, 0x56, 0x57];
|
||||
|
@ -31,7 +37,7 @@ pub enum WaterRegionType {
|
|||
}
|
||||
|
||||
impl Map {
|
||||
pub fn load_from<R: io::Read>(mut map_data: R, mut attrib_data: R) -> GameResult<Map> {
|
||||
pub fn load_pxm<R: io::Read>(mut map_data: R, mut attrib_data: R) -> GameResult<Map> {
|
||||
let mut magic = [0; 3];
|
||||
|
||||
map_data.read_exact(&mut magic)?;
|
||||
|
@ -58,7 +64,215 @@ impl Map {
|
|||
log::warn!("Map attribute data is shorter than 256 bytes!");
|
||||
}
|
||||
|
||||
Ok(Map { width, height, tiles, attrib })
|
||||
Ok(Map { width, height, tiles, attrib, tile_size: TileSize::Tile16x16 })
|
||||
}
|
||||
|
||||
pub fn load_pxpack<R: io::Read>(mut map_data: R, root: &str, data: &mut StageData, ctx: &mut Context) -> GameResult<Map> {
|
||||
let mut magic = [0u8; 16];
|
||||
|
||||
map_data.read_exact(&mut magic)?;
|
||||
|
||||
// based on https://github.com/tilderain/pxEdit/blob/kero/pxMap.py
|
||||
|
||||
if &magic != b"PXPACK121127a**\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
}
|
||||
|
||||
fn read_string<R: io::Read>(map_data: &mut R) -> GameResult<String> {
|
||||
let mut bytes = map_data.read_u8()? as u32;
|
||||
let mut raw_chars = Vec::new();
|
||||
raw_chars.resize(bytes as usize, 0u8);
|
||||
map_data.read(&mut raw_chars)?;
|
||||
let mut raw_chars = Cursor::new(raw_chars);
|
||||
|
||||
let mut chars = Vec::new();
|
||||
chars.reserve(bytes as usize);
|
||||
|
||||
while bytes > 0 {
|
||||
let (consumed, chr) = read_cur_shift_jis(&mut raw_chars, bytes);
|
||||
chars.push(chr);
|
||||
bytes -= consumed;
|
||||
}
|
||||
|
||||
Ok(chars.iter().collect())
|
||||
};
|
||||
|
||||
fn skip_string<R: io::Read>(map_data: &mut R) -> GameResult {
|
||||
let bytes = map_data.read_u8()? as u32;
|
||||
for _ in 0..bytes {
|
||||
map_data.read_u8()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let map_name = read_string(&mut map_data)?;
|
||||
skip_string(&mut map_data)?; // left, right, up, down
|
||||
skip_string(&mut map_data)?;
|
||||
skip_string(&mut map_data)?;
|
||||
skip_string(&mut map_data)?;
|
||||
skip_string(&mut map_data)?; // spritesheet
|
||||
|
||||
map_data.read_u16::<LE>()?;
|
||||
map_data.read_u16::<LE>()?;
|
||||
map_data.read_u8()?;
|
||||
|
||||
let bg_color = Color::from_rgb(map_data.read_u8()?, map_data.read_u8()?, map_data.read_u8()?);
|
||||
|
||||
let mut tileset_fg = read_string(&mut map_data)?;
|
||||
map_data.read_u8()?; // ignored
|
||||
let scroll_fg = PxPackScroll::from(map_data.read_u8()?);
|
||||
|
||||
let mut tileset_mg = read_string(&mut map_data)?;
|
||||
map_data.read_u8()?; // ignored
|
||||
let scroll_mg = PxPackScroll::from(map_data.read_u8()?);
|
||||
|
||||
let mut tileset_bg = read_string(&mut map_data)?;
|
||||
map_data.read_u8()?; // ignored
|
||||
let scroll_bg = PxPackScroll::from(map_data.read_u8()?);
|
||||
|
||||
if tileset_fg == "" { tileset_fg = data.tileset.filename() }
|
||||
if tileset_mg == "" { tileset_mg = data.tileset.filename() }
|
||||
if tileset_bg == "" { tileset_bg = data.tileset.filename() }
|
||||
|
||||
let mut tiles = Vec::new();
|
||||
let mut attrib = [0u8; 0x100];
|
||||
|
||||
let mut magic = [0u8; 8];
|
||||
map_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"pxMAP01\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
}
|
||||
|
||||
let width_fg = map_data.read_u16::<LE>()?;
|
||||
let height_fg = map_data.read_u16::<LE>()?;
|
||||
map_data.read_u8()?;
|
||||
|
||||
log::info!("Foreground map size: {}x{}", width_fg, height_fg);
|
||||
|
||||
let size_fg = width_fg as u32 * height_fg as u32;
|
||||
tiles.resize(size_fg as usize, 0u8);
|
||||
|
||||
map_data.read_exact(&mut tiles[0..size_fg as usize])?;
|
||||
|
||||
map_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"pxMAP01\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
}
|
||||
|
||||
let width_mg = map_data.read_u16::<LE>()?;
|
||||
let height_mg = map_data.read_u16::<LE>()?;
|
||||
|
||||
log::info!("Middleground map size: {}x{}", width_mg, height_mg);
|
||||
|
||||
let size_mg = width_mg as u32 * height_mg as u32;
|
||||
if size_mg != 0 {
|
||||
tiles.resize(size_fg as usize + size_mg as usize, 0u8);
|
||||
map_data.read_u8()?;
|
||||
map_data.read_exact(&mut tiles[size_fg as usize..(size_fg as usize + size_mg as usize)])?;
|
||||
}
|
||||
|
||||
map_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"pxMAP01\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
}
|
||||
|
||||
let width_bg = map_data.read_u16::<LE>()?;
|
||||
let height_bg = map_data.read_u16::<LE>()?;
|
||||
|
||||
log::info!("Background map size: {}x{}", width_bg, height_bg);
|
||||
|
||||
let size_bg = width_bg as u32 * height_bg as u32;
|
||||
if size_bg != 0 {
|
||||
map_data.read_u8()?;
|
||||
tiles.resize(size_fg as usize + size_mg as usize + size_bg as usize, 0u8);
|
||||
map_data.read_exact(&mut tiles[(size_fg as usize + size_mg as usize)..(size_fg as usize + size_mg as usize + size_bg as usize)])?;
|
||||
}
|
||||
|
||||
if let Ok(mut attrib_data) = filesystem::open(ctx, [root, "Stage/", &tileset_fg, ".pxa"].join("")) {
|
||||
if attrib_data.read_exact(&mut attrib).is_err() {
|
||||
log::warn!("Map attribute data is shorter than 256 bytes!");
|
||||
}
|
||||
} else if let Ok(mut attrib_data) = filesystem::open(ctx, [root, "Stage/", &tileset_fg, ".pxattr"].join("")) {
|
||||
attrib_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"pxMAP01\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
}
|
||||
|
||||
attrib_data.read_u16::<LE>()?;
|
||||
attrib_data.read_u16::<LE>()?;
|
||||
attrib_data.read_u8()?;
|
||||
|
||||
if attrib_data.read_exact(&mut attrib).is_err() {
|
||||
log::warn!("Map attribute data is shorter than 256 bytes!");
|
||||
}
|
||||
|
||||
for attr in attrib.iter_mut() {
|
||||
*attr = match *attr {
|
||||
1 | 45 => 0x41,
|
||||
2 | 66 => 0x44,
|
||||
3 | 67 => 0x46,
|
||||
4 | 68 => 0x43,
|
||||
5 => 0x42,
|
||||
7 => 0x4a,
|
||||
8 => 0x50,
|
||||
9 => 0x51,
|
||||
10 => 0x52,
|
||||
11 => 0x53,
|
||||
12 => 0x54,
|
||||
13 => 0x55,
|
||||
14 => 0x56,
|
||||
15 => 0x57,
|
||||
40 => 0x5a,
|
||||
41 => 0x5b,
|
||||
42 => 0x5c,
|
||||
43 => 0x5d,
|
||||
64 => 0x60,
|
||||
65 | 109 => 0x61,
|
||||
69 => 0x62,
|
||||
72 => 0x70,
|
||||
73 => 0x71,
|
||||
74 => 0x72,
|
||||
75 => 0x73,
|
||||
76 => 0x74,
|
||||
77 => 0x75,
|
||||
78 => 0x76,
|
||||
79 => 0x77,
|
||||
104 => 0x7a,
|
||||
105 => 0x7b,
|
||||
106 => 0x7c,
|
||||
107 => 0x7d,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
log::warn!("No tile attribute data found for foreground tileset {}, collision might be broken.", tileset_fg);
|
||||
}
|
||||
|
||||
if map_name != "" {
|
||||
data.name = map_name;
|
||||
}
|
||||
|
||||
data.background_color = bg_color;
|
||||
data.pxpack_data = Some(PxPackStageData {
|
||||
tileset_fg,
|
||||
tileset_mg,
|
||||
tileset_bg,
|
||||
scroll_fg,
|
||||
scroll_mg,
|
||||
scroll_bg,
|
||||
size_fg: (width_fg, height_fg),
|
||||
size_mg: (width_mg, height_mg),
|
||||
size_bg: (width_bg, height_bg),
|
||||
offset_mg: size_fg,
|
||||
offset_bg: size_fg + size_mg
|
||||
});
|
||||
|
||||
Ok(Map { width: width_fg, height: height_fg, tiles, attrib, tile_size: TileSize::Tile8x8 })
|
||||
}
|
||||
|
||||
pub fn get_attribute(&self, x: usize, y: usize) -> u8 {
|
||||
|
@ -149,10 +363,18 @@ impl Map {
|
|||
}
|
||||
};
|
||||
|
||||
if flow_flags & 0b0001 != 0 { check(0b1011, fx as i32 - 1, fy as i32); }
|
||||
if flow_flags & 0b0100 != 0 { check(0b1110, fx as i32 + 1, fy as i32); }
|
||||
if flow_flags & 0b0010 != 0 { check(0b0111, fx as i32, fy as i32 - 1); }
|
||||
if flow_flags & 0b1000 != 0 { check(0b1101, fx as i32, fy as i32 + 1); }
|
||||
if flow_flags & 0b0001 != 0 {
|
||||
check(0b1011, fx as i32 - 1, fy as i32);
|
||||
}
|
||||
if flow_flags & 0b0100 != 0 {
|
||||
check(0b1110, fx as i32 + 1, fy as i32);
|
||||
}
|
||||
if flow_flags & 0b0010 != 0 {
|
||||
check(0b0111, fx as i32, fy as i32 - 1);
|
||||
}
|
||||
if flow_flags & 0b1000 != 0 {
|
||||
check(0b1101, fx as i32, fy as i32 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
rects.push(rect);
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use crate::framework::error::GameResult;
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::npc::NPC;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::{Stage, BackgroundType};
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n001_experience(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
if state.control_flags.wind() {
|
||||
pub(crate) fn tick_n001_experience(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
|
||||
if stage.data.background_type == BackgroundType::Scrolling || stage.data.background_type == BackgroundType::OutsideWind {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
|
||||
|
@ -46,18 +45,10 @@ impl NPC {
|
|||
self.vel_x = self.rng.range(-0x200..0x200) as i32;
|
||||
self.vel_y = self.rng.range(-0x400..0) as i32;
|
||||
|
||||
self.direction = if self.rng.range(0..1) != 0 {
|
||||
Direction::Left
|
||||
} else {
|
||||
Direction::Right
|
||||
};
|
||||
self.direction = if self.rng.range(0..1) != 0 { Direction::Left } else { Direction::Right };
|
||||
}
|
||||
|
||||
self.vel_y += if self.flags.in_water() {
|
||||
0x15
|
||||
} else {
|
||||
0x2a
|
||||
};
|
||||
self.vel_y += if self.flags.in_water() { 0x15 } else { 0x2a };
|
||||
|
||||
if self.flags.hit_left_wall() && self.vel_x < 0 {
|
||||
self.vel_x = -self.vel_x;
|
||||
|
@ -89,8 +80,8 @@ impl NPC {
|
|||
self.action_counter2 = 0;
|
||||
}
|
||||
|
||||
self.vel_x = clamp(self.vel_x, -0x5ff, 0x5ff);
|
||||
self.vel_y = clamp(self.vel_y, -0x5ff, 0x5ff);
|
||||
self.vel_x = self.vel_x.clamp(-0x5ff, 0x5ff);
|
||||
self.vel_y = self.vel_y.clamp(-0x5ff, 0x5ff);
|
||||
}
|
||||
|
||||
self.x += self.vel_x;
|
||||
|
@ -120,13 +111,15 @@ impl NPC {
|
|||
self.anim_rect = state.constants.npc.n001_experience[self.anim_num as usize];
|
||||
|
||||
if self.action_num != 0 {
|
||||
if self.exp >= 5 {
|
||||
self.anim_rect.top += 16;
|
||||
self.anim_rect.bottom += 16;
|
||||
} else if self.exp >= 20 {
|
||||
if self.exp >= 20 {
|
||||
self.anim_rect.top += 32;
|
||||
self.anim_rect.bottom += 32;
|
||||
} else if self.exp >= 5 {
|
||||
self.anim_rect.top += 16;
|
||||
self.anim_rect.bottom += 16;
|
||||
}
|
||||
|
||||
self.action_num = 1;
|
||||
}
|
||||
|
||||
self.action_counter += 1;
|
||||
|
@ -145,7 +138,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n086_missile_pickup(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n086_missile_pickup(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
|
||||
if self.direction == Direction::Left {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2 {
|
||||
|
@ -159,7 +152,7 @@ impl NPC {
|
|||
self.action_counter2 += 1;
|
||||
}
|
||||
|
||||
if state.control_flags.wind() {
|
||||
if stage.data.background_type == BackgroundType::Scrolling || stage.data.background_type == BackgroundType::OutsideWind {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.vel_x = self.rng.range(0x7f..0x100) as i32;
|
||||
|
@ -213,7 +206,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n087_heart_pickup(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n087_heart_pickup(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
|
||||
if self.direction == Direction::Left {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2 {
|
||||
|
@ -227,7 +220,7 @@ impl NPC {
|
|||
self.action_counter2 += 1;
|
||||
}
|
||||
|
||||
if state.control_flags.wind() {
|
||||
if stage.data.background_type == BackgroundType::Scrolling || stage.data.background_type == BackgroundType::OutsideWind {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.vel_x = self.rng.range(0x7f..0x100) as i32;
|
||||
|
|
|
@ -168,7 +168,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl
|
|||
fn tick(&mut self, state: &mut SharedGameState, (players, npc_list, stage, bullet_manager, flash): ([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Flash)) -> GameResult {
|
||||
match self.npc_type {
|
||||
0 => self.tick_n000_null(),
|
||||
1 => self.tick_n001_experience(state),
|
||||
1 => self.tick_n001_experience(state, stage),
|
||||
2 => self.tick_n002_behemoth(state),
|
||||
3 => self.tick_n003_dead_enemy(),
|
||||
4 => self.tick_n004_smoke(state),
|
||||
|
@ -253,8 +253,8 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl
|
|||
83 => self.tick_n083_igor_cutscene(state),
|
||||
84 => self.tick_n084_basu_projectile(state),
|
||||
85 => self.tick_n085_terminal(state, players),
|
||||
86 => self.tick_n086_missile_pickup(state),
|
||||
87 => self.tick_n087_heart_pickup(state),
|
||||
86 => self.tick_n086_missile_pickup(state, stage),
|
||||
87 => self.tick_n087_heart_pickup(state, stage),
|
||||
88 => self.tick_n088_igor_boss(state, players, npc_list),
|
||||
89 => self.tick_n089_igor_dead(state, players, npc_list),
|
||||
90 => self.tick_n090_background(state),
|
||||
|
@ -477,6 +477,11 @@ impl PhysicalEntity for NPC {
|
|||
&self.hit_bounds
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn display_bounds(&self) -> &Rect<u32> {
|
||||
&self.display_bounds
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_x(&mut self, x: i32) {
|
||||
self.x = x;
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::npc::{NPC, NPCFlag, NPCTable, NPCLayer};
|
|||
use crate::npc::list::NPCList;
|
||||
use crate::player::Player;
|
||||
use crate::rng::{RNG, Xoroshiro32PlusPlus};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::components::number_popup::NumberPopup;
|
||||
|
||||
impl NPC {
|
||||
|
@ -85,12 +85,14 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn create_from_data(data: &NPCData, table: &NPCTable) -> NPC {
|
||||
pub fn create_from_data(data: &NPCData, table: &NPCTable, tile_size: TileSize) -> NPC {
|
||||
let mut npc = NPC::create(data.npc_type, table);
|
||||
|
||||
let ti = tile_size.as_int() * 0x200;
|
||||
|
||||
npc.id = data.id;
|
||||
npc.x = data.x as i32 * 0x2000;
|
||||
npc.y = data.y as i32 * 0x2000;
|
||||
npc.x = data.x as i32 * ti;
|
||||
npc.y = data.y as i32 * ti;
|
||||
npc.flag_num = data.flag_num;
|
||||
npc.event_num = data.event_num;
|
||||
npc.npc_flags = NPCFlag(data.flags | npc.npc_flags.0);
|
||||
|
|
672
src/physics.rs
672
src/physics.rs
|
@ -2,33 +2,87 @@ use num_traits::clamp;
|
|||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Condition, Direction, Flag, Rect};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::stage::Stage;
|
||||
|
||||
// -2 -1 0 1 2 3
|
||||
// +------------------
|
||||
// -2 | 26 32 33 34 35 36
|
||||
// -1 | 27 10 14 15 16 18
|
||||
// 0 | 28 11 1 2 5 19
|
||||
// 1 | 29 12 3 4 6 20
|
||||
// 2 | 30 13 8 9 7 21
|
||||
// 3 | 31 22 23 24 25 17
|
||||
// -3 -2 -1 0 1 2 3 4
|
||||
// +------------------------
|
||||
// -3 | 37 44 45 46 47 48 49 50
|
||||
// -2 | 38 26 32 33 34 35 36 51
|
||||
// -1 | 39 27 10 14 15 16 18 52
|
||||
// 0 | 40 28 11 1 2 5 19 53
|
||||
// 1 | 41 29 12 3 4 6 20 54
|
||||
// 2 | 42 30 13 8 9 7 21 55
|
||||
// 3 | 43 31 22 23 24 25 17 56
|
||||
// 4 | 57 58 59 60 61 62 63 64
|
||||
|
||||
pub const OFF_X: [i32; 36] = [
|
||||
0, 1, 0, 1, 2, 2,
|
||||
2, 0, 1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 3,
|
||||
3, 3, 3, -1, 0, 1,
|
||||
2, -2, -2, -2, -2, -2,
|
||||
-2, -1, 0, 1, 2, 3 ];
|
||||
pub const OFF_Y: [i32; 36] = [
|
||||
0, 0, 1, 1, 0, 1,
|
||||
2, 2, 2, -1, 0, 1,
|
||||
2, -1, -1, -1, 3, -1,
|
||||
0, 1, 2, 3, 3, 3,
|
||||
3, -2, -1, 0, 1, 2,
|
||||
3, -2, -2, -2, -2, -2 ];
|
||||
pub const OFFSETS: [(i32, i32); 64] = [
|
||||
(0, 0),
|
||||
(1, 0),
|
||||
(0, 1),
|
||||
(1, 1),
|
||||
(2, 0),
|
||||
(2, 1),
|
||||
(2, 2),
|
||||
(0, 2),
|
||||
(1, 2),
|
||||
(-1, -1),
|
||||
(-1, 0),
|
||||
(-1, 1),
|
||||
(-1, 2),
|
||||
(0, -1),
|
||||
(1, -1),
|
||||
(2, -1),
|
||||
(3, 3),
|
||||
(3, -1),
|
||||
(3, 0),
|
||||
(3, 1),
|
||||
(3, 2),
|
||||
(-1, 3),
|
||||
(0, 3),
|
||||
(1, 3),
|
||||
(2, 3),
|
||||
(-2, -2),
|
||||
(-2, -1),
|
||||
(-2, 0),
|
||||
(-2, 1),
|
||||
(-2, 2),
|
||||
(-2, 3),
|
||||
(-1, -2),
|
||||
(0, -2),
|
||||
(1, -2),
|
||||
(2, -2),
|
||||
(3, -2),
|
||||
(-3, -3),
|
||||
(-3, -2),
|
||||
(-3, -1),
|
||||
(-3, 0),
|
||||
(-3, 1),
|
||||
(-3, 2),
|
||||
(-3, 3),
|
||||
(-2, -3),
|
||||
(-1, -3),
|
||||
(0, -3),
|
||||
(1, -3),
|
||||
(2, -3),
|
||||
(3, -3),
|
||||
(4, -3),
|
||||
(4, -2),
|
||||
(4, -1),
|
||||
(4, 0),
|
||||
(4, 1),
|
||||
(4, 2),
|
||||
(4, 3),
|
||||
(-3, 4),
|
||||
(-2, 4),
|
||||
(-1, 4),
|
||||
(0, 4),
|
||||
(1, 4),
|
||||
(2, 4),
|
||||
(3, 4),
|
||||
(4, 4),
|
||||
];
|
||||
|
||||
pub trait PhysicalEntity {
|
||||
fn x(&self) -> i32;
|
||||
|
@ -37,10 +91,15 @@ pub trait PhysicalEntity {
|
|||
fn vel_y(&self) -> i32;
|
||||
|
||||
fn hit_rect_size(&self) -> usize;
|
||||
fn offset_x(&self) -> i32 { 0 }
|
||||
fn offset_y(&self) -> i32 { 0 }
|
||||
fn offset_x(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
fn offset_y(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn hit_bounds(&self) -> &Rect<u32>;
|
||||
fn display_bounds(&self) -> &Rect<u32>;
|
||||
|
||||
fn set_x(&mut self, x: i32);
|
||||
fn set_y(&mut self, y: i32);
|
||||
|
@ -52,21 +111,28 @@ pub trait PhysicalEntity {
|
|||
|
||||
fn direction(&self) -> Direction;
|
||||
fn is_player(&self) -> bool;
|
||||
fn ignore_tile_44(&self) -> bool { true }
|
||||
fn player_left_pressed(&self) -> bool { false }
|
||||
fn player_right_pressed(&self) -> bool { false }
|
||||
fn ignore_tile_44(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn player_left_pressed(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn player_right_pressed(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn test_block_hit(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
let bounds_x = if self.is_player() { 0x600 } else { 0x600 };
|
||||
let bounds_top = if self.is_player() { 0x800 } else { 0x600 };
|
||||
let bounds_bottom = if self.is_player() { 0x800 } else { 0x600 };
|
||||
let half_tile_size = 16 * 0x100;
|
||||
let half_tile_size = state.tile_size.as_int() * 0x100;
|
||||
|
||||
// left wall
|
||||
if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * half_tile_size - bounds_top)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size + bounds_bottom)
|
||||
&& (self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size
|
||||
&& (self.x() - self.hit_bounds().right as i32) > (x * 2) * half_tile_size {
|
||||
&& (self.x() - self.hit_bounds().right as i32) > (x * 2) * half_tile_size
|
||||
{
|
||||
self.set_x(((x * 2 + 1) * half_tile_size) + self.hit_bounds().right as i32);
|
||||
|
||||
if self.is_player() {
|
||||
|
@ -86,7 +152,8 @@ pub trait PhysicalEntity {
|
|||
if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * half_tile_size - bounds_top)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size + bounds_bottom)
|
||||
&& (self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size
|
||||
&& (self.x() + self.hit_bounds().right as i32) < (x * 2) * half_tile_size {
|
||||
&& (self.x() + self.hit_bounds().right as i32) < (x * 2) * half_tile_size
|
||||
{
|
||||
self.set_x(((x * 2 - 1) * half_tile_size) - self.hit_bounds().right as i32);
|
||||
|
||||
if self.is_player() {
|
||||
|
@ -106,14 +173,25 @@ pub trait PhysicalEntity {
|
|||
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size - bounds_x)
|
||||
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + bounds_x)
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) > (y * 2) * half_tile_size {
|
||||
&& (self.y() - self.hit_bounds().top as i32) > (y * 2) * half_tile_size
|
||||
{
|
||||
self.set_y(((y * 2 + 1) * half_tile_size) + self.hit_bounds().top as i32);
|
||||
|
||||
if self.is_player() {
|
||||
if !self.cond().hidden() && self.vel_y() < -0x200 {
|
||||
state.sound_manager.play_sfx(3);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if self.vel_y() < 0 {
|
||||
|
@ -130,7 +208,34 @@ pub trait PhysicalEntity {
|
|||
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size - bounds_x)
|
||||
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + bounds_x)
|
||||
&& ((self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size))
|
||||
&& ((self.y() + self.hit_bounds().bottom as i32) < (y * 2) * half_tile_size) {
|
||||
&& ((self.y() + self.hit_bounds().bottom as i32) < (y * 2) * half_tile_size)
|
||||
{
|
||||
self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32);
|
||||
|
||||
if self.is_player() {
|
||||
if self.vel_y() > 0x400 {
|
||||
state.sound_manager.play_sfx(23);
|
||||
}
|
||||
|
||||
if self.vel_y() > 0 {
|
||||
self.set_vel_y(0);
|
||||
}
|
||||
} else {
|
||||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_bottom_wall(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_platform_hit(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
let half_tile_size = state.tile_size.as_int() * 0x100;
|
||||
|
||||
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size)
|
||||
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size)
|
||||
&& ((self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size))
|
||||
&& ((self.y() + self.hit_bounds().bottom as i32) < (y * 2 - 1) * half_tile_size + 0x400)
|
||||
{
|
||||
self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32);
|
||||
|
||||
if self.is_player() {
|
||||
|
@ -151,16 +256,34 @@ pub trait PhysicalEntity {
|
|||
|
||||
// upper left slope (bigger half)
|
||||
fn test_hit_upper_left_slope_high(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
if self.x() < (x * 16 + 8) * 0x200
|
||||
&& self.x() > (x * 16 - 8) * 0x200
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 0x2000) - (self.x() - x * 0x2000) / 2 + 0x800
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 16 - 8) * 0x200 {
|
||||
self.set_y((y * 0x2000) - ((self.x() - x * 0x2000) / 2) + 0x800 + self.hit_bounds().top as i32);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if self.x() < (x * 2 + 1) * half_tile_size
|
||||
&& self.x() > (x * 2 - 1) * half_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32)
|
||||
< (y * tile_size) - (self.x() - x * tile_size) / 2 + quarter_tile_size
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) - ((self.x() - x * tile_size) / 2) + quarter_tile_size + self.hit_bounds().top as i32,
|
||||
);
|
||||
|
||||
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
|
||||
state.sound_manager.play_sfx(3);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if self.vel_y() < 0 {
|
||||
|
@ -173,16 +296,34 @@ pub trait PhysicalEntity {
|
|||
|
||||
// upper left slope (smaller half)
|
||||
fn test_hit_upper_left_slope_low(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
if self.x() < (x * 16 + 8) * 0x200
|
||||
&& self.x() > (x * 16 - 8) * 0x200
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 0x2000) - (self.x() - x * 0x2000) / 2 - 0x800
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 16 - 8) * 0x200 {
|
||||
self.set_y((y * 0x2000) - ((self.x() - x * 0x2000) / 2) - 0x800 + self.hit_bounds().top as i32);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if self.x() < (x * 2 + 1) * half_tile_size
|
||||
&& self.x() > (x * 2 - 1) * half_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32)
|
||||
< (y * tile_size) - (self.x() - x * tile_size) / 2 - quarter_tile_size
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) - ((self.x() - x * tile_size) / 2) - quarter_tile_size + self.hit_bounds().top as i32,
|
||||
);
|
||||
|
||||
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
|
||||
state.sound_manager.play_sfx(3);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if self.vel_y() < 0 {
|
||||
|
@ -195,16 +336,34 @@ pub trait PhysicalEntity {
|
|||
|
||||
// upper right slope (smaller half)
|
||||
fn test_hit_upper_right_slope_low(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
if self.x() < (x * 16 + 8) * 0x200
|
||||
&& self.x() > (x * 16 - 8) * 0x200
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 0x2000) + (self.x() - x * 0x2000) / 2 - 0x800
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 16 - 8) * 0x200 {
|
||||
self.set_y((y * 0x2000) + ((self.x() - x * 0x2000) / 2) - 0x800 + self.hit_bounds().top as i32);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if self.x() < (x * 2 + 1) * half_tile_size
|
||||
&& self.x() > (x * 2 - 1) * half_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32)
|
||||
< (y * tile_size) + (self.x() - x * tile_size) / 2 - quarter_tile_size
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) + ((self.x() - x * tile_size) / 2) - quarter_tile_size + self.hit_bounds().top as i32,
|
||||
);
|
||||
|
||||
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
|
||||
state.sound_manager.play_sfx(3);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if self.vel_y() < 0 {
|
||||
|
@ -217,16 +376,34 @@ pub trait PhysicalEntity {
|
|||
|
||||
// upper right slope (bigger half)
|
||||
fn test_hit_upper_right_slope_high(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
if (self.x() < (x * 16 + 8) * 0x200)
|
||||
&& (self.x() > (x * 16 - 8) * 0x200)
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 0x2000) + (self.x() - x * 0x2000) / 2 + 0x800
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 16 - 8) * 0x200 {
|
||||
self.set_y((y * 0x2000) + ((self.x() - x * 0x2000) / 2) + 0x800 + self.hit_bounds().top as i32);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if self.x() < (x * 2 + 1) * half_tile_size
|
||||
&& self.x() > (x * 2 - 1) * half_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32)
|
||||
< (y * tile_size) + (self.x() - x * tile_size) / 2 + quarter_tile_size
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) + ((self.x() - x * tile_size) / 2) + quarter_tile_size + self.hit_bounds().top as i32,
|
||||
);
|
||||
|
||||
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
|
||||
state.sound_manager.play_sfx(3);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(self.x(), self.y() - self.hit_bounds().top as i32, CaretType::LittleParticles, Direction::Left);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if self.vel_y() < 0 {
|
||||
|
@ -239,13 +416,21 @@ pub trait PhysicalEntity {
|
|||
|
||||
// lower left half (bigger)
|
||||
fn test_hit_lower_left_slope_high(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
self.flags().set_hit_left_higher_half(true);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if (self.x() < (x * 16 + 8) * 0x200)
|
||||
&& (self.x() > (x * 16 - 8) * 0x200)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 0x2000) + (self.x() - x * 0x2000) / 2 - 0x800
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 16 + 8) * 0x200 {
|
||||
self.set_y((y * 0x2000) + ((self.x() - x * 0x2000) / 2) - 0x800 - self.hit_bounds().bottom as i32);
|
||||
if self.x() < (x * 2 + 1) * half_tile_size
|
||||
&& self.x() > (x * 2 - 1) * half_tile_size
|
||||
&& (self.y() + self.hit_bounds().bottom as i32)
|
||||
> (y * tile_size) + (self.x() - x * tile_size) / 2 - quarter_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) + ((self.x() - x * tile_size) / 2)
|
||||
- quarter_tile_size
|
||||
- self.hit_bounds().bottom as i32,
|
||||
);
|
||||
|
||||
if self.is_player() && self.vel_y() > 0x400 {
|
||||
state.sound_manager.play_sfx(23);
|
||||
|
@ -255,6 +440,7 @@ pub trait PhysicalEntity {
|
|||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_left_higher_half(true);
|
||||
self.flags().set_hit_left_slope(true);
|
||||
self.flags().set_hit_bottom_wall(true);
|
||||
}
|
||||
|
@ -262,13 +448,20 @@ pub trait PhysicalEntity {
|
|||
|
||||
// lower left half (smaller)
|
||||
fn test_hit_lower_left_slope_low(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
self.flags().set_hit_left_lower_half(true);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if (self.x() < (x * 16 + 8) * 0x200)
|
||||
&& (self.x() > (x * 16 - 8) * 0x200)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 0x2000) + (self.x() - x * 0x2000) / 2 + 0x800
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 16 + 8) * 0x200 {
|
||||
self.set_y((y * 0x2000) + ((self.x() - x * 0x2000) / 2) + 0x800 - self.hit_bounds().bottom as i32);
|
||||
if (self.x() < (x * 2 + 1) * half_tile_size)
|
||||
&& (self.x() > (x * 2 - 1) * half_tile_size)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32)
|
||||
> (y * tile_size) + (self.x() - x * tile_size) / 2 + quarter_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) + ((self.x() - x * tile_size) / 2) + quarter_tile_size
|
||||
- self.hit_bounds().bottom as i32,
|
||||
);
|
||||
|
||||
if self.is_player() && self.vel_y() > 0x400 {
|
||||
state.sound_manager.play_sfx(23);
|
||||
|
@ -278,6 +471,7 @@ pub trait PhysicalEntity {
|
|||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_left_lower_half(true);
|
||||
self.flags().set_hit_left_slope(true);
|
||||
self.flags().set_hit_bottom_wall(true);
|
||||
}
|
||||
|
@ -285,13 +479,20 @@ pub trait PhysicalEntity {
|
|||
|
||||
// lower right half (smaller)
|
||||
fn test_hit_lower_right_slope_low(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
self.flags().set_hit_right_lower_half(true);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if (self.x() < (x * 16 + 8) * 0x200)
|
||||
&& (self.x() > (x * 16 - 8) * 0x200)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 0x2000) - (self.x() - x * 0x2000) / 2 + 0x800
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 16 + 8) * 0x200 {
|
||||
self.set_y((y * 0x2000) - ((self.x() - x * 0x2000) / 2) + 0x800 - self.hit_bounds().bottom as i32);
|
||||
if (self.x() < (x * 2 + 1) * half_tile_size)
|
||||
&& (self.x() > (x * 2 - 1) * half_tile_size)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32)
|
||||
> (y * tile_size) - (self.x() - x * tile_size) / 2 + quarter_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) - ((self.x() - x * tile_size) / 2) + quarter_tile_size
|
||||
- self.hit_bounds().bottom as i32,
|
||||
);
|
||||
|
||||
if self.is_player() && self.vel_y() > 0x400 {
|
||||
state.sound_manager.play_sfx(23);
|
||||
|
@ -301,6 +502,7 @@ pub trait PhysicalEntity {
|
|||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_right_lower_half(true);
|
||||
self.flags().set_hit_right_slope(true);
|
||||
self.flags().set_hit_bottom_wall(true);
|
||||
}
|
||||
|
@ -308,13 +510,22 @@ pub trait PhysicalEntity {
|
|||
|
||||
// lower right half (bigger)
|
||||
fn test_hit_lower_right_slope_high(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
self.flags().set_hit_right_higher_half(true);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if (self.x() < (x * 16 + 8) * 0x200)
|
||||
&& (self.x() > (x * 16 - 8) * 0x200)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 0x2000) - (self.x() - x * 0x2000) / 2 - 0x800
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 16 + 8) * 0x200 {
|
||||
self.set_y((y * 0x2000) - ((self.x() - x * 0x2000) / 2) - 0x800 - self.hit_bounds().bottom as i32);
|
||||
if (self.x() < (x * 2 + 1) * half_tile_size)
|
||||
&& (self.x() > (x * 2 - 1) * half_tile_size)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32)
|
||||
> (y * tile_size) - (self.x() - x * tile_size) / 2 - quarter_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size)
|
||||
- ((self.x() - x * tile_size) / 2)
|
||||
- quarter_tile_size
|
||||
- self.hit_bounds().bottom as i32,
|
||||
);
|
||||
|
||||
if self.is_player() && self.vel_y() > 0x400 {
|
||||
state.sound_manager.play_sfx(23);
|
||||
|
@ -324,28 +535,166 @@ pub trait PhysicalEntity {
|
|||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_right_higher_half(true);
|
||||
self.flags().set_hit_right_slope(true);
|
||||
self.flags().set_hit_bottom_wall(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_hit_water(&mut self, x: i32, y: i32) {
|
||||
let bounds_x = if self.is_player() { 5 } else { 6 };
|
||||
let bounds_up = if self.is_player() { 5 } else { 6 };
|
||||
let bounds_down = if self.is_player() { 0 } else { 6 };
|
||||
if (self.x() - self.hit_bounds().right as i32) < (x * 16 + bounds_x) * 0x200
|
||||
&& (self.x() + self.hit_bounds().right as i32) > (x * 16 - bounds_x) * 0x200
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 16 + bounds_up) * 0x200
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 16 - bounds_down) * 0x200 {
|
||||
// upper left slope
|
||||
fn test_hit_upper_left_slope(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
|
||||
if self.x() < (x * 2 + 1) * half_tile_size
|
||||
&& self.x() > (x * 2 - 1) * half_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * tile_size) - (self.x() - x * tile_size)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) - (self.x() - x * tile_size) + self.hit_bounds().top as i32
|
||||
);
|
||||
|
||||
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
|
||||
state.sound_manager.play_sfx(3);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if self.vel_y() < 0 {
|
||||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_top_wall(true);
|
||||
}
|
||||
}
|
||||
|
||||
// upper right slope
|
||||
fn test_hit_upper_right_slope(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
|
||||
if self.x() < (x * 2 + 1) * half_tile_size
|
||||
&& self.x() > (x * 2 - 1) * half_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * tile_size) + (self.x() - x * tile_size)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
|
||||
{
|
||||
self.set_y(
|
||||
(y * tile_size) + (self.x() - x * tile_size) + self.hit_bounds().top as i32
|
||||
);
|
||||
|
||||
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
|
||||
state.sound_manager.play_sfx(3);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
state.create_caret(
|
||||
self.x(),
|
||||
self.y() - self.hit_bounds().top as i32,
|
||||
CaretType::LittleParticles,
|
||||
Direction::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if self.vel_y() < 0 {
|
||||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_top_wall(true);
|
||||
}
|
||||
}
|
||||
|
||||
// lower left slope
|
||||
fn test_hit_lower_left_slope(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if self.x() < (x * 2 + 1) * half_tile_size
|
||||
&& self.x() > (x * 2 - 1) * half_tile_size
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * tile_size) + (self.x() - x * tile_size) - quarter_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
|
||||
{
|
||||
self.set_y((y * tile_size) + (self.x() - x * tile_size) - quarter_tile_size - self.hit_bounds().bottom as i32);
|
||||
|
||||
if self.is_player() && self.vel_y() > 0x400 {
|
||||
state.sound_manager.play_sfx(23);
|
||||
}
|
||||
|
||||
if self.vel_y() > 0 {
|
||||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_left_higher_half(true);
|
||||
self.flags().set_hit_left_slope(true);
|
||||
self.flags().set_hit_bottom_wall(true);
|
||||
}
|
||||
}
|
||||
|
||||
// lower right slope
|
||||
fn test_hit_lower_right_slope(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let half_tile_size = tile_size / 2;
|
||||
let quarter_tile_size = half_tile_size / 2;
|
||||
|
||||
if (self.x() < (x * 2 + 1) * half_tile_size)
|
||||
&& (self.x() > (x * 2 - 1) * half_tile_size)
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * tile_size) - (self.x() - x * tile_size) - quarter_tile_size
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
|
||||
{
|
||||
self.set_y((y * tile_size) - (self.x() - x * tile_size) - quarter_tile_size - self.hit_bounds().bottom as i32);
|
||||
|
||||
if self.is_player() && self.vel_y() > 0x400 {
|
||||
state.sound_manager.play_sfx(23);
|
||||
}
|
||||
|
||||
if self.vel_y() > 0 {
|
||||
self.set_vel_y(0);
|
||||
}
|
||||
|
||||
self.flags().set_hit_right_higher_half(true);
|
||||
self.flags().set_hit_right_slope(true);
|
||||
self.flags().set_hit_bottom_wall(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_hit_water(&mut self, state: &SharedGameState, x: i32, y: i32) {
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let mult = tile_size / 16;
|
||||
let bounds_x = if self.is_player() { 5 } else { 6 } * mult;
|
||||
let bounds_up = if self.is_player() { 5 } else { 6 } * mult;
|
||||
let bounds_down = if self.is_player() { 0 } else { 6 } * mult;
|
||||
|
||||
if (self.x() - self.hit_bounds().right as i32) < x * tile_size + bounds_x
|
||||
&& (self.x() + self.hit_bounds().right as i32) > x * tile_size - bounds_x
|
||||
&& (self.y() - self.hit_bounds().top as i32) < y * tile_size + bounds_up
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > y * tile_size - bounds_down
|
||||
{
|
||||
self.flags().set_in_water(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_hit_spike(&mut self, x: i32, y: i32, water: bool) {
|
||||
if (self.x() - 0x800) < (x * 16 + 4) * 0x200
|
||||
&& (self.x() + 0x800) > (x * 16 - 4) * 0x200
|
||||
&& (self.y() - 0x800) < (y * 16 + 3) * 0x200
|
||||
&& (self.y() + 0x800) > (y * 16 - 3) * 0x200 {
|
||||
fn test_hit_spike(&mut self, state: &SharedGameState, x: i32, y: i32, water: bool) {
|
||||
let mult = state.tile_size.as_int() * 0x200 / 16;
|
||||
|
||||
if (self.x() - 0x800) < (x * 16 + 4) * mult
|
||||
&& (self.x() + 0x800) > (x * 16 - 4) * mult
|
||||
&& (self.y() - 0x800) < (y * 16 + 3) * mult
|
||||
&& (self.y() + 0x800) > (y * 16 - 3) * mult
|
||||
{
|
||||
self.flags().set_hit_by_spike(true);
|
||||
if water {
|
||||
self.flags().set_in_water(true);
|
||||
|
@ -353,11 +702,14 @@ pub trait PhysicalEntity {
|
|||
}
|
||||
}
|
||||
|
||||
fn test_hit_force(&mut self, x: i32, y: i32, direction: Direction, water: bool) {
|
||||
if (self.x() - self.hit_bounds().left as i32) < (x * 16 + 6) * 0x200
|
||||
&& (self.x() + self.hit_bounds().right as i32) > (x * 16 - 6) * 0x200
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 16 + 6) * 0x200
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 16 - 6) * 0x200 {
|
||||
fn test_hit_force(&mut self, state: &SharedGameState, x: i32, y: i32, direction: Direction, water: bool) {
|
||||
let mult = state.tile_size.as_int() * 0x200 / 16;
|
||||
|
||||
if (self.x() - self.hit_bounds().left as i32) < (x * 16 + 6) * mult
|
||||
&& (self.x() + self.hit_bounds().right as i32) > (x * 16 - 6) * mult
|
||||
&& (self.y() - self.hit_bounds().top as i32) < (y * 16 + 6) * mult
|
||||
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 16 - 6) * mult
|
||||
{
|
||||
match direction {
|
||||
Direction::Left => self.flags().set_force_left(true),
|
||||
Direction::Up => self.flags().set_force_up(true),
|
||||
|
@ -373,14 +725,19 @@ pub trait PhysicalEntity {
|
|||
}
|
||||
|
||||
fn tick_map_collisions(&mut self, state: &mut SharedGameState, _npc_list: &NPCList, stage: &mut Stage) {
|
||||
let hit_rect_size = clamp(self.hit_rect_size(), 1, 4);
|
||||
let hit_rect_size = hit_rect_size * hit_rect_size;
|
||||
let hit_rect_size = self.hit_rect_size().clamp(1, 4);
|
||||
let hit_rect_size = if state.tile_size == TileSize::Tile8x8 {
|
||||
4 * hit_rect_size * hit_rect_size
|
||||
} else {
|
||||
hit_rect_size * hit_rect_size
|
||||
};
|
||||
|
||||
let x = (self.x() + self.offset_x()) / (0x2000);
|
||||
let y = (self.y() + self.offset_y()) / (0x2000);
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let x = (self.x() + self.offset_x()) / tile_size;
|
||||
let y = (self.y() + self.offset_y()) / tile_size;
|
||||
|
||||
self.flags().0 = 0;
|
||||
for (idx, (&ox, &oy)) in OFF_X.iter().zip(OFF_Y.iter()).enumerate() {
|
||||
for (idx, &(ox, oy)) in OFFSETS.iter().enumerate() {
|
||||
if idx == hit_rect_size {
|
||||
break;
|
||||
}
|
||||
|
@ -389,23 +746,23 @@ pub trait PhysicalEntity {
|
|||
match attrib {
|
||||
// Spikes
|
||||
0x62 | 0x42 if self.is_player() => {
|
||||
self.test_hit_spike(x + ox, y + oy, attrib & 0x20 != 0);
|
||||
self.test_hit_spike(state, x + ox, y + oy, attrib & 0x20 != 0);
|
||||
}
|
||||
|
||||
// Blocks
|
||||
0x02 | 0x60 => {
|
||||
self.test_hit_water(x + ox, y + oy);
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
0x62 if !self.is_player() => {
|
||||
self.test_hit_water(x + ox, y + oy);
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
0x61 => {
|
||||
self.test_block_hit(state, x + ox, y + oy);
|
||||
self.test_hit_water(x + ox, y + oy);
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
0x04 | 0x64 if !self.is_player() => {
|
||||
self.test_block_hit(state, x + ox, y + oy);
|
||||
self.test_hit_water(x + ox, y + oy);
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
0x05 | 0x41 | 0x43 | 0x46 if self.is_player() => {
|
||||
self.test_block_hit(state, x + ox, y + oy);
|
||||
|
@ -418,69 +775,120 @@ pub trait PhysicalEntity {
|
|||
self.test_block_hit(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x4a => {
|
||||
self.test_platform_hit(state, x + ox, y + oy);
|
||||
}
|
||||
|
||||
// Slopes
|
||||
0x50 | 0x70 => {
|
||||
self.test_hit_upper_left_slope_high(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 { self.test_hit_water(x + ox, y + oy); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x51 | 0x71 => {
|
||||
self.test_hit_upper_left_slope_low(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 { self.test_hit_water(x + ox, y + oy); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x52 | 0x72 => {
|
||||
self.test_hit_upper_right_slope_low(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 { self.test_hit_water(x + ox, y + oy); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x53 | 0x73 => {
|
||||
self.test_hit_upper_right_slope_high(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 { self.test_hit_water(x + ox, y + oy); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x54 | 0x74 => {
|
||||
self.test_hit_lower_left_slope_high(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 { self.test_hit_water(x + ox, y + oy); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x55 | 0x75 => {
|
||||
self.test_hit_lower_left_slope_low(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 { self.test_hit_water(x + ox, y + oy); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x56 | 0x76 => {
|
||||
self.test_hit_lower_right_slope_low(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 { self.test_hit_water(x + ox, y + oy); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x57 | 0x77 => {
|
||||
self.test_hit_lower_right_slope_high(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 { self.test_hit_water(x + ox, y + oy); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x5a | 0x7a => {
|
||||
self.test_hit_upper_left_slope(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x5b | 0x7b => {
|
||||
self.test_hit_upper_right_slope(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x5c | 0x7c => {
|
||||
self.test_hit_lower_left_slope(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
0x5d | 0x7d => {
|
||||
self.test_hit_lower_right_slope(state, x + ox, y + oy);
|
||||
if attrib & 0x20 != 0 {
|
||||
self.test_hit_water(state, x + ox, y + oy);
|
||||
}
|
||||
}
|
||||
|
||||
// Forces
|
||||
0x80 | 0xa0 if self.is_player() => {
|
||||
self.test_hit_force(x + ox, y + oy, Direction::Left, attrib & 0x20 != 0);
|
||||
self.test_hit_force(state, x + ox, y + oy, Direction::Left, attrib & 0x20 != 0);
|
||||
}
|
||||
0x81 | 0xa1 if self.is_player() => {
|
||||
self.test_hit_force(x + ox, y + oy, Direction::Up, attrib & 0x20 != 0);
|
||||
self.test_hit_force(state, x + ox, y + oy, Direction::Up, attrib & 0x20 != 0);
|
||||
}
|
||||
0x82 | 0xa2 if self.is_player() => {
|
||||
self.test_hit_force(x + ox, y + oy, Direction::Right, attrib & 0x20 != 0);
|
||||
self.test_hit_force(state, x + ox, y + oy, Direction::Right, attrib & 0x20 != 0);
|
||||
}
|
||||
0x83 | 0xa3 if self.is_player() => {
|
||||
self.test_hit_force(x + ox, y + oy, Direction::Bottom, attrib & 0x20 != 0);
|
||||
self.test_hit_force(state, x + ox, y + oy, Direction::Bottom, attrib & 0x20 != 0);
|
||||
}
|
||||
0x80 | 0xa0 if !self.is_player() => {
|
||||
self.flags().set_force_left(true);
|
||||
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.flags().set_in_water(true);
|
||||
}
|
||||
}
|
||||
0x81 | 0xa1 if !self.is_player() => {
|
||||
self.flags().set_force_up(true);
|
||||
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.flags().set_in_water(true);
|
||||
}
|
||||
}
|
||||
0x82 | 0xa2 if !self.is_player() => {
|
||||
self.flags().set_force_right(true);
|
||||
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.flags().set_in_water(true);
|
||||
}
|
||||
}
|
||||
0x83 | 0xa3 if !self.is_player() => {
|
||||
self.flags().set_force_down(true);
|
||||
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
|
||||
if attrib & 0x20 != 0 {
|
||||
self.flags().set_in_water(true);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ pub struct Player {
|
|||
pub vel_y: i32,
|
||||
pub target_x: i32,
|
||||
pub target_y: i32,
|
||||
pub camera_target_x: i32,
|
||||
pub camera_target_y: i32,
|
||||
pub prev_x: i32,
|
||||
pub prev_y: i32,
|
||||
pub life: u16,
|
||||
|
@ -75,8 +77,6 @@ pub struct Player {
|
|||
pub controller: Box<dyn PlayerController>,
|
||||
pub popup: NumberPopup,
|
||||
weapon_offset_y: i8,
|
||||
camera_target_x: i32,
|
||||
camera_target_y: i32,
|
||||
splash: bool,
|
||||
tick: u8,
|
||||
booster_switch: u8,
|
||||
|
@ -100,6 +100,8 @@ impl Player {
|
|||
vel_y: 0,
|
||||
target_x: 0,
|
||||
target_y: 0,
|
||||
camera_target_x: 0,
|
||||
camera_target_y: 0,
|
||||
prev_x: 0,
|
||||
prev_y: 0,
|
||||
life: constants.player.life,
|
||||
|
@ -113,8 +115,6 @@ impl Player {
|
|||
control_mode: constants.player.control_mode,
|
||||
question: false,
|
||||
booster_fuel: 0,
|
||||
camera_target_x: 0,
|
||||
camera_target_y: 0,
|
||||
splash: false,
|
||||
up: false,
|
||||
down: false,
|
||||
|
@ -801,18 +801,20 @@ impl GameEntity<&NPCList> for Player {
|
|||
|
||||
if self.current_weapon != 0 {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Arms")?;
|
||||
let (gun_off_x, gun_off_y) = self.skin.get_gun_offset();
|
||||
|
||||
batch.add_rect(
|
||||
interpolate_fix9_scale(
|
||||
self.prev_x - self.display_bounds.left as i32,
|
||||
self.x - self.display_bounds.left as i32,
|
||||
state.frame_time,
|
||||
) + if self.direction == Direction::Left { -8.0 } else { 0.0 }
|
||||
) + if self.direction == Direction::Left { -8.0 - gun_off_x as f32 } else { gun_off_x as f32 }
|
||||
- frame_x,
|
||||
interpolate_fix9_scale(
|
||||
self.prev_y - self.display_bounds.top as i32,
|
||||
self.y - self.display_bounds.top as i32,
|
||||
state.frame_time,
|
||||
) + self.weapon_offset_y as f32
|
||||
) + self.weapon_offset_y as f32 + gun_off_y as f32
|
||||
- frame_y,
|
||||
&self.weapon_rect,
|
||||
);
|
||||
|
|
|
@ -47,6 +47,11 @@ impl PhysicalEntity for Player {
|
|||
&self.hit_bounds
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn display_bounds(&self) -> &Rect<u32> {
|
||||
&self.display_bounds
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_x(&mut self, x: i32) {
|
||||
self.x = x;
|
||||
|
|
|
@ -222,4 +222,8 @@ impl PlayerSkin for BasicPlayerSkin {
|
|||
bottom: ubox.bottom as u32 * 0x200,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_gun_offset(&self) -> (i32, i32) {
|
||||
(self.metadata.gun_offset_x as i32, self.metadata.gun_offset_y as i32)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,8 @@ pub trait PlayerSkin: PlayerSkinClone {
|
|||
|
||||
/// Returns display bounds of skin.
|
||||
fn get_display_bounds(&self) -> Rect<u32>;
|
||||
|
||||
fn get_gun_offset(&self) -> (i32, i32);
|
||||
}
|
||||
|
||||
pub trait PlayerSkinClone {
|
||||
|
|
|
@ -25,12 +25,12 @@ use crate::map::WaterParams;
|
|||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::{NPCLayer, NPC};
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::physics::{PhysicalEntity, OFFSETS};
|
||||
use crate::player::{Player, TargetPlayer};
|
||||
use crate::rng::XorShift;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::stage::{BackgroundType, Stage};
|
||||
use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
|
||||
use crate::texture_set::SizedBatch;
|
||||
|
@ -60,6 +60,8 @@ pub struct GameScene {
|
|||
pub intro_mode: bool,
|
||||
tex_background_name: String,
|
||||
tex_tileset_name: String,
|
||||
tex_tileset_name_mg: String,
|
||||
tex_tileset_name_bg: String,
|
||||
map_name_counter: u16,
|
||||
skip_counter: u16,
|
||||
inventory_dim: f32,
|
||||
|
@ -67,8 +69,8 @@ pub struct GameScene {
|
|||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum TileLayer {
|
||||
All,
|
||||
Background,
|
||||
Middleground,
|
||||
Foreground,
|
||||
Snack,
|
||||
}
|
||||
|
@ -97,7 +99,18 @@ impl GameScene {
|
|||
}
|
||||
|
||||
let tex_background_name = stage.data.background.filename();
|
||||
let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join("");
|
||||
let (tex_tileset_name, tex_tileset_name_mg, tex_tileset_name_bg) =
|
||||
if let Some(pxpack_data) = stage.data.pxpack_data.as_ref() {
|
||||
let t_fg = ["Stage/", &pxpack_data.tileset_fg].join("");
|
||||
let t_mg = ["Stage/", &pxpack_data.tileset_mg].join("");
|
||||
let t_bg = ["Stage/", &pxpack_data.tileset_bg].join("");
|
||||
|
||||
(t_fg, t_mg, t_bg)
|
||||
} else {
|
||||
let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join("");
|
||||
|
||||
(tex_tileset_name.clone(), tex_tileset_name.clone(), tex_tileset_name)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
tick: 0,
|
||||
|
@ -131,6 +144,8 @@ impl GameScene {
|
|||
intro_mode: false,
|
||||
tex_background_name,
|
||||
tex_tileset_name,
|
||||
tex_tileset_name_mg,
|
||||
tex_tileset_name_bg,
|
||||
map_name_counter: 0,
|
||||
skip_counter: 0,
|
||||
inventory_dim: 0.0,
|
||||
|
@ -161,7 +176,7 @@ impl GameScene {
|
|||
|
||||
match self.stage.data.background_type {
|
||||
BackgroundType::TiledStatic => {
|
||||
graphics::clear(ctx, Color::from_rgb(0, 0, 0));
|
||||
graphics::clear(ctx, self.stage.data.background_color);
|
||||
|
||||
let count_x = state.canvas_size.0 as usize / batch.width() + 1;
|
||||
let count_y = state.canvas_size.1 as usize / batch.height() + 1;
|
||||
|
@ -173,7 +188,7 @@ impl GameScene {
|
|||
}
|
||||
}
|
||||
BackgroundType::TiledParallax | BackgroundType::Tiled | BackgroundType::Waterway => {
|
||||
graphics::clear(ctx, Color::from_rgb(0, 0, 0));
|
||||
graphics::clear(ctx, self.stage.data.background_color);
|
||||
|
||||
let (off_x, off_y) = if self.stage.data.background_type == BackgroundType::Tiled {
|
||||
(frame_x % (batch.width() as f32), frame_y % (batch.height() as f32))
|
||||
|
@ -194,12 +209,14 @@ impl GameScene {
|
|||
}
|
||||
}
|
||||
BackgroundType::Water => {
|
||||
graphics::clear(ctx, Color::from_rgb(0, 0, 32));
|
||||
graphics::clear(ctx, self.stage.data.background_color);
|
||||
}
|
||||
BackgroundType::Black => {
|
||||
graphics::clear(ctx, Color::from_rgb(0, 0, 32));
|
||||
graphics::clear(ctx, self.stage.data.background_color);
|
||||
}
|
||||
BackgroundType::Scrolling => {
|
||||
graphics::clear(ctx, self.stage.data.background_color);
|
||||
}
|
||||
BackgroundType::Scrolling => {}
|
||||
BackgroundType::OutsideWind | BackgroundType::Outside | BackgroundType::OutsideUnknown => {
|
||||
graphics::clear(ctx, Color::from_rgb(0, 0, 0));
|
||||
|
||||
|
@ -575,11 +592,7 @@ impl GameScene {
|
|||
|
||||
let text_offset = if state.textscript_vm.face == 0 { 0.0 } else { 56.0 };
|
||||
|
||||
let lines = [
|
||||
&state.textscript_vm.line_1,
|
||||
&state.textscript_vm.line_2,
|
||||
&state.textscript_vm.line_3,
|
||||
];
|
||||
let lines = [&state.textscript_vm.line_1, &state.textscript_vm.line_2, &state.textscript_vm.line_3];
|
||||
|
||||
for (idx, line) in lines.iter().enumerate() {
|
||||
if !line.is_empty() {
|
||||
|
@ -652,6 +665,7 @@ impl GameScene {
|
|||
|
||||
fn draw_light_raycast(
|
||||
&self,
|
||||
tile_size: TileSize,
|
||||
world_point_x: i32,
|
||||
world_point_y: i32,
|
||||
(br, bg, bb): (u8, u8, u8),
|
||||
|
@ -665,6 +679,11 @@ impl GameScene {
|
|||
let fx2 = self.frame.x as f32 / 512.0;
|
||||
let fy2 = self.frame.y as f32 / 512.0;
|
||||
|
||||
let ti = tile_size.as_int();
|
||||
let tf = tile_size.as_float();
|
||||
let tih = ti / 2;
|
||||
let tfq = tf / 4.0;
|
||||
|
||||
'ray: for deg in angle {
|
||||
let d = deg as f32 * (std::f32::consts::PI / 180.0);
|
||||
let dx = d.cos() * -5.0;
|
||||
|
@ -681,56 +700,56 @@ impl GameScene {
|
|||
|
||||
const ARR: [(i32, i32); 4] = [(0, 0), (0, 1), (1, 0), (1, 1)];
|
||||
for (ox, oy) in ARR.iter() {
|
||||
let bx = x as i32 / 16 + *ox;
|
||||
let by = y as i32 / 16 + *oy;
|
||||
let bx = x as i32 / ti + *ox;
|
||||
let by = y as i32 / ti + *oy;
|
||||
|
||||
let tile = self.stage.map.attrib[self.stage.tile_at(bx as usize, by as usize) as usize];
|
||||
|
||||
if ((tile == 0x62 || tile == 0x41 || tile == 0x43 || tile == 0x46)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= (by * 16 - 8) as f32
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y >= (by * ti - tih) as f32
|
||||
&& y <= (by * ti + tih) as f32)
|
||||
|| ((tile == 0x50 || tile == 0x70)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y <= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 + 4.0)
|
||||
&& y >= (by * 16 - 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y <= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 + tfq)
|
||||
&& y >= (by * ti - tih) as f32)
|
||||
|| ((tile == 0x51 || tile == 0x71)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y <= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 - 4.0)
|
||||
&& y >= (by * 16 - 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y <= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 - tfq)
|
||||
&& y >= (by * ti - tih) as f32)
|
||||
|| ((tile == 0x52 || tile == 0x72)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y <= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 - 4.0)
|
||||
&& y >= (by * 16 - 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y <= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 - tfq)
|
||||
&& y >= (by * ti - tih) as f32)
|
||||
|| ((tile == 0x53 || tile == 0x73)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y <= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 + 4.0)
|
||||
&& y >= (by * 16 - 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y <= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 + tfq)
|
||||
&& y >= (by * ti - tih) as f32)
|
||||
|| ((tile == 0x54 || tile == 0x74)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 - 4.0)
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y >= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 - tfq)
|
||||
&& y <= (by * ti + tih) as f32)
|
||||
|| ((tile == 0x55 || tile == 0x75)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 + 4.0)
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y >= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 + tfq)
|
||||
&& y <= (by * ti + tih) as f32)
|
||||
|| ((tile == 0x56 || tile == 0x76)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 + 4.0)
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y >= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 + tfq)
|
||||
&& y <= (by * ti + tih) as f32)
|
||||
|| ((tile == 0x57 || tile == 0x77)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 - 4.0)
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
&& x >= (bx * ti - tih) as f32
|
||||
&& x <= (bx * ti + tih) as f32
|
||||
&& y >= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 - tfq)
|
||||
&& y <= (by * ti + tih) as f32)
|
||||
{
|
||||
continue 'ray;
|
||||
}
|
||||
|
@ -783,7 +802,7 @@ impl GameScene {
|
|||
_ => ((150u8, 150u8, 150u8), 7),
|
||||
};
|
||||
|
||||
self.draw_light_raycast(player.x, player.y, color, att, range, batch);
|
||||
self.draw_light_raycast(state.tile_size, player.x, player.y, color, att, range, batch);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1072,20 +1091,57 @@ impl GameScene {
|
|||
}
|
||||
|
||||
fn draw_tiles(&self, state: &mut SharedGameState, ctx: &mut Context, layer: TileLayer) -> GameResult {
|
||||
if state.tile_size == TileSize::Tile8x8 && layer == TileLayer::Snack {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tex = match layer {
|
||||
TileLayer::Snack => "Npc/NpcSym",
|
||||
_ => &self.tex_tileset_name,
|
||||
TileLayer::Background => &self.tex_tileset_name_bg,
|
||||
TileLayer::Middleground => &self.tex_tileset_name_mg,
|
||||
TileLayer::Foreground => &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 (frame_x, frame_y) = self.frame.xy_interpolated(state.frame_time);
|
||||
|
||||
let tile_start_x = (frame_x as i32 / 16).clamp(0, self.stage.map.width as i32) as usize;
|
||||
let tile_start_y = (frame_y as i32 / 16).clamp(0, self.stage.map.height as i32) as usize;
|
||||
let tile_end_x =
|
||||
((frame_x as i32 + 8 + state.canvas_size.0 as i32) / 16 + 1).clamp(0, self.stage.map.width as i32) as usize;
|
||||
let tile_end_y = ((frame_y as i32 + 8 + state.canvas_size.1 as i32) / 16 + 1)
|
||||
.clamp(0, self.stage.map.height as i32) as usize;
|
||||
let (layer_offset, layer_width, layer_height, uses_layers) = if let Some(pxpack_data) = self.stage.data.pxpack_data.as_ref() {
|
||||
match layer {
|
||||
TileLayer::Background => (pxpack_data.offset_bg as usize, pxpack_data.size_bg.0, pxpack_data.size_bg.1, true),
|
||||
TileLayer::Middleground => (pxpack_data.offset_mg as usize, pxpack_data.size_mg.0, pxpack_data.size_mg.1, true),
|
||||
_ => (0, pxpack_data.size_fg.0, pxpack_data.size_fg.1, true),
|
||||
}
|
||||
} else {
|
||||
(0, self.stage.map.width, self.stage.map.height, false)
|
||||
};
|
||||
|
||||
if !uses_layers && layer == TileLayer::Middleground {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tile_size = state.tile_size.as_int();
|
||||
let tile_sizef = state.tile_size.as_float();
|
||||
let halft = tile_size / 2;
|
||||
let halftf = tile_sizef / 2.0;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex)?;
|
||||
let mut rect = Rect::new(0, 0, tile_size as u16, tile_size as u16);
|
||||
let (mut frame_x, mut frame_y) = self.frame.xy_interpolated(state.frame_time);
|
||||
|
||||
if let Some(pxpack_data) = self.stage.data.pxpack_data.as_ref() {
|
||||
let (fx, fy) = match layer {
|
||||
TileLayer::Background => pxpack_data.scroll_bg.transform_camera_pos(frame_x, frame_y),
|
||||
TileLayer::Middleground => pxpack_data.scroll_mg.transform_camera_pos(frame_x, frame_y),
|
||||
_ => pxpack_data.scroll_fg.transform_camera_pos(frame_x, frame_y),
|
||||
};
|
||||
|
||||
frame_x = fx;
|
||||
frame_y = fy;
|
||||
}
|
||||
|
||||
let tile_start_x = (frame_x as i32 / tile_size).clamp(0, layer_width as i32) as usize;
|
||||
let tile_start_y = (frame_y as i32 / tile_size).clamp(0, layer_height as i32) as usize;
|
||||
let tile_end_x = ((frame_x as i32 + 8 + state.canvas_size.0 as i32) / tile_size + 1)
|
||||
.clamp(0, layer_width as i32) as usize;
|
||||
let tile_end_y = ((frame_y as i32 + halft + state.canvas_size.1 as i32) / tile_size + 1)
|
||||
.clamp(0, layer_height as i32) as usize;
|
||||
|
||||
if layer == TileLayer::Snack {
|
||||
rect = state.constants.world.snack_rect;
|
||||
|
@ -1093,18 +1149,27 @@ impl GameScene {
|
|||
|
||||
for y in tile_start_y..tile_end_y {
|
||||
for x in tile_start_x..tile_end_x {
|
||||
let tile = *self.stage.map.tiles.get((y * self.stage.map.width as usize) + x).unwrap();
|
||||
|
||||
let tile = *self.stage.map.tiles.get((y * layer_width as usize) + x + layer_offset).unwrap();
|
||||
match layer {
|
||||
_ if uses_layers => {
|
||||
if tile == 0 { continue; }
|
||||
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Background => {
|
||||
if self.stage.map.attrib[tile as usize] >= 0x20 {
|
||||
continue;
|
||||
}
|
||||
|
||||
rect.left = (tile as u16 % 16) * 16;
|
||||
rect.top = (tile as u16 / 16) * 16;
|
||||
rect.right = rect.left + 16;
|
||||
rect.bottom = rect.top + 16;
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Foreground => {
|
||||
let attr = self.stage.map.attrib[tile as usize];
|
||||
|
@ -1113,10 +1178,11 @@ impl GameScene {
|
|||
continue;
|
||||
}
|
||||
|
||||
rect.left = (tile as u16 % 16) * 16;
|
||||
rect.top = (tile as u16 / 16) * 16;
|
||||
rect.right = rect.left + 16;
|
||||
rect.bottom = rect.top + 16;
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Snack => {
|
||||
if self.stage.map.attrib[tile as usize] != 0x43 {
|
||||
|
@ -1126,7 +1192,11 @@ impl GameScene {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
batch.add_rect((x as f32 * 16.0 - 8.0) - frame_x, (y as f32 * 16.0 - 8.0) - frame_y, &rect);
|
||||
batch.add_rect(
|
||||
(x as f32 * tile_sizef - halftf) - frame_x,
|
||||
(y as f32 * tile_sizef - halftf) - frame_y,
|
||||
&rect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1479,47 +1549,59 @@ impl GameScene {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_debug_npc(&self, npc: &NPC, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as i32 * 0x200)
|
||||
|| npc.x > (self.frame.x + 128 + (state.canvas_size.0 as i32 + npc.display_bounds.width() as i32) * 0x200)
|
||||
&& npc.y < (self.frame.y - 128 - npc.display_bounds.height() as i32 * 0x200)
|
||||
|| npc.y > (self.frame.y + 128 + (state.canvas_size.1 as i32 + npc.display_bounds.height() as i32) * 0x200)
|
||||
fn draw_debug_object(&self, entity: &dyn PhysicalEntity, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if entity.x() < (self.frame.x - 128 - entity.display_bounds().width() as i32 * 0x200)
|
||||
|| entity.x() > (self.frame.x + 128 + (state.canvas_size.0 as i32 + entity.display_bounds().width() as i32) * 0x200)
|
||||
&& entity.y() < (self.frame.y - 128 - entity.display_bounds().height() as i32 * 0x200)
|
||||
|| entity.y() > (self.frame.y + 128 + (state.canvas_size.1 as i32 + entity.display_bounds().height() as i32) * 0x200)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
{
|
||||
let hit_rect_size = npc.hit_rect_size().clamp(1, 4);
|
||||
let hit_rect_size = hit_rect_size * hit_rect_size;
|
||||
let hit_rect_size = entity.hit_rect_size().clamp(1, 4);
|
||||
let hit_rect_size = if state.tile_size == TileSize::Tile8x8 {
|
||||
4 * hit_rect_size * hit_rect_size
|
||||
} else {
|
||||
hit_rect_size * hit_rect_size
|
||||
};
|
||||
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let x = (entity.x() + entity.offset_x()) / tile_size;
|
||||
let y = (entity.y() + entity.offset_y()) / tile_size;
|
||||
|
||||
let x = (npc.x + npc.offset_x()) / (0x2000);
|
||||
let y = (npc.y + npc.offset_y()) / (0x2000);
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?;
|
||||
|
||||
let caret_rect = Rect::new_size(2, 74, 4, 4);
|
||||
let caret2_rect = Rect::new_size(65, 9, 6, 6);
|
||||
const CARET_RECT: Rect<u16> = Rect { left:2, top: 74, right: 6, bottom: 78};
|
||||
const CARET2_RECT: Rect<u16> = Rect { left: 65, top: 9, right: 71, bottom: 15 };
|
||||
|
||||
for (idx, (&ox, &oy)) in crate::physics::OFF_X.iter().zip(crate::physics::OFF_Y.iter()).enumerate() {
|
||||
for (idx, &(ox, oy)) in OFFSETS.iter().enumerate() {
|
||||
if idx == hit_rect_size {
|
||||
break;
|
||||
}
|
||||
|
||||
batch.add_rect(
|
||||
((x + ox) * 16 - self.frame.x / 0x200) as f32 - 2.0,
|
||||
((y + oy) * 16 - self.frame.y / 0x200) as f32 - 2.0,
|
||||
&caret_rect,
|
||||
((x + ox) * tile_size - self.frame.x) as f32 / 512.0 - 2.0,
|
||||
((y + oy) * tile_size - self.frame.y) as f32 / 512.0 - 2.0,
|
||||
&CARET_RECT,
|
||||
);
|
||||
}
|
||||
|
||||
batch.add_rect(
|
||||
((npc.x - self.frame.x) / 0x200) as f32 - 3.0,
|
||||
((npc.y - self.frame.y) / 0x200) as f32 - 3.0,
|
||||
&caret2_rect,
|
||||
(entity.x() - self.frame.x) as f32 / 512.0 - 3.0,
|
||||
(entity.y() - self.frame.y) as f32 / 512.0 - 3.0,
|
||||
&CARET2_RECT,
|
||||
);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_debug_npc(&self, npc: &NPC, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
self.draw_debug_object(npc, state, ctx)?;
|
||||
|
||||
let text = format!("{}:{}:{}", npc.id, npc.npc_type, npc.action_num);
|
||||
state.font.draw_colored_text_scaled(
|
||||
text.chars(),
|
||||
|
@ -1544,6 +1626,8 @@ impl GameScene {
|
|||
self.draw_debug_npc(boss, state, ctx)?;
|
||||
}
|
||||
|
||||
self.draw_debug_object(&self.player1, state, ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1558,6 +1642,7 @@ impl Scene for GameScene {
|
|||
state.game_rng = XorShift::new(seed);
|
||||
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?);
|
||||
state.textscript_vm.suspend = false;
|
||||
state.tile_size = self.stage.map.tile_size;
|
||||
|
||||
self.player1.controller = state.settings.create_player1_controller();
|
||||
self.player2.controller = state.settings.create_player2_controller();
|
||||
|
@ -1566,7 +1651,7 @@ impl Scene for GameScene {
|
|||
for npc_data in npcs.iter() {
|
||||
log::info!("creating npc: {:?}", npc_data);
|
||||
|
||||
let mut npc = NPC::create_from_data(npc_data, &state.npc_table);
|
||||
let mut npc = NPC::create_from_data(npc_data, &state.npc_table, state.tile_size);
|
||||
if npc.npc_flags.appear_when_flag_set() {
|
||||
if state.get_flag(npc_data.flag_num as usize) {
|
||||
npc.cond.set_alive(true);
|
||||
|
@ -1597,8 +1682,14 @@ impl Scene for GameScene {
|
|||
self.boss.boss_type = self.stage.data.boss_no as u16;
|
||||
self.player1.target_x = self.player1.x;
|
||||
self.player1.target_y = self.player1.y;
|
||||
self.frame.target_x = self.player1.target_x;
|
||||
self.frame.target_y = self.player1.target_y;
|
||||
self.player1.camera_target_x = 0;
|
||||
self.player1.camera_target_y = 0;
|
||||
self.player2.target_x = self.player2.x;
|
||||
self.player2.target_y = self.player2.y;
|
||||
self.player2.camera_target_x = 0;
|
||||
self.player2.camera_target_y = 0;
|
||||
self.frame.target_x = self.player1.x;
|
||||
self.frame.target_y = self.player1.y;
|
||||
self.frame.immediate_update(state, &self.stage);
|
||||
|
||||
Ok(())
|
||||
|
@ -1745,6 +1836,8 @@ impl Scene for GameScene {
|
|||
self.draw_background(state, ctx)?;
|
||||
self.draw_tiles(state, ctx, TileLayer::Background)?;
|
||||
self.draw_npc_layer(state, ctx, NPCLayer::Background)?;
|
||||
self.draw_tiles(state, ctx, TileLayer::Middleground)?;
|
||||
|
||||
if state.settings.shader_effects
|
||||
&& self.stage.data.background_type != BackgroundType::Black
|
||||
&& self.stage.data.background_type != BackgroundType::Outside
|
||||
|
@ -1849,7 +1942,11 @@ impl Scene for GameScene {
|
|||
|
||||
self.draw_fade(state, ctx)?;
|
||||
if state.textscript_vm.mode == ScriptMode::Map && self.map_name_counter > 0 {
|
||||
let map_name = if self.stage.data.name == "u" { state.constants.title.intro_text.chars() } else { self.stage.data.name.chars() };
|
||||
let map_name = if self.stage.data.name == "u" {
|
||||
state.constants.title.intro_text.chars()
|
||||
} else {
|
||||
self.stage.data.name.chars()
|
||||
};
|
||||
let width = state.font.text_width(map_name.clone(), &state.constants);
|
||||
|
||||
state.font.draw_text_with_shadow(
|
||||
|
|
|
@ -87,6 +87,29 @@ impl Season {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TileSize {
|
||||
Tile8x8,
|
||||
Tile16x16,
|
||||
}
|
||||
|
||||
impl TileSize {
|
||||
pub const fn as_float(self) -> f32 {
|
||||
match self {
|
||||
TileSize::Tile8x8 => 8.0,
|
||||
TileSize::Tile16x16 => 16.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn as_int(self) -> i32 {
|
||||
match self {
|
||||
TileSize::Tile8x8 => 8,
|
||||
TileSize::Tile16x16 => 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SharedGameState {
|
||||
pub timing_mode: TimingMode,
|
||||
pub control_flags: ControlFlags,
|
||||
|
@ -98,6 +121,7 @@ pub struct SharedGameState {
|
|||
pub game_rng: XorShift,
|
||||
/// RNG used by graphics effects that aren't dependent on game's state.
|
||||
pub effect_rng: XorShift,
|
||||
pub tile_size: TileSize,
|
||||
pub quake_counter: u16,
|
||||
pub teleporter_slots: Vec<(u16, u16)>,
|
||||
pub carets: Vec<Caret>,
|
||||
|
@ -198,6 +222,7 @@ impl SharedGameState {
|
|||
fade_state: FadeState::Hidden,
|
||||
game_rng: XorShift::new(0),
|
||||
effect_rng: XorShift::new(123),
|
||||
tile_size: TileSize::Tile16x16,
|
||||
quake_counter: 0,
|
||||
teleporter_slots: Vec::with_capacity(8),
|
||||
carets: Vec::with_capacity(32),
|
||||
|
|
152
src/stage.rs
152
src/stage.rs
|
@ -13,6 +13,7 @@ use crate::framework::error::GameResult;
|
|||
use crate::framework::filesystem;
|
||||
use crate::map::{Map, NPCData};
|
||||
use crate::text_script::TextScript;
|
||||
use crate::common::Color;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NpcType {
|
||||
|
@ -109,9 +110,9 @@ pub enum BackgroundType {
|
|||
Waterway,
|
||||
}
|
||||
|
||||
impl BackgroundType {
|
||||
pub fn new(id: usize) -> Self {
|
||||
match id {
|
||||
impl From<u8> for BackgroundType {
|
||||
fn from(val: u8) -> Self {
|
||||
match val {
|
||||
0 => Self::TiledStatic,
|
||||
1 => Self::TiledParallax,
|
||||
2 => Self::Tiled,
|
||||
|
@ -123,21 +124,87 @@ impl BackgroundType {
|
|||
8 => Self::OutsideUnknown,
|
||||
9 => Self::Waterway,
|
||||
_ => {
|
||||
log::warn!("Unknown background type: {}", id);
|
||||
// log::warn!("Unknown background type: {}", val);
|
||||
Self::Black
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum PxPackScroll {
|
||||
Normal,
|
||||
ThreeQuarters,
|
||||
Half,
|
||||
Quarter,
|
||||
Eighth,
|
||||
Zero,
|
||||
HThreeQuarters,
|
||||
HHalf,
|
||||
HQuarter,
|
||||
V0Half,
|
||||
}
|
||||
|
||||
impl From<u8> for PxPackScroll {
|
||||
fn from(val: u8) -> Self {
|
||||
match val {
|
||||
0 => PxPackScroll::Normal,
|
||||
1 => PxPackScroll::ThreeQuarters,
|
||||
2 => PxPackScroll::Half,
|
||||
3 => PxPackScroll::Quarter,
|
||||
4 => PxPackScroll::Eighth,
|
||||
5 => PxPackScroll::Zero,
|
||||
6 => PxPackScroll::HThreeQuarters,
|
||||
7 => PxPackScroll::HHalf,
|
||||
8 => PxPackScroll::HQuarter,
|
||||
9 => PxPackScroll::V0Half,
|
||||
_ => PxPackScroll::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PxPackScroll {
|
||||
pub fn transform_camera_pos(self, x: f32, y: f32) -> (f32, f32) {
|
||||
match self {
|
||||
PxPackScroll::Normal => (x, y),
|
||||
PxPackScroll::ThreeQuarters => (x * 0.75, y * 0.75),
|
||||
PxPackScroll::Half => (x * 0.5, y * 0.5),
|
||||
PxPackScroll::Quarter => (x * 0.25, y * 0.25),
|
||||
PxPackScroll::Eighth => (x * 0.125, y * 0.125),
|
||||
PxPackScroll::Zero => (0.0, 0.0),
|
||||
PxPackScroll::HThreeQuarters => (x * 0.75, y),
|
||||
PxPackScroll::HHalf => (x * 0.5, y),
|
||||
PxPackScroll::HQuarter => (x * 0.25, y),
|
||||
PxPackScroll::V0Half => (x, y) // ???
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PxPackStageData {
|
||||
pub tileset_fg: String,
|
||||
pub tileset_mg: String,
|
||||
pub tileset_bg: String,
|
||||
pub scroll_fg: PxPackScroll,
|
||||
pub scroll_mg: PxPackScroll,
|
||||
pub scroll_bg: PxPackScroll,
|
||||
pub size_fg: (u16, u16),
|
||||
pub size_mg: (u16, u16),
|
||||
pub size_bg: (u16, u16),
|
||||
pub offset_mg: u32,
|
||||
pub offset_bg: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StageData {
|
||||
pub name: String,
|
||||
pub map: String,
|
||||
pub boss_no: usize,
|
||||
pub boss_no: u8,
|
||||
pub tileset: Tileset,
|
||||
pub pxpack_data: Option<PxPackStageData>,
|
||||
pub background: Background,
|
||||
pub background_type: BackgroundType,
|
||||
pub background_color: Color,
|
||||
pub npc1: NpcType,
|
||||
pub npc2: NpcType,
|
||||
}
|
||||
|
@ -149,8 +216,10 @@ impl Clone for StageData {
|
|||
map: self.map.clone(),
|
||||
boss_no: self.boss_no,
|
||||
tileset: self.tileset.clone(),
|
||||
pxpack_data: self.pxpack_data.clone(),
|
||||
background: self.background.clone(),
|
||||
background_type: self.background_type,
|
||||
background_color: self.background_color,
|
||||
npc1: self.npc1.clone(),
|
||||
npc2: self.npc2.clone(),
|
||||
}
|
||||
|
@ -215,7 +284,7 @@ impl StageData {
|
|||
// Cave Story+ stage table.
|
||||
let mut stages = Vec::new();
|
||||
|
||||
info!("Loading Cave Story+/Booster's Lab style stage table from {}", &stage_tbl_path);
|
||||
info!("Loading Cave Story+ stage table from {}", &stage_tbl_path);
|
||||
|
||||
let mut data = Vec::new();
|
||||
filesystem::open(ctx, stage_tbl_path)?.read_to_end(&mut data)?;
|
||||
|
@ -233,11 +302,11 @@ impl StageData {
|
|||
|
||||
f.read_exact(&mut ts_buf)?;
|
||||
f.read_exact(&mut map_buf)?;
|
||||
let bg_type = f.read_u32::<LE>()? as usize;
|
||||
let bg_type = f.read_u32::<LE>()? as u8;
|
||||
f.read_exact(&mut back_buf)?;
|
||||
f.read_exact(&mut npc1_buf)?;
|
||||
f.read_exact(&mut npc2_buf)?;
|
||||
let boss_no = f.read_u8()? as usize;
|
||||
let boss_no = f.read_u8()?;
|
||||
f.read_exact(&mut name_jap_buf)?;
|
||||
f.read_exact(&mut name_buf)?;
|
||||
|
||||
|
@ -253,8 +322,10 @@ impl StageData {
|
|||
map: map.clone(),
|
||||
boss_no,
|
||||
tileset: Tileset::new(&tileset),
|
||||
pxpack_data: None,
|
||||
background: Background::new(&background),
|
||||
background_type: BackgroundType::new(bg_type),
|
||||
background_type: BackgroundType::from(bg_type),
|
||||
background_color: Color::from_rgb(0, 0, 32),
|
||||
npc1: NpcType::new(&npc1),
|
||||
npc2: NpcType::new(&npc2),
|
||||
};
|
||||
|
@ -266,7 +337,7 @@ impl StageData {
|
|||
// Cave Story freeware executable dump.
|
||||
let mut stages = Vec::new();
|
||||
|
||||
info!("Loading Cave Story freeware exe dump style stage table from {}", &stage_sect_path);
|
||||
info!("Loading Cave Story freeware exe dump stage table from {}", &stage_sect_path);
|
||||
|
||||
let mut data = Vec::new();
|
||||
filesystem::open(ctx, stage_sect_path)?.read_to_end(&mut data)?;
|
||||
|
@ -283,16 +354,17 @@ impl StageData {
|
|||
|
||||
f.read_exact(&mut ts_buf)?;
|
||||
f.read_exact(&mut map_buf)?;
|
||||
let bg_type = f.read_u32::<LE>()? as usize;
|
||||
let bg_type = f.read_u32::<LE>()? as u8;
|
||||
f.read_exact(&mut back_buf)?;
|
||||
f.read_exact(&mut npc1_buf)?;
|
||||
f.read_exact(&mut npc2_buf)?;
|
||||
let boss_no = f.read_u8()? as usize;
|
||||
let boss_no = f.read_u8()?;
|
||||
f.read_exact(&mut name_buf)?;
|
||||
// alignment
|
||||
let _ = f.read_u8()?;
|
||||
let _ = f.read_u8()?;
|
||||
let _ = f.read_u8()?;
|
||||
{
|
||||
let mut lol = [0u8; 3];
|
||||
let _ = f.read(&mut lol)?;
|
||||
}
|
||||
|
||||
let tileset = from_shift_jis(&ts_buf[0..zero_index(&ts_buf)]);
|
||||
let map = from_shift_jis(&map_buf[0..zero_index(&map_buf)]);
|
||||
|
@ -306,8 +378,10 @@ impl StageData {
|
|||
map: map.clone(),
|
||||
boss_no,
|
||||
tileset: Tileset::new(&tileset),
|
||||
pxpack_data: None,
|
||||
background: Background::new(&background),
|
||||
background_type: BackgroundType::new(bg_type),
|
||||
background_type: BackgroundType::from(bg_type),
|
||||
background_color: Color::from_rgb(0, 0, 32),
|
||||
npc1: NpcType::new(&npc1),
|
||||
npc2: NpcType::new(&npc2),
|
||||
};
|
||||
|
@ -316,10 +390,10 @@ impl StageData {
|
|||
|
||||
return Ok(stages);
|
||||
} else if filesystem::exists(ctx, &mrmap_bin_path) {
|
||||
// CSE2E stage table
|
||||
// Moustache Rider stage table
|
||||
let mut stages = Vec::new();
|
||||
|
||||
info!("Loading CSE2E style stage table from {}", &mrmap_bin_path);
|
||||
info!("Loading Moustache Rider stage table from {}", &mrmap_bin_path);
|
||||
|
||||
let mut data = Vec::new();
|
||||
let mut fh = filesystem::open(ctx, &mrmap_bin_path)?;
|
||||
|
@ -344,11 +418,11 @@ impl StageData {
|
|||
|
||||
f.read_exact(&mut ts_buf)?;
|
||||
f.read_exact(&mut map_buf)?;
|
||||
let bg_type = f.read_u8()? as usize;
|
||||
let bg_type = f.read_u8()?;
|
||||
f.read_exact(&mut back_buf)?;
|
||||
f.read_exact(&mut npc1_buf)?;
|
||||
f.read_exact(&mut npc2_buf)?;
|
||||
let boss_no = f.read_u8()? as usize;
|
||||
let boss_no = f.read_u8()?;
|
||||
f.read_exact(&mut name_buf)?;
|
||||
|
||||
let tileset = from_shift_jis(&ts_buf[0..zero_index(&ts_buf)]);
|
||||
|
@ -358,15 +432,15 @@ impl StageData {
|
|||
let npc2 = from_shift_jis(&npc2_buf[0..zero_index(&npc2_buf)]);
|
||||
let name = from_shift_jis(&name_buf[0..zero_index(&name_buf)]);
|
||||
|
||||
println!("bg type: {}", bg_type);
|
||||
|
||||
let stage = StageData {
|
||||
name: name.clone(),
|
||||
map: map.clone(),
|
||||
boss_no,
|
||||
tileset: Tileset::new(&tileset),
|
||||
pxpack_data: None,
|
||||
background: Background::new(&background),
|
||||
background_type: BackgroundType::new(bg_type),
|
||||
background_type: BackgroundType::from(bg_type),
|
||||
background_color: Color::from_rgb(0, 0, 32),
|
||||
npc1: NpcType::new(&npc1),
|
||||
npc2: NpcType::new(&npc2),
|
||||
};
|
||||
|
@ -377,7 +451,7 @@ impl StageData {
|
|||
} else if filesystem::exists(ctx, &stage_dat_path) {
|
||||
let mut stages = Vec::new();
|
||||
|
||||
info!("Loading NXEngine style stage table from {}", &stage_dat_path);
|
||||
info!("Loading NXEngine stage table from {}", &stage_dat_path);
|
||||
|
||||
let mut data = Vec::new();
|
||||
let mut fh = filesystem::open(ctx, &stage_dat_path)?;
|
||||
|
@ -401,8 +475,8 @@ impl StageData {
|
|||
|
||||
let tileset_id = f.read_u8()? as usize;
|
||||
let bg_id = f.read_u8()? as usize;
|
||||
let bg_type = f.read_u8()? as usize;
|
||||
let boss_no = f.read_u8()? as usize;
|
||||
let bg_type = f.read_u8()?;
|
||||
let boss_no = f.read_u8()?;
|
||||
let npc1 = f.read_u8()? as usize;
|
||||
let npc2 = f.read_u8()? as usize;
|
||||
|
||||
|
@ -420,8 +494,10 @@ impl StageData {
|
|||
map: map.clone(),
|
||||
boss_no,
|
||||
tileset: Tileset::new(NXENGINE_TILESETS.get(tileset_id).unwrap_or(&"0")),
|
||||
pxpack_data: None,
|
||||
background: Background::new(NXENGINE_BACKDROPS.get(bg_id).unwrap_or(&"0")),
|
||||
background_type: BackgroundType::new(bg_type),
|
||||
background_type: BackgroundType::from(bg_type),
|
||||
background_color: Color::from_rgb(0, 0, 32),
|
||||
npc1: NpcType::new(NXENGINE_NPCS.get(npc1).unwrap_or(&"0")),
|
||||
npc2: NpcType::new(NXENGINE_NPCS.get(npc2).unwrap_or(&"0")),
|
||||
};
|
||||
|
@ -442,14 +518,23 @@ pub struct Stage {
|
|||
|
||||
impl Stage {
|
||||
pub fn load(root: &str, data: &StageData, ctx: &mut Context) -> GameResult<Self> {
|
||||
let map_file = filesystem::open(ctx, [root, "Stage/", &data.map, ".pxm"].join(""))?;
|
||||
let attrib_file = filesystem::open(ctx, [root, "Stage/", &data.tileset.name, ".pxa"].join(""))?;
|
||||
let mut data = data.clone();
|
||||
|
||||
let map = Map::load_from(map_file, attrib_file)?;
|
||||
if let Ok(pxpack_file) = filesystem::open(ctx, [root, "Stage/", &data.map, ".pxpack"].join("")) {
|
||||
let map = Map::load_pxpack(pxpack_file, root, &mut data, ctx)?;
|
||||
let stage = Self { map, data };
|
||||
|
||||
let stage = Self { map, data: data.clone() };
|
||||
Ok(stage)
|
||||
} else {
|
||||
let map_file = filesystem::open(ctx, [root, "Stage/", &data.map, ".pxm"].join(""))?;
|
||||
let attrib_file = filesystem::open(ctx, [root, "Stage/", &data.tileset.name, ".pxa"].join(""))?;
|
||||
|
||||
Ok(stage)
|
||||
let map = Map::load_pxm(map_file, attrib_file)?;
|
||||
|
||||
let stage = Self { map, data };
|
||||
|
||||
Ok(stage)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_text_script(
|
||||
|
@ -471,6 +556,7 @@ impl Stage {
|
|||
Ok(npc_data)
|
||||
}
|
||||
|
||||
/// Returns map tile from foreground layer.
|
||||
pub fn tile_at(&self, x: usize, y: usize) -> u8 {
|
||||
if let Some(&tile) = self.map.tiles.get(y.wrapping_mul(self.map.width as usize).wrapping_add(x)) {
|
||||
tile
|
||||
|
@ -479,7 +565,7 @@ impl Stage {
|
|||
}
|
||||
}
|
||||
|
||||
/// Changes map tile. Returns true if smoke should be emitted
|
||||
/// Changes map tile on foreground layer. Returns true if smoke should be emitted
|
||||
pub fn change_tile(&mut self, x: usize, y: usize, tile_type: u8) -> bool {
|
||||
if let Some(ptr) = self.map.tiles.get_mut(y.wrapping_mul(self.map.width as usize).wrapping_add(x)) {
|
||||
if *ptr != tile_type {
|
||||
|
|
|
@ -1201,10 +1201,13 @@ impl TextScriptVM {
|
|||
OpCode::TRA => {
|
||||
let map_id = read_cur_varint(&mut cursor)? as usize;
|
||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
let pos_x = read_cur_varint(&mut cursor)? as i32 * 0x2000;
|
||||
let pos_y = read_cur_varint(&mut cursor)? as i32 * 0x2000;
|
||||
|
||||
let mut new_scene = GameScene::new(state, ctx, map_id)?;
|
||||
|
||||
let block_size = new_scene.stage.map.tile_size.as_int() * 0x200;
|
||||
let pos_x = read_cur_varint(&mut cursor)? as i32 * block_size;
|
||||
let pos_y = read_cur_varint(&mut cursor)? as i32 * block_size;
|
||||
|
||||
new_scene.intro_mode = game_scene.intro_mode;
|
||||
new_scene.inventory_player1 = game_scene.inventory_player1.clone();
|
||||
new_scene.inventory_player2 = game_scene.inventory_player2.clone();
|
||||
|
@ -1218,6 +1221,7 @@ impl TextScriptVM {
|
|||
new_scene.player2.vel_y = 0;
|
||||
new_scene.player2.x = pos_x;
|
||||
new_scene.player2.y = pos_y;
|
||||
new_scene.frame.wait = game_scene.frame.wait;
|
||||
|
||||
let skip = state.textscript_vm.flags.cutscene_skip();
|
||||
state.control_flags.set_tick_world(true);
|
||||
|
@ -1236,8 +1240,10 @@ impl TextScriptVM {
|
|||
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
||||
}
|
||||
OpCode::MOV => {
|
||||
let pos_x = read_cur_varint(&mut cursor)? as i32 * 0x2000;
|
||||
let pos_y = read_cur_varint(&mut cursor)? as i32 * 0x2000;
|
||||
let block_size = state.tile_size.as_int() * 0x200;
|
||||
|
||||
let pos_x = read_cur_varint(&mut cursor)? as i32 * block_size;
|
||||
let pos_y = read_cur_varint(&mut cursor)? as i32 * block_size;
|
||||
|
||||
for player in [&mut game_scene.player1, &mut game_scene.player2].iter_mut() {
|
||||
player.vel_x = 0;
|
||||
|
@ -1512,11 +1518,12 @@ impl TextScriptVM {
|
|||
let y = read_cur_varint(&mut cursor)? as i32;
|
||||
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
||||
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
||||
let block_size = state.tile_size.as_int() * 0x200;
|
||||
|
||||
for npc in game_scene.npc_list.iter_alive() {
|
||||
if npc.event_num == event_num {
|
||||
npc.x = x * 0x2000;
|
||||
npc.y = y * 0x2000;
|
||||
npc.x = x * block_size;
|
||||
npc.y = y * block_size;
|
||||
npc.tsc_direction = tsc_direction as u16;
|
||||
|
||||
if direction == Direction::FacingPlayer {
|
||||
|
@ -1538,11 +1545,12 @@ impl TextScriptVM {
|
|||
let y = read_cur_varint(&mut cursor)? as i32;
|
||||
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
||||
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
||||
let block_size = state.tile_size.as_int() * 0x200;
|
||||
|
||||
let mut npc = NPC::create(npc_type, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = x * 0x2000;
|
||||
npc.y = y * 0x2000;
|
||||
npc.x = x * block_size;
|
||||
npc.y = y * block_size;
|
||||
npc.tsc_direction = tsc_direction as u16;
|
||||
|
||||
if direction == Direction::FacingPlayer {
|
||||
|
|
|
@ -5,10 +5,10 @@ use crate::common::{BulletFlag, Condition, Direction, Flag, Rect};
|
|||
use crate::engine_constants::{BulletData, EngineConstants};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::physics::{PhysicalEntity, OFF_X, OFF_Y};
|
||||
use crate::physics::{PhysicalEntity, OFFSETS};
|
||||
use crate::player::{Player, TargetPlayer};
|
||||
use crate::rng::{XorShift, Xoroshiro32PlusPlus, RNG};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::stage::Stage;
|
||||
|
||||
pub struct BulletManager {
|
||||
|
@ -1406,12 +1406,19 @@ impl Bullet {
|
|||
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Right);
|
||||
}
|
||||
|
||||
fn test_hit_block_destructible(&mut self, x: i32, y: i32, attributes: &[u8; 4], state: &mut SharedGameState) {
|
||||
let mut hits = [false; 4];
|
||||
let block_x = (x * 16 + 8) * 0x200;
|
||||
let block_y = (y * 16 + 8) * 0x200;
|
||||
fn test_hit_block_destructible(&mut self, x: i32, y: i32, attributes: &[u8; 16], state: &mut SharedGameState) {
|
||||
let mut hits = [false; 16];
|
||||
|
||||
let (block_x, block_y, max_attr) = match state.tile_size {
|
||||
TileSize::Tile8x8 => ((x * 2 + 1) * 0x800, (y * 2 + 1) * 0x800, 4),
|
||||
TileSize::Tile16x16 => ((x * 2 + 1) * 0x1000, (y * 2 + 1) * 0x1000, 4),
|
||||
};
|
||||
|
||||
for (i, &attr) in attributes.iter().enumerate() {
|
||||
if i == max_attr {
|
||||
break;
|
||||
}
|
||||
|
||||
if self.weapon_flags.can_destroy_snack() {
|
||||
hits[i] = attr == 0x41 || attr == 0x61;
|
||||
} else {
|
||||
|
@ -1517,6 +1524,11 @@ impl PhysicalEntity for Bullet {
|
|||
&self.hit_bounds
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn display_bounds(&self) -> &Rect<u32> {
|
||||
&self.display_bounds
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_x(&mut self, x: i32) {
|
||||
self.x = x;
|
||||
|
@ -1557,11 +1569,13 @@ impl PhysicalEntity for Bullet {
|
|||
false
|
||||
}
|
||||
|
||||
fn test_block_hit(&mut self, _state: &mut SharedGameState, x: i32, y: i32) {
|
||||
if (self.x - self.hit_bounds.left as i32) < (x * 16 + 8) * 0x200
|
||||
&& (self.x + self.hit_bounds.right as i32) > (x * 16 - 8) * 0x200
|
||||
&& (self.y - self.hit_bounds.top as i32) < (y * 16 + 8) * 0x200
|
||||
&& (self.y + self.hit_bounds.bottom as i32) > (y * 16 - 8) * 0x200
|
||||
fn test_block_hit(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
|
||||
let ti = state.tile_size.as_int() * 0x100;
|
||||
|
||||
if (self.x - self.hit_bounds.left as i32) < (x * 2 + 1) * ti
|
||||
&& (self.x + self.hit_bounds.right as i32) > (x * 2 - 1) * ti
|
||||
&& (self.y - self.hit_bounds.top as i32) < (y * 2 + 1) * ti
|
||||
&& (self.y + self.hit_bounds.bottom as i32) > (y * 2 - 1) * ti
|
||||
{
|
||||
self.flags.set_weapon_hit_block(true);
|
||||
}
|
||||
|
@ -1574,12 +1588,18 @@ impl PhysicalEntity for Bullet {
|
|||
return;
|
||||
}
|
||||
|
||||
let x = clamp(self.x() / 16 / 0x200, 0, stage.map.width as i32);
|
||||
let y = clamp(self.y() / 16 / 0x200, 0, stage.map.height as i32);
|
||||
let mut hit_attribs = [0u8; 4];
|
||||
let tile_size = state.tile_size.as_int() * 0x200;
|
||||
let max_hits = match state.tile_size {
|
||||
TileSize::Tile8x8 => 16,
|
||||
TileSize::Tile16x16 => 4,
|
||||
};
|
||||
|
||||
for (idx, (&ox, &oy)) in OFF_X.iter().zip(OFF_Y.iter()).enumerate() {
|
||||
if idx == 4 || !self.cond.alive() {
|
||||
let x = clamp(self.x() / tile_size, 0, stage.map.width as i32);
|
||||
let y = clamp(self.y() / tile_size, 0, stage.map.height as i32);
|
||||
let mut hit_attribs = [0u8; 16];
|
||||
|
||||
for (idx, &(ox, oy)) in OFFSETS.iter().enumerate() {
|
||||
if idx == max_hits || !self.cond.alive() {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue