doukutsu-rs/src/game/player/mod.rs

1095 lines
38 KiB
Rust

use std::clone::Clone;
use num_derive::FromPrimitive;
use num_traits::clamp;
use crate::common::{interpolate_fix9_scale, Condition, Direction, Equipment, Flag, Rect};
use crate::components::number_popup::NumberPopup;
use crate::entity::GameEntity;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::frame::Frame;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::skin::basic::BasicPlayerSkin;
use crate::game::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin};
use crate::game::shared_game_state::SharedGameState;
use crate::input::dummy_player_controller::DummyPlayerController;
use crate::input::player_controller::PlayerController;
use crate::util::rng::RNG;
mod player_hit;
pub mod skin;
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)]
#[repr(u8)]
pub enum ControlMode {
Normal = 0,
IronHead,
}
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum TargetPlayer {
Player1,
Player2,
}
impl TargetPlayer {
#[inline]
pub fn index(self) -> usize {
self as usize
}
}
#[derive(PartialEq, Eq, Copy, Clone)]
enum BoosterSwitch {
None,
Up,
Left,
Right,
Down,
}
#[derive(Clone)]
struct DogStack {
pub offset_x: f32,
pub speed: f32,
pub prev_speed: f32,
}
impl DogStack {
const TENSION: f32 = 0.25;
const DAMPENING: f32 = 0.1;
const MULT: f32 = 1.2;
pub fn new() -> DogStack {
DogStack { offset_x: 0.0, speed: 0.0, prev_speed: 0.0 }
}
pub fn tick(&mut self) {
self.prev_speed = self.speed;
self.speed += -self.offset_x * DogStack::TENSION as f32 - self.speed * DogStack::DAMPENING;
self.offset_x += self.speed;
}
}
#[derive(Clone)]
pub struct Player {
pub x: i32,
pub y: i32,
pub vel_x: i32,
pub vel_y: i32,
pub target_x: i32,
pub target_y: i32,
pub camera_target_x: i32,
pub camera_target_y: i32,
pub prev_x: i32,
pub prev_y: i32,
pub life: u16,
pub max_life: u16,
pub cond: Condition,
pub flags: Flag,
pub equip: Equipment,
pub direction: Direction,
pub display_bounds: Rect<u32>,
pub hit_bounds: Rect<u32>,
pub control_mode: ControlMode,
pub question: bool,
pub booster_fuel: u32,
pub up: bool,
pub down: bool,
pub shock_counter: u8,
pub xp_counter: u8,
pub current_weapon: u8,
pub stars: u8,
pub damage: u16,
pub air_counter: u16,
pub air: u16,
pub skin: Box<dyn PlayerSkin>,
pub controller: Box<dyn PlayerController>,
pub damage_popup: NumberPopup,
pub exp_popup: NumberPopup,
strafe_up: bool,
weapon_offset_y: i8,
splash: bool,
tick: u8,
booster_switch: BoosterSwitch,
pub anim_num: u16,
anim_counter: u16,
anim_rect: Rect<u16>,
weapon_rect: Rect<u16>,
dog_stack: Vec<DogStack>,
pub has_dog: bool,
pub teleport_counter: u16,
}
impl Player {
pub fn new(state: &mut SharedGameState, ctx: &mut Context) -> Player {
let constants = &state.constants;
let skin = Box::new(BasicPlayerSkin::new("MyChar".to_string(), state, ctx));
Player {
x: 0,
y: 0,
vel_x: 0,
vel_y: 0,
target_x: 0,
target_y: 0,
camera_target_x: 0,
camera_target_y: 0,
prev_x: 0,
prev_y: 0,
life: constants.player.life,
max_life: constants.player.max_life,
cond: Condition(0),
flags: Flag(0),
equip: Equipment(0),
direction: Direction::Right,
display_bounds: skin.get_display_bounds(),
hit_bounds: skin.get_hit_bounds(),
control_mode: constants.player.control_mode,
question: false,
booster_fuel: 0,
splash: false,
up: false,
down: false,
current_weapon: 0,
weapon_offset_y: 0,
shock_counter: 0,
xp_counter: 0,
tick: 0,
booster_switch: BoosterSwitch::None,
stars: 0,
damage: 0,
air_counter: 0,
air: 0,
skin,
controller: Box::new(DummyPlayerController::new()),
damage_popup: NumberPopup::new(),
exp_popup: NumberPopup::new(),
strafe_up: false,
anim_num: 0,
anim_counter: 0,
anim_rect: constants.player.frames_right[0],
weapon_rect: Rect::new(0, 0, 0, 0),
dog_stack: Vec::new(),
has_dog: false,
teleport_counter: 0,
}
}
pub fn get_texture_offset(&self) -> u16 {
if self.equip.has_mimiga_mask() {
32
} else {
0
}
}
pub fn load_skin(&mut self, texture_name: String, state: &mut SharedGameState, ctx: &mut Context) {
self.skin = Box::new(BasicPlayerSkin::new(texture_name, state, ctx));
self.display_bounds = self.skin.get_display_bounds();
self.hit_bounds = self.skin.get_hit_bounds();
}
fn tick_normal(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
if !state.control_flags.interactions_disabled() && state.control_flags.control_enabled() {
if self.equip.has_air_tank() {
self.air = 1000;
self.air_counter = 0;
} else if !state.settings.god_mode && self.flags.in_water() {
self.air_counter = 60;
if self.air > 0 {
self.air -= 1;
} else if state.get_flag(4000) {
state.textscript_vm.start_script(1100);
} else {
// Switch uses player sprite for drowned effect
if !state.constants.is_switch {
self.cond.set_hidden(true);
state.create_caret(self.x, self.y, CaretType::DrownedQuote, self.direction);
}
state.textscript_vm.start_script(41);
}
} else {
self.air = 1000;
if self.air_counter > 0 {
self.air_counter -= 1;
}
}
}
if self.cond.hidden() {
return Ok(());
}
let physics = if self.flags.in_water() {
state.constants.player.water_physics
} else {
state.constants.player.air_physics
};
self.question = false;
if !state.control_flags.control_enabled() {
self.booster_switch = BoosterSwitch::None;
}
if state.control_flags.control_enabled() && state.settings.allow_strafe {
if self.controller.trigger_strafe() {
if self.controller.move_up() {
self.strafe_up = true;
}
} else if !self.controller.strafe() {
self.strafe_up = false;
}
} else {
self.strafe_up = false;
}
// ground movement
if self.flags.hit_bottom_wall() || self.flags.hit_right_slope() || self.flags.hit_left_slope() {
self.booster_switch = BoosterSwitch::None;
if state.settings.infinite_booster {
self.booster_fuel = u32::MAX;
} else if self.equip.has_booster_0_8() || self.equip.has_booster_2_0() {
self.booster_fuel = state.constants.booster.fuel;
} else {
self.booster_fuel = 0;
}
if state.control_flags.control_enabled() {
let only_down = self.controller.move_down()
&& !self.controller.move_up()
&& !self.controller.move_left()
&& !self.controller.move_right()
&& !self.controller.shoot()
&& !self.controller.jump()
&& !self.controller.prev_weapon()
&& !self.controller.next_weapon()
&& !self.controller.map()
&& !self.controller.inventory()
&& !self.controller.strafe();
// Leaving the skip button unchecked as a "feature" :)
if self.controller.trigger_down()
&& only_down
&& !self.cond.interacted()
&& !state.control_flags.interactions_disabled()
{
self.cond.set_interacted(true);
self.question = true;
} else {
if self.controller.move_left() && self.vel_x > -physics.max_dash {
self.vel_x -= physics.dash_ground;
}
if self.controller.move_right() && self.vel_x < physics.max_dash {
self.vel_x += physics.dash_ground;
}
if !self.controller.strafe() || !state.settings.allow_strafe {
if self.controller.move_left() {
self.direction = Direction::Left;
}
if self.controller.move_right() {
self.direction = Direction::Right;
}
}
}
}
if !self.cond.increase_acceleration() {
if self.vel_x < 0 {
if self.vel_x > -physics.resist {
self.vel_x = 0;
} else {
self.vel_x += physics.resist;
}
}
if self.vel_x > 0 {
if self.vel_x < physics.resist {
self.vel_x = 0;
} else {
self.vel_x -= physics.resist;
}
}
}
} else {
// air movement
if state.control_flags.control_enabled() {
if self.controller.trigger_jump() && self.booster_fuel != 0 {
if self.equip.has_booster_0_8() {
self.booster_switch = BoosterSwitch::Up;
if self.vel_y > 0x100 {
self.vel_y /= 2;
}
}
if state.settings.infinite_booster || self.equip.has_booster_2_0() {
if self.controller.move_up() {
self.booster_switch = BoosterSwitch::Up;
self.vel_x = 0;
self.vel_y = state.constants.booster.b2_0_up;
} else if self.controller.move_left() {
self.booster_switch = BoosterSwitch::Left;
self.vel_x = state.constants.booster.b2_0_left;
self.vel_y = 0;
} else if self.controller.move_right() {
self.booster_switch = BoosterSwitch::Right;
self.vel_x = state.constants.booster.b2_0_right;
self.vel_y = 0;
} else if self.controller.move_down() {
self.booster_switch = BoosterSwitch::Down;
self.vel_x = 0;
self.vel_y = state.constants.booster.b2_0_down;
} else {
self.booster_switch = BoosterSwitch::Up;
self.vel_x = 0;
self.vel_y = state.constants.booster.b2_0_up_nokey;
}
}
}
if self.controller.move_left() && self.vel_x > -physics.max_dash {
self.vel_x -= physics.dash_air;
}
if self.controller.move_right() && self.vel_x < physics.max_dash {
self.vel_x += physics.dash_air;
}
if !self.controller.strafe() || !state.settings.allow_strafe {
if self.controller.look_left() {
self.direction = Direction::Left;
}
if self.controller.look_right() {
self.direction = Direction::Right;
}
}
}
if (state.settings.infinite_booster || self.equip.has_booster_2_0())
&& self.booster_switch != BoosterSwitch::None
&& (!self.controller.jump() || self.booster_fuel == 0)
{
match self.booster_switch {
BoosterSwitch::Left | BoosterSwitch::Right => self.vel_x /= 2,
BoosterSwitch::Up => self.vel_y /= 2,
_ => (),
}
}
if self.booster_fuel == 0 || !self.controller.jump() {
self.booster_switch = BoosterSwitch::None;
}
}
// jumping
if state.control_flags.control_enabled() {
self.up = self.controller.move_up() || self.strafe_up;
self.down = self.controller.move_down() && !self.flags.hit_bottom_wall();
if self.controller.trigger_jump()
&& (self.flags.hit_bottom_wall() || self.flags.hit_right_slope() || self.flags.hit_left_slope())
&& !self.flags.force_up()
{
self.vel_y = -physics.jump;
state.sound_manager.play_sfx(15);
}
}
// stop interacting when moved
if state.control_flags.control_enabled()
&& (self.controller.move_left()
|| self.controller.move_right()
|| self.controller.move_up()
|| self.controller.jump()
|| self.controller.shoot())
{
self.cond.set_interacted(false);
}
// booster losing fuel
if self.booster_switch != BoosterSwitch::None && self.booster_fuel != 0 {
self.booster_fuel -= 1;
}
// wind / current forces
if self.flags.force_left() {
self.vel_x -= 0x88;
}
if self.flags.force_up() {
self.vel_y -= 0x80;
}
if self.flags.force_right() {
self.vel_x += 0x88;
}
if self.flags.force_down() {
self.vel_y += 0x55;
}
if (state.settings.infinite_booster || self.equip.has_booster_2_0())
&& self.booster_switch != BoosterSwitch::None
{
match self.booster_switch {
BoosterSwitch::Left | BoosterSwitch::Right => {
if self.flags.hit_left_wall() || self.flags.hit_right_wall() {
self.vel_y = -0x100;
}
let mut booster_dir = self.direction;
if self.controller.strafe() && state.settings.allow_strafe {
if self.controller.move_left() {
self.booster_switch = BoosterSwitch::Left;
} else if self.controller.move_right() {
self.booster_switch = BoosterSwitch::Right;
}
if self.booster_switch == BoosterSwitch::Left {
booster_dir = Direction::Left;
} else if self.booster_switch == BoosterSwitch::Right {
booster_dir = Direction::Right;
}
}
self.vel_x += match booster_dir {
Direction::Left => -0x20,
Direction::Right => 0x20,
_ => 0,
};
if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 {
if self.direction == Direction::Left || self.direction == Direction::Right {
state.create_caret(
self.x - (0x400 * self.direction.vector_x()),
self.y + 0x400,
CaretType::Exhaust,
booster_dir.opposite(),
);
}
state.sound_manager.play_sfx(113);
}
}
BoosterSwitch::Up => {
self.vel_y -= 0x20;
if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 {
state.create_caret(self.x, self.y + 0xc00, CaretType::Exhaust, Direction::Bottom);
state.sound_manager.play_sfx(113);
}
}
BoosterSwitch::Down if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 => {
state.create_caret(self.x, self.y - 0xc00, CaretType::Exhaust, Direction::Up);
state.sound_manager.play_sfx(113);
}
_ => {}
}
} else if self.flags.force_up() {
self.vel_y += physics.gravity_ground;
} else if self.equip.has_booster_0_8() && self.booster_switch != BoosterSwitch::None && self.vel_y > -0x400 {
self.vel_y -= 0x20;
if self.booster_fuel % 3 == 0 {
state.create_caret(
self.x,
self.y + self.hit_bounds.bottom as i32 / 2,
CaretType::Exhaust,
Direction::Bottom,
);
state.sound_manager.play_sfx(113);
}
// bounce off of ceiling
if self.flags.hit_top_wall() {
self.vel_y = 0x200; // 1.0fix9
}
} else if self.vel_y < 0 && state.control_flags.control_enabled() && self.controller.jump() {
self.vel_y += physics.gravity_air;
} else {
self.vel_y += physics.gravity_ground;
}
if !state.control_flags.control_enabled() || !self.controller.trigger_jump() {
if self.flags.hit_right_slope() && self.vel_x < 0 {
self.vel_y = -self.vel_x;
}
if self.flags.hit_left_slope() && self.vel_x > 0 {
self.vel_y = self.vel_x;
}
if (self.flags.hit_bottom_wall() && self.flags.hit_right_higher_half() && self.vel_x < 0)
|| (self.flags.hit_bottom_wall() && self.flags.hit_left_higher_half() && self.vel_x > 0)
|| (self.flags.hit_bottom_wall()
&& self.flags.hit_left_lower_half()
&& self.flags.hit_right_lower_half())
{
self.vel_y = 0x400; // 2.0fix9
}
}
let max_move = if self.flags.in_water()
&& !(self.flags.force_left()
|| self.flags.force_up()
|| self.flags.force_right()
|| self.flags.force_down())
{
state.constants.player.water_physics.max_move
} else {
state.constants.player.air_physics.max_move
};
self.vel_x = self.vel_x.clamp(-max_move, max_move);
self.vel_y = self.vel_y.clamp(-max_move, max_move);
if !self.splash && self.flags.in_water() {
let vertical_splash = !self.flags.hit_bottom_wall() && self.vel_y > 0x200;
let horizontal_splash = self.vel_x > 0x200 || self.vel_x < -0x200;
if vertical_splash || horizontal_splash {
let mut droplet = NPC::create(73, &state.npc_table);
droplet.cond.set_alive(true);
droplet.y = self.y;
droplet.direction =
if self.flags.water_splash_facing_right() { Direction::Right } else { Direction::Left };
for _ in 0..7 {
droplet.x = self.x + (state.game_rng.range(-8..8) * 0x200) as i32;
droplet.vel_x = self.vel_x + state.game_rng.range(-0x200..0x200);
droplet.vel_y = match () {
_ if vertical_splash => state.game_rng.range(-0x200..0x80) - (self.vel_y / 2),
_ if horizontal_splash => state.game_rng.range(-0x200..0x80),
_ => 0,
};
let _ = npc_list.spawn(0x100, droplet.clone());
}
state.sound_manager.play_sfx(56);
}
self.splash = true;
}
if !self.flags.in_water() {
self.splash = false;
}
// spike damage
if self.flags.hit_by_spike() {
self.damage(10, state, npc_list);
}
// camera
self.camera_target_x = clamp(self.camera_target_x + self.direction.vector_x() * 0x200, -0x8000, 0x8000);
if state.control_flags.control_enabled() && self.controller.look_up() {
self.camera_target_y -= 0x200;
if self.camera_target_y < -0x8000 {
// -64.0fix9
self.camera_target_y = -0x8000;
}
} else if state.control_flags.control_enabled() && self.controller.look_down() {
self.camera_target_y += 0x200;
if self.camera_target_y > 0x8000 {
// -64.0fix9
self.camera_target_y = 0x8000;
}
} else {
if self.camera_target_y > 0x200 {
self.camera_target_y -= 0x200;
}
if self.camera_target_y < -0x200 {
self.camera_target_y += 0x200;
}
}
self.target_x = self.x + self.camera_target_x;
self.target_y = self.y + self.camera_target_y;
if self.vel_x > physics.resist || self.vel_x < -physics.resist {
self.x += self.vel_x;
}
self.y += self.vel_y;
Ok(())
}
fn tick_ironhead(&mut self, state: &mut SharedGameState) -> GameResult {
self.up = false;
self.down = false;
if state.control_flags.control_enabled() {
if self.controller.move_left() || self.controller.move_right() {
if self.controller.move_left() {
self.vel_x -= 0x100;
}
if self.controller.move_right() {
self.vel_x += 0x100;
}
} else if self.vel_x > 0x7f || self.vel_x < -0x7f {
self.vel_x += 0x80 * -self.vel_x.signum();
} else {
self.vel_x = 0;
}
if self.controller.move_up() || self.controller.move_down() {
if self.controller.move_up() {
self.vel_y -= 0x100;
}
if self.controller.move_down() {
self.vel_y += 0x100;
}
} else if self.vel_y > 0x7f || self.vel_y < -0x7f {
self.vel_y += 0x80 * -self.vel_y.signum();
} else {
self.vel_y = 0;
}
if state.settings.noclip {
self.target_x = self.x + self.camera_target_x;
self.target_y = self.y + self.camera_target_y;
}
} else {
if self.vel_x > 0x7f || self.vel_x < -0x7f {
self.vel_x += 0x80 * -self.vel_x.signum();
} else {
self.vel_x = 0;
}
if self.vel_y > 0x7f || self.vel_y < -0x7f {
self.vel_y += 0x80 * -self.vel_y.signum();
} else {
self.vel_y = 0;
}
}
//Toggles bonk particles
if !state.settings.noclip {
if self.vel_y < -0x200 && self.flags.hit_top_wall() {
state.create_caret(
self.x,
self.y - self.hit_bounds.top as i32,
CaretType::LittleParticles,
Direction::FacingPlayer,
);
}
if self.vel_y > 0x200 && self.flags.hit_bottom_wall() {
state.create_caret(
self.x,
self.y + self.hit_bounds.bottom as i32,
CaretType::LittleParticles,
Direction::FacingPlayer,
);
}
}
self.vel_x = self.vel_x.clamp(-0x400, 0x400);
self.vel_y = self.vel_y.clamp(-0x400, 0x400);
if self.controller.move_left() && self.controller.move_up() {
if self.vel_x < -0x30c {
self.vel_x = -0x30c;
}
if self.vel_y < -0x30c {
self.vel_y = -0x30c;
}
}
if self.controller.move_right() && self.controller.move_up() {
if self.vel_x > 0x30c {
self.vel_x = 0x30c;
}
if self.vel_y < -0x30c {
self.vel_y = -0x30c;
}
}
if self.controller.move_left() && self.controller.move_down() {
if self.vel_x < -0x30c {
self.vel_x = -0x30c;
}
if self.vel_y > 0x30c {
self.vel_y = 0x30c;
}
}
if self.controller.move_right() && self.controller.move_down() {
if self.vel_x > 0x30c {
self.vel_x = 0x30c;
}
if self.vel_y > 0x30c {
self.vel_y = 0x30c;
}
}
self.x += self.vel_x;
self.y += self.vel_y;
Ok(())
}
fn tick_animation(&mut self, state: &mut SharedGameState) {
if self.cond.hidden() {
return;
}
if self.flags.hit_bottom_wall() {
if self.cond.interacted() {
self.skin.set_state(PlayerAnimationState::Examining);
self.anim_num = 11;
self.anim_counter = 0;
} else if state.control_flags.control_enabled()
&& (self.controller.move_up() || self.strafe_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 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num == 7 || self.anim_num == 9 {
state.sound_manager.play_sfx(24);
}
}
if self.anim_num > 9 || self.anim_num < 6 {
self.anim_num = 6;
}
} else if state.control_flags.control_enabled()
&& (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 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num == 2 || self.anim_num == 4 {
state.sound_manager.play_sfx(24);
}
}
if self.anim_num > 4 || self.anim_num < 1 {
self.anim_num = 1;
}
} else if state.control_flags.control_enabled() && (self.controller.move_up() || self.strafe_up) {
if self.cond.fallen() {
state.sound_manager.play_sfx(24);
}
self.cond.set_fallen(false);
self.skin.set_state(PlayerAnimationState::LookingUp);
self.anim_num = 5;
self.anim_counter = 0;
} else {
if self.cond.fallen() {
state.sound_manager.play_sfx(24);
}
self.cond.set_fallen(false);
self.skin.set_state(PlayerAnimationState::Idle);
self.anim_num = 0;
self.anim_counter = 0;
}
} else if self.up {
self.skin.set_state(PlayerAnimationState::FallingLookingUp);
self.anim_num = 6;
self.anim_counter = 0;
} else if self.down {
self.skin.set_state(PlayerAnimationState::FallingLookingDown);
self.anim_num = 10;
self.anim_counter = 0;
} else {
if self.vel_y > 0 {
self.skin.set_state(PlayerAnimationState::Falling);
self.anim_num = 1;
} else {
self.skin.set_state(PlayerAnimationState::Jumping);
self.anim_num = 3;
}
self.anim_counter = 0;
}
self.weapon_offset_y = 0;
self.weapon_rect.left = (self.current_weapon as u16 % 13) * 24;
self.weapon_rect.top = (self.current_weapon as u16 / 13) * 96;
self.weapon_rect.right = self.weapon_rect.left + 24;
self.weapon_rect.bottom = self.weapon_rect.top + 16;
if self.direction == Direction::Right {
self.weapon_rect.top += 16;
self.weapon_rect.bottom += 16;
}
if self.up {
self.weapon_offset_y = -4;
self.weapon_rect.top += 32;
self.weapon_rect.bottom += 32;
} else if self.down {
self.weapon_offset_y = 4;
self.weapon_rect.top += 64;
self.weapon_rect.bottom += 64;
}
if self.anim_num == 1 || self.anim_num == 3 || self.anim_num == 6 || self.anim_num == 8 {
self.weapon_rect.top += 1;
}
self.skin.tick();
self.skin.set_direction(self.direction);
self.skin.set_appearance(if self.equip.has_mimiga_mask() {
PlayerAppearanceState::MimigaMask
} else {
PlayerAppearanceState::Default
});
if state.constants.is_switch && self.air == 0 && self.flags.in_water() && !state.get_flag(4000) {
self.skin.set_appearance(PlayerAppearanceState::Default);
self.skin.set_state(PlayerAnimationState::Drowned);
}
self.anim_rect = self.skin.animation_frame();
self.tick = self.tick.wrapping_add(1);
}
pub fn damage(&mut self, hp: i32, state: &mut SharedGameState, npc_list: &NPCList) {
if self.life == 0 || hp <= 0 || state.settings.god_mode || self.shock_counter > 0 {
return;
}
state.sound_manager.play_sfx(16);
self.shock_counter = 128;
self.cond.set_interacted(false);
if self.control_mode == ControlMode::Normal {
self.vel_y = -0x400; // -2.0fix9
}
let final_hp = state.get_damage(hp);
self.life = self.life.saturating_sub(final_hp as u16);
if self.equip.has_whimsical_star() && self.stars > 0 {
self.stars -= 1;
}
let rumble_intensity =
(0x4000 + ((1.0 - (self.life as f32 / self.max_life as f32)) * 0x2000 as f32) as i32).min(0xFFFF) as u16;
self.controller.set_rumble(rumble_intensity, rumble_intensity, 20);
self.damage = self.damage.saturating_add(final_hp as u16);
self.damage_popup.add_value(-(self.damage as i16));
self.damage_popup.update_displayed_value();
if self.life == 0 {
state.sound_manager.play_sfx(17);
self.cond.0 = 0;
state.control_flags.set_tick_world(true);
state.control_flags.set_interactions_disabled(true);
state.textscript_vm.start_script(40);
state.create_caret(self.x, self.y, CaretType::Explosion, Direction::Left);
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
for _ in 0..0x40 {
npc.x = self.x + state.game_rng.range(-10..10) as i32 * 0x200;
npc.y = self.y + state.game_rng.range(-10..10) as i32 * 0x200;
let _ = npc_list.spawn(0x100, npc.clone());
}
}
#[cfg(feature = "discord-rpc")]
let _ = state.discord_rpc.update_hp(&self);
}
pub fn update_teleport_counter(&mut self, state: &SharedGameState) {
self.teleport_counter += 1;
if self.teleport_counter == (state.settings.timing_mode.get_tps() * 4) as u16 {
self.teleport_counter = 0;
}
}
}
impl GameEntity<&NPCList> for Player {
fn tick(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
if !self.cond.alive() {
if self.life == 0 {
self.damage_popup.x = self.x;
self.damage_popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
self.damage_popup.tick(state, ())?;
}
return Ok(());
}
if state.textscript_vm.reset_invicibility && state.constants.textscript.reset_invicibility_on_any_script {
self.shock_counter = 0;
}
if self.xp_counter != 0 {
self.xp_counter -= 1;
}
if self.shock_counter != 0 {
self.shock_counter -= 1;
} else if self.exp_popup.value != 0 {
self.exp_popup.update_displayed_value();
}
match (self.control_mode, state.settings.noclip) {
(_, true) => self.tick_ironhead(state)?,
(ControlMode::Normal, _) => self.tick_normal(state, npc_list)?,
(ControlMode::IronHead, _) => self.tick_ironhead(state)?,
}
self.damage_popup.x = self.x;
self.damage_popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
self.damage_popup.tick(state, ())?;
self.exp_popup.x = self.x;
self.exp_popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
self.exp_popup.tick(state, ())?;
self.cond.set_increase_acceleration(false);
self.tick_animation(state);
let dog_amount = (3000..=3005).filter(|id| state.get_flag(*id as usize)).count();
self.dog_stack.resize(dog_amount, DogStack::new());
if self.has_dog && self.dog_stack.len() > 0 {
if self.vel_x.abs() > 0x100 {
self.dog_stack[0].speed = (self.vel_x / 2) as f32;
}
for i in 1..self.dog_stack.len() {
self.dog_stack[i].speed = self.dog_stack[i - 1].prev_speed * DogStack::MULT;
}
for dog in &mut self.dog_stack {
dog.tick();
}
}
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult {
if !self.cond.alive() || self.cond.hidden() {
return Ok(());
}
let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
// hack for stacked dogs
if self.has_dog {
if self.dog_stack.len() > 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_ref(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;
for i in (1..=(self.dog_stack.len() as i32)).rev() {
batch.add_rect(
interpolate_fix9_scale(
self.prev_x - off_x - vec_x * i - self.dog_stack[i as usize - 1].offset_x as i32,
self.x - off_x - vec_x * i - self.dog_stack[i as usize - 1].offset_x as i32,
state.frame_time,
) - frame_x,
interpolate_fix9_scale(
self.prev_y - off_y - vec_y * i - (self.y - self.prev_y) * i,
self.y - off_y - vec_y * i - (self.y - self.prev_y) * i,
state.frame_time,
) - frame_y,
&state.constants.npc.n136_puppy_carried[frame_id],
);
}
batch.draw(ctx)?;
}
}
}
if self.shock_counter / 2 % 2 != 0 {
return Ok(());
}
if self.current_weapon != 0 {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Arms")?;
let (gun_off_x, gun_off_y) = self.skin.get_gun_offset();
batch.add_rect(
interpolate_fix9_scale(
self.prev_x - self.display_bounds.left as i32,
self.x - self.display_bounds.left as i32,
state.frame_time,
) + if self.direction == Direction::Left { -8.0 - gun_off_x as f32 } else { gun_off_x as f32 }
- frame_x,
interpolate_fix9_scale(
self.prev_y - self.display_bounds.top as i32,
self.y - self.display_bounds.top as i32,
state.frame_time,
) + self.weapon_offset_y as f32
+ gun_off_y as f32
- frame_y,
&self.weapon_rect,
);
batch.draw(ctx)?;
}
{
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,
self.x - self.display_bounds.left as i32,
state.frame_time,
) - frame_x,
interpolate_fix9_scale(
self.prev_y - self.display_bounds.top as i32,
self.y - self.display_bounds.top as i32,
state.frame_time,
) - frame_y,
&self.anim_rect,
);
batch.draw(ctx)?;
}
if (self.equip.has_air_tank() && self.flags.in_water()) || self.control_mode == ControlMode::IronHead {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?;
batch.add_rect(
interpolate_fix9_scale(self.prev_x - 12 * 0x200, self.x - 12 * 0x200, state.frame_time) - frame_x,
interpolate_fix9_scale(self.prev_y - 12 * 0x200, self.y - 12 * 0x200, state.frame_time) - frame_y,
&state.constants.player.frames_bubble[(self.tick / 2 % 2) as usize],
);
batch.draw(ctx)?;
}
Ok(())
}
}