mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-07-23 04:50:51 +00:00
Compare commits
6 commits
539af32722
...
d1d188ac77
Author | SHA1 | Date | |
---|---|---|---|
|
d1d188ac77 | ||
|
4043830e56 | ||
|
c4b32f28ae | ||
|
f6c9a03126 | ||
|
a0c8cfa26d | ||
|
b49cc83a5b |
|
@ -240,7 +240,7 @@ fn test_builtin_fs() {
|
|||
FSNode::Directory("memes", vec![
|
||||
FSNode::File("nothing.txt", &[]),
|
||||
FSNode::Directory("secret stuff", vec![
|
||||
FSNode::File("passwords.txt", &b"12345678"),
|
||||
FSNode::File("passwords.txt", b"12345678"),
|
||||
]),
|
||||
]),
|
||||
FSNode::File("test2.txt", &[]),
|
||||
|
|
|
@ -44,8 +44,9 @@ impl BackendEventLoop for NullEventLoop {
|
|||
game.loops = 0;
|
||||
state_ref.frame_time = 0.0;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
game.draw(ctx).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
|
||||
//game.draw(ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,26 @@ impl CombinedMenuController {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn trigger_left(&self) -> bool {
|
||||
for cont in self.controllers.iter() {
|
||||
if cont.trigger_left() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn trigger_right(&self) -> bool {
|
||||
for cont in self.controllers.iter() {
|
||||
if cont.trigger_right() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn trigger_ok(&self) -> bool {
|
||||
for cont in self.controllers.iter() {
|
||||
if cont.trigger_menu_ok() {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::common::Rect;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::input::combined_menu_controller::CombinedMenuController;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
|
@ -35,6 +34,8 @@ pub enum MenuSelectionResult<'a> {
|
|||
None,
|
||||
Canceled,
|
||||
Selected(usize, &'a mut MenuEntry),
|
||||
Left(usize, &'a mut MenuEntry),
|
||||
Right(usize, &'a mut MenuEntry),
|
||||
}
|
||||
|
||||
pub struct Menu {
|
||||
|
@ -53,17 +54,7 @@ static QUOTE_FRAMES: [u16; 4] = [0, 1, 0, 2];
|
|||
|
||||
impl Menu {
|
||||
pub fn new(x: isize, y: isize, width: u16, height: u16) -> Menu {
|
||||
Menu {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
selected: 0,
|
||||
entry_y: 0,
|
||||
anim_num: 0,
|
||||
anim_wait: 0,
|
||||
entries: Vec::new(),
|
||||
}
|
||||
Menu { x, y, width, height, selected: 0, entry_y: 0, anim_num: 0, anim_wait: 0, entries: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push_entry(&mut self, entry: MenuEntry) {
|
||||
|
@ -181,9 +172,7 @@ impl Menu {
|
|||
rect.right = rect.left + 16;
|
||||
rect.bottom = rect.top + 16;
|
||||
|
||||
batch.add_rect(self.x as f32,
|
||||
self.y as f32 + 2.0 + self.entry_y as f32,
|
||||
&rect);
|
||||
batch.add_rect(self.x as f32, self.y as f32 + 2.0 + self.entry_y as f32, &rect);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
|
@ -191,18 +180,47 @@ impl Menu {
|
|||
for entry in self.entries.iter() {
|
||||
match entry {
|
||||
MenuEntry::Active(name) => {
|
||||
state.font.draw_text(name.chars(), self.x as f32 + 20.0, y, &state.constants, &mut state.texture_set, ctx)?;
|
||||
state.font.draw_text(
|
||||
name.chars(),
|
||||
self.x as f32 + 20.0,
|
||||
y,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
MenuEntry::Disabled(name) => {
|
||||
state.font.draw_colored_text(name.chars(), self.x as f32 + 20.0, y, (0xa0, 0xa0, 0xff, 0xff), &state.constants, &mut state.texture_set, ctx)?;
|
||||
state.font.draw_colored_text(
|
||||
name.chars(),
|
||||
self.x as f32 + 20.0,
|
||||
y,
|
||||
(0xa0, 0xa0, 0xff, 0xff),
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
MenuEntry::Toggle(name, value) => {
|
||||
let value_text = if *value { "ON" } else { "OFF" };
|
||||
let val_text_len = state.font.text_width(value_text.chars(), &state.constants);
|
||||
|
||||
state.font.draw_text(name.chars(), self.x as f32 + 20.0, y, &state.constants, &mut state.texture_set, ctx)?;
|
||||
state.font.draw_text(
|
||||
name.chars(),
|
||||
self.x as f32 + 20.0,
|
||||
y,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
|
||||
state.font.draw_text(value_text.chars(), self.x as f32 + self.width as f32 - val_text_len, y, &state.constants, &mut state.texture_set, ctx)?;
|
||||
state.font.draw_text(
|
||||
value_text.chars(),
|
||||
self.x as f32 + self.width as f32 - val_text_len,
|
||||
y,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
MenuEntry::Hidden => {}
|
||||
_ => {}
|
||||
|
@ -214,7 +232,11 @@ impl Menu {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, controller: &mut CombinedMenuController, state: &mut SharedGameState) -> MenuSelectionResult {
|
||||
pub fn tick(
|
||||
&mut self,
|
||||
controller: &mut CombinedMenuController,
|
||||
state: &mut SharedGameState,
|
||||
) -> MenuSelectionResult {
|
||||
if controller.trigger_back() {
|
||||
state.sound_manager.play_sfx(5);
|
||||
return MenuSelectionResult::Canceled;
|
||||
|
@ -237,8 +259,12 @@ impl Menu {
|
|||
|
||||
if let Some(entry) = self.entries.get(self.selected) {
|
||||
match entry {
|
||||
MenuEntry::Active(_) => { break; }
|
||||
MenuEntry::Toggle(_, _) => { break; }
|
||||
MenuEntry::Active(_) => {
|
||||
break;
|
||||
}
|
||||
MenuEntry::Toggle(_, _) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
|
@ -248,11 +274,7 @@ impl Menu {
|
|||
}
|
||||
|
||||
if !self.entries.is_empty() {
|
||||
self.entry_y = self.entries[0..(self.selected)]
|
||||
.iter()
|
||||
.map(|e| e.height())
|
||||
.sum::<f64>()
|
||||
.max(0.0) as u16;
|
||||
self.entry_y = self.entries[0..(self.selected)].iter().map(|e| e.height()).sum::<f64>().max(0.0) as u16;
|
||||
}
|
||||
|
||||
let mut y = self.y as f32 + 6.0;
|
||||
|
@ -260,17 +282,22 @@ impl Menu {
|
|||
let entry_bounds = Rect::new_size(self.x, y as isize, self.width as isize, entry.height() as isize);
|
||||
y += entry.height() as f32;
|
||||
|
||||
if !((controller.trigger_ok() && self.selected == idx)
|
||||
|| state.touch_controls.consume_click_in(entry_bounds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match entry {
|
||||
MenuEntry::Active(_) | MenuEntry::Toggle(_, _) => {
|
||||
self.selected = idx;
|
||||
MenuEntry::Active(_) | MenuEntry::Toggle(_, _)
|
||||
if (self.selected == idx && controller.trigger_ok())
|
||||
|| state.touch_controls.consume_click_in(entry_bounds) =>
|
||||
{
|
||||
state.sound_manager.play_sfx(18);
|
||||
return MenuSelectionResult::Selected(idx, entry);
|
||||
}
|
||||
MenuEntry::Options(_, _, _) if controller.trigger_left() => {
|
||||
state.sound_manager.play_sfx(1);
|
||||
return MenuSelectionResult::Left(self.selected, entry);
|
||||
}
|
||||
MenuEntry::Options(_, _, _) if controller.trigger_right() => {
|
||||
state.sound_manager.play_sfx(1);
|
||||
return MenuSelectionResult::Right(self.selected, entry);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,8 @@ impl NPC {
|
|||
self.vel_y = 0x5ff;
|
||||
}
|
||||
|
||||
self.y += self.vel_y;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::player::Player;
|
|||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::weapon::bullet::BulletManager;
|
||||
use crate::caret::CaretType;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n117_curly(
|
||||
|
@ -271,4 +272,53 @@ impl NPC {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n123_curly_boss_bullet(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
|
||||
state.sound_manager.play_sfx(32);
|
||||
|
||||
match self.direction {
|
||||
Direction::Left => {
|
||||
self.vel_x = -0x1000;
|
||||
self.vel_y = self.rng.range(-0x80..0x80);
|
||||
}
|
||||
Direction::Up => {
|
||||
self.vel_x = self.rng.range(-0x80..0x80);
|
||||
self.vel_y = -0x1000;
|
||||
}
|
||||
Direction::Right => {
|
||||
self.vel_x = 0x1000;
|
||||
self.vel_y = self.rng.range(-0x80..0x80);
|
||||
}
|
||||
Direction::Bottom => {
|
||||
self.vel_x = self.rng.range(-0x80..0x80);
|
||||
self.vel_y = 0x1000;
|
||||
}
|
||||
Direction::FacingPlayer => unreachable!(),
|
||||
}
|
||||
|
||||
self.anim_rect = state.constants.npc.n123_curly_boss_bullet[self.direction as usize];
|
||||
}
|
||||
|
||||
if match self.direction {
|
||||
Direction::Left if self.flags.hit_left_wall() => true,
|
||||
Direction::Right if self.flags.hit_right_wall() => true,
|
||||
Direction::Up if self.flags.hit_top_wall() => true,
|
||||
Direction::Bottom if self.flags.hit_bottom_wall() => true,
|
||||
_ => false,
|
||||
} {
|
||||
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Right);
|
||||
state.sound_manager.play_sfx(28);
|
||||
self.cond.set_alive(false);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -688,6 +688,166 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n056_tan_beetle(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
players: [&mut Player; 2],
|
||||
) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = if self.direction == Direction::Left { 1 } else { 3 };
|
||||
}
|
||||
1 => {
|
||||
self.vel_x -= 0x10;
|
||||
if self.vel_x < -0x400 {
|
||||
self.vel_x = -0x400;
|
||||
}
|
||||
|
||||
self.x += if self.shock != 0 { self.vel_x / 2 } else { self.vel_x };
|
||||
|
||||
self.animate(1, 1, 2);
|
||||
|
||||
if self.flags.hit_left_wall() {
|
||||
self.action_num = 2;
|
||||
self.action_counter = 0;
|
||||
self.anim_num = 0;
|
||||
self.vel_x = 0;
|
||||
self.direction = Direction::Right;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x < player.x && self.x > player.x - 0x20000 && (self.y - player.y).abs() < 0x1000 {
|
||||
self.action_num = 3;
|
||||
self.anim_num = 1;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
self.vel_x += 0x10;
|
||||
if self.vel_x > 0x400 {
|
||||
self.vel_x = 0x400;
|
||||
}
|
||||
|
||||
self.x += if self.shock != 0 { self.vel_x / 2 } else { self.vel_x };
|
||||
|
||||
self.animate(1, 1, 2);
|
||||
|
||||
if self.flags.hit_right_wall() {
|
||||
self.action_num = 4;
|
||||
self.action_counter = 0;
|
||||
self.anim_num = 0;
|
||||
self.vel_x = 0;
|
||||
self.direction = Direction::Left;
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x > player.x && self.x < player.x + 0x20000 && (self.y - player.y).abs() < 0x1000 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 1;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 3 };
|
||||
|
||||
self.anim_rect = state.constants.npc.n056_tan_beetle[self.anim_num as usize + dir_offset];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n057_crow(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = self.rng.range(0..1) as u16;
|
||||
self.anim_counter = self.rng.range(0..4) as u16;
|
||||
self.action_counter2 = 120;
|
||||
|
||||
let mut angle = self.rng.range(0..255);
|
||||
|
||||
self.vel_x = ((angle as f64 * CDEG_RAD).cos() * -512.0) as i32;
|
||||
angle += 0x40;
|
||||
self.target_x = self.x + 8 * ((angle as f64 * CDEG_RAD).cos() * -512.0) as i32;
|
||||
self.vel_y = ((angle as f64 * CDEG_RAD).sin() * -512.0) as i32;
|
||||
angle += 0x40;
|
||||
self.target_y = self.y + 8 * ((angle as f64 * CDEG_RAD).sin() * -512.0) as i32;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
|
||||
|
||||
self.vel_x += ((self.target_x - self.x).signum() * 0x10).clamp(-0x200, 0x200);
|
||||
self.vel_y += ((self.target_y - self.y).signum() * 0x10).clamp(-0x200, 0x200);
|
||||
|
||||
if self.shock != 0 {
|
||||
self.action_num = 2;
|
||||
self.action_counter = 0;
|
||||
|
||||
self.vel_x = self.direction.opposite().vector_x() * 0x200;
|
||||
self.vel_y = 0;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
|
||||
self.vel_x += if self.y <= player.y + 0x4000 {
|
||||
(player.x - self.x).signum() * 0x10
|
||||
} else {
|
||||
(self.x - player.x).signum() * 0x10
|
||||
};
|
||||
|
||||
self.vel_y += (player.y - self.y).signum() * 0x10;
|
||||
|
||||
if self.shock > 0 {
|
||||
self.vel_x = 0;
|
||||
self.vel_y += 0x20;
|
||||
}
|
||||
|
||||
if self.vel_x < 0 && self.flags.hit_left_wall() {
|
||||
self.vel_x = 0x200;
|
||||
}
|
||||
|
||||
if self.vel_x > 0 && self.flags.hit_right_wall() {
|
||||
self.vel_x = -0x200;
|
||||
}
|
||||
|
||||
if self.vel_y < 0 && self.flags.hit_top_wall() {
|
||||
self.vel_y = 0x200;
|
||||
}
|
||||
|
||||
if self.vel_y > 0 && self.flags.hit_bottom_wall() {
|
||||
self.vel_y = -0x200;
|
||||
}
|
||||
|
||||
self.vel_x = clamp(self.vel_x, -0x5ff, 0x5ff);
|
||||
self.vel_y = clamp(self.vel_y, -0x5ff, 0x5ff);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
if self.shock > 0 {
|
||||
self.anim_num = 4;
|
||||
} else {
|
||||
self.animate(1, 0, 1);
|
||||
}
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 5 };
|
||||
|
||||
self.anim_rect = state.constants.npc.n057_crow[self.anim_num as usize + dir_offset];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n120_colon_a(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
let anim = if self.direction == Direction::Left { 0 } else { 1 };
|
||||
|
||||
|
@ -696,6 +856,48 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n121_colon_b(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
if self.direction != Direction::Left {
|
||||
self.anim_rect = state.constants.npc.n121_colon_b[2];
|
||||
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > 100 {
|
||||
self.action_counter = 0;
|
||||
state.create_caret(self.x, self.y, CaretType::Zzz, Direction::Left);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
if self.rng.range(0..120) == 10 {
|
||||
self.action_num = 2;
|
||||
self.action_counter = 0;
|
||||
self.anim_num = 1;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > 8 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.anim_rect = state.constants.npc.n121_colon_b[self.anim_num as usize];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n124_sunstone(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
|
@ -738,6 +940,105 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n126_puppy_running(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
players: [&mut Player; 2],
|
||||
) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
if self.rng.range(0..120) == 10 {
|
||||
self.action_num = 2;
|
||||
self.action_counter = 0;
|
||||
self.anim_num = 1;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_ref(&players);
|
||||
|
||||
if (self.x - player.x).abs() < 0xc000 && self.y - 0x4000 < player.y && self.y + 0x2000 > player.y {
|
||||
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
|
||||
}
|
||||
|
||||
if (self.x - player.x).abs() < 0x4000 && self.y - 0x4000 < player.y && self.y + 0x2000 > player.y {
|
||||
self.action_num = 10;
|
||||
self.direction = if self.x > player.x { Direction::Right } else { Direction::Left };
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > 8 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
}
|
||||
}
|
||||
10 | 11 => {
|
||||
if self.action_num == 10 {
|
||||
self.action_num = 11;
|
||||
self.anim_num = 4;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
if self.flags.hit_bottom_wall() {
|
||||
self.animate(2, 4, 5);
|
||||
} else {
|
||||
self.anim_num = 5;
|
||||
self.anim_counter =0;
|
||||
}
|
||||
|
||||
if self.vel_x < 0 && self.flags.hit_left_wall() {
|
||||
self.vel_x /= -2;
|
||||
self.direction = Direction::Right;
|
||||
}
|
||||
|
||||
if self.vel_x > 0 && self.flags.hit_right_wall() {
|
||||
self.vel_x /= -2;
|
||||
self.direction = Direction::Left;
|
||||
}
|
||||
|
||||
self.vel_x += self.direction.vector_x() * 0x40;
|
||||
|
||||
// what the hell pixel?
|
||||
if self.vel_x > 0x5ff {
|
||||
self.vel_x = 0x400;
|
||||
}
|
||||
|
||||
if self.vel_x < -0x5ff {
|
||||
self.vel_x = -0x400;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// why
|
||||
self.npc_flags.set_interactable(false);
|
||||
for player in players.iter() {
|
||||
if player.controller.trigger_down() {
|
||||
self.npc_flags.set_interactable(true);
|
||||
}
|
||||
}
|
||||
|
||||
self.vel_y += 0x40;
|
||||
if self.vel_y > 0x5ff {
|
||||
self.vel_y = 0x5ff;
|
||||
}
|
||||
|
||||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 6 };
|
||||
|
||||
self.anim_rect = state.constants.npc.n126_puppy_running[self.anim_num as usize + dir_offset];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n131_puppy_sleeping(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > 100 {
|
||||
|
@ -752,6 +1053,153 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n132_puppy_barking(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
players: [&mut Player; 2],
|
||||
) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
if self.rng.range(0..120) == 10 {
|
||||
self.action_num = 2;
|
||||
self.action_counter = 0;
|
||||
self.anim_num = 1;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (self.x - player.x).abs() < 0x8000 && (self.y - player.y).abs() < 0x2000 {
|
||||
self.animate(4, 2, 4);
|
||||
|
||||
if self.anim_num == 4 && self.anim_counter == 0 {
|
||||
state.sound_manager.play_sfx(105);
|
||||
}
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > 8 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
}
|
||||
}
|
||||
10 | 11 => {
|
||||
if self.action_num == 10 {
|
||||
self.action_num = 11;
|
||||
self.anim_num = 0;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
if self.rng.range(0..120) == 10 {
|
||||
self.action_num = 12;
|
||||
self.action_counter = 0;
|
||||
self.anim_num = 1;
|
||||
}
|
||||
}
|
||||
12 => {
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > 8 {
|
||||
self.action_num = 11;
|
||||
self.anim_num = 0;
|
||||
}
|
||||
}
|
||||
100 => {
|
||||
self.action_num = 101;
|
||||
self.action_counter2 = 0;
|
||||
}
|
||||
101 => {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 4 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
if self.anim_num > 4 {
|
||||
if self.action_counter2 > 2 {
|
||||
self.anim_num = 0;
|
||||
self.action_counter2 = 0;
|
||||
} else {
|
||||
self.anim_num = 2;
|
||||
self.action_counter2 += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.anim_num == 4 && self.anim_counter == 0 {
|
||||
state.sound_manager.play_sfx(105);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.vel_y += 0x40;
|
||||
if self.vel_y > 0x5ff {
|
||||
self.vel_y = 0x5ff;
|
||||
}
|
||||
|
||||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 5 };
|
||||
|
||||
self.anim_rect = state.constants.npc.n132_puppy_barking[self.anim_num as usize + dir_offset];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n136_puppy_carried(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
players: [&mut Player; 2],
|
||||
) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
self.anim_counter = 0;
|
||||
|
||||
self.npc_flags.set_interactable(false);
|
||||
}
|
||||
|
||||
if self.rng.range(0..120) == 10 {
|
||||
self.action_num = 2;
|
||||
self.action_counter = 0;
|
||||
self.anim_num = 1;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > 8 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// todo dog stacking?
|
||||
let player_index = 0;
|
||||
let player = &players[player_index];
|
||||
|
||||
self.direction = player.direction;
|
||||
self.y = player.y - 0x1400;
|
||||
self.x = player.x + 0x800 * self.direction.opposite().vector_x();
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
|
||||
|
||||
self.anim_rect = state.constants.npc.n136_puppy_carried[self.anim_num as usize + dir_offset];
|
||||
|
||||
if (player.anim_num & 1) != 0 {
|
||||
self.anim_rect.top += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n143_jenka_collapsed(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
let anim = if self.direction == Direction::Left { 0 } else { 1 };
|
||||
|
||||
|
|
|
@ -198,6 +198,8 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)> for NP
|
|||
53 => self.tick_n053_skullstep_leg(state, npc_list),
|
||||
54 => self.tick_n054_skullstep(state, npc_list),
|
||||
55 => self.tick_n055_kazuma(state),
|
||||
56 => self.tick_n056_tan_beetle(state, players),
|
||||
57 => self.tick_n057_crow(state, players),
|
||||
58 => self.tick_n058_basu(state, players, npc_list),
|
||||
59 => self.tick_n059_eye_door(state, players),
|
||||
60 => self.tick_n060_toroko(state, players),
|
||||
|
@ -260,12 +262,17 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)> for NP
|
|||
118 => self.tick_n118_curly_boss(state, players, npc_list, bullet_manager),
|
||||
119 => self.tick_n119_table_chair(state),
|
||||
120 => self.tick_n120_colon_a(state),
|
||||
121 => self.tick_n121_colon_b(state),
|
||||
123 => self.tick_n123_curly_boss_bullet(state),
|
||||
124 => self.tick_n124_sunstone(state),
|
||||
125 => self.tick_n125_hidden_item(state, npc_list),
|
||||
126 => self.tick_n126_puppy_running(state, players),
|
||||
127 => self.tick_n127_machine_gun_trail_l2(state),
|
||||
128 => self.tick_n128_machine_gun_trail_l3(state),
|
||||
129 => self.tick_n129_fireball_snake_trail(state),
|
||||
131 => self.tick_n131_puppy_sleeping(state),
|
||||
132 => self.tick_n132_puppy_barking(state, players),
|
||||
136 => self.tick_n136_puppy_carried(state, players),
|
||||
137 => self.tick_n137_large_door_frame(state),
|
||||
143 => self.tick_n143_jenka_collapsed(state),
|
||||
149 => self.tick_n149_horizontal_moving_block(state, players, npc_list),
|
||||
|
|
|
@ -146,6 +146,13 @@ impl NPC {
|
|||
players[idx]
|
||||
}
|
||||
|
||||
/// Returns a reference to closest player.
|
||||
pub fn get_closest_player_ref<'a, 'b: 'a>(&self, players: &'a [&'a mut Player; 2]) -> &'b &'a mut Player {
|
||||
let idx = self.get_closest_player_idx_mut(&players);
|
||||
|
||||
&players[idx]
|
||||
}
|
||||
|
||||
/// Returns true if the [NPC] collides with a [Bullet].
|
||||
pub fn collides_with_bullet(&self, bullet: &Bullet) -> bool {
|
||||
(
|
||||
|
|
|
@ -13,6 +13,8 @@ use crate::input::dummy_player_controller::DummyPlayerController;
|
|||
use crate::input::player_controller::PlayerController;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::skin::basic::BasicPlayerSkin;
|
||||
use crate::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin};
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
|
@ -26,18 +28,6 @@ pub enum ControlMode {
|
|||
IronHead,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum PlayerAppearance {
|
||||
Quote = 0,
|
||||
/// Cave Story+ player skins
|
||||
YellowQuote,
|
||||
HumanQuote,
|
||||
HalloweenQuote,
|
||||
ReindeerQuote,
|
||||
Curly,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TargetPlayer {
|
||||
Player1,
|
||||
|
@ -80,7 +70,7 @@ pub struct Player {
|
|||
pub damage: u16,
|
||||
pub air_counter: u16,
|
||||
pub air: u16,
|
||||
pub appearance: PlayerAppearance,
|
||||
pub skin: Box<dyn PlayerSkin>,
|
||||
pub controller: Box<dyn PlayerController>,
|
||||
weapon_offset_y: i8,
|
||||
camera_target_x: i32,
|
||||
|
@ -134,7 +124,7 @@ impl Player {
|
|||
damage: 0,
|
||||
air_counter: 0,
|
||||
air: 0,
|
||||
appearance: PlayerAppearance::Quote,
|
||||
skin: Box::new(BasicPlayerSkin::new("MyChar".to_string())),
|
||||
controller: Box::new(DummyPlayerController::new()),
|
||||
damage_counter: 0,
|
||||
damage_taken: 0,
|
||||
|
@ -146,7 +136,11 @@ impl Player {
|
|||
}
|
||||
|
||||
pub fn get_texture_offset(&self) -> u16 {
|
||||
(self.appearance as u16 % 6) * 64 + if self.equip.has_mimiga_mask() { 32 } else { 0 }
|
||||
if self.equip.has_mimiga_mask() {
|
||||
32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn tick_normal(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
|
@ -555,12 +549,14 @@ impl Player {
|
|||
|
||||
if self.flags.hit_bottom_wall() {
|
||||
if self.cond.interacted() {
|
||||
self.skin.set_state(PlayerAnimationState::Examining);
|
||||
self.anim_num = 11;
|
||||
} else if state.control_flags.control_enabled()
|
||||
&& self.controller.move_up()
|
||||
&& (self.controller.move_left() || self.controller.move_right())
|
||||
{
|
||||
self.cond.set_fallen(true);
|
||||
self.skin.set_state(PlayerAnimationState::WalkingUp);
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 4 {
|
||||
|
@ -579,6 +575,7 @@ impl Player {
|
|||
&& (self.controller.move_left() || self.controller.move_right())
|
||||
{
|
||||
self.cond.set_fallen(true);
|
||||
self.skin.set_state(PlayerAnimationState::Walking);
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 4 {
|
||||
|
@ -599,6 +596,7 @@ impl Player {
|
|||
}
|
||||
|
||||
self.cond.set_fallen(false);
|
||||
self.skin.set_state(PlayerAnimationState::LookingUp);
|
||||
self.anim_num = 5;
|
||||
} else {
|
||||
if self.cond.fallen() {
|
||||
|
@ -606,13 +604,21 @@ impl Player {
|
|||
}
|
||||
|
||||
self.cond.set_fallen(false);
|
||||
self.skin.set_state(PlayerAnimationState::Idle);
|
||||
self.anim_num = 0;
|
||||
}
|
||||
} else if self.controller.look_up() {
|
||||
self.skin.set_state(PlayerAnimationState::FallingLookingUp);
|
||||
self.anim_num = 6;
|
||||
} else if self.controller.look_down() {
|
||||
self.skin.set_state(PlayerAnimationState::FallingLookingDown);
|
||||
self.anim_num = 10;
|
||||
} else {
|
||||
self.skin.set_state(if self.vel_y > 0 {
|
||||
PlayerAnimationState::Jumping
|
||||
} else {
|
||||
PlayerAnimationState::Falling
|
||||
});
|
||||
self.anim_num = if self.vel_y > 0 { 1 } else { 3 };
|
||||
}
|
||||
|
||||
|
@ -648,9 +654,15 @@ impl Player {
|
|||
self.weapon_rect.top += 1;
|
||||
}
|
||||
|
||||
let offset = self.get_texture_offset();
|
||||
self.anim_rect.top += offset;
|
||||
self.anim_rect.bottom += offset;
|
||||
self.skin.tick();
|
||||
self.skin.set_direction(self.direction);
|
||||
self.skin.set_appearance(if self.equip.has_mimiga_mask() {
|
||||
PlayerAppearanceState::MimigaMask
|
||||
} else {
|
||||
PlayerAppearanceState::Default
|
||||
});
|
||||
self.anim_rect = self.skin.animation_frame();
|
||||
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
}
|
||||
|
||||
|
@ -733,42 +745,44 @@ impl GameEntity<&NPCList> for Player {
|
|||
if state.constants.is_switch {
|
||||
let dog_amount = (3000..=3005).filter(|id| state.get_flag(*id as usize)).count();
|
||||
|
||||
let vec_x = self.direction.vector_x() * 0x800;
|
||||
let vec_y = 0x1400;
|
||||
if dog_amount > 0 {
|
||||
let vec_x = self.direction.vector_x() * 0x800;
|
||||
let vec_y = 0x1400;
|
||||
|
||||
if let Some(entry) = state.npc_table.get_entry(136) {
|
||||
let sprite = state.npc_table.get_texture_name(entry.spritesheet_id as u16);
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, sprite)?;
|
||||
if let Some(entry) = state.npc_table.get_entry(136) {
|
||||
let sprite = state.npc_table.get_texture_name(entry.spritesheet_id as u16);
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, sprite)?;
|
||||
|
||||
let (off_x, frame_id) = if self.direction == Direction::Left {
|
||||
(entry.display_bounds.right as i32 * 0x200, 0)
|
||||
} else {
|
||||
(entry.display_bounds.left as i32 * 0x200, 2)
|
||||
};
|
||||
let off_y = entry.display_bounds.top as i32 * 0x200;
|
||||
let (off_x, frame_id) = if self.direction == Direction::Left {
|
||||
(entry.display_bounds.right as i32 * 0x200, 0)
|
||||
} else {
|
||||
(entry.display_bounds.left as i32 * 0x200, 2)
|
||||
};
|
||||
let off_y = entry.display_bounds.top as i32 * 0x200;
|
||||
|
||||
for i in 1..=(dog_amount as i32) {
|
||||
batch.add_rect(
|
||||
interpolate_fix9_scale(
|
||||
self.prev_x - frame.prev_x - off_x - vec_x * i,
|
||||
self.x - frame.x - off_x - vec_x * i,
|
||||
state.frame_time,
|
||||
),
|
||||
interpolate_fix9_scale(
|
||||
self.prev_y - frame.prev_y - off_y - vec_y * i,
|
||||
self.y - frame.y - off_y - vec_y * i,
|
||||
state.frame_time,
|
||||
),
|
||||
&state.constants.npc.n136_puppy_carried[frame_id],
|
||||
);
|
||||
for i in 1..=(dog_amount as i32) {
|
||||
batch.add_rect(
|
||||
interpolate_fix9_scale(
|
||||
self.prev_x - frame.prev_x - off_x - vec_x * i,
|
||||
self.x - frame.x - off_x - vec_x * i,
|
||||
state.frame_time,
|
||||
),
|
||||
interpolate_fix9_scale(
|
||||
self.prev_y - frame.prev_y - off_y - vec_y * i,
|
||||
self.y - frame.y - off_y - vec_y * i,
|
||||
state.frame_time,
|
||||
),
|
||||
&state.constants.npc.n136_puppy_carried[frame_id],
|
||||
);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
if self.shock_counter / 2 % 2 != 0 {
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.current_weapon != 0 {
|
||||
|
@ -791,7 +805,8 @@ impl GameEntity<&NPCList> for Player {
|
|||
}
|
||||
|
||||
{
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "MyChar")?;
|
||||
let batch =
|
||||
state.texture_set.get_or_load_batch(ctx, &state.constants, self.skin.get_skin_texture_name())?;
|
||||
batch.add_rect(
|
||||
interpolate_fix9_scale(
|
||||
self.prev_x - self.display_bounds.left as i32 - frame.prev_x,
|
||||
|
|
112
src/player/skin/basic.rs
Normal file
112
src/player/skin/basic.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
use crate::common::{Color, Direction, Rect};
|
||||
use crate::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BasicPlayerSkin {
|
||||
texture_name: String,
|
||||
color: Color,
|
||||
state: PlayerAnimationState,
|
||||
appearance: PlayerAppearanceState,
|
||||
direction: Direction,
|
||||
tick: u16,
|
||||
}
|
||||
|
||||
impl BasicPlayerSkin {
|
||||
pub fn new(texture_name: String) -> BasicPlayerSkin {
|
||||
BasicPlayerSkin {
|
||||
texture_name,
|
||||
color: Color::new(1.0, 1.0, 1.0, 1.0),
|
||||
state: PlayerAnimationState::Idle,
|
||||
appearance: PlayerAppearanceState::Default,
|
||||
direction: Direction::Left,
|
||||
tick: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerSkin for BasicPlayerSkin {
|
||||
fn animation_frame_for(&self, state: PlayerAnimationState, direction: Direction, tick: u16) -> Rect<u16> {
|
||||
let frame_id = match self.state {
|
||||
PlayerAnimationState::Idle => 0u16,
|
||||
PlayerAnimationState::Walking => {
|
||||
const WALK_INDEXES: [u16; 4] = [1, 0, 2, 0];
|
||||
|
||||
WALK_INDEXES[(self.tick as usize / 4) % 4]
|
||||
}
|
||||
PlayerAnimationState::WalkingUp => {
|
||||
const WALK_UP_INDEXES: [u16; 4] = [4, 3, 5, 3];
|
||||
|
||||
WALK_UP_INDEXES[(self.tick as usize / 4) % 4]
|
||||
}
|
||||
PlayerAnimationState::LookingUp => 3,
|
||||
PlayerAnimationState::Examining => 7,
|
||||
PlayerAnimationState::Sitting => 8,
|
||||
PlayerAnimationState::Collapsed => 9,
|
||||
PlayerAnimationState::Jumping => 2,
|
||||
PlayerAnimationState::Falling => 1,
|
||||
PlayerAnimationState::FallingLookingUp => 4,
|
||||
PlayerAnimationState::FallingLookingDown => 6,
|
||||
PlayerAnimationState::FallingUpsideDown => 10,
|
||||
};
|
||||
|
||||
let y_offset = if self.direction == Direction::Left { 0 } else { 16 }
|
||||
+ match self.appearance {
|
||||
PlayerAppearanceState::Default => 0,
|
||||
PlayerAppearanceState::MimigaMask => 32,
|
||||
PlayerAppearanceState::Custom(i) => (i as u16).saturating_mul(16),
|
||||
};
|
||||
|
||||
Rect::new_size(frame_id * 16, y_offset, 16, 16)
|
||||
}
|
||||
|
||||
fn animation_frame(&self) -> Rect<u16> {
|
||||
self.animation_frame_for(self.state, self.direction, self.tick)
|
||||
}
|
||||
|
||||
fn tick(&mut self) {
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: PlayerAnimationState) {
|
||||
if self.state != state {
|
||||
self.state = state;
|
||||
self.tick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state(&self) -> PlayerAnimationState {
|
||||
self.state
|
||||
}
|
||||
|
||||
fn set_appearance(&mut self, appearance: PlayerAppearanceState) {
|
||||
self.appearance = appearance;
|
||||
}
|
||||
|
||||
fn get_appearance(&mut self) -> PlayerAppearanceState {
|
||||
self.appearance
|
||||
}
|
||||
|
||||
fn set_color(&mut self, color: Color) {
|
||||
self.color = color;
|
||||
}
|
||||
|
||||
fn get_color(&self) -> Color {
|
||||
self.color
|
||||
}
|
||||
|
||||
fn set_direction(&mut self, direction: Direction) {
|
||||
self.direction = direction;
|
||||
}
|
||||
|
||||
fn get_direction(&self) -> Direction {
|
||||
self.direction
|
||||
}
|
||||
|
||||
fn get_skin_texture_name(&self) -> &str {
|
||||
self.texture_name.as_str()
|
||||
}
|
||||
|
||||
fn get_mask_texture_name(&self) -> &str {
|
||||
""
|
||||
}
|
||||
}
|
98
src/player/skin/mod.rs
Normal file
98
src/player/skin/mod.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use crate::bitfield;
|
||||
use crate::common::{Color, Direction, Rect};
|
||||
|
||||
pub mod basic;
|
||||
pub mod pxchar;
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PlayerSkinFlags(u16);
|
||||
impl Debug;
|
||||
|
||||
pub supports_color, set_supports_color: 0;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
/// Represents a player animation state.
|
||||
pub enum PlayerAnimationState {
|
||||
Idle,
|
||||
Walking,
|
||||
WalkingUp,
|
||||
LookingUp,
|
||||
Examining,
|
||||
Sitting,
|
||||
Collapsed,
|
||||
Jumping,
|
||||
Falling,
|
||||
FallingLookingUp,
|
||||
FallingLookingDown,
|
||||
FallingUpsideDown,
|
||||
}
|
||||
|
||||
/// Represents an alternative appearance of player eg. wearing a Mimiga Mask
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PlayerAppearanceState {
|
||||
Default,
|
||||
MimigaMask,
|
||||
/// Freeware hacks add a <MIMxxxx TSC instruction that sets an offset in player spritesheet to
|
||||
/// have more than two player appearances.
|
||||
Custom(u8),
|
||||
}
|
||||
|
||||
/// Represents an interface for implementations of player skin providers.
|
||||
pub trait PlayerSkin: PlayerSkinClone {
|
||||
/// Returns animation frame bounds for specified state.
|
||||
fn animation_frame_for(&self, state: PlayerAnimationState, direction: Direction, tick: u16) -> Rect<u16>;
|
||||
|
||||
/// Returns animation frame bounds for current state.
|
||||
fn animation_frame(&self) -> Rect<u16>;
|
||||
|
||||
/// Updates internal animation counters, must be called every game tick.
|
||||
fn tick(&mut self);
|
||||
|
||||
/// Sets the current animation state.
|
||||
fn set_state(&mut self, state: PlayerAnimationState);
|
||||
|
||||
/// Returns current animation state.
|
||||
fn get_state(&self) -> PlayerAnimationState;
|
||||
|
||||
/// Sets the current appearance of skin.
|
||||
fn set_appearance(&mut self, appearance: PlayerAppearanceState);
|
||||
|
||||
/// Returns current appearance of skin.
|
||||
fn get_appearance(&mut self) -> PlayerAppearanceState;
|
||||
|
||||
/// Sets the current color of skin.
|
||||
fn set_color(&mut self, color: Color);
|
||||
|
||||
/// Returns the current color of skin.
|
||||
fn get_color(&self) -> Color;
|
||||
|
||||
/// Sets the current direction;
|
||||
fn set_direction(&mut self, direction: Direction);
|
||||
|
||||
/// Returns the current direction;
|
||||
fn get_direction(&self) -> Direction;
|
||||
|
||||
/// Returns the name of skin spritesheet texture.
|
||||
fn get_skin_texture_name(&self) -> &str;
|
||||
|
||||
/// Returns the name of skin color mask texture.
|
||||
fn get_mask_texture_name(&self) -> &str;
|
||||
}
|
||||
|
||||
pub trait PlayerSkinClone {
|
||||
fn clone_box(&self) -> Box<dyn PlayerSkin>;
|
||||
}
|
||||
|
||||
impl<T: 'static + PlayerSkin + Clone> PlayerSkinClone for T {
|
||||
fn clone_box(&self) -> Box<dyn PlayerSkin> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn PlayerSkin> {
|
||||
fn clone(&self) -> Box<dyn PlayerSkin> {
|
||||
self.clone_box()
|
||||
}
|
||||
}
|
141
src/player/skin/pxchar.rs
Normal file
141
src/player/skin/pxchar.rs
Normal file
|
@ -0,0 +1,141 @@
|
|||
use std::io::{Cursor, Read};
|
||||
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
use num_traits::{AsPrimitive, Num};
|
||||
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::error::GameError::{ResourceLoadError, InvalidValue};
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::context::Context;
|
||||
|
||||
struct PxCharReader<T: AsRef<[u8]>> {
|
||||
cursor: Cursor<T>,
|
||||
bit_ptr: usize,
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> PxCharReader<T> {
|
||||
fn read_integer_shifted<O>(&mut self, bits: usize) -> GameResult<O>
|
||||
where
|
||||
O: 'static + Num + Copy,
|
||||
u128: AsPrimitive<O>,
|
||||
u64: AsPrimitive<O>,
|
||||
u32: AsPrimitive<O>,
|
||||
u16: AsPrimitive<O>,
|
||||
u8: AsPrimitive<O>,
|
||||
{
|
||||
let shift = self.bit_ptr & 7;
|
||||
self.cursor.set_position((self.bit_ptr / 8) as u64);
|
||||
self.bit_ptr += bits;
|
||||
|
||||
match bits {
|
||||
// fast paths for aligned bit sizes
|
||||
0 => Ok((0u8).as_()),
|
||||
8 if shift == 0 => Ok(self.cursor.read_u8()?.as_()),
|
||||
16 if shift == 0 => Ok(self.cursor.read_u16::<LE>()?.as_()),
|
||||
24 if shift == 0 => Ok(self.cursor.read_u24::<LE>()?.as_()),
|
||||
32 if shift == 0 => Ok(self.cursor.read_u32::<LE>()?.as_()),
|
||||
48 if shift == 0 => Ok(self.cursor.read_u48::<LE>()?.as_()),
|
||||
64 if shift == 0 => Ok(self.cursor.read_u64::<LE>()?.as_()),
|
||||
128 if shift == 0 => Ok(self.cursor.read_u128::<LE>()?.as_()),
|
||||
// paths for bit shifted numbers
|
||||
1..=8 => Ok(((self.cursor.read_u16::<LE>()? >> shift) & ((1 << bits) - 1)).as_()),
|
||||
9..=16 => Ok(((self.cursor.read_u24::<LE>()? >> shift) & ((1 << bits) - 1)).as_()),
|
||||
17..=24 => Ok(((self.cursor.read_u32::<LE>()? >> shift) & ((1 << bits) - 1)).as_()),
|
||||
25..=40 => Ok(((self.cursor.read_u48::<LE>()? >> shift) & ((1 << bits) - 1)).as_()),
|
||||
41..=56 => Ok(((self.cursor.read_u64::<LE>()? >> shift) & ((1 << bits) - 1)).as_()),
|
||||
57..=120 => Ok(((self.cursor.read_u128::<LE>()? >> shift) & ((1 << bits) - 1)).as_()),
|
||||
121..=128 => {
|
||||
let mut result = self.cursor.read_u128::<LE>()? >> shift;
|
||||
result |= (self.cursor.read_u8()? as u128) << (128 - shift);
|
||||
Ok(result.as_())
|
||||
}
|
||||
_ => Err(InvalidValue("Cannot read integers bigger than 128 bits.".to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_ranged<O>(&mut self, max_value: u32) -> GameResult<O>
|
||||
where
|
||||
O: 'static + Num + Copy,
|
||||
u128: AsPrimitive<O>,
|
||||
u64: AsPrimitive<O>,
|
||||
u32: AsPrimitive<O>,
|
||||
u16: AsPrimitive<O>,
|
||||
u8: AsPrimitive<O>,
|
||||
{
|
||||
self.read_integer_shifted((32 - max_value.next_power_of_two().leading_zeros()) as usize)
|
||||
}
|
||||
|
||||
fn read_string(&mut self, max_length: u32) -> GameResult<String> {
|
||||
let mut output = Vec::new();
|
||||
|
||||
let length = self.read_ranged::<u32>(max_length)?;
|
||||
output.reserve(length as usize);
|
||||
|
||||
for _ in 0..length {
|
||||
output.push(self.read_integer_shifted::<u8>(8)?)
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> Read for PxCharReader<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
// align to byte
|
||||
if self.bit_ptr & 7 != 0 {
|
||||
self.bit_ptr = (self.bit_ptr + 7) & !7;
|
||||
self.cursor.read_u8()?;
|
||||
}
|
||||
|
||||
let result = self.cursor.read(buf);
|
||||
self.bit_ptr = (self.cursor.position() * 8) as usize;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PxChar {}
|
||||
|
||||
impl PxChar {
|
||||
pub fn load_pxchar(path: &str, ctx: &mut Context) -> GameResult<PxChar> {
|
||||
let mut reader = PxCharReader {
|
||||
cursor: Cursor::new({
|
||||
let mut stream = filesystem::open(ctx, path)?;
|
||||
let mut data = Vec::new();
|
||||
stream.read_to_end(&mut data)?;
|
||||
data
|
||||
}),
|
||||
bit_ptr: 0,
|
||||
};
|
||||
|
||||
let mut magic_buf = [0u8; 6];
|
||||
reader.read_exact(&mut magic_buf)?;
|
||||
|
||||
if &magic_buf != b"PXCHAR" {
|
||||
return Err(ResourceLoadError("Invalid magic number.".to_string()));
|
||||
}
|
||||
|
||||
let version = reader.read_u8()?;
|
||||
if version > 5 {
|
||||
return Err(ResourceLoadError("Unsupported version.".to_string()));
|
||||
}
|
||||
|
||||
let string = reader.read_string(0x100)?;
|
||||
println!("{}", string);
|
||||
let description = reader.read_string(0x100)?;
|
||||
println!("{}", description);
|
||||
|
||||
Ok(PxChar {})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::framework::filesystem::mount_vfs;
|
||||
use crate::framework::vfs::PhysicalFS;
|
||||
|
||||
let mut ctx = crate::framework::context::Context::new();
|
||||
mount_vfs(&mut ctx, Box::new(PhysicalFS::new("data".as_ref(), true)));
|
||||
|
||||
println!("lol");
|
||||
PxChar::load_pxchar("/Player.pxchar", &mut ctx).unwrap();
|
||||
}
|
|
@ -2,9 +2,9 @@ use log::info;
|
|||
use num_traits::{abs, clamp};
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{fix9_scale, interpolate_fix9_scale, Color, Direction, FadeDirection, FadeState, Rect};
|
||||
use crate::common::{Color, Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect};
|
||||
use crate::components::boss_life_bar::BossLifeBar;
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::components::draw_common::{Alignment, draw_number};
|
||||
use crate::components::flash::Flash;
|
||||
use crate::components::hud::HUD;
|
||||
use crate::components::stage_select::StageSelect;
|
||||
|
@ -14,7 +14,7 @@ use crate::framework::backend::SpriteBatchCommand;
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::framework::graphics::{draw_rect, BlendMode, FilterMode};
|
||||
use crate::framework::graphics::{BlendMode, draw_rect, FilterMode};
|
||||
use crate::framework::ui::Components;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::inventory::{Inventory, TakeExperienceResult};
|
||||
|
@ -22,10 +22,10 @@ use crate::npc::boss::BossNPC;
|
|||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::{Player, PlayerAppearance, TargetPlayer};
|
||||
use crate::player::{Player, TargetPlayer};
|
||||
use crate::rng::XorShift;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::shared_game_state::{Season, SharedGameState};
|
||||
use crate::stage::{BackgroundType, Stage};
|
||||
use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
|
||||
|
@ -120,7 +120,6 @@ impl GameScene {
|
|||
pub fn add_player2(&mut self) {
|
||||
self.player2.cond.set_alive(true);
|
||||
self.player2.cond.set_hidden(self.player1.cond.hidden());
|
||||
self.player2.appearance = PlayerAppearance::YellowQuote;
|
||||
self.player2.x = self.player1.x;
|
||||
self.player2.y = self.player1.y;
|
||||
self.player2.vel_x = self.player1.vel_x;
|
||||
|
@ -1117,6 +1116,8 @@ impl GameScene {
|
|||
)?;
|
||||
|
||||
self.player1.tick_map_collisions(state, &self.npc_list, &mut self.stage);
|
||||
self.player2.tick_map_collisions(state, &self.npc_list, &mut self.stage);
|
||||
|
||||
self.player1.tick_npc_collisions(
|
||||
TargetPlayer::Player1,
|
||||
state,
|
||||
|
@ -1124,8 +1125,6 @@ impl GameScene {
|
|||
&mut self.boss,
|
||||
&mut self.inventory_player1,
|
||||
);
|
||||
|
||||
self.player2.tick_map_collisions(state, &self.npc_list, &mut self.stage);
|
||||
self.player2.tick_npc_collisions(
|
||||
TargetPlayer::Player2,
|
||||
state,
|
||||
|
@ -1313,13 +1312,13 @@ impl Scene for GameScene {
|
|||
state.npc_table.tex_npc1_name = ["Npc/", &self.stage.data.npc1.filename()].join("");
|
||||
state.npc_table.tex_npc2_name = ["Npc/", &self.stage.data.npc2.filename()].join("");
|
||||
|
||||
if state.constants.is_cs_plus {
|
||||
/*if state.constants.is_cs_plus {
|
||||
match state.season {
|
||||
Season::Halloween => self.player1.appearance = PlayerAppearance::HalloweenQuote,
|
||||
Season::Christmas => self.player1.appearance = PlayerAppearance::ReindeerQuote,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
self.boss.boss_type = self.stage.data.boss_no as u16;
|
||||
self.player1.target_x = self.player1.x;
|
||||
|
@ -1344,8 +1343,14 @@ impl Scene for GameScene {
|
|||
state.touch_controls.interact_icon = false;
|
||||
}
|
||||
|
||||
if self.intro_mode && (self.player1.controller.trigger_menu_ok() || self.tick >= 500) {
|
||||
state.next_scene = Some(Box::new(TitleScene::new()));
|
||||
if self.intro_mode {
|
||||
if let TextScriptExecutionState::WaitTicks(_, _, 9999) = state.textscript_vm.state {
|
||||
state.next_scene = Some(Box::new(TitleScene::new()));
|
||||
}
|
||||
|
||||
if self.player1.controller.trigger_menu_ok() {
|
||||
state.next_scene = Some(Box::new(TitleScene::new()));
|
||||
}
|
||||
}
|
||||
|
||||
match state.textscript_vm.mode {
|
||||
|
|
|
@ -84,10 +84,6 @@ impl TitleScene {
|
|||
|
||||
// asset copyright for freeware version
|
||||
static COPYRIGHT_PIXEL: &str = "2004.12 Studio Pixel";
|
||||
// asset copyright for Nicalis
|
||||
static COPYRIGHT_NICALIS: &str = "@2011 NICALIS INC.";
|
||||
static COPYRIGHT_NICALIS_SWITCH: &str = "@2017 NICALIS INC.";
|
||||
|
||||
static DISCORD_LINK: &str = "https://discord.gg/fbRsNNB";
|
||||
|
||||
impl Scene for TitleScene {
|
||||
|
@ -261,14 +257,7 @@ impl Scene for TitleScene {
|
|||
}
|
||||
|
||||
self.draw_text_centered(VERSION_BANNER.as_str(), state.canvas_size.1 - 15.0, state, ctx)?;
|
||||
|
||||
if state.constants.is_switch {
|
||||
self.draw_text_centered(COPYRIGHT_NICALIS_SWITCH, state.canvas_size.1 - 30.0, state, ctx)?;
|
||||
} else if state.constants.is_cs_plus {
|
||||
self.draw_text_centered(COPYRIGHT_NICALIS, state.canvas_size.1 - 30.0, state, ctx)?;
|
||||
} else {
|
||||
self.draw_text_centered(COPYRIGHT_PIXEL, state.canvas_size.1 - 30.0, state, ctx)?;
|
||||
}
|
||||
self.draw_text_centered(COPYRIGHT_PIXEL, state.canvas_size.1 - 30.0, state, ctx)?;
|
||||
|
||||
match self.current_menu {
|
||||
CurrentMenu::MainMenu => {
|
||||
|
|
|
@ -731,7 +731,6 @@ impl TextScriptVM {
|
|||
FromPrimitive::from_i32(read_cur_varint(&mut cursor).unwrap_or_else(|_| OpCode::END as i32));
|
||||
|
||||
if let Some(op) = op_maybe {
|
||||
log::info!("opcode: {:?}", op);
|
||||
match op {
|
||||
OpCode::_NOP => {
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
|
Loading…
Reference in a new issue