1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-03-23 10:29:18 +00:00

physics abstraction and npc physics implementation

This commit is contained in:
Alula 2020-09-09 15:06:11 +02:00
parent 51b9184a85
commit d79657bb6a
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
9 changed files with 628 additions and 363 deletions

View file

@ -64,6 +64,28 @@ bitfield! {
pub alive, set_alive: 7; // 0x80
}
bitfield! {
pub struct KeyState(u16);
impl Debug;
pub left, set_left: 0;
pub right, set_right: 1;
pub up, set_up: 2;
pub down, set_down: 3;
pub map, set_map: 4;
pub jump, set_jump: 5;
pub fire, set_fire: 6;
pub weapon_next, set_weapon_next: 7;
pub weapon_prev, set_weapon_prev: 8;
}
bitfield! {
pub struct ControlFlags(u16);
impl Debug;
pub flag_x01, set_flag_x01: 0;
pub control_enabled, set_control_enabled: 1;
pub flag_x04, set_flag_x04: 2;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FadeDirection {

View file

@ -24,7 +24,7 @@ use winit::{ElementState, Event, KeyboardInput, WindowEvent};
use crate::bmfont_renderer::BMFontRenderer;
use crate::builtin_fs::BuiltinFS;
use crate::caret::{Caret, CaretType};
use crate::common::{Direction, FadeState};
use crate::common::{Direction, FadeState, KeyState, ControlFlags};
use crate::engine_constants::EngineConstants;
use crate::ggez::{Context, ContextBuilder, event, filesystem, GameResult};
use crate::ggez::conf::{WindowMode, WindowSetup};
@ -57,6 +57,7 @@ mod live_debugger;
mod macros;
mod map;
mod npc;
mod physics;
mod player;
mod player_hit;
mod rng;
@ -68,28 +69,6 @@ mod texture_set;
mod ui;
mod weapon;
bitfield! {
pub struct KeyState(u16);
impl Debug;
left, set_left: 0;
right, set_right: 1;
up, set_up: 2;
down, set_down: 3;
map, set_map: 4;
jump, set_jump: 5;
fire, set_fire: 6;
weapon_next, set_weapon_next: 7;
weapon_prev, set_weapon_prev: 8;
}
bitfield! {
pub struct ControlFlags(u16);
impl Debug;
pub flag_x01, set_flag_x01: 0;
pub control_enabled, set_control_enabled: 1;
pub flag_x04, set_flag_x04: 2;
}
struct Game {
scene: Option<Box<dyn Scene>>,
state: SharedGameState,

View file

@ -1,9 +1,10 @@
use num_traits::clamp;
use crate::common::Direction;
use crate::ggez::GameResult;
use crate::npc::NPC;
use crate::player::Player;
use crate::SharedGameState;
use num_traits::clamp;
impl NPC {
pub(crate) fn tick_n052_sitting_blue_robot(&mut self, state: &mut SharedGameState) -> GameResult {
@ -82,7 +83,6 @@ impl NPC {
if (self.x - (16 * 0x200) < player.x) && (self.x + (16 * 0x200) > player.x)
&& (self.y - (16 * 0x200) < player.y) && (self.y + (16 * 0x200) > player.y) {
if self.x > player.x {
self.direction = Direction::Left;
} else {
@ -97,7 +97,13 @@ impl NPC {
self.anim_num = 0;
}
}
3 => {
3 | 4 => {
if self.action_num == 3 {
self.action_num = 4;
self.anim_num = 1;
self.anim_counter = 0;
}
self.anim_counter += 1;
if self.anim_counter > 2 {
self.anim_counter = 0;
@ -110,10 +116,12 @@ impl NPC {
if self.flags.hit_left_wall() {
self.direction = Direction::Right;
self.vel_x = 0x200;
}
if self.flags.hit_right_wall() {
self.direction = Direction::Left;
self.vel_x = -0x200;
}
if self.direction == Direction::Left {
@ -202,8 +210,8 @@ impl NPC {
self.vel_y = 0x5ff;
}
//self.x += self.vel_x;
//self.y += self.vel_y;
self.x += self.vel_x;
self.y += self.vel_y;
if self.direction == Direction::Left {
self.anim_rect = state.constants.npc.n060_toroko[self.anim_num as usize];
@ -327,8 +335,8 @@ impl NPC {
}
}
//self.x += self.vel_x;
//self.y += self.vel_y;
self.x += self.vel_x;
self.y += self.vel_y;
if self.direction == Direction::Left {
self.anim_rect = state.constants.npc.n061_king[self.anim_num as usize];
@ -456,8 +464,8 @@ impl NPC {
self.vel_y = 0x5ff;
}
//self.x += self.vel_x;
//self.y += self.vel_y;
self.x += self.vel_x;
self.y += self.vel_y;
if self.direction == Direction::Left {
self.anim_rect = state.constants.npc.n074_jack[self.anim_num as usize];

View file

@ -149,7 +149,7 @@ impl NPC {
self.vel_y = 0x5ff;
}
//self.y += self.vel_y;
self.y += self.vel_y;
if self.direction == Direction::Left {
self.anim_rect = state.constants.npc.n079_mahin[self.anim_num as usize];

View file

@ -13,6 +13,7 @@ use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::ggez::{Context, GameResult};
use crate::map::NPCData;
use crate::physics::PhysicalEntity;
use crate::player::Player;
use crate::str;
use crate::text_script::TextScriptExecutionState;
@ -54,6 +55,7 @@ pub struct NPC {
pub vel_y: isize,
pub target_x: isize,
pub target_y: isize,
pub size: u8,
pub shock: u16,
pub life: u16,
pub cond: Condition,
@ -128,6 +130,78 @@ impl GameEntity<&mut Player> for NPC {
}
}
impl PhysicalEntity for NPC {
#[inline(always)]
fn x(&self) -> isize {
self.x
}
#[inline(always)]
fn y(&self) -> isize {
self.y
}
#[inline(always)]
fn vel_x(&self) -> isize {
self.vel_x
}
#[inline(always)]
fn vel_y(&self) -> isize {
self.vel_y
}
#[inline(always)]
fn size(&self) -> u8 {
self.size
}
#[inline(always)]
fn hit_bounds(&self) -> &Rect<usize> {
&self.hit_bounds
}
#[inline(always)]
fn set_x(&mut self, x: isize) {
self.x = x;
}
#[inline(always)]
fn set_y(&mut self, y: isize) {
self.y = y;
}
#[inline(always)]
fn set_vel_x(&mut self, vel_x: isize) {
self.vel_x = vel_x;
}
#[inline(always)]
fn set_vel_y(&mut self, vel_y: isize) {
self.vel_y = vel_y;
}
#[inline(always)]
fn cond(&mut self) -> &mut Condition {
&mut self.cond
}
#[inline(always)]
fn flags(&mut self) -> &mut Flag {
&mut self.flags
}
#[inline(always)]
fn is_player(&self) -> bool {
false
}
#[inline(always)]
fn ignore_tile_44(&self) -> bool {
self.npc_flags.ignore_tile_44()
}
}
pub struct NPCMap {
/// A sorted pool of free IDs to make ID assignment for new entities a bit cheaper.
free_npc_ids: BTreeSet<u16>,
@ -155,6 +229,13 @@ impl NPCMap {
pub fn create_npc_from_data(&mut self, table: &NPCTable, data: &NPCData) -> &mut NPC {
let npc_flags = NPCFlag(data.flags);
let display_bounds = table.get_display_bounds(data.npc_type);
let hit_bounds = table.get_hit_bounds(data.npc_type);
let (size, life) = match table.get_entry(data.npc_type) {
Some(entry) => { (entry.size, entry.life) }
None => { (1, 0) }
};
let npc = NPC {
id: data.id,
npc_type: data.npc_type,
@ -169,13 +250,14 @@ impl NPCMap {
flag_num: data.flag_num,
event_num: data.event_num,
shock: 0,
life: table.get_life(data.npc_type),
size,
life,
cond: Condition(0x00),
flags: Flag(data.flag_num as u32),
direction: if npc_flags.spawn_facing_right() { Direction::Right } else { Direction::Left },
npc_flags,
display_bounds: table.get_display_bounds(data.npc_type),
hit_bounds: table.get_hit_bounds(data.npc_type),
display_bounds,
hit_bounds,
action_counter: 0,
anim_counter: 0,
anim_rect: Rect::new(0, 0, 0, 0),
@ -204,7 +286,7 @@ pub struct NPCTableEntry {
pub spritesheet_id: u8,
pub death_sound: u8,
pub hurt_sound: u8,
pub death_smoke: u8,
pub size: u8,
pub experience: u32,
pub damage: u32,
pub display_bounds: Rect<u8>,
@ -242,7 +324,7 @@ impl NPCTable {
spritesheet_id: 0,
death_sound: 0,
hurt_sound: 0,
death_smoke: 0,
size: 0,
experience: 0,
damage: 0,
display_bounds: Rect::new(0, 0, 0, 0),
@ -271,7 +353,7 @@ impl NPCTable {
}
for npc in table.entries.iter_mut() {
npc.death_smoke = f.read_u8()?;
npc.size = f.read_u8()?;
}
for npc in table.entries.iter_mut() {
@ -303,14 +385,6 @@ impl NPCTable {
self.entries.get(npc_type as usize)
}
pub fn get_life(&self, npc_type: u16) -> u16 {
if let Some(npc) = self.entries.get(npc_type as usize) {
npc.life
} else {
0
}
}
pub fn get_display_bounds(&self, npc_type: u16) -> Rect<usize> {
if let Some(npc) = self.entries.get(npc_type as usize) {
Rect {

View file

@ -1,3 +1,404 @@
trait PhysicalEntity {
use num_traits::clamp;
use crate::common::{Condition, Flag, Rect};
use crate::SharedGameState;
use crate::stage::Stage;
const OFF_X: [isize; 9] = [0, 1, 0, 1, 2, 2, 2, 0, 1];
const OFF_Y: [isize; 9] = [0, 0, 1, 1, 0, 1, 2, 2, 2];
pub trait PhysicalEntity {
fn x(&self) -> isize;
fn y(&self) -> isize;
fn vel_x(&self) -> isize;
fn vel_y(&self) -> isize;
fn size(&self) -> u8;
fn hit_bounds(&self) -> &Rect<usize>;
fn set_x(&mut self, x: isize);
fn set_y(&mut self, y: isize);
fn set_vel_x(&mut self, x: isize);
fn set_vel_y(&mut self, y: isize);
fn cond(&mut self) -> &mut Condition;
fn flags(&mut self) -> &mut Flag;
fn is_player(&self) -> bool;
fn ignore_tile_44(&self) -> bool { true }
fn judge_hit_block(&mut self, state: &SharedGameState, x: isize, y: isize) {
// left wall
if (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 4) * 0x200
&& self.y() + self.hit_bounds().bottom as isize > (y * 0x10 - 4) * 0x200
&& (self.x() - self.hit_bounds().right as isize) < (x * 0x10 + 8) * 0x200
&& (self.x() - self.hit_bounds().right as isize) > x * 0x10 * 0x200 {
self.set_x(((x * 0x10 + 8) * 0x200) + self.hit_bounds().right as isize);
if self.is_player() {
if self.vel_x() < -0x180 {
self.set_vel_x(-0x180);
}
if !state.key_state.left() && self.vel_x() < 0 {
self.set_vel_x(0);
}
}
self.flags().set_hit_left_wall(true);
}
// right wall
if (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 4) * 0x200
&& self.y() + self.hit_bounds().bottom as isize > (y * 0x10 - 4) * 0x200
&& (self.x() + self.hit_bounds().right as isize) > (x * 0x10 - 8) * 0x200
&& (self.x() + self.hit_bounds().right as isize) < x * 0x10 * 0x200 {
self.set_x(((x * 0x10 - 8) * 0x200) - self.hit_bounds().right as isize);
if self.is_player() {
if self.vel_x() > 0x180 {
self.set_vel_x(0x180);
}
if !state.key_state.right() && self.vel_x() > 0 {
self.set_vel_x(0);
}
}
self.flags().set_hit_right_wall(true);
}
// ceiling
if (self.x() - self.hit_bounds().right as isize) < (x * 0x10 + 5) * 0x200
&& (self.x() + self.hit_bounds().right as isize) > (x * 0x10 - 5) * 0x200
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 8) * 0x200
&& (self.y() - self.hit_bounds().top as isize) > y * 0x10 * 0x200 {
self.set_y(((y * 0x10 + 8) * 0x200) + self.hit_bounds().top as isize);
if self.is_player() {
if !self.cond().hidden() && self.vel_y() < -0x200 {
self.flags().set_head_bounced(true);
}
if self.vel_y() < 0 {
self.set_vel_y(0);
}
} else {
self.set_vel_y(0);
}
self.flags().set_hit_top_wall(true);
}
// floor
if ((self.x() - self.hit_bounds().right as isize) < (x * 0x10 + 5) * 0x200)
&& ((self.x() + self.hit_bounds().right as isize) > (x * 0x10 - 5) * 0x200)
&& ((self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 - 8) * 0x200)
&& ((self.y() + self.hit_bounds().bottom as isize) < y * 0x10 * 0x200) {
self.set_y(((y * 0x10 - 8) * 0x200) - self.hit_bounds().bottom as isize);
if self.is_player() {
if self.vel_y() > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y() > 0 {
self.set_vel_y(0);
}
} else {
self.set_vel_y(0);
}
self.flags().set_hit_bottom_wall(true);
}
}
// upper left slope (bigger half)
fn judge_hit_triangle_a(&mut self, x: isize, y: isize) {
if self.x() < (x * 0x10 + 8) * 0x200
&& self.x() > (x * 0x10 - 8) * 0x200
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 * 0x200) - (self.x() - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.set_y((y * 0x10 * 0x200) - ((self.x() - x * 0x10 * 0x200) / 2) + 0x800 + self.hit_bounds().top as isize);
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
self.flags().set_head_bounced(true);
}
if self.vel_y() < 0 {
self.set_vel_y(0);
}
self.flags().set_hit_top_wall(true);
}
}
// upper left slope (smaller half)
fn judge_hit_triangle_b(&mut self, x: isize, y: isize) {
if self.x() < (x * 0x10 + 8) * 0x200
&& self.x() > (x * 0x10 - 8) * 0x200
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 * 0x200) - (self.x() - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.set_y((y * 0x10 * 0x200) - ((self.x() - x * 0x10 * 0x200) / 2) - 0x800 + self.hit_bounds().top as isize);
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
self.flags().set_head_bounced(true);
}
if self.vel_y() < 0 {
self.set_vel_y(0);
}
self.flags().set_hit_top_wall(true);
}
}
// upper right slope (smaller half)
fn judge_hit_triangle_c(&mut self, x: isize, y: isize) {
if self.x() < (x * 0x10 + 8) * 0x200
&& self.x() > (x * 0x10 - 8) * 0x200
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 * 0x200) + (self.x() - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.set_y((y * 0x10 * 0x200) + ((self.x() - x * 0x10 * 0x200) / 2) - 0x800 + self.hit_bounds().top as isize);
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
self.flags().set_head_bounced(true);
}
if self.vel_y() < 0 {
self.set_vel_y(0);
}
self.flags().set_hit_top_wall(true);
}
}
// upper right slope (bigger half)
fn judge_hit_triangle_d(&mut self, x: isize, y: isize) {
if (self.x() < (x * 0x10 + 8) * 0x200)
&& (self.x() > (x * 0x10 - 8) * 0x200)
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 * 0x200) + (self.x() - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.set_y((y * 0x10 * 0x200) + ((self.x() - x * 0x10 * 0x200) / 2) + 0x800 + self.hit_bounds().top as isize);
if self.is_player() && !self.cond().hidden() && self.vel_y() < -0x200 {
self.flags().set_head_bounced(true);
}
if self.vel_y() < 0 {
self.set_vel_y(0);
}
self.flags().set_hit_top_wall(true);
}
}
// lower left half (bigger)
fn judge_hit_triangle_e(&mut self, x: isize, y: isize) {
self.flags().set_hit_left_bigger_half(true);
if (self.x() < (x * 0x10 + 8) * 0x200)
&& (self.x() > (x * 0x10 - 8) * 0x200)
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 * 0x200) + (self.x() - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 8) * 0x200 {
self.set_y((y * 0x10 * 0x200) + ((self.x() - x * 0x10 * 0x200) / 2) - 0x800 - self.hit_bounds().bottom as isize);
if self.is_player() && self.vel_y() > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y() > 0 {
self.set_vel_y(0);
}
self.flags().set_hit_left_slope(true);
self.flags().set_hit_bottom_wall(true);
}
}
// lower left half (smaller)
fn judge_hit_triangle_f(&mut self, x: isize, y: isize) {
self.flags().set_hit_left_smaller_half(true);
if (self.x() < (x * 0x10 + 8) * 0x200)
&& (self.x() > (x * 0x10 - 8) * 0x200)
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 * 0x200) + (self.x() - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 8) * 0x200 {
self.set_y((y * 0x10 * 0x200) + ((self.x() - x * 0x10 * 0x200) / 2) + 0x800 - self.hit_bounds().bottom as isize);
if self.is_player() && self.vel_y() > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y() > 0 {
self.set_vel_y(0);
}
self.flags().set_hit_left_slope(true);
self.flags().set_hit_bottom_wall(true);
}
}
// lower right half (smaller)
fn judge_hit_triangle_g(&mut self, x: isize, y: isize) {
self.flags().set_hit_right_smaller_half(true);
if (self.x() < (x * 0x10 + 8) * 0x200)
&& (self.x() > (x * 0x10 - 8) * 0x200)
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 * 0x200) - (self.x() - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 8) * 0x200 {
self.set_y((y * 0x10 * 0x200) - ((self.x() - x * 0x10 * 0x200) / 2) + 0x800 - self.hit_bounds().bottom as isize);
if self.is_player() && self.vel_y() > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y() > 0 {
self.set_vel_y(0);
}
self.flags().set_hit_right_slope(true);
self.flags().set_hit_bottom_wall(true);
}
}
// lower right half (bigger)
fn judge_hit_triangle_h(&mut self, x: isize, y: isize) {
self.flags().set_hit_right_bigger_half(true);
if (self.x() < (x * 0x10 + 8) * 0x200)
&& (self.x() > (x * 0x10 - 8) * 0x200)
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 * 0x200) - (self.x() - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 8) * 0x200 {
self.set_y((y * 0x10 * 0x200) - ((self.x() - x * 0x10 * 0x200) / 2) - 0x800 - self.hit_bounds().bottom as isize);
if self.is_player() && self.vel_y() > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y() > 0 {
self.set_vel_y(0);
}
self.flags().set_hit_right_slope(true);
self.flags().set_hit_bottom_wall(true);
}
}
fn judge_hit_water(&mut self, x: isize, y: isize) {
if (self.x() - self.hit_bounds().right as isize) < (x * 0x10 + 5) * 0x200
&& (self.x() + self.hit_bounds().right as isize) > (x * 0x10 - 5) * 0x200
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 5) * 0x200
&& (self.y() + self.hit_bounds().bottom as isize) > y * 0x10 * 0x200 {
self.flags().set_in_water(true);
}
}
fn judge_hit_spike(&mut self, x: isize, y: isize) {
if (self.x() - self.hit_bounds().right as isize) < (x * 0x10 + 4) * 0x200
&& (self.x() + self.hit_bounds().right as isize) > (x * 0x10 - 4) * 0x200
&& (self.y() - self.hit_bounds().top as isize) < (y * 0x10 + 3) * 0x200
&& (self.y() + self.hit_bounds().bottom as isize) > (y * 0x10 - 3) * 0x200 {
self.flags().set_hit_by_spike(true);
}
}
fn tick_map_collisions(&mut self, state: &SharedGameState, stage: &Stage) {
let big = self.size() >= 3;
let x = clamp((self.x() - if big { 0x1000 } else { 0 }) / 0x10 / 0x200, 0, stage.map.width as isize);
let y = clamp((self.y() - if big { 0x1000 } else { 0 }) / 0x10 / 0x200, 0, stage.map.height as isize);
for (idx, (&ox, &oy)) in OFF_X.iter().zip(OFF_Y.iter()).enumerate() {
if idx == 4 && big {
break;
}
let attrib = stage.map.get_attribute((x + ox) as usize, (y + oy) as usize);
match attrib {
// Spikes
0x62 | 0x42 if self.is_player() => {
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
self.judge_hit_spike(x + ox, y + ox);
}
// Blocks
0x02 | 0x60 | 0x62 => {
self.judge_hit_water(x + ox, y + oy);
}
0x05 | 0x41 | 0x43 | 0x46 if self.is_player() => {
self.judge_hit_block(state, x + ox, y + oy);
}
0x03 | 0x05 | 0x41 | 0x43 if !self.is_player() => {
self.judge_hit_block(state, x + ox, y + oy);
}
0x44 => {
if !self.ignore_tile_44() {
self.judge_hit_block(state, x + ox, y + oy);
}
}
// Slopes
0x50 | 0x70 => {
self.judge_hit_triangle_a(x + ox, y + oy);
if attrib & 0x20 != 0 { self.judge_hit_water(x + ox, y + oy); }
}
0x51 | 0x71 => {
self.judge_hit_triangle_b(x + ox, y + oy);
if attrib & 0x20 != 0 { self.judge_hit_water(x + ox, y + oy); }
}
0x52 | 0x72 => {
self.judge_hit_triangle_c(x + ox, y + oy);
if attrib & 0x20 != 0 { self.judge_hit_water(x + ox, y + oy); }
}
0x53 | 0x73 => {
self.judge_hit_triangle_d(x + ox, y + oy);
if attrib & 0x20 != 0 { self.judge_hit_water(x + ox, y + oy); }
}
0x54 | 0x74 => {
self.judge_hit_triangle_e(x + ox, y + oy);
if attrib & 0x20 != 0 { self.judge_hit_water(x + ox, y + oy); }
}
0x55 | 0x75 => {
self.judge_hit_triangle_f(x + ox, y + oy);
if attrib & 0x20 != 0 { self.judge_hit_water(x + ox, y + oy); }
}
0x56 | 0x76 => {
self.judge_hit_triangle_g(x + ox, y + oy);
if attrib & 0x20 != 0 { self.judge_hit_water(x + ox, y + oy); }
}
0x57 | 0x77 => {
self.judge_hit_triangle_h(x + ox, y + oy);
if attrib & 0x20 != 0 { self.judge_hit_water(x + ox, y + oy); }
}
0x61 => {
self.judge_hit_water(x + ox, y + oy);
self.judge_hit_block(state, x + ox, y + oy);
}
0x04 | 0x64 if !self.is_player() => {
self.judge_hit_water(x + ox, y + oy);
self.judge_hit_block(state, x + ox, y + oy);
}
// Forces
0x80 | 0xa0 => {
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
self.flags().set_force_left(true);
}
0x81 | 0xa1 => {
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
self.flags().set_force_up(true);
}
0x82 | 0xa2 => {
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
self.flags().set_force_right(true);
}
0x83 | 0xa3 => {
if attrib & 0x20 != 0 { self.flags().set_in_water(true); }
self.flags().set_force_down(true);
}
_ => {}
}
}
}
}

View file

@ -1,326 +1,81 @@
use num_traits::clamp;
use crate::caret::CaretType;
use crate::common::{Direction, Flag};
use crate::common::{Condition, Direction, Flag, Rect};
use crate::npc::{NPC, NPCMap};
use crate::physics::PhysicalEntity;
use crate::player::Player;
use crate::SharedGameState;
use crate::stage::Stage;
const OFF_X: &[isize; 4] = &[0, 1, 0, 1];
const OFF_Y: &[isize; 4] = &[0, 0, 1, 1];
impl PhysicalEntity for Player {
#[inline(always)]
fn x(&self) -> isize {
self.x
}
#[inline(always)]
fn y(&self) -> isize {
self.y
}
#[inline(always)]
fn vel_x(&self) -> isize {
self.vel_x
}
#[inline(always)]
fn vel_y(&self) -> isize {
self.vel_y
}
#[inline(always)]
fn size(&self) -> u8 {
1
}
#[inline(always)]
fn hit_bounds(&self) -> &Rect<usize> {
&self.hit_bounds
}
#[inline(always)]
fn set_x(&mut self, x: isize) {
self.x = x;
}
#[inline(always)]
fn set_y(&mut self, y: isize) {
self.y = y;
}
#[inline(always)]
fn set_vel_x(&mut self, vel_x: isize) {
self.vel_x = vel_x;
}
#[inline(always)]
fn set_vel_y(&mut self, vel_y: isize) {
self.vel_y = vel_y;
}
#[inline(always)]
fn cond(&mut self) -> &mut Condition {
&mut self.cond
}
#[inline(always)]
fn flags(&mut self) -> &mut Flag {
&mut self.flags
}
#[inline(always)]
fn is_player(&self) -> bool {
true
}
}
impl Player {
fn judge_hit_block(&mut self, state: &SharedGameState, x: isize, y: isize) {
// left wall
if (self.y - self.hit_bounds.top as isize) < (y * 0x10 + 4) * 0x200
&& self.y + self.hit_bounds.bottom as isize > (y * 0x10 - 4) * 0x200
&& (self.x - self.hit_bounds.right as isize) < (x * 0x10 + 8) * 0x200
&& (self.x - self.hit_bounds.right as isize) > x * 0x10 * 0x200 {
self.x = ((x * 0x10 + 8) * 0x200) + self.hit_bounds.right as isize;
if self.vel_x < -0x180 {
self.vel_x = -0x180;
}
if !state.key_state.left() && self.vel_x < 0 {
self.vel_x = 0;
}
self.flags.set_hit_left_wall(true);
}
// right wall
if (self.y - self.hit_bounds.top as isize) < (y * 0x10 + 4) * 0x200
&& self.y + self.hit_bounds.bottom as isize > (y * 0x10 - 4) * 0x200
&& (self.x + self.hit_bounds.right as isize) > (x * 0x10 - 8) * 0x200
&& (self.x + self.hit_bounds.right as isize) < x * 0x10 * 0x200 {
self.x = ((x * 0x10 - 8) * 0x200) - self.hit_bounds.right as isize;
if self.vel_x > 0x180 {
self.vel_x = 0x180;
}
if !state.key_state.right() && self.vel_x > 0 {
self.vel_x = 0;
}
self.flags.set_hit_right_wall(true);
}
// ceiling
if (self.x - self.hit_bounds.right as isize) < (x * 0x10 + 5) * 0x200
&& (self.x + self.hit_bounds.right as isize) > (x * 0x10 - 5) * 0x200
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 + 8) * 0x200
&& (self.y - self.hit_bounds.top as isize) > y * 0x10 * 0x200 {
self.y = ((y * 0x10 + 8) * 0x200) + self.hit_bounds.top as isize;
if !self.cond.hidden() && self.vel_y < -0x200 {
self.flags.set_head_bounced(true);
}
if self.vel_y < 0 {
self.vel_y = 0;
}
self.flags.set_hit_top_wall(true);
}
// floor
if ((self.x - self.hit_bounds.right as isize) < (x * 0x10 + 5) * 0x200)
&& ((self.x + self.hit_bounds.right as isize) > (x * 0x10 - 5) * 0x200)
&& ((self.y + self.hit_bounds.bottom as isize) > (y * 0x10 - 8) * 0x200)
&& ((self.y + self.hit_bounds.bottom as isize) < y * 0x10 * 0x200) {
self.y = ((y * 0x10 - 8) * 0x200) - self.hit_bounds.bottom as isize;
if self.vel_y > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y > 0 {
self.vel_y = 0;
}
self.flags.set_hit_bottom_wall(true);
}
}
// upper left slope (bigger half)
fn judge_hit_triangle_a(&mut self, state: &SharedGameState, x: isize, y: isize) {
if self.x < (x * 0x10 + 8) * 0x200
&& self.x > (x * 0x10 - 8) * 0x200
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 * 0x200) - (self.x - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y + self.hit_bounds.bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) + 0x800 + self.hit_bounds.top as isize;
if !self.cond.hidden() && self.vel_y < -0x200 {
self.flags.set_head_bounced(true);
}
if self.vel_y < 0 {
self.vel_y = 0;
}
self.flags.set_hit_top_wall(true);
}
}
// upper left slope (smaller half)
fn judge_hit_triangle_b(&mut self, state: &SharedGameState, x: isize, y: isize) {
if self.x < (x * 0x10 + 8) * 0x200
&& self.x > (x * 0x10 - 8) * 0x200
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 * 0x200) - (self.x - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y + self.hit_bounds.bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) - 0x800 + self.hit_bounds.top as isize;
if !self.cond.hidden() && self.vel_y < -0x200 {
self.flags.set_head_bounced(true);
}
if self.vel_y < 0 {
self.vel_y = 0;
}
self.flags.set_hit_top_wall(true);
}
}
// upper right slope (smaller half)
fn judge_hit_triangle_c(&mut self, state: &SharedGameState, x: isize, y: isize) {
if self.x < (x * 0x10 + 8) * 0x200
&& self.x > (x * 0x10 - 8) * 0x200
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 * 0x200) + (self.x - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y + self.hit_bounds.bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) - 0x800 + self.hit_bounds.top as isize;
if !self.cond.hidden() && self.vel_y < -0x200 {
self.flags.set_head_bounced(true);
}
if self.vel_y < 0 {
self.vel_y = 0;
}
self.flags.set_hit_top_wall(true);
}
}
// upper right slope (bigger half)
fn judge_hit_triangle_d(&mut self, state: &SharedGameState, x: isize, y: isize) {
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 * 0x200) + (self.x - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y + self.hit_bounds.bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) + 0x800 + self.hit_bounds.top as isize;
if !self.cond.hidden() && self.vel_y < -0x200 {
self.flags.set_head_bounced(true);
}
if self.vel_y < 0 {
self.vel_y = 0;
}
self.flags.set_hit_top_wall(true);
}
}
// lower left half (bigger)
fn judge_hit_triangle_e(&mut self, state: &SharedGameState, x: isize, y: isize) {
self.flags.set_hit_left_bigger_half(true);
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y + self.hit_bounds.bottom as isize) > (y * 0x10 * 0x200) + (self.x - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 + 8) * 0x200 {
self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) - 0x800 - self.hit_bounds.bottom as isize;
if self.vel_y > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y > 0 {
self.vel_y = 0;
}
self.flags.set_hit_left_slope(true);
self.flags.set_hit_bottom_wall(true);
}
}
// lower left half (smaller)
fn judge_hit_triangle_f(&mut self, state: &SharedGameState, x: isize, y: isize) {
self.flags.set_hit_left_smaller_half(true);
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y + self.hit_bounds.bottom as isize) > (y * 0x10 * 0x200) + (self.x - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 + 8) * 0x200 {
self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) + 0x800 - self.hit_bounds.bottom as isize;
if self.vel_y > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y > 0 {
self.vel_y = 0;
}
self.flags.set_hit_left_slope(true);
self.flags.set_hit_bottom_wall(true);
}
}
// lower right half (smaller)
fn judge_hit_triangle_g(&mut self, state: &SharedGameState, x: isize, y: isize) {
self.flags.set_hit_right_smaller_half(true);
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y + self.hit_bounds.bottom as isize) > (y * 0x10 * 0x200) - (self.x - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 + 8) * 0x200 {
self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) + 0x800 - self.hit_bounds.bottom as isize;
if self.vel_y > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y > 0 {
self.vel_y = 0;
}
self.flags.set_hit_right_slope(true);
self.flags.set_hit_bottom_wall(true);
}
}
// lower right half (bigger)
fn judge_hit_triangle_h(&mut self, state: &SharedGameState, x: isize, y: isize) {
self.flags.set_hit_right_bigger_half(true);
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y + self.hit_bounds.bottom as isize) > (y * 0x10 * 0x200) - (self.x - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 + 8) * 0x200 {
self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) - 0x800 - self.hit_bounds.bottom as isize;
if self.vel_y > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.vel_y > 0 {
self.vel_y = 0;
}
self.flags.set_hit_right_slope(true);
self.flags.set_hit_bottom_wall(true);
}
}
fn judge_hit_water(&mut self, state: &SharedGameState, x: isize, y: isize) {
if (self.x - self.hit_bounds.right as isize) < (x * 0x10 + 5) * 0x200
&& (self.x + self.hit_bounds.right as isize) > (x * 0x10 - 5) * 0x200
&& (self.y - self.hit_bounds.top as isize) < (y * 0x10 + 5) * 0x200
&& (self.y + self.hit_bounds.bottom as isize) > y * 0x10 * 0x200 {
self.flags.set_in_water(true);
}
}
pub fn tick_map_collisions(&mut self, state: &SharedGameState, stage: &Stage) {
let x = clamp(self.x / 0x10 / 0x200, 0, stage.map.width as isize);
let y = clamp(self.y / 0x10 / 0x200, 0, stage.map.height as isize);
for (ox, oy) in OFF_X.iter().zip(OFF_Y) {
let attrib = stage.map.get_attribute((x + *ox) as usize, (y + *oy) as usize);
match attrib {
// Block
0x02 | 0x60 => {
self.judge_hit_water(state, x + *ox, y + *oy);
}
0x05 | 0x41 | 0x43 | 0x46 => {
self.judge_hit_block(state, x + *ox, y + *oy);
}
0x50 | 0x70 => {
self.judge_hit_triangle_a(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x51 | 0x71 => {
self.judge_hit_triangle_b(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x52 | 0x72 => {
self.judge_hit_triangle_c(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x53 | 0x73 => {
self.judge_hit_triangle_d(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x54 | 0x74 => {
self.judge_hit_triangle_e(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x55 | 0x75 => {
self.judge_hit_triangle_f(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x56 | 0x76 => {
self.judge_hit_triangle_g(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x57 | 0x77 => {
self.judge_hit_triangle_h(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x61 => {
self.judge_hit_water(state, x + *ox, y + *oy);
self.judge_hit_block(state, x + *ox, y + *oy);
}
_ => {}
}
}
}
fn judge_hit_npc_non_solid(&mut self, npc: &NPC) -> Flag {
let mut flags = Flag(0);
let hit_left = if npc.direction == Direction::Left { npc.hit_bounds.left } else { npc.hit_bounds.right } as isize;

View file

@ -7,6 +7,7 @@ use crate::ggez::{Context, GameResult, graphics, timer};
use crate::ggez::graphics::{Color, Drawable, DrawParam, Text, TextFragment};
use crate::ggez::nalgebra::clamp;
use crate::npc::NPCMap;
use crate::physics::PhysicalEntity;
use crate::player::Player;
use crate::scene::Scene;
use crate::SharedGameState;
@ -468,12 +469,17 @@ impl Scene for GameScene {
self.player.flags.0 = 0;
state.tick_carets();
self.player.tick_map_collisions(state, &self.stage);
self.player.tick_npc_collisions(state, &mut self.npc_map);
for npc_id in self.npc_map.npc_ids.iter() {
if let Some(npc) = self.npc_map.npcs.get_mut(npc_id) {
npc.tick(state, &mut self.player)?;
if npc.cond.alive() && !npc.npc_flags.ignore_solidity() {
npc.tick_map_collisions(state, &self.stage);
}
}
}

View file

@ -519,6 +519,8 @@ impl TextScriptVM {
state.textscript_vm.flags.set_render(false);
state.textscript_vm.flags.set_background_visible(false);
game_scene.player.update_target = true;
exec_state = TextScriptExecutionState::Ended;
}
OpCode::PRI => {
@ -553,6 +555,24 @@ impl TextScriptVM {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::MYB => {
let new_direction = read_cur_varint(&mut cursor)? as usize;
game_scene.player.vel_y = -0x200;
if let Some(direction) = Direction::from_int(new_direction) {
match direction {
Direction::Left => { game_scene.player.vel_x = 0x200 }
Direction::Up => { game_scene.player.vel_y = 0x200 }
Direction::Right => { game_scene.player.vel_x = -0x200 }
Direction::Bottom => { game_scene.player.vel_y = -0x200 }
}
} else {
// todo npc direction dependent bump
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::SMC => {
game_scene.player.cond.set_hidden(false);
@ -864,7 +884,7 @@ impl TextScriptVM {
}
// One operand codes
OpCode::BOA | OpCode::BSL | OpCode::FOB | OpCode::UNI |
OpCode::MYB | OpCode::NUM | OpCode::DNA |
OpCode::NUM | OpCode::DNA |
OpCode::MPp | OpCode::SKm | OpCode::SKp | OpCode::EQp | OpCode::EQm |
OpCode::ITp | OpCode::ITm | OpCode::AMm | OpCode::UNJ | OpCode::MPJ |
OpCode::XX1 | OpCode::SIL | OpCode::LIp | OpCode::SOU |