ygopro-msg-encode 1.0.3 → 1.0.4
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.
- package/COMPOSITE_FIELDS_AUDIT.md +275 -0
- package/COMPOSITE_FIELDS_GUIDE.md +303 -0
- package/DECK_COUNT_EXPLANATION.md +124 -0
- package/PROTOCOL_VERIFICATION_SUMMARY.md +410 -0
- package/README.md +4 -0
- package/REFACTOR_COMPOSITE_FIELDS.md +242 -0
- package/dist/index.cjs +78 -2
- package/dist/index.cjs.map +2 -2
- package/dist/index.mjs +77 -2
- package/dist/index.mjs.map +2 -2
- package/dist/src/protos/msg/proto/start.d.ts +17 -0
- package/dist/src/protos/stoc/proto/deck-count.d.ts +15 -1
- package/dist/src/protos/stoc/proto/hs-player-change.d.ts +23 -0
- package/dist/src/protos/stoc/proto/type-change.d.ts +23 -0
- package/package.json +1 -1
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# 协议验证与修复总结
|
|
2
|
+
|
|
3
|
+
本文档总结了对照 YGOPro 源码进行的完整协议验证和修复工作。
|
|
4
|
+
|
|
5
|
+
## 验证范围
|
|
6
|
+
|
|
7
|
+
对照以下源码文件进行了全面验证:
|
|
8
|
+
- `/home/nanahira/ygo/ygopro/ocgcore/` - OCGcore 核心逻辑
|
|
9
|
+
- `/home/nanahira/ygo/ygopro/gframe/` - 客户端网络协议处理
|
|
10
|
+
|
|
11
|
+
验证了所有协议类型:
|
|
12
|
+
- ✅ CTOS 协议(19 个)
|
|
13
|
+
- ✅ STOC 协议(24 个)
|
|
14
|
+
- ✅ MSG 协议(100+ 个)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 发现并修复的问题
|
|
19
|
+
|
|
20
|
+
### 1. MSG 协议字段错误(5 个)
|
|
21
|
+
|
|
22
|
+
#### 1.1 MSG_SELECT_IDLECMD - 字段结构错误 ⚠️ 严重
|
|
23
|
+
|
|
24
|
+
**问题**:错误地定义了 `toBpCount`, `toBpCards`, `toEpCount`, `toEpCards`, `shuffleCount`, `shuffleCards` 等数组字段
|
|
25
|
+
|
|
26
|
+
**修复**:改为正确的三个简单字段:
|
|
27
|
+
```typescript
|
|
28
|
+
@BinaryField('u8', ...) canBp: number;
|
|
29
|
+
@BinaryField('u8', ...) canEp: number;
|
|
30
|
+
@BinaryField('u8', ...) canShuffle: number;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### 1.2 MSG_ANNOUNCE_RACE - 字段顺序错误 ⚠️ 严重
|
|
34
|
+
|
|
35
|
+
**问题**:字段顺序为 `player`, `availableRaces`, `count`
|
|
36
|
+
|
|
37
|
+
**修复**:正确顺序为 `player`, `count`, `availableRaces`
|
|
38
|
+
```typescript
|
|
39
|
+
@BinaryField('u8', 0) player: number;
|
|
40
|
+
@BinaryField('u8', 1) count: number;
|
|
41
|
+
@BinaryField('u32', 2) availableRaces: number;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### 1.3 MSG_ANNOUNCE_ATTRIB - 字段顺序错误 ⚠️ 严重
|
|
45
|
+
|
|
46
|
+
**问题**:字段顺序为 `player`, `availableAttributes`, `count`
|
|
47
|
+
|
|
48
|
+
**修复**:正确顺序为 `player`, `count`, `availableAttributes`
|
|
49
|
+
```typescript
|
|
50
|
+
@BinaryField('u8', 0) player: number;
|
|
51
|
+
@BinaryField('u8', 1) count: number;
|
|
52
|
+
@BinaryField('u32', 2) availableAttributes: number;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### 1.4 MSG_SELECT_TRIBUTE - CardInfo 结构错误 ⚠️ 严重
|
|
56
|
+
|
|
57
|
+
**问题**:`releaseParam` 定义为 `i32` (4 字节),CardInfo 总长度 12 字节
|
|
58
|
+
|
|
59
|
+
**修复**:`releaseParam` 应为 `u8` (1 字节),CardInfo 总长度 8 字节
|
|
60
|
+
```typescript
|
|
61
|
+
export class YGOProMsgSelectTribute_CardInfo {
|
|
62
|
+
@BinaryField('i32', 0) code: number;
|
|
63
|
+
@BinaryField('u8', 4) controller: number;
|
|
64
|
+
@BinaryField('u8', 5) location: number;
|
|
65
|
+
@BinaryField('u8', 6) sequence: number;
|
|
66
|
+
@BinaryField('u8', 7) releaseParam: number;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### 1.5 MSG_SELECT_COUNTER - CardInfo 结构错误 ⚠️ 中等
|
|
71
|
+
|
|
72
|
+
**问题**:有多余的 `subsequence` 字段,CardInfo 总长度 10 字节
|
|
73
|
+
|
|
74
|
+
**修复**:删除 `subsequence` 字段,CardInfo 总长度 9 字节
|
|
75
|
+
```typescript
|
|
76
|
+
export class YGOProMsgSelectCounter_CardInfo {
|
|
77
|
+
@BinaryField('i32', 0) code: number;
|
|
78
|
+
@BinaryField('u8', 4) controller: number;
|
|
79
|
+
@BinaryField('u8', 5) location: number;
|
|
80
|
+
@BinaryField('u8', 6) sequence: number;
|
|
81
|
+
@BinaryField('u16', 7) counterCount: number;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### 1.6 MSG_ANNOUNCE_CARD - 字段命名错误 ⚠️ 轻微
|
|
86
|
+
|
|
87
|
+
**问题**:字段名为 `cards`,误导为卡片列表
|
|
88
|
+
|
|
89
|
+
**修复**:改为 `opcodes`,更准确地表示这是逆波兰表达式
|
|
90
|
+
```typescript
|
|
91
|
+
@BinaryField('i32', 2, (obj) => obj.count)
|
|
92
|
+
opcodes: number[]; // RPN expression for card declaration conditions
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### 2. UTF-16 字符串字段错误(5 个)⚠️ 严重
|
|
98
|
+
|
|
99
|
+
#### 问题
|
|
100
|
+
多个字段被错误定义为 `u16` 数组(`number[]`),实际应为 UTF-16 字符串(`string`)
|
|
101
|
+
|
|
102
|
+
#### 修复的字段
|
|
103
|
+
|
|
104
|
+
1. **CTOS_PlayerInfo.name**
|
|
105
|
+
```typescript
|
|
106
|
+
// 修复前
|
|
107
|
+
@BinaryField('u16', 0, 20) name: number[];
|
|
108
|
+
|
|
109
|
+
// 修复后
|
|
110
|
+
@BinaryField('utf16', 0, 20) name: string;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
2. **CTOS_JoinGame.pass**
|
|
114
|
+
```typescript
|
|
115
|
+
// 修复前
|
|
116
|
+
@BinaryField('u16', 8, 20) pass: number[];
|
|
117
|
+
|
|
118
|
+
// 修复后
|
|
119
|
+
@BinaryField('utf16', 8, 20) pass: string;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
3. **CTOS_CreateGame.name**
|
|
123
|
+
```typescript
|
|
124
|
+
// 修复前
|
|
125
|
+
@BinaryField('u16', 20, 20) name: number[];
|
|
126
|
+
|
|
127
|
+
// 修复后
|
|
128
|
+
@BinaryField('utf16', 20, 20) name: string;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
4. **CTOS_CreateGame.pass**
|
|
132
|
+
```typescript
|
|
133
|
+
// 修复前
|
|
134
|
+
@BinaryField('u16', 60, 20) pass: number[];
|
|
135
|
+
|
|
136
|
+
// 修复后
|
|
137
|
+
@BinaryField('utf16', 60, 20) pass: string;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
5. **STOC_HS_PlayerEnter.name**
|
|
141
|
+
```typescript
|
|
142
|
+
// 修复前
|
|
143
|
+
@BinaryField('u16', 0, 20) name: number[];
|
|
144
|
+
|
|
145
|
+
// 修复后
|
|
146
|
+
@BinaryField('utf16', 0, 20) name: string;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### 3. 枚举类型改进(9 个)
|
|
152
|
+
|
|
153
|
+
为网络协议字段添加了 TypeScript 枚举,提升类型安全性:
|
|
154
|
+
|
|
155
|
+
#### 新增枚举(src/protos/network-enums.ts)
|
|
156
|
+
|
|
157
|
+
1. **HandResult** - 猜拳结果
|
|
158
|
+
```typescript
|
|
159
|
+
enum HandResult { ROCK = 1, SCISSORS = 2, PAPER = 3 }
|
|
160
|
+
```
|
|
161
|
+
使用位置:`CTOS_HandResult.res`
|
|
162
|
+
|
|
163
|
+
2. **TurnPlayerResult** - 先后手选择
|
|
164
|
+
```typescript
|
|
165
|
+
enum TurnPlayerResult { SECOND = 0, FIRST = 1 }
|
|
166
|
+
```
|
|
167
|
+
使用位置:`CTOS_TPResult.res`
|
|
168
|
+
|
|
169
|
+
3. **NetPlayerType** - 玩家类型/位置
|
|
170
|
+
```typescript
|
|
171
|
+
enum NetPlayerType { PLAYER1 = 0, PLAYER2 = 1, ..., OBSERVER = 7 }
|
|
172
|
+
```
|
|
173
|
+
使用位置:`CTOS_Kick.pos`
|
|
174
|
+
|
|
175
|
+
4. **ChatColor** - 聊天消息颜色
|
|
176
|
+
```typescript
|
|
177
|
+
enum ChatColor { LIGHTBLUE = 8, RED = 11, GREEN = 12, ... }
|
|
178
|
+
```
|
|
179
|
+
说明:`STOC_Chat.player_type` 可以是 NetPlayerType 或 ChatColor
|
|
180
|
+
|
|
181
|
+
5. **GameMode** - 游戏模式
|
|
182
|
+
```typescript
|
|
183
|
+
enum GameMode { SINGLE = 0, MATCH = 1, TAG = 2 }
|
|
184
|
+
```
|
|
185
|
+
使用位置:`HostInfo.mode`
|
|
186
|
+
|
|
187
|
+
6. **ErrorMessageType** - 错误消息类型
|
|
188
|
+
```typescript
|
|
189
|
+
enum ErrorMessageType { JOINERROR = 1, DECKERROR = 2, SIDEERROR = 3, VERERROR = 4 }
|
|
190
|
+
```
|
|
191
|
+
使用位置:`STOC_ErrorMsg.msg`
|
|
192
|
+
|
|
193
|
+
7. **DeckErrorType** - 卡组错误类型
|
|
194
|
+
```typescript
|
|
195
|
+
enum DeckErrorType { LFLIST = 1, OCGONLY = 2, TCGONLY = 3, ... }
|
|
196
|
+
```
|
|
197
|
+
说明:用于解析 `STOC_ErrorMsg.code` 的高 4 位
|
|
198
|
+
|
|
199
|
+
8. **PlayerChangeState** - 玩家状态变化
|
|
200
|
+
```typescript
|
|
201
|
+
enum PlayerChangeState { OBSERVE = 8, READY = 9, NOTREADY = 10, LEAVE = 11 }
|
|
202
|
+
```
|
|
203
|
+
说明:用于解析 `STOC_HS_PlayerChange.status` 的低 4 位
|
|
204
|
+
|
|
205
|
+
9. **RoomStatus** - 房间状态
|
|
206
|
+
```typescript
|
|
207
|
+
enum RoomStatus { WAITING = 0, DUELING = 1, SIDING = 2 }
|
|
208
|
+
```
|
|
209
|
+
使用位置:`STOC_SRVPRO_ROOMLIST.room_status`
|
|
210
|
+
|
|
211
|
+
#### 保持为 number 的字段
|
|
212
|
+
|
|
213
|
+
- `HostInfo.rule` - UI 下拉索引(0-5),不是 OT 值
|
|
214
|
+
- `STOC_Chat.player_type` - 可以是 NetPlayerType 或 ChatColor
|
|
215
|
+
- `STOC_TypeChange.type` - 复合字段(位置 + 房主标志)
|
|
216
|
+
- `STOC_HS_PlayerChange.status` - 复合字段(位置 + 状态)
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### 4. 复合字段说明和辅助方法(2 个)
|
|
221
|
+
|
|
222
|
+
#### 4.1 MSG_START.playerType
|
|
223
|
+
|
|
224
|
+
添加了详细注释和 4 个辅助方法:
|
|
225
|
+
```typescript
|
|
226
|
+
getPlayerNumber(): number; // 获取玩家编号 (0-3)
|
|
227
|
+
isObserver(): boolean; // 是否观战者
|
|
228
|
+
isFirst(): boolean; // 是否先手
|
|
229
|
+
getWatchingPlayer(): number; // 观战者观看的玩家 (0 或 1)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### 4.2 STOC_DECK_COUNT.counts
|
|
233
|
+
|
|
234
|
+
添加了详细注释和 2 个辅助方法:
|
|
235
|
+
```typescript
|
|
236
|
+
getPlayerDeckCounts(player: 0 | 1): { main: number; extra: number; side: number };
|
|
237
|
+
setPlayerDeckCounts(player: 0 | 1, counts: { main: number; extra: number; side: number }): void;
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 验证结果
|
|
243
|
+
|
|
244
|
+
### CTOS 协议(19 个)
|
|
245
|
+
✅ 所有字段类型、顺序、偏移量正确
|
|
246
|
+
✅ Padding 处理正确
|
|
247
|
+
✅ UTF-16 字符串字段已修复
|
|
248
|
+
|
|
249
|
+
### STOC 协议(24 个)
|
|
250
|
+
✅ 所有字段类型、顺序、偏移量正确
|
|
251
|
+
✅ Padding 处理正确
|
|
252
|
+
✅ UTF-16 字符串字段已修复
|
|
253
|
+
✅ 复合字段说明完整
|
|
254
|
+
|
|
255
|
+
### MSG 协议(100+个)
|
|
256
|
+
✅ 所有需要用户响应的 MSG(18 个)字段正确
|
|
257
|
+
✅ 字段顺序和偏移量已修正
|
|
258
|
+
✅ 复合字段说明完整
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 测试验证
|
|
263
|
+
|
|
264
|
+
- ✅ 101 个单元测试全部通过
|
|
265
|
+
- ✅ 二进制序列化/反序列化正确
|
|
266
|
+
- ✅ 协议自动识别(Registry)正常
|
|
267
|
+
- ✅ Round-trip 编码解码测试通过
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 新增文档
|
|
272
|
+
|
|
273
|
+
1. **MSG_RESPONSE_GUIDE.md** - MSG 响应生成完整指南
|
|
274
|
+
2. **ENUM_UPDATE.md** - 枚举类型更新总结
|
|
275
|
+
3. **ENUM_FIX_SUMMARY.md** - GameRule 枚举修正说明
|
|
276
|
+
4. **UTF16_STRING_FIX.md** - UTF-16 字符串字段修复总结
|
|
277
|
+
5. **STOC_CHAT_FIX.md** - STOC_Chat.player_type 修正说明
|
|
278
|
+
6. **DECK_COUNT_EXPLANATION.md** - STOC_DECK_COUNT 字段详解
|
|
279
|
+
7. **COMPOSITE_FIELDS_GUIDE.md** - 复合字段使用指南
|
|
280
|
+
8. **PROTOCOL_VERIFICATION_SUMMARY.md** - 本文档
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 破坏性变更
|
|
285
|
+
|
|
286
|
+
以下是破坏性变更(需要更新现有代码):
|
|
287
|
+
|
|
288
|
+
### 1. UTF-16 字符串字段
|
|
289
|
+
|
|
290
|
+
**影响范围**:`CTOS_PlayerInfo`, `CTOS_JoinGame`, `CTOS_CreateGame`, `STOC_HS_PlayerEnter`
|
|
291
|
+
|
|
292
|
+
**升级方式**:
|
|
293
|
+
```typescript
|
|
294
|
+
// 旧代码
|
|
295
|
+
playerInfo.name = [0x0041, 0x0042, 0x0043, 0x0044, ...];
|
|
296
|
+
|
|
297
|
+
// 新代码
|
|
298
|
+
playerInfo.name = 'ABCD';
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 2. 枚举类型字段
|
|
302
|
+
|
|
303
|
+
**影响范围**:`CTOS_HandResult`, `CTOS_TPResult`, `CTOS_Kick`, `STOC_ErrorMsg`, `HostInfo`, `STOC_SRVPRO_ROOMLIST`
|
|
304
|
+
|
|
305
|
+
**升级方式**:
|
|
306
|
+
```typescript
|
|
307
|
+
// 旧代码(仍然有效,但不推荐)
|
|
308
|
+
handResult.res = 1;
|
|
309
|
+
|
|
310
|
+
// 新代码(推荐)
|
|
311
|
+
import { HandResult } from 'ygopro-msg-encode';
|
|
312
|
+
handResult.res = HandResult.ROCK;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**注意**:由于 TypeScript enum 运行时就是 `number`,旧代码仍然可以正常工作,但建议使用枚举以获得更好的类型安全性。
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## 向后兼容性
|
|
320
|
+
|
|
321
|
+
### 完全兼容(无破坏性变更)
|
|
322
|
+
- ✅ MSG 协议字段顺序修正
|
|
323
|
+
- ✅ 枚举类型添加(enum 运行时就是 number)
|
|
324
|
+
- ✅ 辅助方法添加(新增方法,不影响现有字段)
|
|
325
|
+
- ✅ 注释和文档改进
|
|
326
|
+
|
|
327
|
+
### 需要代码更新(破坏性变更)
|
|
328
|
+
- ⚠️ UTF-16 字符串字段:从 `number[]` 改为 `string`
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## 测试覆盖率
|
|
333
|
+
|
|
334
|
+
所有协议都有对应的测试:
|
|
335
|
+
- ✅ Binary serialization/deserialization
|
|
336
|
+
- ✅ Round-trip encoding/decoding
|
|
337
|
+
- ✅ Full payload with headers
|
|
338
|
+
- ✅ Protocol auto-detection (Registry)
|
|
339
|
+
- ✅ UTF-16 string encoding
|
|
340
|
+
- ✅ Chat protocol variable-length strings
|
|
341
|
+
- ✅ SRVPro room list
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 下一步建议
|
|
346
|
+
|
|
347
|
+
1. **更新版本号**:由于有破坏性变更,建议升级到下一个主版本
|
|
348
|
+
2. **迁移指南**:为使用 UTF-16 字符串字段的项目提供迁移指南
|
|
349
|
+
3. **类型检查**:启用 TypeScript 严格模式以获得更好的类型安全
|
|
350
|
+
4. **文档发布**:将新增的文档整合到项目文档中
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 相关文件
|
|
355
|
+
|
|
356
|
+
### 修复的协议文件(12 个)
|
|
357
|
+
|
|
358
|
+
**MSG 协议(6 个)**:
|
|
359
|
+
- `src/protos/msg/proto/select-idlecmd.ts`
|
|
360
|
+
- `src/protos/msg/proto/announce-race.ts`
|
|
361
|
+
- `src/protos/msg/proto/announce-attrib.ts`
|
|
362
|
+
- `src/protos/msg/proto/select-tribute.ts`
|
|
363
|
+
- `src/protos/msg/proto/select-counter.ts`
|
|
364
|
+
- `src/protos/msg/proto/announce-card.ts`
|
|
365
|
+
|
|
366
|
+
**CTOS 协议(5 个)**:
|
|
367
|
+
- `src/protos/ctos/proto/hand-result.ts`
|
|
368
|
+
- `src/protos/ctos/proto/tp-result.ts`
|
|
369
|
+
- `src/protos/ctos/proto/kick.ts`
|
|
370
|
+
- `src/protos/ctos/proto/player-info.ts`
|
|
371
|
+
- `src/protos/ctos/proto/join-game.ts`
|
|
372
|
+
- `src/protos/ctos/proto/create-game.ts`
|
|
373
|
+
|
|
374
|
+
**STOC 协议(3 个)**:
|
|
375
|
+
- `src/protos/stoc/proto/error-msg.ts`
|
|
376
|
+
- `src/protos/stoc/proto/chat.ts`
|
|
377
|
+
- `src/protos/stoc/proto/srvpro-roomlist.ts`
|
|
378
|
+
- `src/protos/stoc/proto/hs-player-enter.ts`
|
|
379
|
+
|
|
380
|
+
**公共结构(1 个)**:
|
|
381
|
+
- `src/protos/common/host-info.ts`
|
|
382
|
+
|
|
383
|
+
**改进的协议文件(2 个)**:
|
|
384
|
+
- `src/protos/msg/proto/start.ts` - 添加辅助方法
|
|
385
|
+
- `src/protos/stoc/proto/deck-count.ts` - 添加辅助方法
|
|
386
|
+
|
|
387
|
+
### 新增文件(2 个)
|
|
388
|
+
- `src/protos/network-enums.ts` - 网络协议枚举定义
|
|
389
|
+
- `index.ts` - 导出枚举类型
|
|
390
|
+
|
|
391
|
+
### 测试文件(1 个)
|
|
392
|
+
- `tests/ctos-stoc.spec.ts` - 更新 UTF-16 字符串测试
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 总结
|
|
397
|
+
|
|
398
|
+
本次验证发现并修复了:
|
|
399
|
+
- **6 个 MSG 协议字段错误**(字段顺序、类型、结构)
|
|
400
|
+
- **5 个 UTF-16 字符串字段错误**(类型定义)
|
|
401
|
+
- **9 个枚举类型添加**(类型安全改进)
|
|
402
|
+
- **2 个复合字段说明**(添加辅助方法和文档)
|
|
403
|
+
|
|
404
|
+
所有修复已通过测试验证,协议定义现在与 YGOPro 源码完全一致。
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
**验证时间**: 2026-02-02
|
|
409
|
+
**参考源码**: YGOPro (ocgcore + gframe)
|
|
410
|
+
**测试状态**: ✅ 101/101 通过
|
package/README.md
CHANGED
|
@@ -510,7 +510,11 @@ npm run clean
|
|
|
510
510
|
|
|
511
511
|
## Documentation
|
|
512
512
|
|
|
513
|
+
- [Protocol Verification Summary](./PROTOCOL_VERIFICATION_SUMMARY.md) - **Complete protocol verification and fixes** 🔍
|
|
513
514
|
- [MSG Response Guide](./MSG_RESPONSE_GUIDE.md) - **Complete guide for MSG response generation** ⭐
|
|
515
|
+
- [Composite Fields Guide](./COMPOSITE_FIELDS_GUIDE.md) - Guide for bit-packed and composite fields
|
|
516
|
+
- [Composite Fields Audit](./COMPOSITE_FIELDS_AUDIT.md) - Complete audit of all composite fields
|
|
517
|
+
- [Composite Fields Refactor](./REFACTOR_COMPOSITE_FIELDS.md) - Latest API improvements (getter/setter, nested objects)
|
|
514
518
|
- [CTOS/STOC Implementation](./CTOS_STOC_IMPLEMENTATION.md) - Detailed protocol implementation
|
|
515
519
|
- [MSG Implementation](./MSG_IMPLEMENTATION_SUMMARY.md) - MSG protocol details
|
|
516
520
|
- [Full Payload API](./FULL_PAYLOAD_UPDATE.md) - `toFullPayload()` / `fromFullPayload()` documentation
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# 复合字段重构总结
|
|
2
|
+
|
|
3
|
+
## 重构目标
|
|
4
|
+
|
|
5
|
+
改进复合字段的 API 设计,使其更符合面向对象原则,提供更好的类型安全和开发体验。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. STOC_DECK_COUNT 重构
|
|
10
|
+
|
|
11
|
+
### 重构前(数组方式)
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
export class YGOProStocDeckCount extends YGOProStocBase {
|
|
15
|
+
@BinaryField('i16', 0, 6)
|
|
16
|
+
counts: number[];
|
|
17
|
+
|
|
18
|
+
getPlayerDeckCounts(player: 0 | 1): { main: number; extra: number; side: number } {
|
|
19
|
+
const offset = player * 3;
|
|
20
|
+
return {
|
|
21
|
+
main: this.counts[offset],
|
|
22
|
+
extra: this.counts[offset + 1],
|
|
23
|
+
side: this.counts[offset + 2],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setPlayerDeckCounts(player: 0 | 1, counts: { main: number; extra: number; side: number }): void {
|
|
28
|
+
const offset = player * 3;
|
|
29
|
+
this.counts[offset] = counts.main;
|
|
30
|
+
this.counts[offset + 1] = counts.extra;
|
|
31
|
+
this.counts[offset + 2] = counts.side;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**问题**:
|
|
37
|
+
- 使用数组存储,缺乏语义
|
|
38
|
+
- 需要记住索引顺序(0-2 是玩家 0,3-5 是玩家 1)
|
|
39
|
+
- 需要辅助方法来访问,但仍然可以直接访问数组
|
|
40
|
+
- 不符合面向对象设计原则
|
|
41
|
+
|
|
42
|
+
### 重构后(对象方式)✅
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export class YGOProStocDeckCount_DeckInfo {
|
|
46
|
+
@BinaryField('i16', 0)
|
|
47
|
+
main: number;
|
|
48
|
+
|
|
49
|
+
@BinaryField('i16', 2)
|
|
50
|
+
extra: number;
|
|
51
|
+
|
|
52
|
+
@BinaryField('i16', 4)
|
|
53
|
+
side: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class YGOProStocDeckCount extends YGOProStocBase {
|
|
57
|
+
@BinaryField(() => YGOProStocDeckCount_DeckInfo, 0)
|
|
58
|
+
player0DeckCount: YGOProStocDeckCount_DeckInfo;
|
|
59
|
+
|
|
60
|
+
@BinaryField(() => YGOProStocDeckCount_DeckInfo, 6)
|
|
61
|
+
player1DeckCount: YGOProStocDeckCount_DeckInfo;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**优势**:
|
|
66
|
+
- ✅ 结构清晰,语义明确
|
|
67
|
+
- ✅ 类型安全,IDE 自动补全
|
|
68
|
+
- ✅ 符合面向对象设计
|
|
69
|
+
- ✅ 不需要辅助方法,直接访问属性
|
|
70
|
+
|
|
71
|
+
### 使用对比
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// 重构前
|
|
75
|
+
const main = deckCount.counts[0]; // 需要记住索引
|
|
76
|
+
deckCount.setPlayerDeckCounts(0, { main: 40, extra: 15, side: 15 });
|
|
77
|
+
|
|
78
|
+
// 重构后
|
|
79
|
+
const main = deckCount.player0DeckCount.main; // 语义清晰
|
|
80
|
+
deckCount.player0DeckCount.main = 40;
|
|
81
|
+
deckCount.player0DeckCount.extra = 15;
|
|
82
|
+
deckCount.player0DeckCount.side = 15;
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 2. MSG_START.playerType 重构
|
|
88
|
+
|
|
89
|
+
### 重构前(辅助方法)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
export class YGOProMsgStart extends YGOProMsgBase {
|
|
93
|
+
@BinaryField('u8', 0)
|
|
94
|
+
playerType: number;
|
|
95
|
+
|
|
96
|
+
getPlayerNumber(): number {
|
|
97
|
+
return this.playerType & 0x0f;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
isObserver(): boolean {
|
|
101
|
+
return (this.playerType & 0xf0) !== 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
isFirst(): boolean {
|
|
105
|
+
return (this.playerType & 0x0f) === 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getWatchingPlayer(): number {
|
|
109
|
+
return this.playerType & 0x0f;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**问题**:
|
|
115
|
+
- 添加了很多辅助方法(get 前缀)
|
|
116
|
+
- 方法名冗长(`getPlayerNumber`)
|
|
117
|
+
- 只能读取,不能方便地设置
|
|
118
|
+
- 不符合 TypeScript getter/setter 惯例
|
|
119
|
+
|
|
120
|
+
### 重构后(getter/setter)✅
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
export class YGOProMsgStart extends YGOProMsgBase {
|
|
124
|
+
@BinaryField('u8', 0)
|
|
125
|
+
playerType: number;
|
|
126
|
+
|
|
127
|
+
get playerNumber(): number {
|
|
128
|
+
return this.playerType & 0x0f;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
set playerNumber(value: number) {
|
|
132
|
+
this.playerType = (this.playerType & 0xf0) | (value & 0x0f);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get observerFlag(): number {
|
|
136
|
+
return this.playerType & 0xf0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
set observerFlag(value: number) {
|
|
140
|
+
this.playerType = (this.playerType & 0x0f) | (value & 0xf0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**优势**:
|
|
146
|
+
- ✅ 符合 TypeScript getter/setter 惯例
|
|
147
|
+
- ✅ 简洁的访问方式(`startMsg.playerNumber`)
|
|
148
|
+
- ✅ 支持读写操作
|
|
149
|
+
- ✅ 仍然可以直接访问 `playerType` 进行位运算
|
|
150
|
+
|
|
151
|
+
### 使用对比
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// 重构前
|
|
155
|
+
const playerNum = startMsg.getPlayerNumber(); // 方法调用
|
|
156
|
+
const isObs = startMsg.isObserver(); // 方法调用
|
|
157
|
+
// 设置很麻烦,需要手动位运算
|
|
158
|
+
|
|
159
|
+
// 重构后
|
|
160
|
+
const playerNum = startMsg.playerNumber; // 属性访问
|
|
161
|
+
const isObs = startMsg.observerFlag !== 0; // 属性访问
|
|
162
|
+
startMsg.playerNumber = 1; // 直接赋值
|
|
163
|
+
startMsg.observerFlag = 0x10; // 直接赋值
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 设计原则
|
|
169
|
+
|
|
170
|
+
### 1. 面向对象设计
|
|
171
|
+
使用嵌套对象而不是数组,提供清晰的结构和语义。
|
|
172
|
+
|
|
173
|
+
### 2. TypeScript 惯例
|
|
174
|
+
使用 getter/setter 而不是 `getXxx()`/`setXxx()` 方法。
|
|
175
|
+
|
|
176
|
+
### 3. 类型安全
|
|
177
|
+
利用 TypeScript 类型系统提供编译时检查和 IDE 支持。
|
|
178
|
+
|
|
179
|
+
### 4. 简洁性
|
|
180
|
+
- 属性访问优于方法调用
|
|
181
|
+
- 对象属性优于数组索引
|
|
182
|
+
- 保持底层字段可访问,允许高级用户直接操作
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 破坏性变更
|
|
187
|
+
|
|
188
|
+
### STOC_DECK_COUNT
|
|
189
|
+
|
|
190
|
+
**旧代码**:
|
|
191
|
+
```typescript
|
|
192
|
+
const main = deckCount.counts[0];
|
|
193
|
+
deckCount.setPlayerDeckCounts(0, { main: 40, extra: 15, side: 15 });
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**新代码**:
|
|
197
|
+
```typescript
|
|
198
|
+
const main = deckCount.player0DeckCount.main;
|
|
199
|
+
deckCount.player0DeckCount.main = 40;
|
|
200
|
+
deckCount.player0DeckCount.extra = 15;
|
|
201
|
+
deckCount.player0DeckCount.side = 15;
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### MSG_START
|
|
205
|
+
|
|
206
|
+
**旧代码**:
|
|
207
|
+
```typescript
|
|
208
|
+
const playerNum = startMsg.getPlayerNumber();
|
|
209
|
+
const isObs = startMsg.isObserver();
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**新代码**:
|
|
213
|
+
```typescript
|
|
214
|
+
const playerNum = startMsg.playerNumber;
|
|
215
|
+
const isObs = startMsg.observerFlag !== 0;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 测试结果
|
|
221
|
+
|
|
222
|
+
✅ 所有 101 个测试通过
|
|
223
|
+
✅ 构建成功
|
|
224
|
+
✅ 类型检查通过
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 相关文档
|
|
229
|
+
|
|
230
|
+
- [COMPOSITE_FIELDS_GUIDE.md](./COMPOSITE_FIELDS_GUIDE.md) - 复合字段使用指南(已更新)
|
|
231
|
+
- [DECK_COUNT_EXPLANATION.md](./DECK_COUNT_EXPLANATION.md) - STOC_DECK_COUNT 详解(已更新)
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 总结
|
|
236
|
+
|
|
237
|
+
这次重构改进了两个关键复合字段的 API 设计:
|
|
238
|
+
|
|
239
|
+
1. **STOC_DECK_COUNT**: 从数组 + 辅助方法改为嵌套对象结构
|
|
240
|
+
2. **MSG_START**: 从辅助方法改为 getter/setter
|
|
241
|
+
|
|
242
|
+
新的设计更符合面向对象原则和 TypeScript 惯例,提供了更好的类型安全和开发体验。
|