flatland-client-lib 0.2.36

Flatland3 remote game client library (TCP session, bots, game state)
Documentation
//! TCP session connect for remote flatland-server.

use std::net::SocketAddr;

use flatland_protocol::{AuthCredential, Hello, PROTOCOL_VERSION};
use uuid::Uuid;

use crate::client_config::default_game_server_addr;
use crate::remote::{self, RemoteSession};

pub use remote::connect_tcp;

#[derive(Debug, Clone)]
pub struct ConnectOptions {
    pub name: String,
    pub server: SocketAddr,
    pub auth: AuthCredential,
    pub character_id: Option<Uuid>,
}

impl ConnectOptions {
    pub fn to_server(name: impl Into<String>, server: SocketAddr) -> Self {
        Self {
            name: name.into(),
            server,
            auth: AuthCredential::DevLocal,
            character_id: None,
        }
    }

    pub fn with_auth(mut self, auth: AuthCredential, character_id: Option<Uuid>) -> Self {
        self.auth = auth;
        self.character_id = character_id;
        self
    }

    pub fn hello(&self) -> Hello {
        Hello {
            client_name: self.name.clone(),
            protocol_version: PROTOCOL_VERSION,
            auth: self.auth.clone(),
            character_id: self.character_id,
        }
    }
}

pub fn default_server_addr() -> SocketAddr {
    default_game_server_addr()
}

pub async fn connect(opts: ConnectOptions) -> anyhow::Result<RemoteSession> {
    connect_tcp(opts.server, &opts.hello()).await
}

/// Wire / log token for a character that already has a live session.
pub const CHARACTER_ALREADY_CONNECTED: &str = "character_already_connected";

/// Human-facing text for [`CHARACTER_ALREADY_CONNECTED`] (and legacy kick reasons).
pub fn connect_reject_message(reason: &str) -> String {
    if is_character_busy_connect_error(reason) {
        "Character already connected — close the other client or choose a different character."
            .into()
    } else {
        format!("Connect rejected: {reason}")
    }
}

/// True when connect/reconnect should stop auto-retrying (character exclusive).
pub fn is_character_busy_connect_error(err: &str) -> bool {
    let lower = err.to_ascii_lowercase();
    lower.contains(CHARACTER_ALREADY_CONNECTED)
        || lower.contains("character already connected")
        || lower.contains("character_connected_elsewhere")
}