1use async_compat::Compat;
6use bevy::ecs::component::Tick;
7use bevy::ecs::system::{SystemMeta, SystemParam};
8use bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell;
9use bevy::prelude::*;
10use bevy::tasks::{AsyncComputeTaskPool, Task};
11pub use devcade_onboard_types;
12use devcade_onboard_types::{Map, Player as BackendPlayer, RequestBody, ResponseBody, Value};
13use enum_iterator::Sequence;
14use futures_lite::future;
15use std::ops::Deref;
16use std::sync::OnceLock;
17
18#[cfg(not(target_os = "windows"))]
19mod client;
20#[cfg(not(target_os = "windows"))]
21pub use client::{BackendClient, RequestError};
22
23#[derive(SystemParam)]
24struct DevcadeControlsInner<'w> {
25 gamepads: Res<'w, Gamepads>,
26 button_inputs: Res<'w, Input<GamepadButton>>,
27 axes: Res<'w, Axis<GamepadAxis>>,
28 keyboard_input: Res<'w, Input<KeyCode>>,
29}
30
31pub struct DevcadeControls {
55 p1: PlayerControlState,
56 p2: PlayerControlState,
57}
58#[derive(Default, Clone)]
59struct ButtonState {
60 pressed: bool,
61 changed_this_frame: bool,
62}
63#[derive(Default, Clone)]
64struct PlayerControlState {
65 stick_up: ButtonState,
66 stick_down: ButtonState,
67 stick_left: ButtonState,
68 stick_right: ButtonState,
69 menu: ButtonState,
70 a1: ButtonState,
71 a2: ButtonState,
72 a3: ButtonState,
73 a4: ButtonState,
74 b1: ButtonState,
75 b2: ButtonState,
76 b3: ButtonState,
77 b4: ButtonState,
78}
79
80impl PlayerControlState {
81 fn get_state_for(&self, button: Button) -> &ButtonState {
82 match button {
83 Button::StickUp => &self.stick_up,
84 Button::StickDown => &self.stick_down,
85 Button::StickLeft => &self.stick_left,
86 Button::StickRight => &self.stick_right,
87 Button::A1 => &self.a1,
88 Button::A2 => &self.a2,
89 Button::A3 => &self.a3,
90 Button::A4 => &self.a4,
91 Button::B1 => &self.b1,
92 Button::B2 => &self.b2,
93 Button::B3 => &self.b3,
94 Button::B4 => &self.b4,
95 Button::Menu => &self.menu,
96 }
97 }
98
99 fn get_state_for_mut(&mut self, button: Button) -> &mut ButtonState {
100 match button {
101 Button::StickUp => &mut self.stick_up,
102 Button::StickDown => &mut self.stick_down,
103 Button::StickLeft => &mut self.stick_left,
104 Button::StickRight => &mut self.stick_right,
105 Button::A1 => &mut self.a1,
106 Button::A2 => &mut self.a2,
107 Button::A3 => &mut self.a3,
108 Button::A4 => &mut self.a4,
109 Button::B1 => &mut self.b1,
110 Button::B2 => &mut self.b2,
111 Button::B3 => &mut self.b3,
112 Button::B4 => &mut self.b4,
113 Button::Menu => &mut self.menu,
114 }
115 }
116}
117
118pub struct ControlState<'w> {
120 p1: PlayerControlState,
121 p2: PlayerControlState,
122 inner: <DevcadeControlsInner<'w> as SystemParam>::State,
123}
124
125unsafe impl SystemParam for DevcadeControls {
126 type State = ControlState<'static>;
127 type Item<'w, 's> = DevcadeControls;
128 fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
129 Self::State {
130 inner: DevcadeControlsInner::init_state(world, system_meta),
131 p1: PlayerControlState::default(),
132 p2: PlayerControlState::default(),
133 }
134 }
135 unsafe fn get_param<'w, 's>(
136 state: &'s mut Self::State,
137 system_meta: &SystemMeta,
138 world: UnsafeWorldCell<'w>,
139 change_tick: Tick,
140 ) -> Self::Item<'w, 's> {
141 let inner = DevcadeControlsInner::get_param(&mut state.inner, system_meta, world, change_tick);
142 for player in enum_iterator::all::<Player>() {
143 let player_state = match player {
144 Player::P1 => &mut state.p1,
145 Player::P2 => &mut state.p2,
146 };
147 for button in enum_iterator::all::<Button>() {
148 let button_state = player_state.get_state_for_mut(button);
149 let pressed = inner.pressed(button, player);
150 button_state.changed_this_frame = pressed != button_state.pressed;
151 button_state.pressed = pressed;
152 }
153 }
154 DevcadeControls {
155 p1: state.p1.clone(),
156 p2: state.p2.clone(),
157 }
158 }
159}
160
161impl DevcadeControls {
162 fn get_player(&self, player: Player) -> &PlayerControlState {
163 match player {
164 Player::P1 => &self.p1,
165 Player::P2 => &self.p2,
166 }
167 }
168
169 pub fn just_pressed(&self, player: Player, button: Button) -> bool {
171 let player = self.get_player(player);
172 let button_state = player.get_state_for(button);
173 button_state.pressed && button_state.changed_this_frame
174 }
175 pub fn just_released(&self, player: Player, button: Button) -> bool {
177 let player = self.get_player(player);
178 let button_state = player.get_state_for(button);
179 !button_state.pressed && button_state.changed_this_frame
180 }
181 pub fn pressed(&self, player: Player, button: Button) -> bool {
183 self.get_player(player).get_state_for(button).pressed
184 }
185}
186
187#[derive(Debug, Clone, Copy, Sequence, PartialEq, Eq)]
188pub enum Button {
190 A1,
192 A2,
194 A3,
196 A4,
198
199 B1,
201 B2,
203 B3,
205 B4,
207
208 Menu,
210
211 StickLeft,
213 StickUp,
215 StickDown,
217 StickRight,
219}
220
221impl TryFrom<&Button> for GamepadButtonType {
222 type Error = ();
223 fn try_from(value: &Button) -> Result<Self, Self::Error> {
224 match value {
225 Button::Menu => Ok(GamepadButtonType::Start),
226 Button::A1 => Ok(GamepadButtonType::West),
227 Button::A2 => Ok(GamepadButtonType::North),
228 Button::A3 => Ok(GamepadButtonType::RightTrigger),
229 Button::A4 => Ok(GamepadButtonType::LeftTrigger),
230 Button::B1 => Ok(GamepadButtonType::South),
231 Button::B2 => Ok(GamepadButtonType::East),
232 Button::B3 => Ok(GamepadButtonType::RightTrigger2),
233 Button::B4 => Ok(GamepadButtonType::LeftTrigger2),
234 _ => Err(()),
235 }
236 }
237}
238
239enum AxisConfig {
240 Positive(GamepadAxisType),
241 Negative(GamepadAxisType),
242}
243
244impl AxisConfig {
245 fn get_axis(&self) -> GamepadAxisType {
246 *match self {
247 AxisConfig::Positive(axis_type) => axis_type,
248 AxisConfig::Negative(axis_type) => axis_type,
249 }
250 }
251}
252
253impl TryFrom<&Button> for AxisConfig {
254 type Error = ();
255 fn try_from(value: &Button) -> Result<Self, Self::Error> {
256 match value {
257 Button::StickUp => Ok(AxisConfig::Positive(GamepadAxisType::LeftStickY)),
258 Button::StickDown => Ok(AxisConfig::Negative(GamepadAxisType::LeftStickY)),
259 Button::StickRight => Ok(AxisConfig::Positive(GamepadAxisType::LeftStickX)),
260 Button::StickLeft => Ok(AxisConfig::Negative(GamepadAxisType::LeftStickX)),
261 _ => Err(()),
262 }
263 }
264}
265
266pub struct PlayerButton {
268 player: Player,
269 button: Button,
270}
271
272impl From<PlayerButton> for KeyCode {
273 fn from(value: PlayerButton) -> KeyCode {
274 match (value.player, value.button) {
275 (Player::P1, Button::A1) => KeyCode::Q,
276 (Player::P1, Button::A2) => KeyCode::W,
277 (Player::P1, Button::A3) => KeyCode::E,
278 (Player::P1, Button::A4) => KeyCode::R,
279 (Player::P1, Button::B1) => KeyCode::A,
280 (Player::P1, Button::B2) => KeyCode::S,
281 (Player::P1, Button::B3) => KeyCode::D,
282 (Player::P1, Button::B4) => KeyCode::F,
283 (Player::P1, Button::Menu) => KeyCode::Escape,
284 (Player::P1, Button::StickUp) => KeyCode::G,
285 (Player::P1, Button::StickDown) => KeyCode::B,
286 (Player::P1, Button::StickLeft) => KeyCode::V,
287 (Player::P1, Button::StickRight) => KeyCode::N,
288
289 (Player::P2, Button::A1) => KeyCode::Y,
290 (Player::P2, Button::A2) => KeyCode::U,
291 (Player::P2, Button::A3) => KeyCode::I,
292 (Player::P2, Button::A4) => KeyCode::O,
293 (Player::P2, Button::B1) => KeyCode::H,
294 (Player::P2, Button::B2) => KeyCode::J,
295 (Player::P2, Button::B3) => KeyCode::K,
296 (Player::P2, Button::B4) => KeyCode::L,
297 (Player::P2, Button::Menu) => KeyCode::Escape,
298 (Player::P2, Button::StickUp) => KeyCode::Up,
299 (Player::P2, Button::StickDown) => KeyCode::Down,
300 (Player::P2, Button::StickLeft) => KeyCode::Left,
301 (Player::P2, Button::StickRight) => KeyCode::Right,
302 }
303 }
304}
305
306#[derive(Debug, Clone, Copy, Sequence, PartialEq, Eq, Component)]
307pub enum Player {
309 P1,
311 P2,
313}
314
315impl Player {
316 fn index(&self) -> usize {
317 match self {
318 Self::P1 => 0,
319 Self::P2 => 1,
320 }
321 }
322}
323
324impl<'w> DevcadeControlsInner<'w> {
325 fn gamepad_for_player(&self, player: &Player) -> Option<Gamepad> {
326 self.gamepads.iter().nth(player.index())
327 }
328 pub fn pressed(&self, button: Button, player: Player) -> bool {
332 if let Some(gamepad) = self.gamepad_for_player(&player) {
333 if let Ok(button) = GamepadButtonType::try_from(&button) {
334 self
335 .button_inputs
336 .pressed(GamepadButton::new(gamepad, button))
337 } else {
338 let axis_config = AxisConfig::try_from(&button).unwrap();
339 let value = self
340 .axes
341 .get(GamepadAxis::new(gamepad, axis_config.get_axis()))
342 .unwrap();
343 match axis_config {
344 AxisConfig::Positive(_) => value > 0.0,
345 AxisConfig::Negative(_) => value < 0.0,
346 }
347 }
348 } else {
349 self
350 .keyboard_input
351 .pressed(KeyCode::from(PlayerButton { button, player }))
352 }
353 }
354}
355
356pub fn close_on_menu_buttons(
358 mut commands: Commands,
359 focused_windows: Query<(Entity, &Window)>,
360 input: DevcadeControls,
361) {
362 for (window, focus) in focused_windows.iter() {
363 if !focus.focused {
364 continue;
365 }
366 if input.pressed(Player::P1, Button::Menu) && input.pressed(Player::P2, Button::Menu) {
367 commands.entity(window).despawn();
368 }
369 }
370}
371
372struct CellWrapper<T>(OnceLock<T>);
373impl<T> CellWrapper<T> {
374 const fn new() -> Self {
375 Self(OnceLock::new())
376 }
377}
378
379#[cfg(not(target_os = "windows"))]
380impl Deref for CellWrapper<BackendClient> {
381 type Target = BackendClient;
382 fn deref(&self) -> &Self::Target {
383 self.0.get_or_init(Self::Target::default)
384 }
385}
386
387#[cfg(not(target_os = "windows"))]
388static CLIENT: CellWrapper<BackendClient> = CellWrapper::new();
389
390#[derive(Component)]
411#[cfg(not(target_os = "windows"))]
412pub struct NfcTagRequestComponent(Task<Result<Option<String>, RequestError>>);
413#[cfg(not(target_os = "windows"))]
414impl Default for NfcTagRequestComponent {
415 fn default() -> Self {
416 Self::new()
417 }
418}
419
420#[cfg(not(target_os = "windows"))]
421impl NfcTagRequestComponent {
422 pub fn new() -> Self {
424 let pool = AsyncComputeTaskPool::get();
425 Self(pool.spawn(Compat::new(async move {
426 CLIENT
427 .send(RequestBody::GetNfcTag(BackendPlayer::P1))
428 .await
429 .and_then(|response_body| match response_body {
430 ResponseBody::NfcTag(tag_id) => Ok(tag_id),
431 body => Err(RequestError::UnexpectedResponse(body)),
432 })
433 })))
434 }
435 pub fn poll(&mut self) -> Option<Result<Option<String>, RequestError>> {
439 future::block_on(future::poll_once(&mut self.0))
440 }
441}
442
443#[derive(Component)]
483#[cfg(not(target_os = "windows"))]
484pub struct NfcUserRequestComponent(Task<Result<Map<String, Value>, RequestError>>);
485
486#[cfg(not(target_os = "windows"))]
487impl NfcUserRequestComponent {
488 pub fn new(association_id: String) -> Self {
490 let pool = AsyncComputeTaskPool::get();
491 Self(pool.spawn(Compat::new(async move {
492 CLIENT
493 .send(RequestBody::GetNfcUser(association_id))
494 .await
495 .and_then(|response_body| match response_body {
496 ResponseBody::NfcUser(value) => Ok(value),
497 body => Err(RequestError::UnexpectedResponse(body)),
498 })
499 })))
500 }
501
502 pub fn poll(&mut self) -> Option<Result<Map<String, Value>, RequestError>> {
506 future::block_on(future::poll_once(&mut self.0))
507 }
508}