use super::*;
#[derive(bevy::ecs::system::SystemParam)]
pub struct LobbyExits<'w> {
next_screen: ResMut<'w, NextState<Screen>>,
bots: ResMut<'w, crate::app::bot_seats::BotSeats>,
online: Res<'w, Online>,
}
fn open_door_for(
state: &mut LobbyState,
bots: &mut crate::app::bot_seats::BotSeats,
tr: &'static crate::app::i18n::Tr,
at: usize,
) {
let Some(host) = state.hosts.get(at) else {
return;
};
if !host.bots {
state.feedback = tr.lobby_no_bots.to_string();
return;
}
if !host.has_room() {
state.feedback = tr.lobby_beach_full.to_string();
return;
}
let addr = host.addr;
state.selected = Some(addr);
match crate::app::bot_seats::Doorway::open() {
Ok(mut door) => {
door.seat_bots(&[0]);
bots.door = Some(door);
bots.door_for = crate::app::bot_seats::DoorFor::Join(addr);
}
Err(e) => state.feedback = fill(tr.door_could_not_listen, &[("e", &e.to_string())]),
}
}
pub fn lobby_input(
keyboard: crate::app::keycaps::Keyboard,
mut typed: MessageReader<bevy::input::keyboard::KeyboardInput>,
mut settings: ResMut<GameSettings>,
dials: crate::app::match_setup::Dials,
mut state: ResMut<LobbyState>,
exits: LobbyExits,
(pads, mut menu, mut pad_pick): (
crate::app::gamepad::face::PadsOnFace,
ResMut<crate::app::match_setup::MatchMenu>,
Option<ResMut<crate::app::gamepad::PadPick>>,
),
) {
let (keys, caps) = (&keyboard.keys, &keyboard.caps);
let LobbyExits {
mut next_screen,
mut bots,
online,
} = exits;
bots.leave_the_beach(state.joined().is_some() || online.0.is_some());
if bots.door_for.card_up() {
typed.clear();
let tr = settings.tr();
if let Some(door) = &bots.door
&& door.ready()
&& let crate::app::bot_seats::DoorFor::Join(addr) = bots.door_for
{
let name = door.named_for(tr, &settings.names[0]);
bots.door_for = crate::app::bot_seats::DoorFor::Joined(addr);
dial_as(&mut state, &settings, tr, addr, name);
}
return;
}
let crate::app::match_setup::Dials {
mut config,
beaches,
} = dials;
if pick::pick_keys(keys, &mut state, &mut config, &mut menu, &beaches) {
typed.clear();
if !state
.hosted()
.is_some_and(|hosted| hosted.picking.is_some())
{
send_roster(&state, &config, settings.team_mode, &beaches);
}
return;
}
let tr = settings.tr();
let tr: &'static crate::app::i18n::Tr = tr;
use crate::app::gamepad::scheme::Does;
let quick = pads.pressed(Does::Talk) || pads.pressed(Does::NextPhrase);
let mut intent: Option<Intent> = None;
match drive_typing(keys, &mut typed, quick, &mut settings, caps, &mut state, tr) {
Typed::Taken => return,
Typed::Unblocked(unblocked) => intent = Some(unblocked),
Typed::Nothing => {}
}
typed.clear();
let partnering = state.hosted().is_some_and(|hosted| hosted.partnering);
if intent.is_none() && !partnering && keyboard.just('T') && state.can_chat() {
state.typing = Some(Typing::chat());
return;
}
if intent.is_none() && !partnering && quick && state.can_chat() {
state.typing = Some(Typing::chat_saying(tr.quick_chat[0]));
return;
}
if intent.is_none()
&& keyboard.just('J')
&& !state.standing().at_a_beach()
&& state.typing.is_none()
{
state.typing = Some(Typing::address(&settings.last_beach));
return;
}
let auto_host = crate::app::dev::auto_host_quota().is_some() && !state.auto_done;
let auto_watch = crate::app::dev::auto_watch();
let auto_join =
(crate::app::dev::auto_join() || auto_watch) && !state.auto_done && !state.hosts.is_empty();
if auto_watch {
state.set_watching(true);
}
if crate::app::dev::auto_join_bot() && !state.auto_done && !state.hosts.is_empty() {
state.auto_done = true;
open_door_for(&mut state, &mut bots, tr, 0);
return;
}
let join_bot = match intent {
Some(Intent::JoinBot(addr)) => {
let at = state.hosts.iter().position(|host| host.addr == addr);
if at.is_none() {
state.feedback = tr.lobby_host_closed.to_string();
}
at
}
_ if intent.is_none()
&& keyboard.just('B')
&& !state.standing().at_a_beach()
&& state.typing.is_none() =>
{
state.selected_index()
}
_ => None,
};
if let Some(at) = join_bot {
if settings.names[0].trim().is_empty() && intent.is_none() {
state.typing = Some(
Typing::player_name(Intent::JoinBot(state.hosts[at].addr), &settings.names[0])
.or_suggest(|| suggested_name(tr)),
);
} else {
open_door_for(&mut state, &mut bots, tr, at);
}
return;
}
let step = host_step(HostAsk {
answered: intent == Some(Intent::Host),
pressed_h: keyboard.global('H'),
busy: state.standing().at_a_beach(),
auto: auto_host,
});
if step == HostStep::Ask {
state.typing = Some(
Typing::player_name(Intent::Host, &settings.names[0]).or_suggest(|| suggested_name(tr)),
);
return;
}
if step == HostStep::Go && !state.standing.at_a_beach() {
state.auto_done = auto_host;
match (
Announcer::new(crate::app::clock::fresh_seed()),
UdpTransport::host(0),
) {
(Ok(announcer), Ok(transport)) => {
state.feedback = match transport.local_addr() {
Ok(addr) => fill(tr.lobby_hosting, &[("p", &addr.port().to_string())]),
Err(_) => tr.lobby_hosting_noport.into(),
};
if let Some(here) = address_here(&transport) {
let notice = fill(tr.lobby_hosting_at, &[("a", &here)]);
state.say("", ¬ice);
}
state.standing = Standing::hosting(announcer, transport);
state.discovery = None;
state.hosts.clear();
}
(Err(e), _) | (_, Err(e)) => {
state.feedback = fill(tr.lobby_could_not_host, &[("e", &e.to_string())]);
}
}
}
let typing = state.typing.is_some();
if let Some(hosted) = state.hosted_mut()
&& !typing
&& !hosted.partnering
&& keyboard.just('K')
{
hosted.kicking = Some(0);
return;
}
if let Some(hosted) = state.hosted_mut()
&& let Some(page) = hosted.kicking
{
if keys.just_pressed(KeyCode::Escape) {
hosted.kicking = None;
return;
}
if keys.just_pressed(KeyCode::Tab) {
hosted.kicking = Some((page + 1) % kick_pages(&hosted.peers));
return;
}
if let Some(at) =
crate::app::menu_ui::number_pressed(keys, crate::app::menu_ui::NUMBER_KEYS.len())
{
ask_to_leave(&mut state, &mut bots, tr, at + 1);
}
return;
}
let partner_key = caps
.key_for(crate::app::gamepad::scheme::PARTNER_CAP)
.is_some_and(|key| keys.just_pressed(key));
let open = super::partners::partners_open(&state);
if let Some(hosted) = state.hosted_mut()
&& hosted.partnering
{
if keys.just_pressed(KeyCode::Escape) || partner_key || !open {
hosted.partnering = false;
return;
}
if let Some(at) = crate::app::menu_ui::number_pressed(keys, MAX_PLAYERS) {
let door = bots
.door
.as_ref()
.map(|door| door.arrived(tr))
.unwrap_or_default();
let picker = super::partners::pick_partners(&mut state, &door, at as u8);
if let (Some(seat), Some(pad_pick)) = (picker, pad_pick.as_deref_mut()) {
let picks = crate::app::gamepad::scheme::Picks {
list: crate::app::gamepad::scheme::PickList::Partners,
rows: super::partners::partner_rows(&state, tr),
};
pad_pick.stand_on(&picks, seat + 1);
}
send_roster(&state, &config, settings.team_mode, &beaches);
}
return;
}
if let Some(hosted) = state.hosted_mut()
&& !typing
&& partner_key
&& open
{
hosted.partnering = true;
return;
}
if state.hosting() {
turn_the_dials(keys, &mut settings, caps, &mut config, &mut state);
}
if !state.standing.at_a_beach() {
walk_the_list(keys, &mut state);
}
take_a_beach(keys, &settings, &mut state, tr, intent, auto_join);
if keyboard.just('W')
&& let Standing::Choosing { watching } = &mut state.standing
{
*watching = !*watching;
}
if keys.just_pressed(KeyCode::Escape) {
next_screen.set(Screen::Menu);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn w_arms_watching_by_its_cap_on_azerty() {
let mut app = App::new();
app.add_plugins(bevy::state::app::StatesPlugin);
app.init_state::<Screen>();
app.init_resource::<ButtonInput<KeyCode>>();
app.init_resource::<crate::app::gamepad::PadFace>();
app.init_resource::<crate::app::gamepad::LastDevice>();
app.init_resource::<LobbyState>();
app.init_resource::<MatchConfig>();
app.init_resource::<crate::app::bot_seats::BotSeats>();
app.init_resource::<Online>();
app.init_resource::<crate::app::match_setup::CustomBeaches>();
app.init_resource::<crate::app::match_setup::MatchMenu>();
app.insert_resource(GameSettings::default());
let mut azerty = crate::app::keycaps::KeyCaps::default();
azerty.learn(KeyCode::KeyW, 'z');
app.init_resource::<crate::app::cursor::LiveSeatKeys>();
app.insert_resource(azerty);
app.add_message::<bevy::input::keyboard::KeyboardInput>();
app.add_systems(Update, lobby_input);
let tap = |app: &mut App, key: KeyCode| {
app.world_mut()
.resource_mut::<ButtonInput<KeyCode>>()
.press(key);
app.update();
app.world_mut()
.resource_mut::<ButtonInput<KeyCode>>()
.reset_all();
};
tap(&mut app, KeyCode::KeyW);
assert!(
!app.world().resource::<LobbyState>().watching(),
"that key says Z on this keyboard"
);
tap(&mut app, KeyCode::KeyZ);
assert!(
app.world().resource::<LobbyState>().watching(),
"the cap that says W"
);
}
}