aboutsummaryrefslogtreecommitdiff
path: root/console/src/bin
diff options
context:
space:
mode:
Diffstat (limited to 'console/src/bin')
-rw-r--r--console/src/bin/nogpadc.rs436
-rw-r--r--console/src/bin/nogpads.rs257
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