aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author1992414357@qq.com <1992414357@qq.com>2025-04-21 06:25:54 +0800
committer1992414357@qq.com <1992414357@qq.com>2025-04-21 06:25:54 +0800
commit50ea9869616846e5a0448ead8272c397080f36d8 (patch)
tree5f2ec829ecf48441a6d33f4160909dc02ddf83bf
parentd72d700c84794ca420035ceb9c294563f3f9c048 (diff)
.
-rw-r--r--.idea/runConfigurations/Test_Message_Encoder.xml20
-rw-r--r--.idea/workspace.xml93
-rw-r--r--.obsidian/appearance.json5
-rw-r--r--.obsidian/core-plugins.json3
-rw-r--r--.obsidian/workspace.json47
-rw-r--r--README.md2
-rw-r--r--core/Cargo.toml2
-rw-r--r--core/src/client/client.rs29
-rw-r--r--core/src/client/client_commands.rs (renamed from core/src/pads_player_commands.rs)3
-rw-r--r--core/src/client/client_event.rs26
-rw-r--r--core/src/client/mod.rs3
-rw-r--r--core/src/constants.rs9
-rw-r--r--core/src/lib.rs12
-rw-r--r--core/src/message_encoder.rs71
-rw-r--r--core/src/messages.rs78
-rw-r--r--core/src/player/mod.rs1
-rw-r--r--core/src/player/player_info.rs (renamed from core/src/pads_player.rs)58
-rw-r--r--core/src/server/mod.rs3
-rw-r--r--core/src/server/server.rs (renamed from core/src/pads_server.rs)49
-rw-r--r--core/src/server/server_commands.rs (renamed from core/src/pas_server_commands.rs)3
-rw-r--r--core/src/server/server_event.rs36
-rw-r--r--core/test/Cargo.toml4
-rw-r--r--doc/Client - 客户端.md0
-rw-r--r--doc/Player - 玩家.md0
-rw-r--r--doc/STRUCT.canvas (renamed from doc/项目结构.canvas)94
-rw-r--r--doc/Server - 服务器.md0
26 files changed, 470 insertions, 181 deletions
diff --git a/.idea/runConfigurations/Test_Message_Encoder.xml b/.idea/runConfigurations/Test_Message_Encoder.xml
new file mode 100644
index 0000000..eb9d4d6
--- /dev/null
+++ b/.idea/runConfigurations/Test_Message_Encoder.xml
@@ -0,0 +1,20 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration default="false" name="Test Message Encoder" type="CargoCommandRunConfiguration" factoryName="Cargo Command" folderName="Test">
+ <option name="buildProfileId" value="test" />
+ <option name="command" value="test --package there-is-nopads-core --lib message_encoder::encoder_tests::test_encode_decode -- --exact" />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$/core" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
+</component> \ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 8f955fe..47350f4 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,28 +4,63 @@
<option name="autoReloadType" value="ALL" />
</component>
<component name="CargoProjects">
- <cargoProject FILE="$PROJECT_DIR$/core/server_test/Cargo.toml" />
<cargoProject FILE="$PROJECT_DIR$/client/Cargo.toml" />
<cargoProject FILE="$PROJECT_DIR$/core/Cargo.toml" />
+ <cargoProject FILE="$PROJECT_DIR$/core/test/Cargo.toml" />
</component>
<component name="ChangeListManager">
<list default="true" id="43098f42-7a3b-4fc2-ad78-a08c96f7f5db" name="Changes" comment="修改作者所有权">
- <change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.gitignore" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/.init/generate_docs.bat" beforeDir="false" afterPath="$PROJECT_DIR$/.init/generate_docs.bat" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/core/src/client/client.rs" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/core/src/client/client_event.rs" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/core/src/client/mod.rs" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/core/src/message_encoder.rs" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/core/src/messages.rs" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/core/src/player/mod.rs" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/core/src/server/mod.rs" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/core/src/server/server_event.rs" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/doc/Client - 客户端.md" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/doc/Player - 玩家.md" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/doc/STRUCT.canvas" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/doc/Server - 服务器.md" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/.obsidian/appearance.json" beforeDir="false" afterPath="$PROJECT_DIR$/.obsidian/appearance.json" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/.obsidian/core-plugins.json" beforeDir="false" afterPath="$PROJECT_DIR$/.obsidian/core-plugins.json" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/.obsidian/workspace.json" beforeDir="false" afterPath="$PROJECT_DIR$/.obsidian/workspace.json" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/core/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/core/Cargo.toml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/core/src/constants.rs" beforeDir="false" afterPath="$PROJECT_DIR$/core/src/constants.rs" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/core/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/core/src/lib.rs" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/core/src/pads_player.rs" beforeDir="false" afterPath="$PROJECT_DIR$/core/src/player/player_info.rs" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/core/src/pads_player_commands.rs" beforeDir="false" afterPath="$PROJECT_DIR$/core/src/client/client_commands.rs" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/core/src/pads_server.rs" beforeDir="false" afterPath="$PROJECT_DIR$/core/src/server/server.rs" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/core/src/pas_server_commands.rs" beforeDir="false" afterPath="$PROJECT_DIR$/core/src/server/server_commands.rs" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/core/test/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/core/test/Cargo.toml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/doc/基础功能列表.md" beforeDir="false" afterPath="$PROJECT_DIR$/doc/基础功能列表.md" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/doc/项目结构.canvas" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
- <component name="ExecutionTargetManager" SELECTED_TARGET="RsBuildProfile:dev" />
+ <component name="ExecutionTargetManager" SELECTED_TARGET="RsBuildProfile:test" />
+ <component name="FileTemplateManagerImpl">
+ <option name="RECENT_TEMPLATES">
+ <list>
+ <option value="Rust File" />
+ </list>
+ </option>
+ </component>
+ <component name="Git.Settings">
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+ </component>
<component name="MacroExpansionManager">
<option name="directoryName" value="EK7IzXE2" />
</component>
- <component name="ProjectColorInfo"><![CDATA[{
- "customColor": "",
- "associatedIndex": 1
-}]]></component>
+ <component name="ProjectColorInfo">{
+ &quot;customColor&quot;: &quot;&quot;,
+ &quot;associatedIndex&quot;: 1
+}</component>
<component name="ProjectId" id="2vby3gKCsFT9WfRIRxaNtOUu2Bx" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
@@ -36,10 +71,14 @@
"Attach to Node.js/Chrome.Unnamed.executor": "Debug",
"Cargo.Run phantomlink-gamepad-client.executor": "Run",
"Cargo.Run phantomlink-server-test.executor": "Run",
+ "Cargo.Test Message Encoder.executor": "Run",
+ "Cargo.Test encoder_tests.executor": "Run",
+ "Cargo.Test message_encoder::encoder_tests::test_encode_decode.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.rust.reset.selective.auto.import": "true",
"Shell Script.Shell Generate Documents.executor": "Run",
+ "git-widget-placeholder": "main",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
@@ -51,17 +90,40 @@
"vue.rearranger.settings.migration": "true"
}
}]]></component>
- <component name="RunManager" selected="Shell Script.Shell Initialize Project">
+ <component name="RunManager" selected="Cargo.Test encoder_tests">
+ <configuration name="Test encoder_tests" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
+ <option name="command" value="test --package there-is-nopads-core --lib message_encoder::encoder_tests" />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$/core" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
<list>
- <item itemvalue="Cargo.Run Server Test" />
- <item itemvalue="Cargo.Run Client" />
- <item itemvalue="Shell Script.Shell Initialize Project" />
- <item itemvalue="Shell Script.Shell Generate Documents" />
+ <item itemvalue="Cargo.Run Console Client" />
+ <item itemvalue="Cargo.Run Console Server" />
+ <item itemvalue="Cargo.Run Test" />
+ <item itemvalue="Cargo.Test Message Encoder" />
+ <item itemvalue="Cargo.Run Gamepad Client" />
+ <item itemvalue="Cargo.Test encoder_tests" />
+ <item itemvalue="Shell Script.Generate Documents (Linux)" />
+ <item itemvalue="Shell Script.Initialize (Linux)" />
+ <item itemvalue="Shell Script.Generate Documents (Windows)" />
+ <item itemvalue="Shell Script.Initialize (Windows)" />
</list>
<recent_temporary>
<list>
- <item itemvalue="Cargo.Run Client" />
- <item itemvalue="Cargo.Run Server Test" />
+ <item itemvalue="Cargo.Test encoder_tests" />
+ <item itemvalue="Cargo.Test Message Encoder" />
</list>
</recent_temporary>
</component>
@@ -77,6 +139,7 @@
<option name="presentableId" value="Default" />
<updated>1744427871470</updated>
<workItem from="1744427873184" duration="1209000" />
+ <workItem from="1745180836475" duration="6545000" />
</task>
<task id="LOCAL-00001" summary="修改作者所有权">
<option name="closed" value="true" />
diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json
index 9e26dfe..e026aad 100644
--- a/.obsidian/appearance.json
+++ b/.obsidian/appearance.json
@@ -1 +1,4 @@
-{} \ No newline at end of file
+{
+ "accentColor": "#ff1a1a",
+ "nativeMenus": false
+} \ No newline at end of file
diff --git a/.obsidian/core-plugins.json b/.obsidian/core-plugins.json
index 436f43c..c89a841 100644
--- a/.obsidian/core-plugins.json
+++ b/.obsidian/core-plugins.json
@@ -26,5 +26,6 @@
"workspaces": false,
"file-recovery": true,
"publish": false,
- "sync": false
+ "sync": false,
+ "webviewer": false
} \ No newline at end of file
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index 45d7d33..8889e1a 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -11,6 +11,23 @@
"id": "8fab3a873edfc7f3",
"type": "leaf",
"state": {
+ "type": "canvas",
+ "state": {
+ "file": "doc/STRUCT.canvas",
+ "viewState": {
+ "x": 569.6581039476258,
+ "y": -2212.1264050437776,
+ "zoom": -1.5882018370782922
+ }
+ },
+ "icon": "lucide-layout-dashboard",
+ "title": "STRUCT"
+ }
+ },
+ {
+ "id": "f33bc2925c565d58",
+ "type": "leaf",
+ "state": {
"type": "markdown",
"state": {
"file": "doc/基础功能列表.md",
@@ -40,7 +57,8 @@
"state": {
"type": "file-explorer",
"state": {
- "sortOrder": "alphabetical"
+ "sortOrder": "alphabetical",
+ "autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "文件列表"
@@ -164,19 +182,24 @@
},
"active": "8fab3a873edfc7f3",
"lastOpenFiles": [
- "doc/项目结构.canvas",
- "doc/基础功能列表.md",
- "core/src/pads_server.rs~",
- "core/test/desktop.ini",
- "core/desktop.ini~",
- "core/src/pads_player_commands.rs~",
- "core/src/pads_player.rs~",
+ "core/src/message_encoder.rs~",
+ "core/src/player/player_info.rs~",
+ "core/src/messages.rs~",
+ "core/src/constants.rs~",
+ "core/src/lib.rs~",
"core/Cargo.toml~",
- "core/test/src/bin/console_server.rs~",
- "core/test/src/bin/console_client.rs",
- "core/test/src/bin/console_server.rs",
- "core/test/src/main.rs",
+ "core/src/client/client.rs~",
+ "core/src/message_encoder.rs",
+ "core/src/client/client_event.rs~",
+ "core/src/server/server.rs~",
+ "core/src/server/server_event.rs~",
+ "doc/Client - 客户端.md",
+ "doc/Server - 服务器.md",
+ "doc/STRUCT.canvas",
+ "doc/基础功能列表.md",
+ "doc/Player - 玩家.md",
"README.md",
+ "doc/未命名.md",
"Client/Art/Pad.png",
"Client/Art/config/skin.png",
"LICENSE.md",
diff --git a/README.md b/README.md
index 617ad7d..37f8ba3 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
-# PhantomLink-Gamepad
+# There Is No Pads
A mobile device control solution for local multiplayer gaming, featuring an in-game interface, controller client, and support for universal devices.
diff --git a/core/Cargo.toml b/core/Cargo.toml
index df61b91..309ef64 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -13,6 +13,6 @@ edition = "2024"
[dependencies]
sha1 = "0.10.6"
serde = { version = "1.0.219", features = ["derive"] }
-bincode = "2.0.1"
+bincode = { version = "2.0.1", features = ["serde"]}
tokio = { version = "1.0", features = ["full"] }
hex = "0.4.3" \ No newline at end of file
diff --git a/core/src/client/client.rs b/core/src/client/client.rs
new file mode 100644
index 0000000..c11bb61
--- /dev/null
+++ b/core/src/client/client.rs
@@ -0,0 +1,29 @@
+use crate::constants::DEFAULT_PORT;
+use crate::player::player_info::PlayerInfo;
+use std::net::SocketAddr;
+
+/// 玩家客户端
+pub struct PadClient {
+ player_info: PlayerInfo,
+ remote_addr: SocketAddr,
+ receive_skin_package : bool
+}
+
+impl Default for PadClient {
+
+ fn default() -> Self {
+ PadClient {
+ player_info: PlayerInfo::default(),
+ remote_addr: SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT)),
+ receive_skin_package: true
+ }
+ }
+}
+
+impl PadClient {
+
+ /// # 连接到服务器
+ pub fn connect_server(&self) {
+
+ }
+} \ No newline at end of file
diff --git a/core/src/pads_player_commands.rs b/core/src/client/client_commands.rs
index 276805f..f674f6c 100644
--- a/core/src/pads_player_commands.rs
+++ b/core/src/client/client_commands.rs
@@ -1,4 +1,5 @@
-use crate::pads_player::{PadClient, PlayerMessage};
+use crate::client::client::PadClient;
+use crate::messages::PlayerMessage;
impl PadClient {
diff --git a/core/src/client/client_event.rs b/core/src/client/client_event.rs
new file mode 100644
index 0000000..ac52e53
--- /dev/null
+++ b/core/src/client/client_event.rs
@@ -0,0 +1,26 @@
+use crate::messages::ServerMessage;
+
+/// 玩家客户端事件
+pub trait PadClientEvent {
+
+ /// 加入游戏
+ fn joined_game();
+
+ /// 加入游戏失败
+ fn joined_game_failed();
+
+ /// 退出游戏
+ fn left_game();
+
+ /// 被踢出游戏
+ fn kicked();
+
+ /// 被该游戏封禁(若游戏未保存名单,则游戏重启前无法进入)
+ fn banned();
+
+ /// 掉线
+ fn disconnected();
+
+ /// 接收到消息
+ fn msg_received(msg: ServerMessage);
+} \ No newline at end of file
diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs
new file mode 100644
index 0000000..0cb7dd2
--- /dev/null
+++ b/core/src/client/mod.rs
@@ -0,0 +1,3 @@
+pub mod client_commands;
+pub mod client;
+pub mod client_event; \ No newline at end of file
diff --git a/core/src/constants.rs b/core/src/constants.rs
index a66f79f..6257c9d 100644
--- a/core/src/constants.rs
+++ b/core/src/constants.rs
@@ -1 +1,8 @@
-pub const SALT : &str = ":)"; \ No newline at end of file
+use bincode::config;
+use bincode::config::Configuration;
+
+pub const SALT : &str = ":)";
+
+pub const DEFAULT_PORT : u16 = 8916;
+
+pub const BINCODE_CONFIG : Configuration = config::standard(); \ No newline at end of file
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 056b046..d8b1df8 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -1,5 +1,9 @@
-pub mod pads_server;
-pub mod pads_player;
+// Public
+pub mod client;
+pub mod player;
+pub mod server;
+pub mod messages;
+
+// Private
mod constants;
-mod pas_server_commands;
-mod pads_player_commands; \ No newline at end of file
+mod message_encoder;
diff --git a/core/src/message_encoder.rs b/core/src/message_encoder.rs
new file mode 100644
index 0000000..a8575d0
--- /dev/null
+++ b/core/src/message_encoder.rs
@@ -0,0 +1,71 @@
+use std::io::{Read, Write};
+use bincode::error::{DecodeError, EncodeError};
+use serde::de::DeserializeOwned;
+use serde::Serialize;
+use crate::messages::{ClientMessage, PlayerMessage, ServerCallbackMessage, ServerMessage};
+
+pub trait MessageEncoder: Serialize + DeserializeOwned {
+ fn encode(&self) -> Result<usize, EncodeError>;
+ fn decode(encode: usize) -> Result<Self, DecodeError>
+ where
+ Self: Sized;
+}
+
+impl MessageEncoder for PlayerMessage {
+ fn encode(&self) -> Result<usize, EncodeError> {
+ bincode::serde::encode_into_std_write(self, &mut std::io::sink(), bincode::config::standard())
+ }
+
+ fn decode(encode: &usize) -> Result<Self, DecodeError> {
+ bincode::serde::decode_from_std_read(&mut &encode[..], bincode::config::standard())
+ }
+}
+
+impl MessageEncoder for ServerMessage {
+ fn encode(&self) -> Result<usize, EncodeError> {
+ bincode::serde::encode_into_std_write(self, &mut std::io::sink(), bincode::config::standard())
+ }
+
+ fn decode(encode: usize) -> Result<Self, DecodeError> {
+ bincode::serde::decode_from_std_read(&mut &encode[..], bincode::config::standard())
+ }
+}
+
+impl MessageEncoder for ClientMessage {
+ fn encode(&self) -> Result<usize, EncodeError> {
+ bincode::serde::encode_into_std_write(self, &mut std::io::sink(), bincode::config::standard())
+ }
+
+ fn decode(encode: usize) -> Result<Self, DecodeError> {
+ bincode::serde::decode_from_std_read(&mut &encode[..], bincode::config::standard())
+ }
+}
+
+impl MessageEncoder for ServerCallbackMessage {
+ fn encode(&self) -> Result<usize, EncodeError> {
+ bincode::serde::encode_into_std_write(self, &mut std::io::sink(), bincode::config::standard())
+ }
+
+ fn decode(encode: usize) -> Result<Self, DecodeError> {
+ bincode::serde::decode_from_std_read(&mut &encode[..], bincode::config::standard())
+ }
+}
+
+#[cfg(test)]
+mod encoder_tests {
+ use crate::message_encoder::MessageEncoder;
+ use crate::messages::PlayerMessage;
+
+
+ #[test]
+ fn test_encode_decode() {
+
+ let msg: String = String::from("Hello, World!");
+
+ let msg_player = PlayerMessage::Message(msg);
+ let msg_player_encoded = (&msg_player).encode().expect("Encode failed");
+ let msg_player_decoded = PlayerMessage::decode(msg_player_encoded).expect("Decode failed");
+
+ assert!(msg_player == msg_player_decoded)
+ }
+} \ No newline at end of file
diff --git a/core/src/messages.rs b/core/src/messages.rs
new file mode 100644
index 0000000..2ae8328
--- /dev/null
+++ b/core/src/messages.rs
@@ -0,0 +1,78 @@
+use std::time::Duration;
+use serde::{Deserialize, Serialize};
+use crate::player::player_info::PlayerInfo;
+
+/// # 玩家消息
+/// 玩家 --> 服务器
+#[derive(Serialize, Deserialize, PartialEq)]
+pub enum PlayerMessage {
+ /// 消息:发送字符串,服务端无需回复
+ Message(String),
+
+ /// 命令:发送字符串,服务器必须回复
+ Command(String),
+
+ /// 按钮按下:发送 u8 作为按键代号
+ ButtonPressed(u8),
+
+ /// 按钮抬起:发送 u8 作为按键代号
+ ButtonReleased(u8),
+
+ /// 轴向:发送 u8 作为轴向代号,两个 f64 分别代表 x 和 y 方向的轴向
+ /// u8:轴向代号
+ /// f64:X 轴向值 (0 - 1)
+ /// f64:Y 轴向值 (0 - 1)
+ Axis(u8, (f64, f64))
+}
+
+/// # 服务器消息
+/// 服务器 --> 玩家
+#[derive(Serialize, Deserialize, PartialEq)]
+pub enum ServerMessage {
+
+ /// 命令:发送字符串,客户端处理
+ Command(String),
+
+ /// 动画:发送字符串,客户端触发对应名称动画
+ Animation(String),
+
+ /// 震动:发送 Duration 作为时长,f64 作为震动强度 (0 - 1)
+ Shock(Duration, f64),
+
+ /// 踢出:发送踢出指令,以便让客户端明白自己的掉线是因为被踢出
+ /// “杀人还要诛心!!!好可怕啊!”
+ Kick
+}
+
+/// # 客户端消息
+/// 客户端 --> 服务器
+#[derive(Serialize, Deserialize, PartialEq)]
+pub enum ClientMessage {
+
+ /// 连接请求,将和服务端建立连接,并通知服务器将要建立连接的玩家
+ ConnectRequest(PlayerInfo),
+
+ /// 下载布局请求
+ RequestLayout,
+
+ /// 下载皮肤请求
+ RequestSkin,
+
+ /// 客户端准备就绪
+ Ready
+}
+
+/// # 服务器回调消息
+/// 服务器 --> 客户端
+#[derive(Serialize, Deserialize, PartialEq)]
+pub enum ServerCallbackMessage {
+
+ /// 拒绝:发送消息已通知客户端请求被拒绝
+ Refuse,
+
+ /// 接收:发送消息已通知客户端请求被接受,以继续操作
+ Accept,
+
+ /// 欢迎:客户端准备完成后,服务端发送 Welcome 以允许客户端以玩家加入游戏
+ Welcome
+} \ No newline at end of file
diff --git a/core/src/player/mod.rs b/core/src/player/mod.rs
new file mode 100644
index 0000000..fcb7558
--- /dev/null
+++ b/core/src/player/mod.rs
@@ -0,0 +1 @@
+pub mod player_info; \ No newline at end of file
diff --git a/core/src/pads_player.rs b/core/src/player/player_info.rs
index eb9c661..6412b66 100644
--- a/core/src/pads_player.rs
+++ b/core/src/player/player_info.rs
@@ -1,54 +1,10 @@
-use std::net::SocketAddr;
use hex::encode;
use serde::{Deserialize, Serialize};
use sha1::{Digest, Sha1};
use crate::constants::SALT;
-use crate::pads_server::ServerMessage;
-
-/// 玩家客户端
-pub struct PadClient {
- player_info: PlayerInfo,
- remote_addr: SocketAddr,
- receive_skin_package : bool
-}
-
-impl Default for PadClient {
-
- fn default() -> Self {
- PadClient {
- player_info: PlayerInfo::default(),
- remote_addr: SocketAddr::from(([127, 0, 0, 1], 8916)),
- receive_skin_package: true
- }
- }
-}
-
-/// 玩家客户端事件
-pub trait PadClientEvent {
-
- /// 加入游戏
- fn joined_game();
-
- /// 加入游戏失败
- fn joined_game_failed();
-
- /// 退出游戏
- fn left_game();
-
- /// 被踢出游戏
- fn kicked();
-
- /// 被该游戏封禁(若游戏未保存名单,则游戏重启前无法进入)
- fn banned();
-
- /// 掉线
- fn disconnected();
-
- /// 接收到消息
- fn msg_received(msg: ServerMessage);
-}
/// 玩家信息
+#[derive(Serialize, Deserialize, PartialEq)]
pub struct PlayerInfo {
pub display_name: String,
pub name_hash: String,
@@ -56,7 +12,7 @@ pub struct PlayerInfo {
// 颜色 HSV
pub color_hue: i32, // 0 - 360
pub color_saturation: f64, // 0 - 1
- pub color_value: f64 // 0 - 1
+ pub color_value: f64, // 0 - 1
}
impl Default for PlayerInfo {
@@ -72,16 +28,6 @@ impl Default for PlayerInfo {
}
}
-/// 玩家消息
-#[derive(Serialize, Deserialize, PartialEq)]
-pub enum PlayerMessage {
- Message(String),
- Command(String),
- ButtonPressed(u8),
- ButtonReleased(u8),
- Axis(u8, (f64, f64))
-}
-
impl PlayerInfo {
/// # 配置玩家 ID 和 名称
diff --git a/core/src/server/mod.rs b/core/src/server/mod.rs
new file mode 100644
index 0000000..d7553a8
--- /dev/null
+++ b/core/src/server/mod.rs
@@ -0,0 +1,3 @@
+pub mod server;
+pub mod server_commands;
+pub mod server_event; \ No newline at end of file
diff --git a/core/src/pads_server.rs b/core/src/server/server.rs
index d10a88b..c71892c 100644
--- a/core/src/pads_server.rs
+++ b/core/src/server/server.rs
@@ -1,7 +1,6 @@
+use crate::constants::DEFAULT_PORT;
+use crate::server::server_event::PadServerEvent;
use std::net::{SocketAddr, UdpSocket};
-use std::time::Duration;
-use bincode::Encode;
-use crate::pads_player::{PlayerInfo, PlayerMessage};
/// # 监听服务器
/// 监听服务器用于连接玩家设备,并接收玩家的按键输入
@@ -17,7 +16,7 @@ impl Default for PadServer {
fn default() -> Self {
PadServer {
server_name: String::from("This Is No Pads"),
- port: 8916
+ port: DEFAULT_PORT
// TODO :: 补充布局文件
}
}
@@ -54,46 +53,4 @@ impl PadServer {
pub fn stop_listening(&self) {
}
-}
-
-/// # 监听服务器事件
-/// 服务器运行过程中的信息将会发送到该 Event Trait
-pub trait PadServerEvent {
-
- /// 服务器启动时
- fn server_start();
-
- /// 服务器关闭时
- fn server_shutdown();
-
- /// 玩家加入请求
- /// info : 玩家信息
- /// return -> 是否允许加入服务器
- fn join_request_received(info: PlayerInfo) -> bool;
-
- /// 玩家加入游戏
- /// info : 玩家信息
- fn player_joined(info: PlayerInfo);
-
- /// 玩家离开游戏
- /// info : 玩家信息
- fn player_left(info: PlayerInfo);
-
- /// 玩家断开连接
- /// info : 玩家信息
- fn player_disconnected(info: PlayerInfo);
-
- /// 接收到消息
- /// info : 玩家信息
- /// msg : 接受的消息
- fn msg_received(info: PlayerInfo, msg: PlayerMessage);
-}
-
-/// # 监听服务器消息
-/// 监听服务器用于向玩家发送指令
-pub enum ServerMessage {
- Command(String),
- Animation(String),
- Shock(Duration, f64),
- Kick
} \ No newline at end of file
diff --git a/core/src/pas_server_commands.rs b/core/src/server/server_commands.rs
index 8d15261..dcf40a3 100644
--- a/core/src/pas_server_commands.rs
+++ b/core/src/server/server_commands.rs
@@ -1,5 +1,6 @@
use std::time::Duration;
-use crate::pads_server::{PadServer, ServerMessage};
+use crate::messages::ServerMessage;
+use crate::server::server::PadServer;
impl PadServer {
diff --git a/core/src/server/server_event.rs b/core/src/server/server_event.rs
new file mode 100644
index 0000000..f63df5f
--- /dev/null
+++ b/core/src/server/server_event.rs
@@ -0,0 +1,36 @@
+use crate::messages::PlayerMessage;
+use crate::player::player_info::PlayerInfo;
+
+/// # 监听服务器事件
+/// 服务器运行过程中的信息将会发送到该 Event Trait
+pub trait PadServerEvent {
+
+ /// 服务器启动时
+ fn server_start();
+
+ /// 服务器关闭时
+ fn server_shutdown();
+
+ /// 玩家加入请求
+ /// info : 玩家信息
+ /// return -> 是否允许加入服务器
+ fn join_request_received(info: PlayerInfo) -> bool;
+
+ /// 玩家加入游戏
+ /// info : 玩家信息
+ fn player_joined(info: PlayerInfo);
+
+ /// 玩家离开游戏
+ /// info : 玩家信息
+ fn player_left(info: PlayerInfo);
+
+ /// 玩家断开连接
+ /// info : 玩家信息
+ fn player_disconnected(info: PlayerInfo);
+
+ /// 接收到消息
+ /// info : 玩家信息
+ /// msg : 接受的消息
+ fn msg_received(info: PlayerInfo, msg: PlayerMessage);
+}
+
diff --git a/core/test/Cargo.toml b/core/test/Cargo.toml
index d78fa4f..0031ae8 100644
--- a/core/test/Cargo.toml
+++ b/core/test/Cargo.toml
@@ -5,6 +5,4 @@ version = "0.1.0"
edition = "2024"
[dependencies]
-there-is-nopads-core = { path = "../../core" }
-
-# Not A Controller \ No newline at end of file
+there-is-nopads-core = { path = "../../core" } \ No newline at end of file
diff --git a/doc/Client - 客户端.md b/doc/Client - 客户端.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/Client - 客户端.md
diff --git a/doc/Player - 玩家.md b/doc/Player - 玩家.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/Player - 玩家.md
diff --git a/doc/项目结构.canvas b/doc/STRUCT.canvas
index dff1b47..583a86d 100644
--- a/doc/项目结构.canvas
+++ b/doc/STRUCT.canvas
@@ -1,42 +1,53 @@
{
"nodes":[
- {"id":"693c49f59b413ba2","type":"text","text":"**CORE - 内核**","x":-108,"y":-220,"width":250,"height":60},
- {"id":"274e9bdc3ed07a30","type":"text","text":"**ENGINE BINDING - 引擎绑定**","x":-125,"y":-74,"width":284,"height":54},
- {"id":"13029209c27cc6dc","type":"text","text":"**QR CODE - 二维码**","x":-710,"y":-50,"width":250,"height":60},
- {"id":"970b836287034716","type":"text","text":"**CLIENT - 客户端**","x":-710,"y":110,"width":250,"height":50},
- {"id":"ae51ac1a55eb30ff","type":"text","text":"**PLAYER - 玩家**","x":-1120,"y":30,"width":250,"height":60},
- {"id":"6251c445b778d1a3","type":"text","text":"**支持的游戏**","x":-108,"y":40,"width":250,"height":50},
- {"id":"a3d79f917242ddd3","type":"text","text":"**不支持的游戏**","x":-108,"y":180,"width":250,"height":50},
- {"id":"0bbe75b82333d2f1","type":"text","text":"1. 自定义控制器界面\n2. 自定义控制器逻辑\n3. 自定义玩家信息","x":-870,"y":340,"width":250,"height":94},
- {"id":"09f45096019f8e93","type":"text","text":"核心","x":-898,"y":-791,"width":250,"height":60},
- {"id":"c19d7f3e2e7c9dde","type":"text","text":"皮肤制作器","x":-501,"y":-955,"width":250,"height":60},
- {"id":"4138f0aad76fc8c8","type":"text","text":"客户端","x":-560,"y":-786,"width":250,"height":50},
- {"id":"f18abb5436784cee","type":"text","text":"软件操控端","x":-140,"y":-791,"width":250,"height":60},
- {"id":"2b9761ae59d3c1c1","type":"text","text":"服务端","x":-1260,"y":-791,"width":250,"height":60},
- {"id":"933e04c72421cba8","type":"text","text":"游戏实现框架","x":-1620,"y":-791,"width":250,"height":60},
- {"id":"f16c9046ce342942","type":"text","text":"游戏","x":-1620,"y":-600,"width":250,"height":60},
- {"id":"61791938e8aa672c","type":"text","text":"从服务端下载布局文件","x":-648,"y":-2009,"width":240,"height":60},
- {"id":"e8680ff676132dd1","type":"text","text":"应用布局文件","x":-283,"y":-2009,"width":166,"height":60},
- {"id":"a0b3d2f2fba623d9","type":"text","text":"失败","x":-268,"y":-2180,"width":96,"height":60},
- {"id":"aa81015dae795f02","type":"text","text":"重试","x":192,"y":-2180,"width":240,"height":60},
- {"id":"a7eb26e63ce25f28","type":"text","text":"从服务端下载皮肤文件","x":192,"y":-2009,"width":240,"height":60},
- {"id":"f4c69b65009040c9","type":"text","text":"向服务端发送加入信息","x":1232,"y":-2009,"width":250,"height":60},
- {"id":"4ec22f6e59809493","type":"text","text":"重试","x":1232,"y":-2180,"width":250,"height":60},
- {"id":"ee471e71ef3e4254","type":"text","text":"应用皮肤","x":732,"y":-2009,"width":250,"height":60},
- {"id":"3d810b8ede0e7390","type":"text","text":"启用无皮肤模式","x":732,"y":-1840,"width":250,"height":60},
- {"id":"e9902af5372d0567","type":"text","text":"客户端","x":-1633,"y":-2009,"width":250,"height":60,"color":"3"},
- {"id":"b4c5ae3611026103","type":"text","text":"连接到服务端","x":-1193,"y":-2009,"width":242,"height":60},
- {"id":"b25707346da6f026","type":"text","text":"服务端 入口服务","x":-422,"y":-1340,"width":250,"height":60},
- {"id":"d149eb724bb75e1e","type":"text","text":"接收布局文件申请","x":-648,"y":-1740,"width":240,"height":60},
- {"id":"87d43cf10f79b197","type":"text","text":"接收连接申请","x":-1193,"y":-1740,"width":242,"height":60},
- {"id":"439f99fed6b0e32d","type":"text","text":"断开连接","x":-1508,"y":-1740,"width":164,"height":60},
- {"id":"3d7d1730cb96f756","type":"text","text":"失败","x":-1193,"y":-2180,"width":242,"height":60},
- {"id":"8b783d24effb4218","type":"text","text":"接收皮肤文件申请","x":192,"y":-1740,"width":250,"height":60},
- {"id":"5d68be252ba5db2d","type":"text","text":"接收加入游戏信息","x":1232,"y":-1740,"width":250,"height":60},
- {"id":"7faa97ecc512481b","type":"text","text":"创建玩家实例,为该玩家启用专用线程","x":1652,"y":-1740,"width":351,"height":60},
- {"id":"c8d6cc78d7f3a262","type":"text","text":"玩家已加入","x":1750,"y":-2009,"width":156,"height":60},
- {"id":"440f937f373d93ea","type":"text","text":"服务端 IO服务","x":1703,"y":-1340,"width":250,"height":60},
- {"id":"9e80714641f69c35","type":"text","text":"玩家线程","x":2280,"y":-1560,"width":155,"height":60}
+ {"id":"e39d3d8233e5dc17","type":"group","x":-1680,"y":-2600,"width":4135,"height":1080,"color":"#000000","label":"连接逻辑"},
+ {"id":"d3236356b653fa83","type":"group","x":-1680,"y":-3200,"width":1952,"height":560,"color":"#000000","label":"项目整体结构"},
+ {"id":"d2858e7dcde00ef4","type":"group","x":1136,"y":-1480,"width":1319,"height":694,"color":"#000000","label":"废弃:连接逻辑"},
+ {"id":"b2ef8065341cd4b4","type":"group","x":-2720,"y":-1480,"width":975,"height":468,"color":"#000000","label":"游戏玩家"},
+ {"id":"14281eab631d8297","type":"group","x":-2720,"y":-520,"width":975,"height":460,"color":"#000000","label":"游戏"},
+ {"id":"c19d7f3e2e7c9dde","type":"text","text":"皮肤制作器","x":-421,"y":-3115,"width":250,"height":60,"color":"4"},
+ {"id":"933e04c72421cba8","type":"text","text":"游戏实现框架","x":-1540,"y":-2951,"width":250,"height":60,"color":"5"},
+ {"id":"2b9761ae59d3c1c1","type":"text","text":"服务端","x":-1180,"y":-2951,"width":250,"height":60,"color":"5"},
+ {"id":"09f45096019f8e93","type":"text","text":"核心","x":-818,"y":-2951,"width":250,"height":60,"color":"1"},
+ {"id":"4138f0aad76fc8c8","type":"text","text":"客户端","x":-480,"y":-2946,"width":250,"height":50,"color":"4"},
+ {"id":"f16c9046ce342942","type":"text","text":"游戏","x":-1540,"y":-2760,"width":250,"height":60,"color":"5"},
+ {"id":"f18abb5436784cee","type":"text","text":"软件操控端","x":-60,"y":-2951,"width":250,"height":60,"color":"4"},
+ {"id":"991ab04aa54e93dc","type":"file","file":"doc/基础功能列表.md","x":-2720,"y":-3192,"width":975,"height":1672,"color":"#000000"},
+ {"id":"ae51ac1a55eb30ff","type":"text","text":"**PLAYER - 玩家**","x":1156,"y":-1210,"width":250,"height":60},
+ {"id":"0bbe75b82333d2f1","type":"text","text":"1. 自定义控制器界面\n2. 自定义控制器逻辑\n3. 自定义玩家信息","x":1406,"y":-900,"width":250,"height":94},
+ {"id":"13029209c27cc6dc","type":"text","text":"**QR CODE - 二维码**","x":1566,"y":-1290,"width":250,"height":60},
+ {"id":"970b836287034716","type":"text","text":"**CLIENT - 客户端**","x":1566,"y":-1130,"width":250,"height":50},
+ {"id":"b25707346da6f026","type":"text","text":"服务端 入口服务","x":-422,"y":-1660,"width":250,"height":60,"color":"5"},
+ {"id":"9e80714641f69c35","type":"text","text":"玩家线程","x":2280,"y":-1880,"width":155,"height":60,"color":"6"},
+ {"id":"ee471e71ef3e4254","type":"text","text":"应用皮肤","x":732,"y":-2329,"width":250,"height":60,"color":"4"},
+ {"id":"3d810b8ede0e7390","type":"text","text":"启用无皮肤模式","x":732,"y":-2160,"width":250,"height":60},
+ {"id":"4ec22f6e59809493","type":"text","text":"重试","x":1232,"y":-2500,"width":250,"height":60,"color":"2"},
+ {"id":"f4c69b65009040c9","type":"text","text":"向服务端发送加入信息","x":1232,"y":-2329,"width":250,"height":60,"color":"4"},
+ {"id":"5d68be252ba5db2d","type":"text","text":"接收加入游戏信息","x":1232,"y":-2060,"width":250,"height":60},
+ {"id":"e8680ff676132dd1","type":"text","text":"应用布局文件","x":-283,"y":-2329,"width":166,"height":60,"color":"4"},
+ {"id":"a0b3d2f2fba623d9","type":"text","text":"失败","x":-268,"y":-2500,"width":96,"height":60,"color":"1"},
+ {"id":"aa81015dae795f02","type":"text","text":"重试","x":192,"y":-2500,"width":240,"height":60,"color":"2"},
+ {"id":"3d7d1730cb96f756","type":"text","text":"失败","x":-1193,"y":-2500,"width":242,"height":60,"color":"1"},
+ {"id":"e9902af5372d0567","type":"text","text":"客户端","x":-1633,"y":-2329,"width":250,"height":60,"color":"4"},
+ {"id":"61791938e8aa672c","type":"text","text":"从服务端下载布局文件","x":-648,"y":-2329,"width":240,"height":60,"color":"4"},
+ {"id":"d149eb724bb75e1e","type":"text","text":"接收布局文件申请","x":-648,"y":-2060,"width":240,"height":60},
+ {"id":"87d43cf10f79b197","type":"text","text":"接收连接申请","x":-1193,"y":-2060,"width":242,"height":60},
+ {"id":"a7eb26e63ce25f28","type":"text","text":"从服务端下载皮肤文件","x":192,"y":-2329,"width":240,"height":60,"color":"4"},
+ {"id":"8b783d24effb4218","type":"text","text":"接收皮肤文件申请","x":192,"y":-2060,"width":250,"height":60},
+ {"id":"439f99fed6b0e32d","type":"text","text":"断开连接","x":-1508,"y":-2060,"width":164,"height":60,"color":"1"},
+ {"id":"b4c5ae3611026103","type":"text","text":"连接到服务端","x":-1193,"y":-2329,"width":242,"height":60,"color":"4"},
+ {"id":"7faa97ecc512481b","type":"text","text":"创建玩家实例,为该玩家启用专用线程","x":1652,"y":-2060,"width":351,"height":60},
+ {"id":"440f937f373d93ea","type":"text","text":"服务端 IO服务","x":1703,"y":-1660,"width":250,"height":60,"color":"5"},
+ {"id":"c8d6cc78d7f3a262","type":"text","text":"玩家已加入","x":1750,"y":-2329,"width":156,"height":60,"color":"6"},
+ {"id":"274e9bdc3ed07a30","type":"text","text":"**ENGINE BINDING - 引擎绑定**","x":2151,"y":-1314,"width":284,"height":54},
+ {"id":"693c49f59b413ba2","type":"text","text":"**CORE - 内核**","x":2168,"y":-1460,"width":250,"height":60},
+ {"id":"6251c445b778d1a3","type":"text","text":"**支持的游戏**","x":2168,"y":-1200,"width":250,"height":50},
+ {"id":"a3d79f917242ddd3","type":"text","text":"**不支持的游戏**","x":2168,"y":-1060,"width":250,"height":50},
+ {"id":"49f56b9f31dff1b6","type":"file","file":"doc/Player - 玩家.md","x":-2165,"y":-1432,"width":400,"height":400,"color":"6"},
+ {"id":"f3adb8605b9a6341","x":-3680,"y":-3192,"width":900,"height":1672,"color":"#000000","type":"link","url":"https://github.com/CatilGrass/ThereIsNoPads/projects"},
+ {"id":"d9248e03fc8021e3","type":"file","file":"doc/Client - 客户端.md","x":-2700,"y":-1432,"width":400,"height":400,"color":"4"},
+ {"id":"fc81e9051e8214a6","type":"file","file":"doc/Server - 服务器.md","x":-2700,"y":-480,"width":400,"height":400,"color":"5"},
+ {"id":"28fa0236d42adda5","type":"text","text":"游戏","x":-2010,"y":-315,"width":245,"height":70,"color":"3"}
],
"edges":[
{"id":"ce9b8719ff60e770","fromNode":"693c49f59b413ba2","fromSide":"bottom","toNode":"274e9bdc3ed07a30","toSide":"top","label":"导入"},
@@ -85,6 +96,13 @@
{"id":"7fa41a8b1d15da7a","fromNode":"7faa97ecc512481b","fromSide":"top","toNode":"c8d6cc78d7f3a262","toSide":"bottom","label":"回调到客户端"},
{"id":"d66a0a076b23e262","fromNode":"7faa97ecc512481b","fromSide":"right","toNode":"9e80714641f69c35","toSide":"top"},
{"id":"4bc2c01e0f472a4a","fromNode":"440f937f373d93ea","fromSide":"right","toNode":"9e80714641f69c35","toSide":"bottom"},
- {"id":"b0273cbfaea46103","fromNode":"9e80714641f69c35","fromSide":"bottom","toNode":"440f937f373d93ea","toSide":"right","label":"双工通信开始"}
+ {"id":"b0273cbfaea46103","fromNode":"9e80714641f69c35","fromSide":"bottom","toNode":"440f937f373d93ea","toSide":"right","label":"双工通信开始"},
+ {"id":"c4bc830e9ca45069","fromNode":"d9248e03fc8021e3","fromSide":"bottom","toNode":"fc81e9051e8214a6","toSide":"top"},
+ {"id":"ae7cad635a704300","fromNode":"fc81e9051e8214a6","fromSide":"top","toNode":"d9248e03fc8021e3","toSide":"bottom","label":"连接,传输皮肤与布局"},
+ {"id":"0e42a676c8fb9953","fromNode":"fc81e9051e8214a6","fromSide":"right","toNode":"28fa0236d42adda5","toSide":"left"},
+ {"id":"414fb80ca18d8584","fromNode":"28fa0236d42adda5","fromSide":"left","toNode":"fc81e9051e8214a6","toSide":"right","label":"-> 操作的输入\n<- 反馈的输出"},
+ {"id":"73da2cced5e7485a","fromNode":"d9248e03fc8021e3","fromSide":"right","toNode":"49f56b9f31dff1b6","toSide":"left"},
+ {"id":"5c3f6573b57538ae","fromNode":"49f56b9f31dff1b6","fromSide":"left","toNode":"d9248e03fc8021e3","toSide":"right"},
+ {"id":"b5d846c12a376aea","fromNode":"49f56b9f31dff1b6","fromSide":"bottom","toNode":"fc81e9051e8214a6","toSide":"right","label":"发送:\n自身信息、操作信息"}
]
} \ No newline at end of file
diff --git a/doc/Server - 服务器.md b/doc/Server - 服务器.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/Server - 服务器.md