slopes, pxmap, camera fixes

This commit is contained in:
Alula 2021-06-27 03:06:56 +02:00
parent 0d47a0f401
commit 6f3beb6e28
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
17 changed files with 1241 additions and 353 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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);
}
}
_ => {}
}

View File

@ -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,
);

View File

@ -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;

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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(

View File

@ -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),

View File

@ -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 {

View File

@ -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 {

View File

@ -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;
}