use super::PadSeats;
use crate::app::cursor::Cursor;
use crate::app::settings::GameSettings;
use bevy::input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest};
use bevy::prelude::*;
const RAID_RUMBLE: GamepadRumbleIntensity = GamepadRumbleIntensity {
strong_motor: 0.85,
weak_motor: 0.45,
};
const RAID_RUMBLE_SECS: f32 = 0.32;
pub fn rumble_on_raid(
mut events: MessageReader<crate::app::sim_events::SimEvent>,
settings: Res<GameSettings>,
claims: Res<PadSeats>,
pads: Query<(Entity, &Gamepad)>,
cursors: Query<&Cursor>,
mut seated: Local<Vec<u8>>,
mut rumble: MessageWriter<GamepadRumbleRequest>,
) {
use crate::app::sim_events::SimEvent;
if !settings.rumble || events.is_empty() {
return;
}
seated.clear();
seated.extend(cursors.iter().map(|cursor| cursor.player));
seated.sort_unstable();
for event in events.read() {
let SimEvent::CastleRaided { owner, .. } = event else {
continue;
};
if seated.binary_search(owner).is_err() {
continue;
}
let Some(pad) = claims.pad_entity_for(&pads, &settings, &seated, *owner) else {
continue;
};
rumble.write(GamepadRumbleRequest::Add {
duration: std::time::Duration::from_secs_f32(RAID_RUMBLE_SECS),
intensity: RAID_RUMBLE,
gamepad: pad,
});
}
}
#[cfg(test)]
mod tests {
use super::super::keep_pad_order;
use super::*;
use crate::app::settings::SeatInput;
use crate::app::sim_events::SimEvent;
use crate::app::{Screen, Sim};
use crate::sim::classic_arena;
fn table(seats: u8, pads: usize) -> App {
let mut app = App::new();
app.insert_resource(Sim(classic_arena(false, seats.max(2))));
app.insert_resource(GameSettings::default());
app.init_resource::<PadSeats>();
app.init_resource::<crate::app::match_setup::MatchConfig>();
app.init_resource::<crate::app::match_setup::CustomBeaches>();
app.init_resource::<crate::app::bot_seats::BotSeats>();
app.add_message::<SimEvent>();
app.add_message::<GamepadRumbleRequest>();
for player in 0..seats {
app.world_mut().spawn(Cursor::seated(player));
}
for _ in 0..pads {
app.world_mut().spawn(Gamepad::default());
}
app.add_systems(Update, rumble_on_raid);
app
}
fn sit_down(app: &mut App, pad: Entity) {
use bevy::ecs::system::RunSystemOnce;
app.world_mut()
.get_resource_or_init::<crate::app::match_setup::MatchMenu>();
let mut gamepad = app.world_mut().get_mut::<Gamepad>(pad).expect("pad");
gamepad.digital_mut().press(GamepadButton::Start);
app.world_mut()
.run_system_once(super::super::pad_claim_seats)
.expect("runs");
let mut gamepad = app.world_mut().get_mut::<Gamepad>(pad).expect("pad");
gamepad.digital_mut().release(GamepadButton::Start);
gamepad.digital_mut().clear();
}
fn buzzed(app: &mut App) -> Vec<Entity> {
let mut messages = app
.world_mut()
.resource_mut::<Messages<GamepadRumbleRequest>>();
let out: Vec<Entity> = messages.drain().map(|request| request.gamepad()).collect();
out
}
fn raid(app: &mut App, owner: u8) -> Vec<Entity> {
app.world_mut().write_message(SimEvent::CastleRaided {
owner,
pos: Vec2::ZERO,
lost: 4,
by: None,
});
app.update();
buzzed(app)
}
#[test]
fn a_raid_is_felt_by_the_seat_that_was_raided() {
let mut app = table(2, 1);
let pad = app
.world_mut()
.query_filtered::<Entity, With<Gamepad>>()
.single(app.world())
.expect("one pad");
assert_eq!(raid(&mut app, 1), vec![pad], "seat two holds the pad");
assert!(
raid(&mut app, 0).is_empty(),
"seat one is on the keyboard and has nothing to buzz"
);
}
#[test]
fn an_unplugged_pad_does_not_move_the_others_up() {
let mut app = table(2, 2);
app.add_plugins(bevy::state::app::StatesPlugin);
app.init_state::<Screen>();
app.insert_resource(State::new(Screen::Versus));
app.add_systems(Update, keep_pad_order.before(rumble_on_raid));
app.update();
let top = raid(&mut app, 1);
let bottom = raid(&mut app, 0);
assert_eq!((top.len(), bottom.len()), (1, 1));
assert_ne!(top, bottom);
app.world_mut().entity_mut(top[0]).remove::<Gamepad>();
assert_eq!(raid(&mut app, 0), bottom, "P1 keeps its own pad");
assert!(raid(&mut app, 1).is_empty(), "P2's seat waits, empty");
app.world_mut()
.entity_mut(top[0])
.insert(Gamepad::default());
assert_eq!(raid(&mut app, 1), top, "and P2's pad comes back to it");
assert_eq!(raid(&mut app, 0), bottom);
}
#[test]
fn a_new_pad_takes_over_a_dead_claim() {
let mut app = table(2, 2);
app.add_plugins(bevy::state::app::StatesPlugin);
app.init_state::<Screen>();
app.insert_resource(State::new(Screen::Versus));
app.add_systems(Update, keep_pad_order.before(rumble_on_raid));
let plugged: Vec<Entity> = app
.world_mut()
.query_filtered::<Entity, With<Gamepad>>()
.iter(app.world())
.collect();
app.update();
sit_down(&mut app, plugged[0]);
assert_eq!(raid(&mut app, 1), vec![plugged[0]], "the claim, on P2");
app.world_mut().entity_mut(plugged[0]).remove::<Gamepad>();
let spare = app.world_mut().spawn(Gamepad::default()).id();
app.update();
assert_eq!(raid(&mut app, 1), vec![spare], "the spare takes P2");
assert_eq!(
raid(&mut app, 0),
vec![plugged[1]],
"P1 keeps the pad it was dealt: the friend's Start needed its own"
);
}
#[test]
fn two_pads_are_told_apart() {
let mut app = table(2, 2);
let top = raid(&mut app, 1);
let next = raid(&mut app, 0);
assert_eq!(top.len(), 1);
assert_eq!(next.len(), 1);
assert_ne!(top, next, "one raid must not buzz both hands");
}
#[test]
fn a_claimed_pad_beats_the_order_it_was_plugged_in() {
let mut app = table(2, 2);
let plugged: Vec<Entity> = app
.world_mut()
.query_filtered::<Entity, With<Gamepad>>()
.iter(app.world())
.collect();
assert_eq!(plugged.len(), 2);
assert_eq!(raid(&mut app, 1), vec![plugged[0]], "unclaimed, plug order");
sit_down(&mut app, plugged[1]);
assert_eq!(
raid(&mut app, 1),
vec![plugged[1]],
"P2 holds the pad that claimed it"
);
assert_eq!(
raid(&mut app, 0),
vec![plugged[0]],
"and the other is dealt P1, the seat left over"
);
}
#[test]
fn a_bound_pad_still_only_answers_for_a_seat_that_is_here() {
let mut app = table(1, 2);
app.world_mut().resource_mut::<GameSettings>().seat_input[1] = SeatInput::Pad(0);
assert!(
raid(&mut app, 1).is_empty(),
"seat two is bound to a pad here but is not sitting here"
);
assert_eq!(
raid(&mut app, 0).len(),
1,
"and seat one still feels its own, on the pad seat two did not claim"
);
}
#[test]
fn a_seat_that_is_not_here_buzzes_nothing() {
let mut app = table(2, 1);
assert!(
raid(&mut app, 3).is_empty(),
"seat four is not at this table"
);
}
#[test]
fn the_setting_turns_it_off() {
let mut app = table(2, 1);
app.world_mut().resource_mut::<GameSettings>().rumble = false;
assert!(raid(&mut app, 1).is_empty());
}
}