Skip to main content

flatland_cli/
game_session.rs

1use flatland_protocol::AuthCredential;
2use uuid::Uuid;
3
4use crate::api::ControlPlaneClient;
5use crate::auth_context::{load_play_auth, PlayAuth, PlayAuthKind};
6use crate::config::SessionConfig;
7use flatland_client_lib::ConnectOptions;
8
9#[derive(serde::Deserialize)]
10struct CharacterList {
11    characters: Vec<CharacterRow>,
12}
13
14#[derive(serde::Deserialize, Clone)]
15struct CharacterRow {
16    id: Uuid,
17    name: String,
18}
19
20/// Resolve auth + an **existing** character for remote play.
21///
22/// Never creates characters. Use `flatland3 character create <Name>` first.
23pub async fn prepare_remote_play(
24    character_name: Option<&str>,
25    server: std::net::SocketAddr,
26) -> anyhow::Result<ConnectOptions> {
27    let auth = load_play_auth()?;
28    let client = ControlPlaneClient::new(&auth.api_base);
29    let character = resolve_existing_character(&auth, &client, character_name).await?;
30
31    if auth.kind == PlayAuthKind::Session {
32        if let Ok(mut session) = SessionConfig::load() {
33            let _ = session.remember_character(character.id);
34        }
35    }
36
37    let credential = match auth.kind {
38        PlayAuthKind::Session => AuthCredential::Session {
39            token: auth.bearer,
40        },
41        PlayAuthKind::ApiToken => AuthCredential::ApiToken {
42            token: auth.bearer,
43            character_id: character.id,
44        },
45    };
46
47    // Hello.client_name is display-only; character_id is authoritative for persistence.
48    Ok(
49        ConnectOptions::to_server(&character.name, server).with_auth(credential, Some(character.id)),
50    )
51}
52
53async fn resolve_existing_character(
54    auth: &PlayAuth,
55    client: &ControlPlaneClient,
56    character_name: Option<&str>,
57) -> anyhow::Result<CharacterRow> {
58    let list: CharacterList = client.get("/v1/characters", &auth.bearer).await?;
59
60    if list.characters.is_empty() {
61        anyhow::bail!(
62            "no characters on this account.\n\
63             Create one first (requires login):\n\
64               flatland3 character create <Name>\n\
65             Then play with:\n\
66               flatland3 --name <Name>"
67        );
68    }
69
70    if let Ok(id_str) = std::env::var("FLATLAND_CHARACTER_ID") {
71        if !id_str.is_empty() {
72            let id: Uuid = id_str.parse().map_err(|_| {
73                anyhow::anyhow!("FLATLAND_CHARACTER_ID must be a valid UUID")
74            })?;
75            if let Some(row) = list.characters.iter().find(|c| c.id == id) {
76                return Ok(row.clone());
77            }
78            anyhow::bail!("no character with FLATLAND_CHARACTER_ID={id}");
79        }
80    }
81
82    if let Some(raw) = character_name.map(str::trim).filter(|s| !s.is_empty()) {
83        if let Some(existing) = list.characters.iter().find(|c| c.name == raw) {
84            return Ok(existing.clone());
85        }
86        let names: Vec<_> = list.characters.iter().map(|c| c.name.as_str()).collect();
87        anyhow::bail!(
88            "character \"{raw}\" not found on this account.\n\
89             Existing: {}\n\
90             Create with: flatland3 character create {raw}\n\
91             Or list with: flatland3 character list",
92            names.join(", ")
93        );
94    }
95
96    // No --name: use last played character, else the only character, else ask.
97    if auth.kind == PlayAuthKind::Session {
98        if let Ok(session) = SessionConfig::load() {
99            if let Some(id) = session.last_character_id {
100                if let Some(row) = list.characters.iter().find(|c| c.id == id) {
101                    return Ok(row.clone());
102                }
103            }
104        }
105    }
106
107    if list.characters.len() == 1 {
108        return Ok(list.characters[0].clone());
109    }
110
111    let names: Vec<_> = list.characters.iter().map(|c| c.name.as_str()).collect();
112    anyhow::bail!(
113        "multiple characters — pick one:\n\
114           flatland3 --name <Name>\n\
115         Available: {}\n\
116         Or: flatland3 character list",
117        names.join(", ")
118    )
119}