1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-12-08 13:08:30 +00:00

Use HitExtents instead of Rect for hitboxes

This commit is contained in:
Alula 2024-08-03 22:23:57 +02:00 committed by alula
parent f4602687eb
commit 2335cf0064
16 changed files with 241 additions and 139 deletions

View file

@ -279,10 +279,20 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
Rect { left: x, top: y, right: x.add(width), bottom: y.add(height) }
}
pub fn has_point(&self, x: T, y: T) -> bool {
/**
* Returns true if the point (x, y) is inside the rectangle (inclusive).
*/
pub fn has_point_incl(&self, x: T, y: T) -> bool {
self.left.ge(&x) && self.right.le(&x) && self.top.ge(&y) && self.bottom.le(&y)
}
/**
* Returns true if the point (x, y) is inside the rectangle (exclusive).
*/
pub fn has_point_excl(&self, x: T, y: T) -> bool {
self.left.le(&x) && self.right.gt(&x) && self.top.le(&y) && self.bottom.gt(&y)
}
pub fn width(&self) -> T {
if self.left.gt(&self.right) {
self.left.sub(self.right)

View file

@ -4,9 +4,10 @@ use crate::common::{Direction, Rect};
use crate::components::flash::Flash;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::{NPC, NPCLayer};
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::{NPCLayer, NPC};
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::{GameDifficulty, SharedGameState};
use crate::game::stage::Stage;
@ -196,11 +197,11 @@ impl NPC {
if self.action_num == 0 {
self.npc_flags.set_interactable(true);
self.action_num = 1;
if self.direction == Direction::Right {
self.npc_flags.set_interactable(false);
self.vel_y = -0x200;
//Creates smoke
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
@ -235,7 +236,7 @@ impl NPC {
pub(crate) fn tick_n017_health_refill(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
//Creates smoke when spawned in a shelter
if self.direction == Direction::Right {
self.vel_y = -0x200;
@ -659,9 +660,9 @@ impl NPC {
self.action_counter += 1;
if self.action_counter > 10
&& (self.flags.hit_left_wall()
|| self.flags.hit_right_wall()
|| self.flags.hit_bottom_wall()
|| self.flags.in_water())
|| self.flags.hit_right_wall()
|| self.flags.hit_bottom_wall()
|| self.flags.in_water())
{
// hit something
self.cond.set_alive(false);
@ -1996,7 +1997,7 @@ impl NPC {
// Big Block
self.anim_rect = Rect::new(0, 64, 32, 96);
self.display_bounds = Rect::new(0x2000, 0x2000, 0x2000, 0x2000);
self.hit_bounds = Rect::new(0x1800, 0x1800, 0x1800, 0x1800);
self.hit_bounds = HitExtents { left: 0x1800, top: 0x1800, right: 0x1800, bottom: 0x1800 };
} else {
// Small Blocks
let scale = state.tile_size.as_int() as u16;
@ -2080,7 +2081,7 @@ impl NPC {
self.npc_flags.set_invulnerable(true);
self.anim_num = 1;
self.display_bounds = Rect::new(0x1000, 0x1000, 0x1000, 0x1000);
self.hit_bounds = Rect::new(0x1000, 0x1000, 0x1000, 0x1000);
self.hit_bounds = HitExtents { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
}
_ => (),
}
@ -2091,7 +2092,7 @@ impl NPC {
self.action_num = 11;
self.action_counter = 16;
}
self.action_counter = self.action_counter.saturating_sub(2);
if self.action_counter == 0 {
self.action_num = 100;
@ -2204,11 +2205,11 @@ impl NPC {
npc.cond.set_alive(true);
npc.x = self.x
+ if player.equip.has_booster_2_0() {
self.rng.range(-14..14)
} else {
self.rng.range(-11..11)
} * state.tile_size.as_int()
* 0x200;
self.rng.range(-14..14)
} else {
self.rng.range(-11..11)
} * state.tile_size.as_int()
* 0x200;
npc.y = player.y - 0x1C000;
npc.direction = if self.rng.range(0..10) & 1 != 0 { Direction::Left } else { Direction::Right };

View file

@ -4,6 +4,7 @@ use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
@ -42,7 +43,7 @@ impl BossNPC {
self.parts[0].direction = Direction::Right;
self.parts[0].display_bounds =
Rect { left: 48 * 0x200, top: 48 * 0x200, right: 32 * 0x200, bottom: 0x2000 };
self.parts[0].hit_bounds = Rect { left: 24 * 0x200, top: 0x2000, right: 24 * 0x200, bottom: 0x2000 };
self.parts[0].hit_bounds = HitExtents { left: 24 * 0x200, top: 0x2000, right: 24 * 0x200, bottom: 0x2000 };
self.parts[0].size = 3;
self.parts[0].exp = 1;
self.parts[0].event_num = 1000;
@ -478,12 +479,12 @@ impl BossNPC {
self.hurt_sound[1] = 52;
self.parts[1].size = 3;
self.parts[1].npc_flags.set_invulnerable(true);
self.parts[1].hit_bounds = Rect { left: 0x2000, top: 0x2000, right: 0x2000, bottom: 0x2000 };
self.parts[1].hit_bounds = HitExtents { left: 0x2000, top: 0x2000, right: 0x2000, bottom: 0x2000 };
self.hurt_sound[2] = 52;
self.parts[2].size = 3;
self.parts[2].npc_flags.set_invulnerable(true);
self.parts[2].hit_bounds = Rect { left: 24 * 0x200, top: 0x2000, right: 24 * 0x200, bottom: 0x2000 };
self.parts[2].hit_bounds = HitExtents { left: 24 * 0x200, top: 0x2000, right: 24 * 0x200, bottom: 0x2000 };
}
1 => {
self.parts[1].x = self.parts[0].x + self.parts[0].direction.vector_x() * 24 * 0x200;

View file

@ -1,10 +1,11 @@
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::common::{Direction, Rect, CDEG_RAD};
use crate::components::flash::Flash;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
@ -1430,7 +1431,7 @@ impl BossNPC {
self.parts[0].x = 0x28000;
self.parts[0].y = -0x8000;
self.hurt_sound[0] = 54;
self.parts[0].hit_bounds = Rect { left: 0x4000, top: 0x6000, right: 0x4000, bottom: 0x6000 };
self.parts[0].hit_bounds = HitExtents { left: 0x4000, top: 0x6000, right: 0x4000, bottom: 0x6000 };
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_solid_hard(true);
self.parts[0].npc_flags.set_event_when_killed(true);
@ -1445,7 +1446,7 @@ impl BossNPC {
self.parts[1].direction = Direction::Left;
self.parts[1].npc_flags.set_ignore_solidity(true);
self.parts[1].display_bounds = Rect { left: 0x1800, top: 0, right: 0x1800, bottom: 0x2000 };
self.parts[1].hit_bounds = Rect { left: 0x1800, top: 0, right: 0x1800, bottom: 0x2000 };
self.parts[1].hit_bounds = HitExtents { left: 0x1800, top: 0, right: 0x1800, bottom: 0x2000 };
self.parts[2] = self.parts[1].clone();
self.parts[2].direction = Direction::Right;
@ -1456,21 +1457,21 @@ impl BossNPC {
self.parts[3].npc_flags.set_invulnerable(true);
self.parts[3].npc_flags.set_ignore_solidity(true);
self.parts[3].display_bounds = Rect { left: 0x7800, top: 0x7800, right: 0x7800, bottom: 0x7800 };
self.parts[3].hit_bounds = Rect { left: 0x6000, top: 0x3000, right: 0x6000, bottom: 0x4000 };
self.parts[3].hit_bounds = HitExtents { left: 0x6000, top: 0x3000, right: 0x6000, bottom: 0x4000 };
self.parts[4].cond.set_alive(true);
self.parts[4].cond.set_damage_boss(true);
self.parts[4].npc_flags.set_solid_soft(true);
self.parts[4].npc_flags.set_invulnerable(true);
self.parts[4].npc_flags.set_ignore_solidity(true);
self.parts[4].hit_bounds = Rect { left: 0x4000, top: 0x1000, right: 0x4000, bottom: 0x1000 };
self.parts[4].hit_bounds = HitExtents { left: 0x4000, top: 0x1000, right: 0x4000, bottom: 0x1000 };
self.parts[5].cond.set_alive(true);
self.parts[5].cond.set_damage_boss(true);
self.parts[5].npc_flags.set_solid_hard(true);
self.parts[5].npc_flags.set_invulnerable(true);
self.parts[5].npc_flags.set_ignore_solidity(true);
self.parts[5].hit_bounds = Rect { left: 0x4000, top: 0, right: 0x4000, bottom: 0x6000 };
self.parts[5].hit_bounds = HitExtents { left: 0x4000, top: 0, right: 0x4000, bottom: 0x6000 };
}
100 | 101 => {
if self.parts[0].action_num == 100 {

View file

@ -3,6 +3,7 @@ use crate::framework::error::GameResult;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::HitExtents;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
@ -62,7 +63,7 @@ impl BossNPC {
self.parts[0].x = 0;
self.parts[0].y = 0;
self.parts[0].display_bounds = Rect { left: 0x5000, top: 0x7800, right: 0x5000, bottom: 0x7800 };
self.parts[0].hit_bounds = Rect { left: 0x6200, top: 0x7800, right: 0x5000, bottom: 0x6000 };
self.parts[0].hit_bounds = HitExtents { left: 0x6200, top: 0x7800, right: 0x5000, bottom: 0x6000 };
self.hurt_sound[0] = 54;
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_solid_hard(true);
@ -139,14 +140,14 @@ impl BossNPC {
self.parts[1].cond.set_alive(true);
self.parts[1].npc_flags.set_invulnerable(true);
self.parts[1].npc_flags.set_ignore_solidity(true);
self.parts[1].hit_bounds = Rect { left: 0x1C00, top: 0x1000, right: 0x1C00, bottom: 0x1000 };
self.parts[1].hit_bounds = HitExtents { left: 0x1C00, top: 0x1000, right: 0x1C00, bottom: 0x1000 };
self.parts[2] = self.parts[1].clone();
self.parts[3].cond.set_alive(true);
self.parts[3].cond.set_damage_boss(true);
self.parts[3].npc_flags.set_shootable(true);
self.parts[3].hit_bounds = Rect { left: 0xC00, top: 0x1000, right: 0xC00, bottom: 0x1000 };
self.parts[3].hit_bounds = HitExtents { left: 0xC00, top: 0x1000, right: 0xC00, bottom: 0x1000 };
let mut npc = NPC::create(325, &state.npc_table);
npc.cond.set_alive(true);
@ -162,7 +163,8 @@ impl BossNPC {
// This relies heavily on the map not being changed
// Need to calculate offset from the default starting location
for i in 0..5 {
let extra_smoke = if stage.change_tile(i + 8, self.parts[0].action_counter3 as usize, 0) { 3 } else { 0 };
let extra_smoke =
if stage.change_tile(i + 8, self.parts[0].action_counter3 as usize, 0) { 3 } else { 0 };
npc_list.create_death_smoke(
(i as i32 + 8) * 0x2000,
self.parts[0].action_counter3 as i32 * 0x2000,

View file

@ -3,6 +3,7 @@ use crate::framework::error::GameResult;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
@ -198,7 +199,7 @@ impl BossNPC {
self.parts[0].event_num = 1000;
self.parts[0].life = 400;
self.parts[0].display_bounds = Rect::new(0x5000, 0x1800, 0x3000, 0x1800);
self.parts[0].hit_bounds = Rect::new(0x2000, 0x1400, 0x2000, 0x1400);
self.parts[0].hit_bounds = HitExtents { left: 0x2000, top: 0x1400, right: 0x2000, bottom: 0x1400 };
}
100 | 101 => {
if self.parts[0].action_num == 100 {

View file

@ -1,12 +1,13 @@
use num_traits::{abs, clamp};
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::common::{Direction, Rect, CDEG_RAD};
use crate::components::flash::Flash;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
@ -39,7 +40,8 @@ impl NPC {
let player = self.get_closest_player_mut(players);
// Get angle between 0 and 2*PI
let direction = f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64) + std::f64::consts::PI;
let direction =
f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64) + std::f64::consts::PI;
if direction < radians {
if radians - direction < std::f64::consts::PI {
@ -156,7 +158,7 @@ impl BossNPC {
self.parts[0].size = 3;
self.parts[0].event_num = 1000;
self.parts[0].hit_bounds =
Rect { left: 24 * 0x200, top: 24 * 0x200, right: 24 * 0x200, bottom: 24 * 0x200 };
HitExtents { left: 24 * 0x200, top: 24 * 0x200, right: 24 * 0x200, bottom: 24 * 0x200 };
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_event_when_killed(true);
self.parts[0].npc_flags.set_show_damage(true);
@ -178,7 +180,7 @@ impl BossNPC {
self.parts[3].target_x = 0;
self.parts[3].display_bounds = Rect { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
self.parts[3].hit_bounds =
Rect { left: 5 * 0x200, top: 5 * 0x200, right: 5 * 0x200, bottom: 5 * 0x200 };
HitExtents { left: 5 * 0x200, top: 5 * 0x200, right: 5 * 0x200, bottom: 5 * 0x200 };
self.parts[3].npc_flags.set_ignore_solidity(true);
self.parts[4] = self.parts[3].clone();
@ -203,7 +205,7 @@ impl BossNPC {
self.parts[7].anim_num = 0;
self.parts[7].display_bounds =
Rect { left: 52 * 0x200, top: 24 * 0x200, right: 52 * 0x200, bottom: 24 * 0x200 };
self.parts[7].hit_bounds = Rect { left: 0x1000, top: 24 * 0x200, right: 0x1000, bottom: 0x2000 };
self.parts[7].hit_bounds = HitExtents { left: 0x1000, top: 24 * 0x200, right: 0x1000, bottom: 0x2000 };
self.parts[7].npc_flags.set_ignore_solidity(true);
self.hurt_sound[7] = 52;
@ -215,7 +217,8 @@ impl BossNPC {
self.parts[9].direction = Direction::Up;
self.parts[9].display_bounds =
Rect { left: 36 * 0x200, top: 0x1000, right: 36 * 0x200, bottom: 24 * 0x200 };
self.parts[9].hit_bounds = Rect { left: 28 * 0x200, top: 0x1000, right: 28 * 0x200, bottom: 0x2000 };
self.parts[9].hit_bounds =
HitExtents { left: 28 * 0x200, top: 0x1000, right: 28 * 0x200, bottom: 0x2000 };
self.hurt_sound[9] = 52;
self.parts[9].npc_flags.set_rear_and_top_not_hurt(true);
self.parts[9].npc_flags.set_ignore_solidity(true);

View file

@ -5,6 +5,7 @@ use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::bullet::BulletManager;
@ -74,7 +75,7 @@ impl BossNPC {
self.parts[0].target_y = self.parts[0].y;
self.parts[0].display_bounds =
Rect { left: 40 * 0x200, top: 40 * 0x200, right: 40 * 0x200, bottom: 0x2000 };
self.parts[0].hit_bounds = Rect { left: 0x1000, top: 24 * 0x200, right: 0x1000, bottom: 0x2000 };
self.parts[0].hit_bounds = HitExtents { left: 0x1000, top: 24 * 0x200, right: 0x1000, bottom: 0x2000 };
self.hurt_sound[0] = 52;
self.parts[1].cond.set_alive(true);
@ -94,7 +95,7 @@ impl BossNPC {
self.parts[3].x = self.parts[0].x + 0x2000;
self.parts[3].y = self.parts[0].y;
self.parts[3].display_bounds = Rect { left: 24 * 0x200, top: 0x2000, right: 0x2000, bottom: 0x2000 };
self.parts[3].hit_bounds = Rect { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
self.parts[3].hit_bounds = HitExtents { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
self.hurt_sound[3] = 52;
self.parts[4].cond.set_alive(true);
@ -421,7 +422,8 @@ impl BossNPC {
self.parts[5].action_num = 1;
self.parts[5].npc_flags.set_solid_soft(true);
self.parts[5].npc_flags.set_ignore_solidity(true);
self.parts[5].hit_bounds = Rect { left: 20 * 0x200, top: 36 * 0x200, right: 20 * 0x200, bottom: 0x2000 };
self.parts[5].hit_bounds =
HitExtents { left: 20 * 0x200, top: 36 * 0x200, right: 20 * 0x200, bottom: 0x2000 };
}
self.parts[5].x = self.parts[0].x;

View file

@ -1,8 +1,9 @@
use crate::common::{CDEG_RAD, Direction, Rect, SliceExt};
use crate::common::{Direction, Rect, SliceExt, CDEG_RAD};
use crate::components::flash::Flash;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
@ -24,7 +25,7 @@ impl BossNPC {
self.parts[0].x = 0x14000 + (state.constants.game.tile_offset_x * 0x2000);
self.parts[0].y = 0x10000;
self.parts[0].display_bounds = Rect::new(0x1000, 0x1000, 0x10000, 0x1000);
self.parts[0].hit_bounds = Rect::new(0x1000, 0x1000, 0x1000, 0x1000);
self.parts[0].hit_bounds = HitExtents { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
self.hurt_sound[0] = 54;
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_event_when_killed(true);
@ -37,7 +38,7 @@ impl BossNPC {
self.parts[0].target_y = 0x3D;
self.parts[2].display_bounds = Rect::new(0x2800, 0x2000, 0x2800, 0x2000);
self.parts[2].hit_bounds = Rect::new(0x1800, 0x1400, 0x1800, 0x1400);
self.parts[2].hit_bounds = HitExtents { left: 0x1800, top: 0x1400, right: 0x1800, bottom: 0x1400 };
self.parts[2].npc_flags.set_ignore_solidity(true);
self.parts[2].npc_flags.set_invulnerable(true);
self.parts[2].parent_id = 3;
@ -47,7 +48,7 @@ impl BossNPC {
self.parts[3].cond.set_alive(true);
self.parts[3].display_bounds = Rect::new(0x2800, 0x2800, 0x2800, 0x2800);
self.parts[3].hit_bounds = Rect::new(0x1800, 0x400, 0x1800, 0x2000);
self.parts[3].hit_bounds = HitExtents { left: 0x1800, top: 0x400, right: 0x1800, bottom: 0x2000 };
self.parts[3].npc_flags.set_ignore_solidity(true);
self.parts[3].parent_id = 0;
self.parts[3].damage = 10;
@ -261,7 +262,7 @@ impl BossNPC {
} else {
return;
};
match part.action_num {
0 => {
part.action_num = 1;
@ -316,14 +317,14 @@ impl BossNPC {
part.hit_bounds.left = 0x2000;
state.sound_manager.play_sfx(51);
npc_list.create_death_smoke(
part.x,
part.y,
part.display_bounds.right as usize,
4,
state,
&part.rng
&part.rng,
);
}
}

View file

@ -25,6 +25,8 @@ use crate::game::stage::{Stage, StageTexturePaths};
use crate::game::weapon::bullet::BulletManager;
use crate::util::rng::Xoroshiro32PlusPlus;
use super::physics::HitExtents;
pub mod ai;
pub mod boss;
pub mod list;
@ -123,7 +125,7 @@ pub struct NPC {
pub anim_counter: u16,
pub anim_rect: Rect<u16>,
pub display_bounds: Rect<u32>,
pub hit_bounds: Rect<u32>,
pub hit_bounds: HitExtents,
pub rng: Xoroshiro32PlusPlus,
pub popup: NumberPopup,
pub splash: bool,
@ -157,7 +159,7 @@ impl NPC {
direction: Direction::Left,
tsc_direction: 0,
display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 },
hit_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 },
hit_bounds: HitExtents { left: 0, top: 0, right: 0, bottom: 0 },
parent_id: 0,
action_num: 0,
anim_num: 0,
@ -740,7 +742,7 @@ impl PhysicalEntity for NPC {
}
#[inline(always)]
fn hit_bounds(&self) -> &Rect<u32> {
fn hit_bounds(&self) -> &HitExtents {
&self.hit_bounds
}
@ -909,16 +911,16 @@ impl NPCTable {
}
}
pub fn get_hit_bounds(&self, npc_type: u16) -> Rect<u32> {
pub fn get_hit_bounds(&self, npc_type: u16) -> HitExtents {
if let Some(npc) = self.entries.get(npc_type as usize) {
Rect {
HitExtents {
left: npc.hit_bounds.left as u32 * 0x200,
top: npc.hit_bounds.top as u32 * 0x200,
right: npc.hit_bounds.right as u32 * 0x200,
bottom: npc.hit_bounds.bottom as u32 * 0x200,
}
} else {
Rect { left: 0, top: 0, right: 0, bottom: 0 }
HitExtents { left: 0, top: 0, right: 0, bottom: 0 }
}
}

View file

@ -82,6 +82,33 @@ pub const OFFSETS: [(i32, i32); 64] = [
(4, 4),
];
/**
* Represents hitbox extents, with each value being the distance from the center of the entity.
*/
#[derive(Debug, Clone, Copy)]
pub struct HitExtents {
pub left: u32,
pub right: u32,
pub top: u32,
pub bottom: u32,
}
impl HitExtents {
/**
* Check if a point is within the hitbox extents of an entity on X axis (exclusively).
*/
pub const fn point_in_entity_x(&self, entity_x: i32, point_x: i32) -> bool {
point_x > entity_x - self.left as i32 && point_x < entity_x + self.right as i32
}
/**
* Check if a point is within the hitbox extents of an entity on Y axis (exclusively).
*/
pub const fn point_in_entity_y(&self, entity_y: i32, point_y: i32) -> bool {
point_y > entity_y - self.top as i32 && point_y < entity_y + self.bottom as i32
}
}
pub trait PhysicalEntity {
fn x(&self) -> i32;
fn y(&self) -> i32;
@ -96,7 +123,7 @@ pub trait PhysicalEntity {
0
}
fn hit_bounds(&self) -> &Rect<u32>;
fn hit_bounds(&self) -> &HitExtents;
fn display_bounds(&self) -> &Rect<u32>;
fn set_x(&mut self, x: i32);
@ -125,14 +152,23 @@ pub trait PhysicalEntity {
let bounds_bottom = if self.is_player() { 0x800 } else { 0x600 };
let half_tile_size = state.tile_size.as_int() * 0x100;
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)
let hit_bounds = *self.hit_bounds();
let block_center_x = (x * 2) * half_tile_size;
let block_center_y = (y * 2) * half_tile_size;
let block_top = (y * 2 - 1) * half_tile_size;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if (self.y() - hit_bounds.top as i32) < (block_bottom - bounds_top)
&& (self.y() + hit_bounds.bottom as i32) > (block_top + bounds_bottom)
{
// left wall
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) * half_tile_size
if (self.x() - hit_bounds.right as i32) < block_right
&& (self.x() - hit_bounds.right as i32) > block_center_x
{
self.set_x(((x * 2 + 1) * half_tile_size) + self.hit_bounds().right as i32);
self.set_x(block_right + hit_bounds.right as i32);
if self.is_player() {
if self.vel_x() < -0x180 {
@ -148,10 +184,10 @@ pub trait PhysicalEntity {
}
// right wall
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) * half_tile_size
if (self.x() + hit_bounds.right as i32) > block_left
&& (self.x() + hit_bounds.right as i32) < block_center_x
{
self.set_x(((x * 2 - 1) * half_tile_size) - self.hit_bounds().right as i32);
self.set_x(block_left - hit_bounds.right as i32);
if self.is_player() {
if self.vel_x() > 0x180 {
@ -167,27 +203,26 @@ 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)
if ((self.x() - hit_bounds.right as i32) < block_right - bounds_x)
&& ((self.x() + hit_bounds.right as i32) > block_left + bounds_x)
{
// ceiling
if (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
if (self.y() - hit_bounds.top as i32) < block_bottom && (self.y() - hit_bounds.top as i32) > block_center_y
{
self.set_y(((y * 2 + 1) * half_tile_size) + self.hit_bounds().top as i32);
self.set_y(block_bottom + 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,
self.y() - hit_bounds.top as i32,
CaretType::LittleParticles,
Direction::Left,
);
state.create_caret(
self.x(),
self.y() - self.hit_bounds().top as i32,
self.y() - hit_bounds.top as i32,
CaretType::LittleParticles,
Direction::Left,
);
@ -204,10 +239,10 @@ pub trait PhysicalEntity {
}
// floor
if ((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)
if ((self.y() + hit_bounds.bottom as i32) > block_top)
&& ((self.y() + hit_bounds.bottom as i32) < block_center_y)
{
self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32);
self.set_y(block_top - hit_bounds.bottom as i32);
if self.is_player() {
if self.vel_y() > 0x400 {
@ -228,13 +263,16 @@ pub trait PhysicalEntity {
fn test_platform_hit(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
let half_tile_size = state.tile_size.as_int() * 0x100;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
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)
if ((self.x() - self.hit_bounds().right as i32) < block_right)
&& ((self.x() + self.hit_bounds().right as i32) > block_left)
&& ((self.y() + self.hit_bounds().bottom as i32) > block_top)
&& ((self.y() + self.hit_bounds().bottom as i32) < block_top + 0x400)
{
self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32);
self.set_y(block_top - self.hit_bounds().bottom as i32);
if self.is_player() {
if self.vel_y() > 0x400 {
@ -257,12 +295,15 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (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
< (y * tile_size) - (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y(
(y * tile_size) - ((self.x() - x * tile_size) / 2) + quarter_tile_size + self.hit_bounds().top as i32,
@ -298,12 +339,15 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (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
< (y * tile_size) - (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y(
(y * tile_size) - ((self.x() - x * tile_size) / 2) - quarter_tile_size + self.hit_bounds().top as i32,
@ -339,12 +383,15 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (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
< (y * tile_size) + (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y(
(y * tile_size) + ((self.x() - x * tile_size) / 2) - quarter_tile_size + self.hit_bounds().top as i32,
@ -380,12 +427,15 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (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
< (y * tile_size) + (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y(
(y * tile_size) + ((self.x() - x * tile_size) / 2) + quarter_tile_size + self.hit_bounds().top as i32,
@ -421,14 +471,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_left_higher_half(true);
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (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
> (y * tile_size) + (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) + ((self.x() - x * tile_size) / 2)
@ -454,14 +507,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_left_lower_half(true);
if (self.x() < (x * 2 + 1) * half_tile_size)
&& (self.x() > (x * 2 - 1) * half_tile_size)
if (self.x() < block_right)
&& (self.x() > block_left)
&& (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
> (y * tile_size) + (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) + ((self.x() - x * tile_size) / 2) + quarter_tile_size
@ -486,14 +542,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_right_lower_half(true);
if (self.x() < (x * 2 + 1) * half_tile_size)
&& (self.x() > (x * 2 - 1) * half_tile_size)
if (self.x() < block_right)
&& (self.x() > block_left)
&& (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
> (y * tile_size) - (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) - ((self.x() - x * tile_size) / 2) + quarter_tile_size
@ -518,14 +577,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_right_higher_half(true);
if (self.x() < (x * 2 + 1) * half_tile_size)
&& (self.x() > (x * 2 - 1) * half_tile_size)
if (self.x() < block_right)
&& (self.x() > block_left)
&& (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
> (y * tile_size) - (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size)
@ -551,11 +613,14 @@ pub trait PhysicalEntity {
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;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (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.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y((y * tile_size) - (self.x() - x * tile_size) + self.hit_bounds().top as i32);
@ -587,11 +652,14 @@ pub trait PhysicalEntity {
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;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (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.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y((y * tile_size) + (self.x() - x * tile_size) + self.hit_bounds().top as i32);
@ -624,14 +692,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_left_higher_half(true);
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (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
> (y * tile_size) + (self.x() - x * tile_size) - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) + (self.x() - x * tile_size) - quarter_tile_size - self.hit_bounds().bottom as i32,
@ -655,14 +726,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_right_higher_half(true);
if (self.x() < (x * 2 + 1) * half_tile_size)
&& (self.x() > (x * 2 - 1) * half_tile_size)
if (self.x() < block_right)
&& (self.x() > block_left)
&& (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
> (y * tile_size) - (self.x() - x * tile_size) - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) - (self.x() - x * tile_size) - quarter_tile_size - self.hit_bounds().bottom as i32,

View file

@ -19,6 +19,8 @@ use crate::input::dummy_player_controller::DummyPlayerController;
use crate::input::player_controller::PlayerController;
use crate::util::rng::RNG;
use super::physics::HitExtents;
mod player_hit;
pub mod skin;
@ -93,7 +95,7 @@ pub struct Player {
pub equip: Equipment,
pub direction: Direction,
pub display_bounds: Rect<u32>,
pub hit_bounds: Rect<u32>,
pub hit_bounds: HitExtents,
pub control_mode: ControlMode,
pub question: bool,
pub booster_fuel: u32,

View file

@ -8,7 +8,7 @@ use crate::game::inventory::Inventory;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::PhysicalEntity;
use crate::game::physics::{HitExtents, PhysicalEntity};
use crate::game::player::{ControlMode, Player, TargetPlayer};
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::WeaponType;
@ -43,7 +43,7 @@ impl PhysicalEntity for Player {
}
#[inline(always)]
fn hit_bounds(&self) -> &Rect<u32> {
fn hit_bounds(&self) -> &HitExtents {
&self.hit_bounds
}

View file

@ -4,6 +4,7 @@ use crate::common::{Color, Direction, Rect};
use crate::framework::context::Context;
use crate::framework::filesystem;
use crate::framework::filesystem::File;
use crate::game::physics::HitExtents;
use crate::game::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin};
use crate::game::shared_game_state::SharedGameState;
@ -227,10 +228,10 @@ impl PlayerSkin for BasicPlayerSkin {
""
}
fn get_hit_bounds(&self) -> Rect<u32> {
fn get_hit_bounds(&self) -> HitExtents {
let ubox = &self.metadata.hit_box;
Rect {
HitExtents {
left: ubox.left as u32 * 0x200,
top: ubox.top as u32 * 0x200,
right: ubox.right as u32 * 0x200,

View file

@ -1,5 +1,6 @@
use crate::bitfield;
use crate::common::{Color, Direction, Rect};
use crate::game::physics::HitExtents;
use crate::game::shared_game_state::SharedGameState;
pub mod basic;
@ -83,7 +84,7 @@ pub trait PlayerSkin: PlayerSkinClone {
fn get_mask_texture_name(&self) -> &str;
/// Returns hit bounds of skin.
fn get_hit_bounds(&self) -> Rect<u32>;
fn get_hit_bounds(&self) -> HitExtents;
/// Returns display bounds of skin.
fn get_display_bounds(&self) -> Rect<u32>;

View file

@ -5,11 +5,11 @@ use crate::engine_constants::{BulletData, EngineConstants};
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::{OFFSETS, PhysicalEntity};
use crate::game::physics::{HitExtents, PhysicalEntity, OFFSETS};
use crate::game::player::{Player, TargetPlayer};
use crate::game::shared_game_state::{SharedGameState, TileSize};
use crate::game::stage::Stage;
use crate::util::rng::{RNG, Xoroshiro32PlusPlus, XorShift};
use crate::util::rng::{XorShift, Xoroshiro32PlusPlus, RNG};
pub struct BulletManager {
pub bullets: Vec<Bullet>,
@ -121,7 +121,7 @@ pub struct Bullet {
pub anim_counter: u16,
pub action_num: u16,
pub action_counter: u16,
pub hit_bounds: Rect<u32>,
pub hit_bounds: HitExtents,
pub display_bounds: Rect<u32>,
}
@ -180,12 +180,12 @@ impl Bullet {
bullet.display_bounds.right as u32 * 0x200,
bullet.display_bounds.bottom as u32 * 0x200,
),
hit_bounds: Rect::new(
bullet.block_hit_width as u32 * 0x200,
bullet.block_hit_height as u32 * 0x200,
bullet.block_hit_width as u32 * 0x200,
bullet.block_hit_height as u32 * 0x200,
),
hit_bounds: HitExtents {
left: bullet.block_hit_width as u32 * 0x200,
top: bullet.block_hit_height as u32 * 0x200,
right: bullet.block_hit_width as u32 * 0x200,
bottom: bullet.block_hit_height as u32 * 0x200,
},
}
}
@ -1844,7 +1844,7 @@ impl PhysicalEntity for Bullet {
}
#[inline(always)]
fn hit_bounds(&self) -> &Rect<u32> {
fn hit_bounds(&self) -> &HitExtents {
&self.hit_bounds
}
@ -1964,7 +1964,7 @@ impl PhysicalEntity for Bullet {
}
if let Some(tile) =
stage.map.tiles.get_mut(stage.map.width as usize * (y + oy) as usize + (x + ox) as usize)
stage.map.tiles.get_mut(stage.map.width as usize * (y + oy) as usize + (x + ox) as usize)
{
*tile = tile.wrapping_sub(1);
}