ygopro-msg-encode 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ # GameRule 枚举修正总结
2
+
3
+ ## 问题
4
+
5
+ 之前错误地为 `HostInfo.rule` 字段创建了 `GameRule` 枚举,但实际上:
6
+
7
+ 1. **`rule` 字段是 UI 下拉列表的索引**(0-5),不是实际的 OT 值
8
+ 2. **实际的 OT 值**(1=OCG, 2=TCG, 4=OCG+TCG, 8=Traditional)是从 UI 索引映射过来的
9
+ 3. **在协议中传输的是索引值**,而不是 OT 值本身
10
+
11
+ ## 修正
12
+
13
+ ### 1. 删除了 `GameRule` 枚举 ✅
14
+
15
+ 从 `src/protos/network-enums.ts` 中删除:
16
+ ```typescript
17
+ // 已删除
18
+ export enum GameRule {
19
+ OCG_ONLY = 1,
20
+ TCG_ONLY = 2,
21
+ OCG_TCG = 4,
22
+ TRADITIONAL = 8,
23
+ }
24
+ ```
25
+
26
+ ### 2. 将 `HostInfo.rule` 改回 `number` ✅
27
+
28
+ ```typescript
29
+ export class HostInfo {
30
+ @BinaryField('u32', 0)
31
+ lflist: number;
32
+
33
+ @BinaryField('u8', 4)
34
+ rule: number; // Rule index (0-5), maps to OT values in UI
35
+
36
+ @BinaryField('u8', 5)
37
+ mode: GameMode;
38
+
39
+ // ...
40
+ }
41
+ ```
42
+
43
+ ### 3. 更新了文档 ✅
44
+
45
+ - `README.md` - 移除了 `GameRule` 的引用
46
+ - `ENUM_UPDATE.md` - 添加了 `HostInfo.rule` 保持为 `number` 的说明
47
+
48
+ ## UI 索引到 OT 值的映射(参考)
49
+
50
+ 从 `gframe/game.cpp` 中可以看到映射关系:
51
+
52
+ | UI 索引 | OT 值 | 说明 |
53
+ |---------|-------|------|
54
+ | 0 | 1 | OCG Only (String 1483) |
55
+ | 1 | 2 | TCG Only (String 1484) |
56
+ | 2 | 8 | Traditional (String 1485) |
57
+ | 3 | 4 | OCG + TCG (String 1486) |
58
+ | 4+ | ? | 其他规则 |
59
+
60
+ **注意**:协议中传输的是索引(0-5),不是 OT 值(1, 2, 4, 8)。
61
+
62
+ ## 保留的枚举
63
+
64
+ 以下枚举保持不变:
65
+
66
+ 1. `HandResult` - 猜拳结果
67
+ 2. `TurnPlayerResult` - 先后手选择
68
+ 3. `NetPlayerType` - 玩家类型/位置
69
+ 4. `GameMode` - 游戏模式
70
+ 5. `ErrorMessageType` - 错误消息类型
71
+ 6. `DeckErrorType` - 卡组错误类型
72
+ 7. `PlayerChangeState` - 玩家状态变化
73
+ 8. `RoomStatus` - 房间状态
74
+
75
+ ## 使用示例
76
+
77
+ ```typescript
78
+ import { YGOProCtosCreateGame, GameMode } from 'ygopro-msg-encode';
79
+
80
+ const createGame = new YGOProCtosCreateGame();
81
+ createGame.info.mode = GameMode.MATCH;
82
+ createGame.info.rule = 0; // UI 索引 0 (会映射到 OT=1, OCG Only)
83
+ createGame.info.duel_rule = 5; // MASTER_RULE_2020
84
+ ```
package/ENUM_UPDATE.md ADDED
@@ -0,0 +1,219 @@
1
+ # 枚举类型更新总结
2
+
3
+ 本文档记录了将网络协议中的数字字段改为 TypeScript 枚举的更新。
4
+
5
+ ## 更新原则
6
+
7
+ 1. **仅更新网络协议相关的枚举**:CTOS 和 STOC 协议中的语义字段
8
+ 2. **不修改已有常量**:MSG 协议中的字段(如 `phase`、`position`、`location`、`reason`)已在 `OcgcoreCommonConstants` 中定义,保持使用常量
9
+ 3. **使用 TypeScript enum**:提供类型安全和更好的 IDE 支持
10
+
11
+ ## 新增枚举类型
12
+
13
+ 所有枚举定义在 `src/protos/network-enums.ts`:
14
+
15
+ ### 1. HandResult - 猜拳结果
16
+ ```typescript
17
+ enum HandResult {
18
+ ROCK = 1,
19
+ SCISSORS = 2,
20
+ PAPER = 3,
21
+ }
22
+ ```
23
+ **使用位置**:`CTOS_HandResult.res`
24
+
25
+ ### 2. TurnPlayerResult - 先后手选择
26
+ ```typescript
27
+ enum TurnPlayerResult {
28
+ SECOND = 0,
29
+ FIRST = 1,
30
+ }
31
+ ```
32
+ **使用位置**:`CTOS_TPResult.res`
33
+
34
+ ### 3. NetPlayerType - 玩家类型/位置
35
+ ```typescript
36
+ enum NetPlayerType {
37
+ PLAYER1 = 0,
38
+ PLAYER2 = 1,
39
+ PLAYER3 = 2,
40
+ PLAYER4 = 3,
41
+ PLAYER5 = 4,
42
+ PLAYER6 = 5,
43
+ OBSERVER = 7,
44
+ }
45
+ ```
46
+ **使用位置**:
47
+ - `CTOS_Kick.pos`
48
+
49
+ ### 4. ChatColor - 聊天消息颜色
50
+ ```typescript
51
+ enum ChatColor {
52
+ LIGHTBLUE = 8,
53
+ RED = 11,
54
+ GREEN = 12,
55
+ BLUE = 13,
56
+ BABYBLUE = 14,
57
+ PINK = 15,
58
+ YELLOW = 16,
59
+ WHITE = 17,
60
+ GRAY = 18,
61
+ DARKGRAY = 19,
62
+ }
63
+ ```
64
+ **说明**:用于 `STOC_Chat.player_type` 字段,但该字段保持为 `number` 类型,因为它可以是玩家类型(0-7)或聊天颜色(8-19)
65
+
66
+ ### 5. GameMode - 游戏模式
67
+ ```typescript
68
+ enum GameMode {
69
+ SINGLE = 0,
70
+ MATCH = 1,
71
+ TAG = 2,
72
+ }
73
+ ```
74
+ **使用位置**:`HostInfo.mode`
75
+
76
+ ### 6. ErrorMessageType - 错误消息类型
77
+ ```typescript
78
+ enum ErrorMessageType {
79
+ JOINERROR = 1,
80
+ DECKERROR = 2,
81
+ SIDEERROR = 3,
82
+ VERERROR = 4,
83
+ }
84
+ ```
85
+ **使用位置**:`STOC_ErrorMsg.msg`
86
+
87
+ ### 7. DeckErrorType - 卡组错误类型
88
+ ```typescript
89
+ enum DeckErrorType {
90
+ LFLIST = 1,
91
+ OCGONLY = 2,
92
+ TCGONLY = 3,
93
+ UNKNOWNCARD = 4,
94
+ CARDCOUNT = 5,
95
+ MAINCOUNT = 6,
96
+ EXTRACOUNT = 7,
97
+ SIDECOUNT = 8,
98
+ NOTAVAIL = 9,
99
+ }
100
+ ```
101
+ **说明**:用于解析 `STOC_ErrorMsg.code` 的高 4 位(当 `msg == DECKERROR` 时)
102
+
103
+ ### 8. PlayerChangeState - 玩家状态变化
104
+ ```typescript
105
+ enum PlayerChangeState {
106
+ OBSERVE = 8,
107
+ READY = 9,
108
+ NOTREADY = 10,
109
+ LEAVE = 11,
110
+ }
111
+ ```
112
+ **说明**:用于解析 `STOC_HS_PlayerChange.status` 的低 4 位
113
+
114
+ ### 9. RoomStatus - 房间状态(SRVPro)
115
+ ```typescript
116
+ enum RoomStatus {
117
+ WAITING = 0,
118
+ DUELING = 1,
119
+ SIDING = 2,
120
+ }
121
+ ```
122
+ **使用位置**:`STOC_SRVPRO_ROOMLIST.room_status`
123
+
124
+ ## 已更新的文件
125
+
126
+ ### CTOS 协议
127
+ 1. `src/protos/ctos/proto/hand-result.ts` - `res: HandResult`
128
+ 2. `src/protos/ctos/proto/tp-result.ts` - `res: TurnPlayerResult`
129
+ 3. `src/protos/ctos/proto/kick.ts` - `pos: NetPlayerType`
130
+
131
+ ### STOC 协议
132
+ 1. `src/protos/stoc/proto/error-msg.ts` - `msg: ErrorMessageType`
133
+ 2. `src/protos/stoc/proto/chat.ts` - `player_type: number` (可以是 NetPlayerType 0-7 或 ChatColor 8-19)
134
+ 3. `src/protos/stoc/proto/srvpro-roomlist.ts` - `room_status: RoomStatus`
135
+
136
+ ### 公共结构
137
+ 1. `src/protos/common/host-info.ts` - `mode: GameMode`
138
+
139
+ **注意**:`HostInfo.rule` 保持为 `number` 类型,因为它是UI下拉列表的索引(0-5),而不是实际的OT值。
140
+
141
+ ### 主入口
142
+ 1. `index.ts` - 导出所有枚举类型
143
+
144
+ ## 未更改的字段
145
+
146
+ 以下字段已在 `OcgcoreCommonConstants` 中有常量定义,因此**不改为枚举**:
147
+
148
+ ### MSG 协议字段
149
+ - `MSG_HINT.type` - 使用 `HINT_*` 常量
150
+ - `MSG_CARD_HINT.type` - 使用 `HINT_*` 常量
151
+ - `MSG_PLAYER_HINT.type` - 使用 `PHINT_*` 常量
152
+ - `MSG_NEW_PHASE.phase` - 使用 `PHASE_*` 常量
153
+ - `MSG_MOVE.reason` - 使用 `REASON_*` 常量
154
+ - `MSG_POS_CHANGE.position` - 使用 `POS_*` 常量
155
+ - `MSG_SELECT_POSITION.positions` - 使用 `POS_*` 常量
156
+ - 所有包含 `location` 的字段 - 使用 `LOCATION_*` 常量(注:部分基础常量可能在 vendor 生成脚本中缺失)
157
+
158
+ ### HostInfo 字段
159
+ - `HostInfo.duel_rule` - 使用 `MASTER_RULE3`, `NEW_MASTER_RULE`, `MASTER_RULE_2020` 等常量
160
+
161
+ ## 使用示例
162
+
163
+ ```typescript
164
+ import {
165
+ YGOProCtosHandResult,
166
+ HandResult,
167
+ YGOProCtosCreateGame,
168
+ GameMode,
169
+ NetPlayerType,
170
+ YGOProCtosKick,
171
+ } from 'ygopro-msg-encode';
172
+
173
+ // 创建猜拳消息
174
+ const handResult = new YGOProCtosHandResult();
175
+ handResult.res = HandResult.ROCK;
176
+
177
+ // 创建游戏
178
+ const createGame = new YGOProCtosCreateGame();
179
+ createGame.info.mode = GameMode.MATCH;
180
+ createGame.info.rule = 0; // Rule index (0-5), maps to OT values in UI
181
+ createGame.info.duel_rule = 5; // MASTER_RULE_2020 (from OcgcoreCommonConstants)
182
+
183
+ // 踢出玩家
184
+ const kick = new YGOProCtosKick();
185
+ kick.pos = NetPlayerType.PLAYER2;
186
+ ```
187
+
188
+ ## 向后兼容性
189
+
190
+ 由于 TypeScript enum 在运行时就是 `number`,这些改动是**完全向后兼容**的:
191
+
192
+ ```typescript
193
+ // 旧代码仍然可以工作
194
+ handResult.res = 1; // OK
195
+
196
+ // 但现在有类型检查和 IDE 自动补全
197
+ handResult.res = HandResult.ROCK; // Better!
198
+ handResult.res = 99; // TypeScript 会警告(如果启用严格模式)
199
+ ```
200
+
201
+ ## 类型安全改进
202
+
203
+ ```typescript
204
+ // 之前:没有类型检查
205
+ function handleHandResult(res: number) {
206
+ if (res === 1) { /* ... */ }
207
+ }
208
+
209
+ // 现在:类型安全
210
+ function handleHandResult(res: HandResult) {
211
+ if (res === HandResult.ROCK) { /* ... */ }
212
+ }
213
+
214
+ // IDE 会提供自动补全和类型检查
215
+ ```
216
+
217
+ ## 测试
218
+
219
+ 所有现有测试均通过,枚举更改不影响二进制序列化/反序列化逻辑。
package/README.md CHANGED
@@ -517,6 +517,34 @@ npm run clean
517
517
  - [Quick Reference](./QUICK_REFERENCE.md) - Quick API reference
518
518
  - [Tests Migration](./TESTS_MIGRATION.md) - Testing guide
519
519
 
520
+ ## Network Protocol Enums
521
+
522
+ This library provides TypeScript enums for network protocol fields with semantic meaning:
523
+
524
+ ```typescript
525
+ import {
526
+ HandResult, // Rock-paper-scissors result
527
+ TurnPlayerResult, // First/second turn selection
528
+ NetPlayerType, // Player position/type
529
+ ChatColor, // Chat message colors
530
+ GameMode, // Single/Match/Tag
531
+ ErrorMessageType, // Error message types
532
+ RoomStatus, // Room status (SRVPro)
533
+ } from 'ygopro-msg-encode';
534
+
535
+ // Example: Create a game with specific settings
536
+ const createGame = new YGOProCtosCreateGame();
537
+ createGame.info.mode = GameMode.MATCH;
538
+ createGame.info.rule = 0; // Rule index (0-5), maps to OT values in UI
539
+
540
+ // Example: Chat with color
541
+ const chat = new YGOProStocChat();
542
+ chat.player_type = ChatColor.RED; // Use color for system message
543
+ chat.msg = "Welcome to the game!";
544
+ ```
545
+
546
+ **Note**: MSG protocol fields (like `phase`, `position`, `location`, `reason`) use constants from `OcgcoreCommonConstants` instead of enums, as they are automatically generated from YGOPro's `common.h`.
547
+
520
548
  ## Dependencies
521
549
 
522
550
  - **Runtime Dependencies**:
@@ -0,0 +1,156 @@
1
+ # STOC_CHAT player_type 修正总结
2
+
3
+ ## 问题
4
+
5
+ `STOC_Chat.player_type` 字段之前被定义为 `NetPlayerType` 枚举,但实际上该字段可以包含两种不同类型的值:
6
+
7
+ 1. **玩家类型**(0-7):PLAYER1-6, OBSERVER
8
+ 2. **聊天颜色**(8-19):用于系统消息或特殊格式的聊天
9
+
10
+ ## 修正
11
+
12
+ ### 1. 添加 ChatColor 枚举 ✅
13
+
14
+ 在 `src/protos/network-enums.ts` 中添加:
15
+
16
+ ```typescript
17
+ /**
18
+ * Chat message colors (used in STOC_Chat.player_type)
19
+ */
20
+ export enum ChatColor {
21
+ LIGHTBLUE = 8,
22
+ RED = 11,
23
+ GREEN = 12,
24
+ BLUE = 13,
25
+ BABYBLUE = 14,
26
+ PINK = 15,
27
+ YELLOW = 16,
28
+ WHITE = 17,
29
+ GRAY = 18,
30
+ DARKGRAY = 19,
31
+ }
32
+ ```
33
+
34
+ **来源**:`/home/nanahira/ygo/srvpro/data/constants.json` 中的 `COLORS` 定义
35
+
36
+ ### 2. 将 player_type 改回 number ✅
37
+
38
+ ```typescript
39
+ export class YGOProStocChat extends YGOProStocBase {
40
+ static identifier = 0x19;
41
+
42
+ player_type: number; // NetPlayerType (0-7) or ChatColor (8-19)
43
+ msg: string;
44
+
45
+ // ...
46
+ }
47
+ ```
48
+
49
+ **原因**:该字段可以是玩家类型或聊天颜色,使用联合类型更合适。
50
+
51
+ ### 3. 更新了文档 ✅
52
+
53
+ - `README.md` - 添加了 `ChatColor` 的使用示例
54
+ - `ENUM_UPDATE.md` - 说明了 `player_type` 保持为 `number` 的原因
55
+
56
+ ## 使用场景
57
+
58
+ ### 场景 1:玩家发送的聊天消息
59
+
60
+ ```typescript
61
+ import { YGOProStocChat, NetPlayerType } from 'ygopro-msg-encode';
62
+
63
+ const chat = new YGOProStocChat();
64
+ chat.player_type = NetPlayerType.PLAYER1; // 或直接使用 0
65
+ chat.msg = "Good game!";
66
+ ```
67
+
68
+ 在这种情况下,`player_type` 表示发送消息的玩家位置。
69
+
70
+ ### 场景 2:系统消息或特殊颜色
71
+
72
+ ```typescript
73
+ import { YGOProStocChat, ChatColor } from 'ygopro-msg-encode';
74
+
75
+ const chat = new YGOProStocChat();
76
+ chat.player_type = ChatColor.RED; // 红色系统消息
77
+ chat.msg = "Game will start in 10 seconds!";
78
+ ```
79
+
80
+ 在这种情况下,`player_type` 表示聊天消息的颜色。
81
+
82
+ ## duelclient.cpp 中的处理逻辑
83
+
84
+ 从源码可以看到:
85
+
86
+ ```cpp
87
+ uint16_t chat_player_type = BufferIO::Read<uint16_t>(pdata);
88
+ // ...
89
+ int player = chat_player_type;
90
+ auto play_sound = false;
91
+ if(player < 4) { // 玩家消息(0-3)
92
+ auto localplayer = mainGame->ChatLocalPlayer(player);
93
+ player = localplayer & 0xf;
94
+ if(!(localplayer & 0x10))
95
+ play_sound = true;
96
+ if(play_sound && mainGame->chkIgnore1->isChecked())
97
+ break;
98
+ } else { // 系统消息或特殊颜色(>= 4)
99
+ if(player == 8) { //system custom message.
100
+ // ...
101
+ }
102
+ }
103
+ ```
104
+
105
+ 客户端根据 `player_type` 的值:
106
+ - **< 4**:当作玩家消息处理
107
+ - **>= 4**:当作系统消息或特殊格式处理(可能包含颜色信息)
108
+
109
+ ## 颜色值映射(参考)
110
+
111
+ | 值 | 常量名 | 说明 |
112
+ |----|--------|------|
113
+ | 8 | LIGHTBLUE | 浅蓝色 |
114
+ | 11 | RED | 红色 |
115
+ | 12 | GREEN | 绿色 |
116
+ | 13 | BLUE | 蓝色 |
117
+ | 14 | BABYBLUE | 婴儿蓝 |
118
+ | 15 | PINK | 粉色 |
119
+ | 16 | YELLOW | 黄色 |
120
+ | 17 | WHITE | 白色 |
121
+ | 18 | GRAY | 灰色 |
122
+ | 19 | DARKGRAY | 深灰色 |
123
+
124
+ ## 类型安全建议
125
+
126
+ 虽然 `player_type` 是 `number` 类型,但可以使用类型守卫来确保类型安全:
127
+
128
+ ```typescript
129
+ function isPlayerType(value: number): value is NetPlayerType {
130
+ return value >= 0 && value <= 7 && value !== 6;
131
+ }
132
+
133
+ function isChatColor(value: number): value is ChatColor {
134
+ return value >= 8 && value <= 19;
135
+ }
136
+
137
+ // 使用
138
+ const chat = new YGOProStocChat();
139
+ chat.fromFullPayload(data);
140
+
141
+ if (isPlayerType(chat.player_type)) {
142
+ console.log('Player message from:', NetPlayerType[chat.player_type]);
143
+ } else if (isChatColor(chat.player_type)) {
144
+ console.log('System message with color:', ChatColor[chat.player_type]);
145
+ } else {
146
+ console.log('Unknown player_type:', chat.player_type);
147
+ }
148
+ ```
149
+
150
+ ## 总结
151
+
152
+ `STOC_Chat.player_type` 是一个多用途字段:
153
+ - ✅ 保持为 `number` 类型以支持两种不同的语义
154
+ - ✅ 提供 `NetPlayerType` 和 `ChatColor` 枚举供参考
155
+ - ✅ 用户可以根据值的范围判断其含义
156
+ - ✅ 完全向后兼容,不影响现有代码
@@ -0,0 +1,190 @@
1
+ # UTF-16 字符串字段修复总结
2
+
3
+ ## 问题
4
+
5
+ 多个网络协议中的字段被错误地定义为 `u16` 数组(`number[]`),但实际上它们应该是 UTF-16 字符串(`string`)。
6
+
7
+ 这些字段在源码 `network.h` 中都定义为 `uint16_t name[20]` 或 `uint16_t pass[20]`,表示 UTF-16 编码的字符串,而不是整数数组。
8
+
9
+ ## 修复的字段
10
+
11
+ ### 1. CTOS_PlayerInfo (`src/protos/ctos/proto/player-info.ts`)
12
+
13
+ **修复前**:
14
+ ```typescript
15
+ @BinaryField('u16', 0, 20)
16
+ name: number[];
17
+ ```
18
+
19
+ **修复后**:
20
+ ```typescript
21
+ @BinaryField('utf16', 0, 20)
22
+ name: string;
23
+ ```
24
+
25
+ ### 2. CTOS_JoinGame (`src/protos/ctos/proto/join-game.ts`)
26
+
27
+ **修复前**:
28
+ ```typescript
29
+ @BinaryField('u16', 8, 20)
30
+ pass: number[];
31
+ ```
32
+
33
+ **修复后**:
34
+ ```typescript
35
+ @BinaryField('utf16', 8, 20)
36
+ pass: string;
37
+ ```
38
+
39
+ ### 3. CTOS_CreateGame (`src/protos/ctos/proto/create-game.ts`)
40
+
41
+ **修复前**:
42
+ ```typescript
43
+ @BinaryField('u16', 20, 20)
44
+ name: number[];
45
+
46
+ @BinaryField('u16', 60, 20)
47
+ pass: number[];
48
+ ```
49
+
50
+ **修复后**:
51
+ ```typescript
52
+ @BinaryField('utf16', 20, 20)
53
+ name: string;
54
+
55
+ @BinaryField('utf16', 60, 20)
56
+ pass: string;
57
+ ```
58
+
59
+ ### 4. STOC_HS_PlayerEnter (`src/protos/stoc/proto/hs-player-enter.ts`)
60
+
61
+ **修复前**:
62
+ ```typescript
63
+ @BinaryField('u16', 0, 20)
64
+ name: number[];
65
+ ```
66
+
67
+ **修复后**:
68
+ ```typescript
69
+ @BinaryField('utf16', 0, 20)
70
+ name: string;
71
+ ```
72
+
73
+ ## 测试文件修复
74
+
75
+ 同时更新了 `tests/ctos-stoc.spec.ts` 中的相关测试:
76
+
77
+ **修复前**:
78
+ ```typescript
79
+ playerInfo.name = Array.from({ length: 20 }, (_, i) =>
80
+ i < 4 ? 0x0041 + i : 0,
81
+ ); // "ABCD"
82
+ ```
83
+
84
+ **修复后**:
85
+ ```typescript
86
+ playerInfo.name = 'ABCD';
87
+ ```
88
+
89
+ ## 源码参考
90
+
91
+ 根据 `gframe/network.h` 的定义:
92
+
93
+ ```cpp
94
+ struct CTOS_PlayerInfo {
95
+ uint16_t name[20]{}; // UTF-16 string, 20 characters max
96
+ };
97
+
98
+ struct CTOS_CreateGame {
99
+ HostInfo info;
100
+ uint16_t name[20]{}; // UTF-16 string, 20 characters max
101
+ uint16_t pass[20]{}; // UTF-16 string, 20 characters max
102
+ };
103
+
104
+ struct CTOS_JoinGame {
105
+ uint16_t version{};
106
+ // byte padding[2]
107
+ uint32_t gameid{};
108
+ uint16_t pass[20]{}; // UTF-16 string, 20 characters max
109
+ };
110
+
111
+ struct STOC_HS_PlayerEnter {
112
+ uint16_t name[20]{}; // UTF-16 string, 20 characters max
113
+ uint8_t pos{};
114
+ // byte padding[1]
115
+ };
116
+ ```
117
+
118
+ 所有这些 `uint16_t array[20]` 字段都是用于存储 UTF-16 编码的字符串,最多 20 个字符(包括 null 终止符)。
119
+
120
+ ## 使用示例
121
+
122
+ ### 修复前(错误):
123
+ ```typescript
124
+ const playerInfo = new YGOProCtosPlayerInfo();
125
+ playerInfo.name = [0x0041, 0x0042, 0x0043, 0x0044, 0, 0, ...]; // "ABCD"
126
+ ```
127
+
128
+ ### 修复后(正确):
129
+ ```typescript
130
+ const playerInfo = new YGOProCtosPlayerInfo();
131
+ playerInfo.name = 'ABCD'; // 直接使用字符串
132
+
133
+ const createGame = new YGOProCtosCreateGame();
134
+ createGame.name = 'MyGame';
135
+ createGame.pass = 'password123';
136
+
137
+ const joinGame = new YGOProCtosJoinGame();
138
+ joinGame.version = 0x1353;
139
+ joinGame.gameid = 12345;
140
+ joinGame.pass = 'password123';
141
+ ```
142
+
143
+ ## 二进制格式
144
+
145
+ UTF-16 字符串在二进制中的存储方式:
146
+ - 每个字符占 2 字节(UTF-16LE 编码)
147
+ - 字符串以 null 终止符(`\0`, `0x0000`)结尾
148
+ - 固定长度字段会被 null 字符填充到指定长度
149
+
150
+ 例如,字符串 `"ABCD"` 在 20 字符的字段中:
151
+ ```
152
+ 41 00 42 00 43 00 44 00 00 00 00 00 ... (共 40 字节)
153
+ A B C D \0 \0 ...
154
+ ```
155
+
156
+ ## 向后兼容性
157
+
158
+ 这个修复是**破坏性变更**,因为字段类型从 `number[]` 改为了 `string`。
159
+
160
+ **升级指南**:
161
+ ```typescript
162
+ // 旧代码
163
+ playerInfo.name = [0x0041, 0x0042, 0x0043, 0x0044, ...];
164
+
165
+ // 新代码
166
+ playerInfo.name = 'ABCD';
167
+
168
+ // 如果需要从旧格式转换
169
+ const oldName = [0x0041, 0x0042, 0x0043, 0x0044, 0, ...];
170
+ const newName = String.fromCharCode(...oldName.filter(c => c !== 0));
171
+ playerInfo.name = newName;
172
+ ```
173
+
174
+ ## 验证
175
+
176
+ 所有测试通过:
177
+ - ✅ Binary serialization/deserialization tests
178
+ - ✅ CTOS/STOC protocol tests
179
+ - ✅ Round-trip encoding/decoding tests
180
+
181
+ ## 总结
182
+
183
+ 共修复了 **4 个文件** 中的 **5 个字段**:
184
+ 1. `CTOS_PlayerInfo.name` - 玩家名称
185
+ 2. `CTOS_JoinGame.pass` - 游戏密码
186
+ 3. `CTOS_CreateGame.name` - 游戏名称
187
+ 4. `CTOS_CreateGame.pass` - 游戏密码
188
+ 5. `STOC_HS_PlayerEnter.name` - 玩家名称
189
+
190
+ 所有字段都从 `@BinaryField('u16', ..., 20)` + `number[]` 改为 `@BinaryField('utf16', ..., 20)` + `string`。