diff options
| author | 1992414357@qq.com <1992414357@qq.com> | 2025-05-27 07:21:37 +0800 |
|---|---|---|
| committer | 1992414357@qq.com <1992414357@qq.com> | 2025-05-27 07:21:37 +0800 |
| commit | f8336ed832fa8e1ddd2f1783bdfc23d3279d5e6a (patch) | |
| tree | 42097cd72e46736ac3a544bd3d6dc50e2327a677 /console/src/bin/nogpadc.rs | |
First commit
Diffstat (limited to 'console/src/bin/nogpadc.rs')
| -rw-r--r-- | console/src/bin/nogpadc.rs | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/console/src/bin/nogpadc.rs b/console/src/bin/nogpadc.rs new file mode 100644 index 0000000..a4b6245 --- /dev/null +++ b/console/src/bin/nogpadc.rs @@ -0,0 +1,436 @@ +use crate::AccountCommands::{Add, Customize, List, Remove}; +use crate::Commands::{Account, Connect}; +use clap::{Args, Parser, Subcommand}; +use nogamepads_core::pad_io::client::nogamepads_client::PadClient; +use nogamepads_core::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo; +use nogamepads_core::DEFAULT_PORT; +use prettytable::{row, Table}; +use rand::Rng; +use std::env::current_dir; +use std::fs::{create_dir_all, remove_file, File}; +use std::io::{BufReader, Write}; +use std::net::{IpAddr, Ipv4Addr}; +use std::path::PathBuf; +use std::process::exit; +use std::str::FromStr; + +/// NoGamePads Console - Client Cli +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct NoGamepadClientCli { + #[command(subcommand)] + command: Commands, +} + +/// 主要命令 +#[derive(Subcommand, Debug)] +enum Commands { + + // 账户设置 + #[command(subcommand, about = "Operation of player account")] + Account(AccountCommands), + + // 连接到服务器 + #[command(about = "Connect a player to server")] + Connect(ConnectArgs) +} + +/// 账户设置 命令 +#[derive(Subcommand, Debug)] +enum AccountCommands { + + // 列出所有账号 + #[command(about = "List all local players")] + List(ListAccountArgs), + + // 添加账号 + #[command(about = "Add a local player")] + Add(AddAccountArgs), + + // 移除账号 + #[command(about = "Remove a local player")] + Remove(RemoveAccountArgs), + + // 自定义账号显示信息 + #[command(about = "Customize how the player appears")] + Customize(CustomizeAccountArgs) +} + +/// 列出所有账号 参数 +#[derive(Args, Debug)] +struct ListAccountArgs { } + +/// 添加账号 参数 +#[derive(Args, Debug)] +struct AddAccountArgs { + + // 注册的角色 ID + #[arg(value_name = "NAME")] + id: String +} + +/// 移除账号 参数 +#[derive(Args, Debug)] +struct RemoveAccountArgs { + + // 删除的角色 ID + #[arg(value_name = "NAME")] + id: String +} + +/// 自定义账号显示信息 参数 +#[derive(Args, Debug)] +struct CustomizeAccountArgs { + + // 定制的角色 ID + #[arg(value_name = "WHO")] + id: String, + + // 昵称 + #[arg(short, long, help = "Nickname")] + name: Option<String>, + + // 颜色 + #[arg(short = 'H', long = "hsv", num_args = 3, value_names = ["H", "S", "V"], help = "Player color, h(0 - 360), s(0 - 1), v(0 - 1)")] + hsv: Option<Vec<f64>> +} + +/// 连接到服务器 +#[derive(Args, Debug)] +struct ConnectArgs { + + // 连接的玩家 + #[arg(value_name = "WHO")] + id: String, + + // 目标服务器 + #[arg(value_name = "WHERE", default_value = "127.0.0.1")] + target: String, + + // 目标端口 + #[arg(short, long, default_value = "5989")] // <---- DEFAULT_PORT + port: Option<u16>, + + // 启用调试 + #[arg(long)] + debug: bool, +} + +/// 配置文件后缀名称 +const EXTENSION_NAME : &str = "yaml"; + +fn main() { + + // 初始化 + let root = get_config_folder_path(); + if ! root.exists() { + create_dir_all(root.as_path()).unwrap(); + } + + // 命令行 + let cli = NoGamepadClientCli::parse(); + match cli.command { + + // 账户设置部分 + Account(commands) => { + match commands { + + // 添加账号 + Add(args) => { + if is_account_exist(&args.id) { + println!("Account already exists!"); + } else { + // 账号 ID 和 配置路径 + let account_id = process_inputted_text(args.id); + let account_config_path = get_account_config_path(&account_id); + + // 为新号准备的配置文件 + let mut new_info = PlayerInfo::new(); + + // 密码输入和密码验证 + let password = rpassword::prompt_password("Type password: ").unwrap(); + let password_confirm = rpassword::prompt_password("Confirm password: ").unwrap(); + if ! password_confirm.eq(&password) { + println!("Password does not match!"); + exit(1); + } + + // 随机生成 色调 值 + let mut rng = rand::rng(); + let random_hue: i32 = rng.random_range(0..=360); + + // 建立账号信息并预填入信息 + new_info.setup_account_info(account_id.as_str(), password.as_str()); + new_info.set_nickname(account_id.as_str()); + new_info.set_customize_color_hsv(random_hue, 0.8, 0.8); + + // 新配置信息的文本 + let new_info_yaml = serde_yaml::to_string(&new_info); + + // 将信息写入文件系统 + let mut buffer = File::create(account_config_path).unwrap(); + buffer.write_all(new_info_yaml.unwrap().as_bytes()).unwrap(); + + println!("Account created."); + } + }, + + // 移除账号 + Remove(args) => { + if ! is_account_exist(&args.id) { + println!("Account not found!"); + } else { + // 账号 ID 和 配置路径 + let account_id = process_inputted_text(args.id); + let account_config_path = get_account_config_path(&account_id); + + // 删除文件 + remove_file(account_config_path).unwrap(); + + println!("Account removed."); + } + }, + + // 列出所有账号 + List(_args) => { + // 账号信息文件夹 + let folder_path = get_config_folder_path(); + + // 输出表格 + let mut info_table = Table::new(); + + // 表头 + info_table.add_row(row!["ACCOUNT_ID", "NICKNAME", "COLOR", "HASH"]); + + // 遍历目录下文件,将信息逐一填入表格 + for item in folder_path.read_dir().unwrap() { + if let Ok(path) = item { + // 文件名 + let file_name = path.file_name().into_string().unwrap(); + let ext = format!(".{}", EXTENSION_NAME); + + // 判断是否为指定后缀 + if file_name.contains(ext.as_str()) { + + // 去除后缀内容,截取为 ID + let id = file_name.replace(ext.as_str(), ""); + + // 读取并加载其中的玩家信息 + let file = File::open(get_account_config_path(&id)).unwrap(); + let reader = BufReader::new(file); + let info: PlayerInfo = serde_yaml::from_reader(reader).unwrap(); + + // 填入表格 + info_table.add_row(row![ + &id, // ACCOUNT_ID + info.customize.nickname, // NICKNAME + hsv_to_hex( // COLOR + info.customize.color_hue, + info.customize.color_saturation, + info.customize.color_value), + info.account.player_hash // HASH + ]); + } + } + } + println!("{}", info_table.to_string()) + }, + + // 自定义账号显示信息 + Customize(args) => { + + // 加载配置文件 + let file = File::open(get_account_config_path(&args.id)).unwrap(); + let reader = BufReader::new(file); + let mut info: PlayerInfo = serde_yaml::from_reader(reader).unwrap(); + + // HSV 参数 + if args.hsv.is_some() { + let hsv = args.hsv.unwrap(); + let hue = hsv[0].round().clamp(0.0, 360.0); + let sat = hsv[1].clamp(0.0, 1.0); + let val = hsv[2].clamp(0.0, 1.0); + + info.customize.color_hue = hue as i32; + info.customize.color_saturation = sat; + info.customize.color_value = val; + + println!("Set {}'s HSV color to: {}, {}, {}.", &args.id, hue, sat, val); + } + + // 昵称 参数 + if args.name.is_some() { + let name = args.name.unwrap(); + info.customize.nickname = name.clone(); + + println!("Set {}'s display name to: {}.", &args.id, name); + } + + // 写入配置文件 + let yaml_content = serde_yaml::to_string(&info); + let mut buffer = File::create(get_account_config_path(&args.id)).unwrap(); + buffer.write_all(yaml_content.unwrap().as_bytes()).unwrap(); + }, + } + }, + + // 连接到服务器 + Connect(args) => { + if ! is_account_exist(&args.id) { + println!("Player not found!"); + exit(1); + } + + // 加载配置文件 + let file = File::open(get_account_config_path(&args.id)).unwrap(); + let reader = BufReader::new(file); + let info: PlayerInfo = serde_yaml::from_reader(reader).unwrap(); + + // 从参数获得 Ip 地址 (或默认) + let addr : IpAddr; + match IpAddr::from_str(&args.target) { + Ok(result) => { addr = result; } + Err(_err) => { + addr = IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)); + } + } + + // 从参数获得端口地址 (或默认) + let port : u16 = if args.port.is_some() { args.port.unwrap() } else { DEFAULT_PORT } + .clamp(0, 65535); + + // 绑定目标地址 + let mut client = PadClient::bind_addr_with_port(addr, port); + + // 启动调试模式 ? + if args.debug { + println!("- DEBUG MODE -"); + client.enable_console(); + } + + // 写入玩家信息 + client.bind_player(info); + + // 连接 + client.connect(); + println!("Connected {} to {}:{}", args.id, addr.to_string(), port.to_string()); + } + } +} + +/// # 处理输入的文本 +/// +/// 将输入的文本进行初步处理,以适合文件名称显示 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | input | String | 输入原始文本 | +/// | -> | String | 处理后的结果 | +fn process_inputted_text(input: String) -> String { + // 截取前后文本,并转换为小写 + let s = input.trim().to_lowercase(); + let mut result = String::new(); + + // 处理其中的特殊符号,部分用于分割的符号需要转换为下划线 + for c in s.chars() { + match c { + '\n' | '_' => continue, + '-' | '.' | ',' | ' ' => result.push('_'), + _ => result.push(c), + } + } + + // 仅保留 ASCII 字符 + result.chars() + .filter(|&c| c.is_ascii_alphanumeric() || c == '_') + .collect() +} + +/// # 将 HSV 数值转换为 HEX 颜色码字符串 +/// +/// 在显示玩家信息时,因 HSV 不如 RGB 直观,便转换为 HEX 字符串 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | h | i32 | 色相值 (0 - 360) | +/// | s | f64 | 饱和度 (0 - 1) | +/// | v | f64 | 明亮度 (0 - 1) | +/// | -> | String | HEX 字符串 | +fn hsv_to_hex(h: i32, s: f64, v: f64) -> String { + let h = (h as f64).clamp(0.0, 360.0); + let s = s.clamp(0.0, 1.0); + let v = v.clamp(0.0, 1.0); + + let c = v * s; + let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()); + let m = v - c; + + let (r, g, b) = match (h / 60.0) as usize { + 0 => (c, x, 0.0), + 1 => (x, c, 0.0), + 2 => (0.0, c, x), + 3 => (0.0, x, c), + 4 => (x, 0.0, c), + 5 => (c, 0.0, x), + _ => (0.0, 0.0, 0.0), + }; + + let r = ((r + m) * 255.0).round() as u8; + let g = ((g + m) * 255.0).round() as u8; + let b = ((b + m) * 255.0).round() as u8; + format!("#{:02X}{:02X}{:02X}", r, g, b) +} + +/// # 获得配置文件目录地址 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | -> | PathBuf | 地址 | +fn get_config_folder_path() -> PathBuf { + current_dir().unwrap().join(".nogpadc") +} + +/// # 获得账户配置文件地址 +/// +/// 输入指定的账户ID,获得其配置文件的目录 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | id | &str | 账户 ID | +/// | -> | PathBuf | 地址 | +fn get_account_config_path(id: &str) -> PathBuf { + get_config_folder_path().join(format!("{}.{}", process_inputted_text(id.to_string()), EXTENSION_NAME)) +} + +/// # 判断账户是否存在 +/// +/// 输入指定的账户ID,获得其配置文件的目录 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | id | &str | 账户 ID | +/// | -> | bool | 是否存在 | +fn is_account_exist(id: &str) -> bool { + let id = process_inputted_text(id.to_string()); + let path = get_config_folder_path(); + let dir = path.as_path().read_dir().unwrap(); + let mut found = false; + for item in dir { + if let Ok(path) = item { + if path.file_name().eq(format!("{}.{}", id, EXTENSION_NAME).as_str()) { + found = true; + } + } + } + found +}
\ No newline at end of file |
