pinch-points 0.9.0

A fast, kid-friendly crab-routing game: route streams of crabs into your sandcastle before time runs out
//! What a spectator says to the table: a line typed and sent, with free
//! hands being the reason the job falls to them.

use super::*;

/// The line a spectator is typing, if one is open.
///
/// Its own resource rather than a field on the session: the session is
/// rebuilt between rounds and a half-typed line is not worth carrying
/// across, and the shell reads this to know the keyboard is spoken for.
#[derive(Resource, Default)]
pub struct SpectatorChat(pub Option<String>);

impl SpectatorChat {
    pub fn open(&self) -> bool {
        self.0.is_some()
    }
}

/// T opens a line, Enter says it, Esc drops it.
///
/// The key is read only for a spectator, so a player's T is still a player's
/// T: nothing here can take a letter out of a round being played.
pub fn spectator_chat_input(
    mut keys: ResMut<ButtonInput<KeyCode>>,
    caps: Res<crate::app::keycaps::KeyCaps>,
    mut typed: MessageReader<bevy::input::keyboard::KeyboardInput>,
    settings: Res<crate::app::settings::GameSettings>,
    mut chat: ResMut<SpectatorChat>,
    mut online: ResMut<Online>,
) {
    if !is_spectating(&online) {
        // A seat was handed out mid-round, or the session ended under a
        // half-typed line. Either way the line is not going anywhere.
        chat.0 = None;
        typed.clear();
        return;
    }
    let Some(line) = &mut chat.0 else {
        // Not typing: the keystrokes belong to whatever else reads them,
        // and T is the way in.
        if caps.just_pressed(&keys, 'T') {
            chat.0 = Some(String::new());
            typed.clear();
        }
        return;
    };
    // A pad's A reaches here as a pressed Enter with no keystroke behind
    // it, the bridge having no text to give, and says the line all the same.
    let said = crate::app::lobby::type_a_line(&mut typed, line, crate::transport::chat_fits)
        .or_else(|| crate::app::menu_ui::enter(&keys).then(|| line.clone()));
    if keys.just_pressed(KeyCode::Escape) {
        chat.0 = None;
        crate::app::menu_ui::spend(&mut keys, &crate::app::menu_ui::CLOSERS);
        return;
    }
    let Some(said) = said else {
        return;
    };
    chat.0 = None;
    crate::app::menu_ui::spend(&mut keys, &crate::app::menu_ui::CLOSERS);
    if said.trim().is_empty() {
        return;
    }
    let Some(session) = &mut online.0 else {
        return;
    };
    // The name the host vouches this peer's lines under, which for a bot's
    // seat (route 1) is not the player's own.
    let me = crate::app::lobby::speaking_as(session.bot.as_deref(), &settings.names[0]).to_string();
    if me.is_empty() {
        // The hub refuses a line that resolves to no name, because an
        // empty name is the beach's own voice. Echoing it here anyway
        // would show the sender something nobody else was ever given. The
        // lobby asks for a name before it lets anyone watch, so this is
        // the player who went and cleared it again.
        return;
    }
    session.transport.send(NetMsg::chat(&me, &said));
    // Said to the table, and shown here too: the sender is a peer like any
    // other and its own datagram never comes back to it. Shown as the wire
    // carries it, which is how everyone else reads it.
    session.heard.push((
        crate::transport::name_as_sent(&me),
        crate::transport::chat_as_sent(&said),
    ));
}