sf-api 0.3.0

A simple API to send commands to the Shakes & Fidget servers and parse their responses into characters
Documentation
use std::time::Instant;

use enum_map::EnumMap;
use sf_api::{
    gamestate::{GameState, dungeons::LightDungeon},
    misc::EnumMapGet,
    session::*,
    simulate::{Battle, BattleFighter, BattleSide, PlayerFighterSquad},
    sso::SFAccount,
};
use strum::IntoEnumIterator;

#[tokio::main]
pub async fn main() {
    env_logger::builder()
        .filter_level(log::LevelFilter::Debug)
        .init();

    const SSO: bool = false;
    const USE_CACHE: bool = true;

    let custom_resp: Option<&str> = None;
    let command = None;

    let username = std::env::var("USERNAME").unwrap();

    let mut session = match SSO {
        true => SFAccount::login(
            std::env::var("SSO_USERNAME").unwrap(),
            std::env::var("PASSWORD").unwrap(),
        )
        .await
        .unwrap()
        .characters()
        .await
        .unwrap()
        .into_iter()
        .flatten()
        .find(|a| a.username() == username)
        .unwrap(),
        false => Session::new(
            &username,
            &std::env::var("PASSWORD").unwrap(),
            ServerConnection::new(&std::env::var("SERVER").unwrap()).unwrap(),
        ),
    };

    _ = std::fs::create_dir("cache");
    let cache_name = format!("cache/{username}.login");

    let login_data = match (USE_CACHE, std::fs::read_to_string(&cache_name)) {
        (true, Ok(s)) => serde_json::from_str(&s).unwrap(),
        _ => {
            let login_data = session.login().await.unwrap();
            let ld = serde_json::to_string_pretty(&login_data).unwrap();
            std::fs::write(&cache_name, ld).unwrap();
            login_data
        }
    };

    let mut gs = GameState::new(login_data).unwrap();

    if let Some(resp) = custom_resp {
        let bruh = Response::parse(
            resp.to_string(),
            chrono::Local::now().naive_local(),
        )
        .unwrap();
        gs.update(bruh).unwrap();
    }

    let Some(command) = command else {
        let js = serde_json::to_string_pretty(&gs).unwrap();
        std::fs::write("character.json", js).unwrap();

        let squad = PlayerFighterSquad::new(&gs);
        let player = BattleFighter::from_upgradeable(&squad.character);
        let mut player_squad = [player];
        for dungeon in LightDungeon::iter() {
            let Some(monster) = gs.dungeons.current_enemy(dungeon) else {
                continue;
            };
            let monster = BattleFighter::from_monster(monster);
            let mut monster = [monster];
            let mut battle = Battle::new(&mut player_squad, &mut monster);
            let mut winners: EnumMap<BattleSide, u32> = EnumMap::default();
            let rounds: usize = 100_000;
            let now = Instant::now();
            for _ in 0..rounds {
                let winner = battle.simulate(&mut ());
                *winners.get_mut(winner) += 1;
            }
            println!(
                "won {:.2}% against {dungeon:?} ({:?}) lvl {} in {:?}",
                (*winners.get(BattleSide::Left) as f32 / rounds as f32) * 100.0,
                monster[0].class,
                monster[0].level,
                now.elapsed()
            );
        }

        return;
    };
    let cache_name = format!(
        "cache/{username}-{}.response",
        serde_json::to_string(&command).unwrap()
    );

    let resp = match (USE_CACHE, std::fs::read_to_string(&cache_name)) {
        (true, Ok(s)) => serde_json::from_str(&s).unwrap(),
        _ => {
            let resp = session.send_command_raw(&command).await.unwrap();
            let ld = serde_json::to_string_pretty(&resp).unwrap();
            std::fs::write(cache_name, ld).unwrap();
            resp
        }
    };

    gs.update(&resp).unwrap();
    let js = serde_json::to_string_pretty(&gs).unwrap();
    std::fs::write("character.json", js).unwrap();
}