add gamepad rumble

This commit is contained in:
Sallai József 2022-08-13 17:54:05 +03:00
parent 7b359ae4c1
commit 290068dd37
34 changed files with 322 additions and 5 deletions

View File

@ -3,8 +3,16 @@ use std::collections::{HashMap, HashSet};
use sdl2::controller::GameController;
use serde::{Deserialize, Serialize};
use crate::shared_game_state::SharedGameState;
use crate::{common::Rect, engine_constants::EngineConstants, framework::context::Context};
use crate::framework::error::GameResult;
const QUAKE_RUMBLE_LOW_FREQ: u16 = 0x3000;
const QUAKE_RUMBLE_HI_FREQ: u16 = 0;
const SUPER_QUAKE_RUMBLE_LOW_FREQ: u16 = 0x5000;
const SUPER_QUAKE_RUMBLE_HI_FREQ: u16 = 0;
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
#[repr(u32)]
pub enum Axis {
@ -186,6 +194,12 @@ impl GamepadData {
name
}
pub fn set_rumble(&mut self, state: &SharedGameState, low_freq: u16, hi_freq: u16, ticks: u32) -> GameResult {
let duration_ms = (ticks as f32 / state.settings.timing_mode.get_tps() as f32 * 1000.0) as u32;
self.controller.set_rumble(low_freq, hi_freq, duration_ms);
Ok(())
}
}
pub struct GamepadContext {
@ -209,6 +223,10 @@ impl GamepadContext {
self.gamepads.iter_mut().find(|gamepad| gamepad.controller.instance_id() == gamepad_id)
}
fn get_gamepad_by_index_mut(&mut self, gamepad_index: usize) -> Option<&mut GamepadData> {
self.gamepads.get_mut(gamepad_index)
}
pub(crate) fn add_gamepad(&mut self, game_controller: GameController, axis_sensitivity: f64) {
self.gamepads.push(GamepadData::new(game_controller, axis_sensitivity));
}
@ -323,6 +341,35 @@ impl GamepadContext {
HashMap::new()
}
pub(crate) fn set_rumble(
&mut self,
gamepad_index: u32,
state: &SharedGameState,
low_freq: u16,
hi_freq: u16,
ticks: u32,
) -> GameResult {
if let Some(gamepad) = self.get_gamepad_by_index_mut(gamepad_index as usize) {
gamepad.set_rumble(state, low_freq, hi_freq, ticks)?;
}
Ok(())
}
pub(crate) fn set_rumble_all(
&mut self,
state: &SharedGameState,
low_freq: u16,
hi_freq: u16,
ticks: u32,
) -> GameResult {
for gamepad in self.gamepads.iter_mut() {
gamepad.set_rumble(state, low_freq, hi_freq, ticks)?;
}
Ok(())
}
}
impl Default for GamepadContext {
@ -370,3 +417,45 @@ pub fn pressed_buttons(ctx: &Context, gamepad_index: u32) -> HashSet<Button> {
pub fn active_axes(ctx: &Context, gamepad_index: u32) -> HashMap<Axis, f64> {
ctx.gamepad_context.active_axes(gamepad_index)
}
pub fn set_rumble(
ctx: &mut Context,
state: &SharedGameState,
gamepad_index: u32,
low_freq: u16,
hi_freq: u16,
ticks: u32,
) -> GameResult {
ctx.gamepad_context.set_rumble(gamepad_index, state, low_freq, hi_freq, ticks)
}
pub fn set_rumble_all(
ctx: &mut Context,
state: &SharedGameState,
low_freq: u16,
hi_freq: u16,
ticks: u32,
) -> GameResult {
ctx.gamepad_context.set_rumble_all(state, low_freq, hi_freq, ticks)
}
pub fn set_quake_rumble(ctx: &mut Context, state: &SharedGameState, gamepad_index: u32, ticks: u32) -> GameResult {
set_rumble(ctx, state, gamepad_index, QUAKE_RUMBLE_LOW_FREQ, QUAKE_RUMBLE_HI_FREQ, ticks)
}
pub fn set_quake_rumble_all(ctx: &mut Context, state: &SharedGameState, ticks: u32) -> GameResult {
set_rumble_all(ctx, state, QUAKE_RUMBLE_LOW_FREQ, QUAKE_RUMBLE_LOW_FREQ, ticks)
}
pub fn set_super_quake_rumble(
ctx: &mut Context,
state: &SharedGameState,
gamepad_index: u32,
ticks: u32,
) -> GameResult {
set_rumble(ctx, state, gamepad_index, SUPER_QUAKE_RUMBLE_LOW_FREQ, SUPER_QUAKE_RUMBLE_HI_FREQ, ticks)
}
pub fn set_super_quake_rumble_all(ctx: &mut Context, state: &SharedGameState, ticks: u32) -> GameResult {
set_rumble_all(ctx, state, SUPER_QUAKE_RUMBLE_LOW_FREQ, SUPER_QUAKE_RUMBLE_LOW_FREQ, ticks)
}

View File

@ -166,4 +166,10 @@ impl PlayerController for CombinedPlayerController {
fn move_analog_y(&self) -> f64 {
self.controllers.iter().fold(0.0, |acc, cont| acc + cont.move_analog_y()) / self.controllers.len() as f64
}
fn set_rumble(&mut self, low_freq: u16, hi_freq: u16, ticks: u32) {
for cont in &mut self.controllers {
cont.set_rumble(low_freq, hi_freq, ticks);
}
}
}

View File

@ -151,4 +151,6 @@ impl PlayerController for DummyPlayerController {
fn move_analog_y(&self) -> f64 {
0.0
}
fn set_rumble(&mut self, _low_freq: u16, _hi_freq: u16, _ticks: u32) {}
}

View File

@ -35,11 +35,32 @@ pub struct GamepadController {
state: KeyState,
old_state: KeyState,
trigger: KeyState,
rumble_state: Option<RumbleState>,
rumble_enabled: bool,
}
#[derive(Clone)]
pub struct RumbleState {
pub low_freq: u16,
pub hi_freq: u16,
pub ticks: u32,
}
impl GamepadController {
pub fn new(gamepad_id: u32, target: TargetPlayer) -> GamepadController {
GamepadController { gamepad_id, target, state: KeyState(0), old_state: KeyState(0), trigger: KeyState(0) }
GamepadController {
gamepad_id,
target,
state: KeyState(0),
old_state: KeyState(0),
trigger: KeyState(0),
rumble_state: None,
rumble_enabled: false,
}
}
pub fn set_rumble_enabled(&mut self, enabled: bool) {
self.rumble_enabled = enabled;
}
}
@ -70,6 +91,18 @@ impl PlayerController for GamepadController {
self.state.set_menu_ok(gamepad::is_active(ctx, self.gamepad_id, &button_map.menu_ok));
self.state.set_menu_back(gamepad::is_active(ctx, self.gamepad_id, &button_map.menu_back));
if let Some(rumble_data) = &self.rumble_state {
gamepad::set_rumble(
ctx,
state,
self.gamepad_id,
rumble_data.low_freq,
rumble_data.hi_freq,
rumble_data.ticks,
)?;
self.rumble_state = None;
}
Ok(())
}
@ -227,4 +260,16 @@ impl PlayerController for GamepadController {
0.0
}
}
fn set_rumble(&mut self, low_freq: u16, hi_freq: u16, ticks: u32) {
if !self.rumble_enabled {
return;
}
if !self.rumble_state.is_none() {
return;
}
self.rumble_state = Some(RumbleState { low_freq, hi_freq, ticks });
}
}

View File

@ -225,4 +225,6 @@ impl PlayerController for KeyboardController {
0.0
}
}
fn set_rumble(&mut self, _low_freq: u16, _hi_freq: u16, _ticks: u32) {}
}

View File

@ -97,6 +97,9 @@ pub trait PlayerController: PlayerControllerClone {
/// Returns movement analog stick state in Y axis within (-1.0..=1.0) range
/// In case of non-analog controllers this should return -1.0, 0.0 or 1.0, depending on keys pressed.
fn move_analog_y(&self) -> f64;
/// Activates the rumble motors to the specified intensities for a given amount of time (in millis).
fn set_rumble(&mut self, low_freq: u16, hi_freq: u16, ticks: u32);
}
pub trait PlayerControllerClone {

View File

@ -206,4 +206,6 @@ impl PlayerController for ReplayController {
0.0
}
}
fn set_rumble(&mut self, _low_freq: u16, _hi_freq: u16, _ticks: u32) {}
}

View File

@ -401,4 +401,8 @@ impl PlayerController for TouchPlayerController {
0.0
}
}
fn set_rumble(&mut self, _low_freq: u16, _hi_freq: u16, _ticks: u32) {
// we could probably vibrate the phone? do mobile games do that?
}
}

View File

@ -43,6 +43,7 @@ enum MainMenuEntry {
SelectedPlayer,
Controller,
Rebind,
Rumble,
Back,
}
@ -210,6 +211,7 @@ impl ControlsMenu {
self.main
.push_entry(MainMenuEntry::Controller, MenuEntry::Active(state.t("menus.controls_menu.controller.entry")));
self.main.push_entry(MainMenuEntry::Rebind, MenuEntry::Active(state.t("menus.controls_menu.rebind")));
self.main.push_entry(MainMenuEntry::Rumble, MenuEntry::Hidden);
self.main.push_entry(MainMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.confirm_reset.push_entry(
@ -417,14 +419,22 @@ impl ControlsMenu {
Player::Player2 => state.settings.player2_controller_type,
};
let rumble = match self.selected_player {
Player::Player1 => state.settings.player1_rumble,
Player::Player2 => state.settings.player2_rumble,
};
if let ControllerType::Gamepad(index) = controller_type {
if index as usize >= available_gamepads {
self.selected_controller = ControllerType::Keyboard;
self.main.set_entry(MainMenuEntry::Rumble, MenuEntry::Hidden);
} else {
self.selected_controller = controller_type;
self.main.set_entry(MainMenuEntry::Rumble, MenuEntry::Toggle("Rumble".to_string(), rumble));
}
} else {
self.selected_controller = controller_type;
self.main.set_entry(MainMenuEntry::Rumble, MenuEntry::Hidden);
}
match self.selected_controller {
@ -887,6 +897,50 @@ impl ControlsMenu {
MenuSelectionResult::Selected(MainMenuEntry::Rebind, _) => {
self.current = CurrentMenu::RebindMenu;
}
MenuSelectionResult::Selected(MainMenuEntry::Rumble, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
match self.selected_player {
Player::Player1 => {
state.settings.player1_rumble = !state.settings.player1_rumble;
if state.settings.player1_rumble {
if let ControllerType::Gamepad(idx) = self.selected_controller {
gamepad::set_rumble(
ctx,
state,
idx,
0,
0x5000,
(state.settings.timing_mode.get_tps() / 2) as u32,
)?;
}
}
*value = state.settings.player1_rumble;
}
Player::Player2 => {
state.settings.player2_rumble = !state.settings.player2_rumble;
if state.settings.player2_rumble {
if let ControllerType::Gamepad(idx) = self.selected_controller {
gamepad::set_rumble(
ctx,
state,
idx,
0,
0x5000,
(state.settings.timing_mode.get_tps() / 2) as u32,
)?;
}
}
*value = state.settings.player2_rumble;
}
}
state.settings.save(ctx)?;
}
}
MenuSelectionResult::Selected(MainMenuEntry::Back, _) | MenuSelectionResult::Canceled => exit_action(),
_ => {}
},

View File

@ -52,6 +52,7 @@ impl NPC {
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
}
}
2 => {
@ -151,6 +152,7 @@ impl NPC {
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
}
}
5 => {
@ -276,6 +278,7 @@ impl NPC {
self.spritesheet_id = 20; // NpcSym
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
}
}
20 | 21 => {
@ -474,6 +477,7 @@ impl NPC {
if y <= 34 && stage.change_tile(x, y, 0) {
state.sound_manager.play_sfx(44);
state.super_quake_counter = 10;
state.super_quake_rumble_counter = 10;
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
@ -499,6 +503,7 @@ impl NPC {
if self.y < -32 * 0x200 {
self.npc_type = 0;
state.quake_counter = 30;
state.quake_rumble_counter = 30;
}
}
_ => (),
@ -555,6 +560,7 @@ impl NPC {
state.sound_manager.play_sfx(12);
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
self.y += 0x1400;
self.action_num = 1;
@ -571,6 +577,7 @@ impl NPC {
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
}
}
2 => {
@ -751,6 +758,7 @@ impl NPC {
state.sound_manager.play_sfx(25);
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
let mut npc_smoke = NPC::create(4, &state.npc_table);
npc_smoke.cond.set_alive(true);
@ -887,6 +895,7 @@ impl NPC {
self.action_num = 9;
self.anim_num = 8;
state.quake_counter = 30;
state.quake_rumble_counter = 30;
state.sound_manager.play_sfx(26);
}
@ -1107,6 +1116,7 @@ impl NPC {
self.action_num = 9;
self.anim_num = 8;
state.quake_counter = 30;
state.quake_rumble_counter = 30;
state.sound_manager.play_sfx(26);
}
if self.action_counter > 7

View File

@ -658,6 +658,7 @@ impl NPC {
{
self.anim_num = 6;
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
player.damage(5, state, npc_list);
@ -741,6 +742,7 @@ impl NPC {
self.action_counter = 0;
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
}
@ -938,6 +940,7 @@ impl NPC {
}
state.quake_counter = 2;
state.quake_rumble_counter = 2;
self.action_counter += 1;
if self.action_counter % 6 == 3 {
state.sound_manager.play_sfx(25);

View File

@ -64,6 +64,7 @@ impl NPC {
if self.anim_num > 6 {
self.anim_num = 5;
state.quake_counter = 8;
state.quake_rumble_counter = 8;
state.sound_manager.play_sfx(26);
@ -1069,6 +1070,7 @@ impl NPC {
self.cond.set_explode_die(true);
state.quake_counter = 20;
state.quake_rumble_counter = 20;
state.sound_manager.play_sfx(35);
npc_list.create_death_smoke(self.x, self.y, 0x10000, 100 as usize, state, &self.rng);
}

View File

@ -118,6 +118,7 @@ impl NPC {
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
}
}
_ => (),

View File

@ -809,6 +809,7 @@ impl NPC {
self.cond.set_explode_die(true);
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
npc_list.create_death_smoke(self.x, self.y, 0x6000, 40 as usize, state, &self.rng);

View File

@ -188,6 +188,7 @@ impl NPC {
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
@ -459,6 +460,7 @@ impl NPC {
self.action_num = 40;
self.action_counter = 0;
state.quake_counter = 20;
state.quake_rumble_counter = 20;
state.sound_manager.play_sfx(26);
}
}

View File

@ -376,6 +376,7 @@ impl NPC {
self.action_counter = 0;
self.anim_num = 2;
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
}
}
@ -396,6 +397,7 @@ impl NPC {
self.action_counter = 0;
self.anim_num = 2;
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(72);
self.create_xp_drop_custom(self.x, self.y, 19, state, npc_list);

View File

@ -554,6 +554,7 @@ impl NPC {
self.action_num = 3;
self.action_counter = 0;
state.quake_counter = 30;
state.quake_rumble_counter = 30;
state.sound_manager.play_sfx(26);
state.sound_manager.play_sfx(72);
@ -747,6 +748,7 @@ impl NPC {
}
2 => {
state.quake_counter = 2;
state.quake_rumble_counter = 2;
self.action_counter3 += 1;
if self.action_counter3 > 240 {
self.anim_num = 2;
@ -891,6 +893,7 @@ impl NPC {
if self.action_counter != 0 && self.flags.hit_bottom_wall() {
state.sound_manager.play_sfx(35);
state.quake_counter = 40;
state.quake_rumble_counter = 40;
self.action_num = 0;
}
@ -1403,6 +1406,7 @@ impl NPC {
state.sound_manager.play_sfx(26);
}
state.quake_counter = 20;
state.quake_rumble_counter = 20;
}
20 => {
let mut npc = NPC::create(4, &state.npc_table);

View File

@ -569,6 +569,7 @@ impl NPC {
self.action_num = 221;
self.action_counter = 0;
state.quake_counter = 16;
state.quake_rumble_counter = 16;
state.sound_manager.play_sfx(26);
self.damage = 4
}
@ -639,6 +640,7 @@ impl NPC {
self.action_num = 331;
self.action_counter = 0;
state.quake_counter = 16;
state.quake_rumble_counter = 16;
state.sound_manager.play_sfx(26);
}
@ -740,6 +742,7 @@ impl NPC {
self.npc_flags.set_ignore_solidity(true);
state.sound_manager.play_sfx(12);
state.quake_counter = 10;
state.quake_rumble_counter = 10;
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);

View File

@ -1013,6 +1013,7 @@ impl NPC {
}
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
}
@ -1185,6 +1186,7 @@ impl NPC {
self.action_num = 20;
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
let mut npc = NPC::create(4, &state.npc_table);
@ -1236,6 +1238,7 @@ impl NPC {
self.action_num = 10;
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
let mut npc = NPC::create(4, &state.npc_table);
@ -1329,6 +1332,7 @@ impl NPC {
self.action_num = 20;
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
let mut npc = NPC::create(4, &state.npc_table);
@ -1380,6 +1384,7 @@ impl NPC {
self.action_num = 10;
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
let mut npc = NPC::create(4, &state.npc_table);
@ -1880,6 +1885,7 @@ impl NPC {
}
state.quake_counter = 10;
state.quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
}
@ -2085,6 +2091,7 @@ impl NPC {
self.npc_flags.set_ignore_solidity(true);
state.sound_manager.play_sfx(26);
state.quake_counter = 10;
state.quake_rumble_counter = 10;
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);

View File

@ -1061,6 +1061,7 @@ impl NPC {
}
state.quake_counter = 20;
state.quake_rumble_counter = 20;
if self.action_counter % 8 == 0 {
state.sound_manager.play_sfx(26);
}

View File

@ -410,6 +410,7 @@ impl NPC {
self.anim_num = 2;
state.sound_manager.play_sfx(26);
state.quake_counter = 20;
state.quake_rumble_counter = 20;
}
}
26 => {
@ -479,6 +480,7 @@ impl NPC {
self.anim_num = 2;
state.sound_manager.play_sfx(26);
state.quake_counter = 20;
state.quake_rumble_counter = 20;
}
}
102 => {

View File

@ -592,6 +592,7 @@ impl NPC {
}
state.quake_counter = 2;
state.quake_rumble_counter = 2;
self.action_counter += 1;
if self.action_counter % 20 == 0 {

View File

@ -177,6 +177,7 @@ impl BossNPC {
}
state.quake_counter = 30;
state.quake_rumble_counter = 30;
state.sound_manager.play_sfx(26);
}
}
@ -357,6 +358,7 @@ impl BossNPC {
state.sound_manager.play_sfx(26);
state.quake_counter = 60;
state.quake_rumble_counter = 60;
}
}
130 | 131 => {
@ -457,6 +459,7 @@ impl BossNPC {
state.sound_manager.play_sfx(26);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
}
}
_ => {}

View File

@ -321,6 +321,7 @@ impl NPC {
self.action_counter = 0;
self.damage = 3;
state.super_quake_counter = 10;
state.super_quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
}
@ -392,6 +393,7 @@ impl NPC {
let _ = npc_list.spawn(0x100, npc);
state.super_quake_counter = 10;
state.super_quake_rumble_counter = 10;
state.sound_manager.play_sfx(26);
}
@ -950,6 +952,7 @@ impl NPC {
self.npc_flags.set_ignore_solidity(true);
state.sound_manager.play_sfx(12);
state.quake_counter = 10;
state.quake_rumble_counter = 10;
for _ in 0..4 {
let mut npc = NPC::create(4, &state.npc_table);
@ -1361,6 +1364,7 @@ impl NPC {
if self.action_counter > 100 {
self.action_counter = 0;
state.quake_counter = 20;
state.quake_rumble_counter = 20;
state.sound_manager.play_sfx(26);
state.sound_manager.play_sfx(12);
@ -1481,6 +1485,7 @@ impl BossNPC {
self.parts[0].action_num = 103;
self.parts[0].action_counter = 0;
state.super_quake_counter = 30;
state.super_quake_rumble_counter = 30;
state.sound_manager.play_sfx(44);
if player.y > self.parts[0].y + 0x6000
@ -1563,6 +1568,7 @@ impl BossNPC {
self.parts[0].action_num = 201;
self.parts[0].action_counter = 0;
state.super_quake_counter = 30;
state.super_quake_rumble_counter = 30;
state.sound_manager.play_sfx(26);
state.sound_manager.play_sfx(44);
@ -1619,6 +1625,7 @@ impl BossNPC {
self.parts[0].action_num = 222;
self.parts[0].action_counter = 0;
state.super_quake_counter = 30;
state.super_quake_rumble_counter = 30;
state.sound_manager.play_sfx(26);
for _ in 0..16 {
@ -1807,6 +1814,7 @@ impl BossNPC {
self.parts[0].action_counter = 0;
self.parts[0].anim_counter = 0;
state.super_quake_counter = 30;
state.super_quake_rumble_counter = 30;
state.sound_manager.play_sfx(35);
self.parts[1].action_num = 102;
@ -1873,6 +1881,7 @@ impl BossNPC {
}
1002 => {
state.super_quake_counter = 40;
state.super_quake_rumble_counter = 30;
self.parts[0].action_counter += 1;

View File

@ -279,6 +279,7 @@ impl BossNPC {
state.sound_manager.loop_sfx_freq(40, 1000.0 / 2205.0);
state.sound_manager.loop_sfx_freq(41, 1100.0 / 2205.0);
state.quake_counter = 100;
state.quake_rumble_counter = 100;
state.npc_super_pos.1 = 1;
}
@ -347,6 +348,7 @@ impl BossNPC {
self.parts[7].action_num = 200;
state.quake_counter = 20;
state.quake_rumble_counter = 20;
state.sound_manager.stop_sfx(40);
state.sound_manager.stop_sfx(41);
@ -406,6 +408,7 @@ impl BossNPC {
if flag {
state.quake_counter = 20;
state.quake_rumble_counter = 20;
state.sound_manager.play_sfx(26);
self.parts[1].action_num = 100;

View File

@ -295,6 +295,7 @@ impl BossNPC {
self.parts[0].target_x = self.parts[0].x;
self.parts[0].target_y = self.parts[0].y;
state.quake_counter = 20;
state.quake_rumble_counter = 20;
for _ in 0..32 {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);

View File

@ -456,6 +456,7 @@ impl BossNPC {
}
1000 => {
state.quake_counter = 2;
state.quake_rumble_counter = 2;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter % 8 == 0 {
@ -477,6 +478,7 @@ impl BossNPC {
}
1001 => {
state.quake_counter = 40;
state.quake_rumble_counter = 40;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 50 {

View File

@ -116,6 +116,7 @@ impl BossNPC {
self.parts[0].y -= 0x200;
self.parts[0].action_counter += 1;
state.quake_counter = 2;
state.quake_rumble_counter = 2;
if self.parts[0].action_counter % 4 == 0 {
state.sound_manager.play_sfx(26);
@ -215,6 +216,8 @@ impl BossNPC {
}
90 => {
state.quake_counter = 2;
state.quake_rumble_counter = 2;
self.parts[0].y += 0x200;
self.parts[0].action_counter += 1;
@ -321,10 +324,12 @@ impl BossNPC {
state.sound_manager.play_sfx(26);
state.sound_manager.play_sfx(12);
state.quake_counter = 30;
state.quake_rumble_counter = 30;
}
}
150 => {
state.quake_counter = 2;
state.quake_rumble_counter = 2;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter % 12 == 0 {
@ -345,6 +350,7 @@ impl BossNPC {
}
160 => {
state.quake_counter = 40;
state.quake_rumble_counter = 40;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 50 {

View File

@ -526,6 +526,7 @@ impl BossNPC {
self.parts[19].action_counter = self.parts[0].life;
state.quake_counter = 100;
state.quake_rumble_counter = 100;
}
self.parts[0].action_counter += 1;
@ -665,6 +666,7 @@ impl BossNPC {
self.parts[7].action_num = 5;
state.quake_counter = 20;
state.quake_rumble_counter = 20;
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
@ -709,6 +711,7 @@ impl BossNPC {
}
1000 => {
state.quake_counter = 100;
state.quake_rumble_counter = 100;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter % 8 == 0 {
@ -734,6 +737,8 @@ impl BossNPC {
}
1001 => {
state.quake_counter = 40;
state.quake_rumble_counter = 40;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 50 {
for i in 0..20 {
@ -749,6 +754,8 @@ impl BossNPC {
if v19 {
state.quake_counter = 20;
state.quake_rumble_counter = 20;
state.sound_manager.play_sfx(26);
if self.parts[0].action_num == 201 {

View File

@ -889,6 +889,10 @@ impl Player {
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);
if self.popup.value > 0 {
self.popup.set_value(-(self.damage as i16));

View File

@ -31,7 +31,7 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::{draw_rect, BlendMode, FilterMode};
use crate::framework::ui::Components;
use crate::framework::{filesystem, graphics};
use crate::framework::{filesystem, gamepad, graphics};
use crate::input::touch_controls::TouchControlType;
use crate::inventory::{Inventory, TakeExperienceResult};
use crate::map::WaterParams;
@ -1846,6 +1846,16 @@ impl Scene for GameScene {
}
}
if state.quake_rumble_counter > 0 {
gamepad::set_quake_rumble_all(ctx, state, state.quake_rumble_counter)?;
state.quake_rumble_counter = 0;
}
if state.super_quake_rumble_counter > 0 {
gamepad::set_super_quake_rumble_all(ctx, state, state.super_quake_rumble_counter)?;
state.super_quake_rumble_counter = 0;
}
Ok(())
}

View File

@ -1260,6 +1260,7 @@ impl TextScriptVM {
let count = read_cur_varint(&mut cursor)? as u16;
state.quake_counter = count;
state.quake_rumble_counter = count as u32;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}

View File

@ -55,6 +55,10 @@ pub struct Settings {
pub player1_controller_axis_sensitivity: f64,
#[serde(default = "default_controller_axis_sensitivity")]
pub player2_controller_axis_sensitivity: f64,
#[serde(default = "default_rumble")]
pub player1_rumble: bool,
#[serde(default = "default_rumble")]
pub player2_rumble: bool,
#[serde(skip, default = "default_speed")]
pub speed: f64,
#[serde(skip)]
@ -82,7 +86,7 @@ fn default_true() -> bool {
#[inline(always)]
fn current_version() -> u32 {
17
18
}
#[inline(always)]
@ -135,6 +139,11 @@ fn default_pause_on_focus_loss() -> bool {
true
}
#[inline(always)]
fn default_rumble() -> bool {
false
}
impl Settings {
pub fn load(ctx: &Context) -> GameResult<Settings> {
if let Ok(file) = user_open(ctx, "/settings.json") {
@ -270,6 +279,12 @@ impl Settings {
}
}
if self.version == 17 {
self.version = 18;
self.player1_rumble = default_rumble();
self.player2_rumble = default_rumble();
}
if self.version != initial_version {
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
}
@ -293,7 +308,9 @@ impl Settings {
ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player1)),
ControllerType::Gamepad(index) => {
let keyboard_controller = Box::new(KeyboardController::new(TargetPlayer::Player1));
let gamepad_controller = Box::new(GamepadController::new(index, TargetPlayer::Player1));
let mut gamepad_controller = Box::new(GamepadController::new(index, TargetPlayer::Player1));
gamepad_controller.set_rumble_enabled(self.player1_rumble);
let mut combined_player_controller = CombinedPlayerController::new();
combined_player_controller.add(keyboard_controller);
@ -309,7 +326,9 @@ impl Settings {
ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player2)),
ControllerType::Gamepad(index) => {
let keyboard_controller = Box::new(KeyboardController::new(TargetPlayer::Player2));
let gamepad_controller = Box::new(GamepadController::new(index, TargetPlayer::Player2));
let mut gamepad_controller = Box::new(GamepadController::new(index, TargetPlayer::Player2));
gamepad_controller.set_rumble_enabled(self.player2_rumble);
let mut combined_player_controller = CombinedPlayerController::new();
combined_player_controller.add(keyboard_controller);
@ -356,6 +375,8 @@ impl Default for Settings {
player2_controller_button_map: player_default_controller_button_map(),
player1_controller_axis_sensitivity: default_controller_axis_sensitivity(),
player2_controller_axis_sensitivity: default_controller_axis_sensitivity(),
player1_rumble: default_rumble(),
player2_rumble: default_rumble(),
speed: 1.0,
god_mode: false,
infinite_booster: false,

View File

@ -285,6 +285,8 @@ pub struct SharedGameState {
pub tile_size: TileSize,
pub quake_counter: u16,
pub super_quake_counter: u16,
pub quake_rumble_counter: u32,
pub super_quake_rumble_counter: u32,
pub teleporter_slots: Vec<(u16, u16)>,
pub carets: Vec<Caret>,
pub touch_controls: TouchControls,
@ -422,6 +424,8 @@ impl SharedGameState {
tile_size: TileSize::Tile16x16,
quake_counter: 0,
super_quake_counter: 0,
quake_rumble_counter: 0,
super_quake_rumble_counter: 0,
teleporter_slots: Vec::with_capacity(8),
carets: Vec::with_capacity(32),
touch_controls: TouchControls::new(),