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 | |
First commit
Diffstat (limited to 'console/src')
| -rw-r--r-- | console/src/bin/nogpadc.rs | 436 | ||||
| -rw-r--r-- | console/src/bin/nogpads.rs | 257 |
2 files changed, 693 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 diff --git a/console/src/bin/nogpads.rs b/console/src/bin/nogpads.rs new file mode 100644 index 0000000..95f5ec7 --- /dev/null +++ b/console/src/bin/nogpads.rs @@ -0,0 +1,257 @@ +use clap::{arg, Args, Parser, Subcommand}; +use nogamepads_core::pad_io::server::nogamepads_server::{PadServer}; +use nogamepads_core::DEFAULT_PORT; +use serde::{Deserialize, Serialize}; +use std::env::current_dir; +use std::fs::{create_dir, File}; +use std::io::{BufReader, Write}; +use std::net::{IpAddr, Ipv4Addr}; +use std::path::PathBuf; +use nogamepads_core::pad_data::game_profile::game_profile::GameProfile; + +/// NoGamePads Console - Server Cli +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct NoGamepadServerCli { + #[command(subcommand)] + command: Commands, +} + +/// 主要命令 +#[derive(Subcommand, Debug)] +enum Commands { + + // 服务端配置 + #[command(about = "Configure the server")] + Config(ConfigArgs), + + // 运行服务端 + #[command(about = "Run the server")] + Run(RunArgs) +} + +/// 服务端配置 参数 +#[derive(Args, Debug)] +struct ConfigArgs { + + // 绑定的端口号 + #[arg(short, long, help = "Server port (0 = Default)")] // <---- DEFAULT_PORT + port: Option<u16>, + + // 游戏名称 + #[arg(short ='n', long = "name")] + game_name: Option<String>, + + // 游戏描述 + #[arg(short = 'd', long = "description")] + game_description: Option<String>, + + // 游戏组织 + #[arg(short = 'o', long = "organization")] + organization: Option<String>, + + // 游戏版本 + #[arg(short = 'v', long = "version")] + version: Option<String>, + + // 工作室 & 游戏 主页 + #[arg(short = 'w', long = "website")] + website: Option<String>, + + // 交流邮箱 + #[arg(short = 'e', long = "email")] + email: Option<String> +} + +/// 运行服务端 参数 +#[derive(Args, Debug)] +struct RunArgs { + + // 调试模式 + #[arg(long)] + debug: bool, +} + +/// 本地存储的配置信息 +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct ServerConfig { + port: u16, + profile: GameProfile, +} + +impl Default for ServerConfig { + fn default() -> Self { + ServerConfig { + port: DEFAULT_PORT, + profile: GameProfile::default(), + } + } +} + +/// # 快速生成 更新服务端信息 的宏 +macro_rules! update_config { + ($config:expr, $args:expr, $($field:ident),+) => { + $( + if let Some(ref value) = $args.$field { + $config.profile.$field = value.clone(); + println!("Changed profile \"{}\" to \"{}\"", + stringify!($field), + value + ); + } + )+ + }; +} + +fn main () { + + // 命令行 + let cli = NoGamepadServerCli::parse(); + match cli.command { + + // 服务端配置 + Commands::Config(args) => { + // 读取服务端配置 + let mut config = read_config(); + + // 端口信息配置: + // 端口数值被限定在 0 - 65535,但是若端口参数为 0,则会被设置为默认端口 + if args.port.is_some() { + let port = args.port.unwrap_or(DEFAULT_PORT).clamp(0, 65535); + config.port = if port == 0 { DEFAULT_PORT } else { port }; + } + + // 其他信息配置 + update_config!( + config, args, + game_name, + game_description, + organization, + version, + website, + email + ); + + // 写入配置 + write_config(config); + }, + + // 运行服务端 + Commands::Run(args) => { + // 读取服务端配置 + let config = read_config(); + + // 根据调试选项启动服务端 + if args.debug { + println!("- DEBUG MODE -"); + println!("Server started!"); + PadServer::default() + .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), config.port) + .put_profile(config.profile) + .enable_console() + .build() + .start_listening(); + } else { + println!("Server started!"); + PadServer::default() + .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), config.port) + .put_profile(config.profile) + .build() + .start_listening(); + } + } + } +} + +/// # 获得配置文件目录地址 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | -> | PathBuf | 地址 | +#[allow(dead_code)] +fn get_config_folder_path () -> PathBuf { + current_dir().unwrap().join(".nogpads") +} + +/// # 获得配置文件地址 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | -> | PathBuf | 地址 | +#[allow(dead_code)] +fn get_config_file_path () -> PathBuf { + get_config_folder_path().join("config.yaml") +} + +/// # 获得布局文件地址 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | -> | PathBuf | 地址 | +#[allow(dead_code)] +fn get_layout_file_path () -> PathBuf { + get_config_folder_path().join("layout.yaml") +} + +/// # 获得皮肤资源地址 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | -> | PathBuf | 地址 | +#[allow(dead_code)] +fn get_assets_package_path () -> PathBuf { + get_config_folder_path().join("assets.zip") +} + +/// # 读取配置信息 +/// +/// 读取 服务端控制台 的配置信息 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | -> | ServerConfig | 配置信息 | +fn read_config () -> ServerConfig { + let config_folder_path = get_config_folder_path(); + let config_file_path = get_config_file_path(); + + if ! config_folder_path.exists() { + create_dir(&config_folder_path).unwrap(); + } + + if ! config_file_path.exists() { + let config = ServerConfig::default(); + let config_text = serde_yaml::to_string(&config).unwrap(); + File::create(&config_file_path).unwrap().write_all(config_text.as_bytes()).unwrap(); + config + } else { + let config_file = File::open(&config_file_path).unwrap(); + let config_reader = BufReader::new(config_file); + serde_yaml::from_reader(config_reader).unwrap() + } +} + +/// # 写入配置信息 +/// +/// 将 服务端控制台 的配置信息写入本地 +/// +/// ## 参数 - Parameters +/// +/// | Field | Type | Description | +/// | ------ | --------------------- | ----------- | +/// | config | ServerConfig | 配置信息 | +fn write_config (config: ServerConfig) { + let config_file_path = get_config_file_path(); + + let config_text = serde_yaml::to_string(&config).unwrap(); + File::create(config_file_path).unwrap().write_all(config_text.as_bytes()).unwrap(); +}
\ No newline at end of file |
