ygopro-msg-encode 1.0.1

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.
Files changed (178) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc.js +25 -0
  3. package/.prettierrc +4 -0
  4. package/AGENTS.md +12 -0
  5. package/CHANGES.md +270 -0
  6. package/CTOS_STOC_IMPLEMENTATION.md +279 -0
  7. package/FINAL_SUMMARY.md +357 -0
  8. package/FULL_PAYLOAD_SUMMARY.md +491 -0
  9. package/FULL_PAYLOAD_UPDATE.md +598 -0
  10. package/IMPLEMENTATION_SUMMARY.md +294 -0
  11. package/LICENSE +22 -0
  12. package/MSG_IMPLEMENTATION_SUMMARY.md +358 -0
  13. package/OPPONENT_VIEW_SUMMARY.md +387 -0
  14. package/PROJECT_COMPLETE.md +565 -0
  15. package/QUICK_REFERENCE.md +352 -0
  16. package/README.md +494 -0
  17. package/REAL_IP_STRING_UPDATE.md +289 -0
  18. package/TESTS_MIGRATION.md +342 -0
  19. package/VARIABLE_LENGTH_STRINGS.md +224 -0
  20. package/VARIABLE_LENGTH_UPDATE.md +229 -0
  21. package/dist/index.cjs +5131 -0
  22. package/dist/index.cjs.map +7 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.mjs +5185 -0
  25. package/dist/index.mjs.map +7 -0
  26. package/dist/src/binary/binary-meta.d.ts +18 -0
  27. package/dist/src/binary/fill-binary-fields.d.ts +2 -0
  28. package/dist/src/metadata.d.ts +10 -0
  29. package/dist/src/proto-base/payload-base.d.ts +8 -0
  30. package/dist/src/proto-base/registry-base.d.ts +13 -0
  31. package/dist/src/protos/common/host-info.d.ts +12 -0
  32. package/dist/src/protos/common/index.d.ts +1 -0
  33. package/dist/src/protos/ctos/base.d.ts +17 -0
  34. package/dist/src/protos/ctos/index.d.ts +3 -0
  35. package/dist/src/protos/ctos/proto/chat.d.ts +9 -0
  36. package/dist/src/protos/ctos/proto/create-game.d.ts +8 -0
  37. package/dist/src/protos/ctos/proto/external-address.d.ts +12 -0
  38. package/dist/src/protos/ctos/proto/hand-result.d.ts +5 -0
  39. package/dist/src/protos/ctos/proto/hs-notready.d.ts +4 -0
  40. package/dist/src/protos/ctos/proto/hs-ready.d.ts +4 -0
  41. package/dist/src/protos/ctos/proto/hs-start.d.ts +4 -0
  42. package/dist/src/protos/ctos/proto/hs-toduelist.d.ts +4 -0
  43. package/dist/src/protos/ctos/proto/hs-toobserver.d.ts +4 -0
  44. package/dist/src/protos/ctos/proto/index.d.ts +19 -0
  45. package/dist/src/protos/ctos/proto/join-game.d.ts +7 -0
  46. package/dist/src/protos/ctos/proto/kick.d.ts +5 -0
  47. package/dist/src/protos/ctos/proto/leave-game.d.ts +4 -0
  48. package/dist/src/protos/ctos/proto/player-info.d.ts +5 -0
  49. package/dist/src/protos/ctos/proto/request-field.d.ts +4 -0
  50. package/dist/src/protos/ctos/proto/response.d.ts +7 -0
  51. package/dist/src/protos/ctos/proto/surrender.d.ts +4 -0
  52. package/dist/src/protos/ctos/proto/time-confirm.d.ts +4 -0
  53. package/dist/src/protos/ctos/proto/tp-result.d.ts +5 -0
  54. package/dist/src/protos/ctos/proto/update-deck.d.ts +11 -0
  55. package/dist/src/protos/ctos/registry.d.ts +3 -0
  56. package/dist/src/protos/msg/base.d.ts +9 -0
  57. package/dist/src/protos/msg/index.d.ts +3 -0
  58. package/dist/src/protos/msg/proto/add-counter.d.ts +9 -0
  59. package/dist/src/protos/msg/proto/announce-attrib.d.ts +7 -0
  60. package/dist/src/protos/msg/proto/announce-card.d.ts +7 -0
  61. package/dist/src/protos/msg/proto/announce-number.d.ts +7 -0
  62. package/dist/src/protos/msg/proto/announce-race.d.ts +7 -0
  63. package/dist/src/protos/msg/proto/attack-disabled.d.ts +4 -0
  64. package/dist/src/protos/msg/proto/attack.d.ts +12 -0
  65. package/dist/src/protos/msg/proto/battle.d.ts +18 -0
  66. package/dist/src/protos/msg/proto/become-target.d.ts +6 -0
  67. package/dist/src/protos/msg/proto/cancel-target.d.ts +11 -0
  68. package/dist/src/protos/msg/proto/card-hint.d.ts +10 -0
  69. package/dist/src/protos/msg/proto/card-query.d.ts +38 -0
  70. package/dist/src/protos/msg/proto/card-target.d.ts +11 -0
  71. package/dist/src/protos/msg/proto/chain-disabled.d.ts +5 -0
  72. package/dist/src/protos/msg/proto/chain-end.d.ts +4 -0
  73. package/dist/src/protos/msg/proto/chain-negated.d.ts +5 -0
  74. package/dist/src/protos/msg/proto/chain-solved.d.ts +5 -0
  75. package/dist/src/protos/msg/proto/chain-solving.d.ts +5 -0
  76. package/dist/src/protos/msg/proto/chained.d.ts +5 -0
  77. package/dist/src/protos/msg/proto/chaining.d.ts +12 -0
  78. package/dist/src/protos/msg/proto/confirm-cards.d.ts +16 -0
  79. package/dist/src/protos/msg/proto/confirm-decktop.d.ts +14 -0
  80. package/dist/src/protos/msg/proto/confirm-extratop.d.ts +14 -0
  81. package/dist/src/protos/msg/proto/damage-step-end.d.ts +4 -0
  82. package/dist/src/protos/msg/proto/damage-step-start.d.ts +4 -0
  83. package/dist/src/protos/msg/proto/damage.d.ts +6 -0
  84. package/dist/src/protos/msg/proto/deck-top.d.ts +8 -0
  85. package/dist/src/protos/msg/proto/draw.d.ts +8 -0
  86. package/dist/src/protos/msg/proto/equip.d.ts +12 -0
  87. package/dist/src/protos/msg/proto/field-disabled.d.ts +5 -0
  88. package/dist/src/protos/msg/proto/flipsummoned.d.ts +4 -0
  89. package/dist/src/protos/msg/proto/flipsummoning.d.ts +9 -0
  90. package/dist/src/protos/msg/proto/hand-res.d.ts +5 -0
  91. package/dist/src/protos/msg/proto/hint.d.ts +7 -0
  92. package/dist/src/protos/msg/proto/index.d.ts +85 -0
  93. package/dist/src/protos/msg/proto/lpupdate.d.ts +6 -0
  94. package/dist/src/protos/msg/proto/match-kill.d.ts +5 -0
  95. package/dist/src/protos/msg/proto/missed-effect.d.ts +7 -0
  96. package/dist/src/protos/msg/proto/move.d.ts +14 -0
  97. package/dist/src/protos/msg/proto/new-phase.d.ts +5 -0
  98. package/dist/src/protos/msg/proto/new-turn.d.ts +5 -0
  99. package/dist/src/protos/msg/proto/pay-lpcost.d.ts +6 -0
  100. package/dist/src/protos/msg/proto/player-hint.d.ts +7 -0
  101. package/dist/src/protos/msg/proto/pos-change.d.ts +12 -0
  102. package/dist/src/protos/msg/proto/random-selected.d.ts +7 -0
  103. package/dist/src/protos/msg/proto/recover.d.ts +6 -0
  104. package/dist/src/protos/msg/proto/reload-field.d.ts +36 -0
  105. package/dist/src/protos/msg/proto/remove-counter.d.ts +9 -0
  106. package/dist/src/protos/msg/proto/reset-time.d.ts +6 -0
  107. package/dist/src/protos/msg/proto/retry.d.ts +4 -0
  108. package/dist/src/protos/msg/proto/reverse-deck.d.ts +4 -0
  109. package/dist/src/protos/msg/proto/rock-paper-scissors.d.ts +5 -0
  110. package/dist/src/protos/msg/proto/select-battlecmd.d.ts +25 -0
  111. package/dist/src/protos/msg/proto/select-card.d.ts +17 -0
  112. package/dist/src/protos/msg/proto/select-chain.d.ts +19 -0
  113. package/dist/src/protos/msg/proto/select-counter.d.ts +17 -0
  114. package/dist/src/protos/msg/proto/select-disfield.d.ts +7 -0
  115. package/dist/src/protos/msg/proto/select-effectyn.d.ts +11 -0
  116. package/dist/src/protos/msg/proto/select-idlecmd.d.ts +37 -0
  117. package/dist/src/protos/msg/proto/select-option.d.ts +7 -0
  118. package/dist/src/protos/msg/proto/select-place.d.ts +7 -0
  119. package/dist/src/protos/msg/proto/select-position.d.ts +7 -0
  120. package/dist/src/protos/msg/proto/select-sum.d.ts +20 -0
  121. package/dist/src/protos/msg/proto/select-tribute.d.ts +18 -0
  122. package/dist/src/protos/msg/proto/select-unselect-card.d.ts +20 -0
  123. package/dist/src/protos/msg/proto/select-yesno.d.ts +6 -0
  124. package/dist/src/protos/msg/proto/set.d.ts +5 -0
  125. package/dist/src/protos/msg/proto/shuffle-deck.d.ts +5 -0
  126. package/dist/src/protos/msg/proto/shuffle-extra.d.ts +8 -0
  127. package/dist/src/protos/msg/proto/shuffle-hand.d.ts +8 -0
  128. package/dist/src/protos/msg/proto/shuffle-set-card.d.ts +17 -0
  129. package/dist/src/protos/msg/proto/sort-card.d.ts +13 -0
  130. package/dist/src/protos/msg/proto/spsummoned.d.ts +4 -0
  131. package/dist/src/protos/msg/proto/spsummoning.d.ts +9 -0
  132. package/dist/src/protos/msg/proto/start.d.ts +14 -0
  133. package/dist/src/protos/msg/proto/summoned.d.ts +4 -0
  134. package/dist/src/protos/msg/proto/summoning.d.ts +9 -0
  135. package/dist/src/protos/msg/proto/swap-grave-deck.d.ts +5 -0
  136. package/dist/src/protos/msg/proto/swap.d.ts +12 -0
  137. package/dist/src/protos/msg/proto/tag-swap.d.ts +14 -0
  138. package/dist/src/protos/msg/proto/toss-coin.d.ts +7 -0
  139. package/dist/src/protos/msg/proto/toss-dice.d.ts +7 -0
  140. package/dist/src/protos/msg/proto/update-card.d.ts +14 -0
  141. package/dist/src/protos/msg/proto/update-data.d.ts +12 -0
  142. package/dist/src/protos/msg/proto/waiting.d.ts +4 -0
  143. package/dist/src/protos/msg/proto/win.d.ts +6 -0
  144. package/dist/src/protos/msg/registry.d.ts +3 -0
  145. package/dist/src/protos/stoc/base.d.ts +17 -0
  146. package/dist/src/protos/stoc/index.d.ts +3 -0
  147. package/dist/src/protos/stoc/proto/change-side.d.ts +4 -0
  148. package/dist/src/protos/stoc/proto/chat.d.ts +10 -0
  149. package/dist/src/protos/stoc/proto/create-game.d.ts +5 -0
  150. package/dist/src/protos/stoc/proto/deck-count.d.ts +5 -0
  151. package/dist/src/protos/stoc/proto/duel-end.d.ts +4 -0
  152. package/dist/src/protos/stoc/proto/duel-start.d.ts +4 -0
  153. package/dist/src/protos/stoc/proto/error-msg.d.ts +6 -0
  154. package/dist/src/protos/stoc/proto/field-finish.d.ts +4 -0
  155. package/dist/src/protos/stoc/proto/game-msg.d.ts +9 -0
  156. package/dist/src/protos/stoc/proto/hand-result.d.ts +6 -0
  157. package/dist/src/protos/stoc/proto/hs-player-change.d.ts +5 -0
  158. package/dist/src/protos/stoc/proto/hs-player-enter.d.ts +6 -0
  159. package/dist/src/protos/stoc/proto/hs-watch-change.d.ts +5 -0
  160. package/dist/src/protos/stoc/proto/index.d.ts +24 -0
  161. package/dist/src/protos/stoc/proto/join-game.d.ts +6 -0
  162. package/dist/src/protos/stoc/proto/leave-game.d.ts +5 -0
  163. package/dist/src/protos/stoc/proto/replay.d.ts +11 -0
  164. package/dist/src/protos/stoc/proto/select-hand.d.ts +4 -0
  165. package/dist/src/protos/stoc/proto/select-tp.d.ts +4 -0
  166. package/dist/src/protos/stoc/proto/srvpro-roomlist.d.ts +18 -0
  167. package/dist/src/protos/stoc/proto/teammate-surrender.d.ts +4 -0
  168. package/dist/src/protos/stoc/proto/time-limit.d.ts +6 -0
  169. package/dist/src/protos/stoc/proto/tp-result.d.ts +4 -0
  170. package/dist/src/protos/stoc/proto/type-change.d.ts +5 -0
  171. package/dist/src/protos/stoc/proto/waiting-side.d.ts +4 -0
  172. package/dist/src/protos/stoc/registry.d.ts +3 -0
  173. package/dist/src/vendor/ocgcore-constants.d.ts +360 -0
  174. package/dist/src/vendor/script-constants.d.ts +836 -0
  175. package/index.ts +6 -0
  176. package/package.json +74 -0
  177. package/scripts/gen-constants.js +156 -0
  178. package/tsconfig.json +20 -0
package/.eslintignore ADDED
@@ -0,0 +1,4 @@
1
+ webpack.config.js
2
+ dist/*
3
+ build/*
4
+ *.js
package/.eslintrc.js ADDED
@@ -0,0 +1,25 @@
1
+ module.exports = {
2
+ parser: '@typescript-eslint/parser',
3
+ parserOptions: {
4
+ project: 'tsconfig.json',
5
+ tsconfigRootDir : __dirname,
6
+ sourceType: 'module',
7
+ },
8
+ plugins: ['@typescript-eslint/eslint-plugin'],
9
+ extends: [
10
+ 'plugin:@typescript-eslint/recommended',
11
+ 'plugin:prettier/recommended',
12
+ ],
13
+ root: true,
14
+ env: {
15
+ node: true,
16
+ jest: true,
17
+ },
18
+ ignorePatterns: ['.eslintrc.js'],
19
+ rules: {
20
+ '@typescript-eslint/interface-name-prefix': 'off',
21
+ '@typescript-eslint/explicit-function-return-type': 'off',
22
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
23
+ '@typescript-eslint/no-explicit-any': 'off',
24
+ },
25
+ };
package/.prettierrc ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "all"
4
+ }
package/AGENTS.md ADDED
@@ -0,0 +1,12 @@
1
+ # 项目规范
2
+
3
+ - 所有二进制协议相关的类,必须继承于 PayloadBase 类
4
+ - 使用 @BinaryField 装饰器定义二进制协议字段,并使用 fillBinaryFields 和 toBinaryFields 函数进行序列化和反序列化
5
+ - protos 目录下是各种协议类型的定义,有 msg stoc ctos 三种,每种有 proto/ 目录(每个文件是一个消息类型)和 registry.ts 文件(用于注册消息类型),还有 base.ts 文件(用于定义消息基类)
6
+ - 每写一个类,要在 registry 里面加一行,注册这个类
7
+ - 对应 YGOPro 的 ocgcore/common.h 的常量已经在 src/vendor 里面放好了,这是系统自动维护的。请不要抄任何 common.h 已有常量,而是用这里的。
8
+
9
+ ## 参考
10
+
11
+ - YGOPro ocgcore: /home/nanahira/ygo/ygopro/ocgcore
12
+ - YGOPro 源代码: /home/nanahira/ygo/ygopro/gframe
package/CHANGES.md ADDED
@@ -0,0 +1,270 @@
1
+ # MSG 类重构变更说明
2
+
3
+ ## 主要修改
4
+
5
+ ### 1. 基类修改 - 添加 MSG Identifier
6
+
7
+ **文件**: `src/protos/msg/base.ts`
8
+
9
+ 修改了 `YGOProMsgBase` 的 `fromPayload` 和 `toPayload` 方法:
10
+
11
+ - **toPayload()**: 第一个字节写入 MSG identifier
12
+ - **fromPayload()**:
13
+ - 验证第一个字节是否与 identifier 一致
14
+ - 从第二个字节开始解析数据
15
+
16
+ **手动实现修改**: `src/protos/msg/proto/update-card.ts` 同样进行了修改
17
+
18
+ ### 2. 小类重命名和导出
19
+
20
+ 所有嵌套的小类都已:
21
+ - 重命名为 `{主类名}_{小类名}` 格式
22
+ - 改为 `export` 导出
23
+
24
+ #### 修改的文件列表:
25
+
26
+ **使用 1/2 或对称命名改为小类的文件**(共 9 个):
27
+ - `card-target.ts` - 改用 `YGOProMsgCardTarget_CardLocation` (card1/card2)
28
+ - `cancel-target.ts` - 改用 `YGOProMsgCancelTarget_CardLocation` (card1/card2)
29
+ - `swap.ts` - 改用 `YGOProMsgSwap_CardLocation` (card1/card2)
30
+ - `move.ts` - 改用 `YGOProMsgMove_CardLocation` (previous/current)
31
+ - `pos-change.ts` - 改用 `YGOProMsgPosChange_CardLocation` (card + previousPosition/currentPosition)
32
+ - `attack.ts` - 改用 `YGOProMsgAttack_CardLocation` (attacker/defender)
33
+ - `battle.ts` - 改用 `YGOProMsgBattle_CardLocation` + `YGOProMsgBattle_CardStats` (attacker/defender,嵌套 location + atk/def)
34
+ - `equip.ts` - 改用 `YGOProMsgEquip_CardLocation` (equip/target)
35
+ - `shuffle-set-card.ts` - 改用 `YGOProMsgShuffleSetCard_CardLocation` + `YGOProMsgShuffleSetCard_SetCardInfo` (oldLocation/newLocation)
36
+
37
+ **导出小类的文件**:
38
+ - `confirm-decktop.ts` → `YGOProMsgConfirmDeckTop_CardInfo`
39
+ - `confirm-extratop.ts` → `YGOProMsgConfirmExtraTop_CardInfo`
40
+ - `confirm-cards.ts` → `YGOProMsgConfirmCards_CardInfo`
41
+ - `select-card.ts` → `YGOProMsgSelectCard_CardInfo`
42
+ - `select-tribute.ts` → `YGOProMsgSelectTribute_CardInfo`
43
+ - `select-unselect-card.ts` → `YGOProMsgSelectUnselectCard_CardInfo`
44
+ - `sort-card.ts` → `YGOProMsgSortCard_CardInfo`
45
+ - `select-counter.ts` → `YGOProMsgSelectCounter_CardInfo`
46
+ - `select-sum.ts` → `YGOProMsgSelectSum_CardInfo`
47
+ - `select-chain.ts` → `YGOProMsgSelectChain_ChainInfo`
48
+ - `select-battlecmd.ts` → `YGOProMsgSelectBattleCmd_ActivatableInfo`, `YGOProMsgSelectBattleCmd_AttackableInfo`
49
+ - `select-idlecmd.ts` → `YGOProMsgSelectIdleCmd_SimpleCardInfo`, `YGOProMsgSelectIdleCmd_ActivatableInfo`
50
+ - `shuffle-set-card.ts` → `YGOProMsgShuffleSetCard_SetCardInfo`
51
+
52
+ ### 3. 字段重构
53
+
54
+ #### 前(使用 1/2 命名):
55
+ ```typescript
56
+ export class YGOProMsgCardTarget extends YGOProMsgBase {
57
+ @BinaryField('u8', 0) controller1: number;
58
+ @BinaryField('u8', 1) location1: number;
59
+ @BinaryField('u8', 2) sequence1: number;
60
+ @BinaryField('u8', 3) controller2: number;
61
+ @BinaryField('u8', 4) location2: number;
62
+ @BinaryField('u8', 5) sequence2: number;
63
+ }
64
+ ```
65
+
66
+ #### 后(使用小类):
67
+ ```typescript
68
+ export class YGOProMsgCardTarget_CardLocation {
69
+ @BinaryField('u8', 0) controller: number;
70
+ @BinaryField('u8', 1) location: number;
71
+ @BinaryField('u8', 2) sequence: number;
72
+ }
73
+
74
+ export class YGOProMsgCardTarget extends YGOProMsgBase {
75
+ @BinaryField(() => YGOProMsgCardTarget_CardLocation, 0)
76
+ card1: YGOProMsgCardTarget_CardLocation;
77
+
78
+ @BinaryField(() => YGOProMsgCardTarget_CardLocation, 3)
79
+ card2: YGOProMsgCardTarget_CardLocation;
80
+ }
81
+ ```
82
+
83
+ ### 示例 2: 嵌套小类
84
+
85
+ #### 前(使用扁平命名):
86
+ ```typescript
87
+ export class YGOProMsgBattle extends YGOProMsgBase {
88
+ @BinaryField('u8', 0) attackerController: number;
89
+ @BinaryField('u8', 1) attackerLocation: number;
90
+ @BinaryField('u8', 2) attackerSequence: number;
91
+ @BinaryField('u8', 3) attackerPosition: number;
92
+ @BinaryField('i32', 4) attackerAtk: number;
93
+ @BinaryField('i32', 8) attackerDef: number;
94
+ @BinaryField('u8', 12) defenderController: number;
95
+ // ... 类似的 defender 字段
96
+ }
97
+ ```
98
+
99
+ #### 后(使用嵌套小类):
100
+ ```typescript
101
+ export class YGOProMsgBattle_CardLocation {
102
+ @BinaryField('u8', 0) controller: number;
103
+ @BinaryField('u8', 1) location: number;
104
+ @BinaryField('u8', 2) sequence: number;
105
+ @BinaryField('u8', 3) position: number;
106
+ }
107
+
108
+ export class YGOProMsgBattle_CardStats {
109
+ @BinaryField(() => YGOProMsgBattle_CardLocation, 0)
110
+ location: YGOProMsgBattle_CardLocation;
111
+
112
+ @BinaryField('i32', 4) atk: number;
113
+ @BinaryField('i32', 8) def: number;
114
+ }
115
+
116
+ export class YGOProMsgBattle extends YGOProMsgBase {
117
+ @BinaryField(() => YGOProMsgBattle_CardStats, 0)
118
+ attacker: YGOProMsgBattle_CardStats;
119
+
120
+ @BinaryField(() => YGOProMsgBattle_CardStats, 12)
121
+ defender: YGOProMsgBattle_CardStats;
122
+
123
+ @BinaryField('u8', 24) battleDamageCalc: number;
124
+ }
125
+ ```
126
+
127
+ ## 二进制格式变化
128
+
129
+ ### 前:
130
+ ```
131
+ [字段数据...]
132
+ ```
133
+
134
+ ### 后:
135
+ ```
136
+ [MSG_IDENTIFIER (1 byte)][字段数据...]
137
+ ```
138
+
139
+ 所有 MSG 的序列化数据现在第一个字节都是 MSG identifier,解析时会验证该字节。
140
+
141
+ ## 测试更新
142
+
143
+ `tests/msg.spec.ts` 已更新以适配新的二进制格式:
144
+ - 期望长度增加 1 字节(identifier)
145
+ - 验证第一个字节是正确的 MSG identifier
146
+ - 添加了 identifier 验证测试
147
+
148
+ ## 向后兼容性
149
+
150
+ ⚠️ **破坏性变更**:由于二进制格式改变,此修改与旧版本不兼容。
151
+
152
+ ## 新增消息类型
153
+
154
+ ### MSG_START (4)
155
+ 开始决斗的消息,包含:
156
+ - playerType: 玩家类型
157
+ - duelRule: 决斗规则
158
+ - startLp0/startLp1: 双方起始 LP
159
+ - player0/player1: 双方卡组和额外卡组数量
160
+
161
+ ### MSG_WAITING (3)
162
+ 等待消息,无字段
163
+
164
+ ### MSG_UPDATE_CARD (7) - 完整实现 ✅ 视角控制
165
+ 更新单张卡片信息,现在使用完整的 `CardQuery` 类:
166
+ - controller, location, sequence: 卡片位置
167
+ - card: CardQuery 对象,包含完整的查询数据
168
+ - **opponentView**: 对手视角下,盖放卡片(POS_FACEDOWN)只显示 flags = QUERY_CODE 和 code = 0
169
+ - **teammateView**: TAG 决斗中
170
+ - 场上卡片(LOCATION_ONFIELD):队友可以看到己方盖放的卡片
171
+ - 非场上卡片:和对手视角相同
172
+
173
+ ### MSG_UPDATE_DATA (6) - 完整实现 ✅ 视角控制
174
+ 更新某个位置所有卡片的信息,现在使用 `CardQuery[]` 数组:
175
+ - player, location: 位置信息
176
+ - cards: CardQuery 数组,每张卡片的完整查询数据
177
+ - **opponentView**: 对手视角下,盖放卡片(POS_FACEDOWN)清除所有查询数据(flags = 0, empty = true)
178
+ - **teammateView**: TAG 决斗中,根据位置不同处理
179
+ - MZONE/SZONE:队友可以看到己方盖放的卡片
180
+ - HAND:队友也看不到非公开的手牌(只有当前操作玩家能看到)
181
+ - 其他公开区域(GRAVE 等):队友可以看到
182
+
183
+ ### MSG_RELOAD_FIELD (162)
184
+ 重新加载整个场地信息,包含:
185
+ - duelRule: 决斗规则
186
+ - players[2]: 双方玩家状态(LP、各区域卡片数量和位置)
187
+ - chains: 连锁信息数组
188
+
189
+ ## CardQuery 类
190
+
191
+ 新增 `CardQuery` 类,完整实现 OCG 查询数据的序列化和反序列化:
192
+
193
+ ### 支持的查询标志
194
+ - QUERY_CODE (1): 卡片代码
195
+ - QUERY_POSITION (2): 卡片位置
196
+ - QUERY_ALIAS (4): 别名
197
+ - QUERY_TYPE (8): 卡片类型
198
+ - QUERY_LEVEL (16): 等级
199
+ - QUERY_RANK (32): 阶级
200
+ - QUERY_ATTRIBUTE (64): 属性
201
+ - QUERY_RACE (128): 种族
202
+ - QUERY_ATTACK (256): 攻击力
203
+ - QUERY_DEFENSE (512): 守备力
204
+ - QUERY_BASE_ATTACK (1024): 基础攻击力
205
+ - QUERY_BASE_DEFENSE (2048): 基础守备力
206
+ - QUERY_REASON (4096): 原因
207
+ - QUERY_REASON_CARD (8192): 原因卡片(自动跳过)
208
+ - QUERY_EQUIP_CARD (16384): 装备卡位置
209
+ - QUERY_TARGET_CARD (32768): 目标卡片位置数组
210
+ - QUERY_OVERLAY_CARD (65536): 超量素材数组
211
+ - QUERY_COUNTERS (131072): 指示物数组
212
+ - QUERY_OWNER (262144): 拥有者
213
+ - QUERY_STATUS (524288): 状态
214
+ - QUERY_LSCALE (2097152): 左刻度
215
+ - QUERY_RSCALE (4194304): 右刻度
216
+ - QUERY_LINK (8388608): Link 值和 Link 标记
217
+
218
+ ### 小类
219
+ - `CardQuery_CardLocation`: 卡片位置信息
220
+ - `CardQuery_Counter`: 指示物信息
221
+
222
+ ## 视角控制实现
223
+
224
+ ### MSG_UPDATE_CARD
225
+ 根据 single_duel.cpp 和 tag_duel.cpp (RefreshSingle) 的实现:
226
+ - **对卡片控制者**:发送完整的查询数据
227
+ - **对对手**:如果卡片是盖放的(POS_FACEDOWN),只保留 flags = QUERY_CODE 和 code = 0
228
+ - **对队友(TAG 决斗)**:
229
+ - 场上卡片(LOCATION_ONFIELD):发送完整数据(包括盖放)
230
+ - 非场上卡片:和对手视角相同
231
+
232
+ ### MSG_UPDATE_DATA
233
+ 根据 single_duel.cpp 和 tag_duel.cpp (RefreshMzone/RefreshSzone/RefreshHand) 的实现:
234
+ - **对卡片控制者**:发送完整的查询数据
235
+ - **对对手**:遍历每张卡片,盖放的卡片(POS_FACEDOWN)清除所有查询数据
236
+ - **对队友(TAG 决斗)**:
237
+ - MZONE/SZONE:发送完整数据(队友可以看到己方盖放的卡片)
238
+ - HAND:只有当前操作玩家(cur_player[player])能看到完整手牌,队友也看不到非公开的手牌
239
+ - 其他公开区域(GRAVE 等):所有人都能看到
240
+
241
+ ## playerView 实现
242
+
243
+ ### 基类实现
244
+ 基类 `YGOProMsgBase` 提供了默认的 `playerView` 实现:
245
+ - 检查 `this['player']` 字段
246
+ - 如果匹配传入的 `playerId`,返回完整数据
247
+ - 否则返回 `opponentView()`
248
+
249
+ ### 特殊实现
250
+ **MSG_UPDATE_CARD** - 字段名是 `controller` 而不是 `player`,需要重写 `playerView`:
251
+ ```typescript
252
+ playerView(playerId: number): this {
253
+ if (this.controller === playerId) {
254
+ return this.copy();
255
+ }
256
+ return this.opponentView();
257
+ }
258
+ ```
259
+
260
+ ## 测试结果
261
+
262
+ ✅ 所有 66 个测试通过(新增 20 个查询数据和视角控制测试)
263
+ ✅ 构建成功
264
+
265
+ 新增测试:
266
+ - MSG_UPDATE_CARD 的视角控制(对手/队友/playerView)
267
+ - MSG_UPDATE_DATA 的视角控制(对手/队友/playerView)
268
+ - 场上盖放卡片的队友可见性
269
+ - 手牌非公开卡片的队友不可见性
270
+ - playerView 正确使用 controller 和 player 字段
@@ -0,0 +1,279 @@
1
+ # CTOS/STOC 协议实现总结
2
+
3
+ 本文档总结了 CTOS(Client to Server)和 STOC(Server to Client)协议的实现。
4
+
5
+ ## 协议格式
6
+
7
+ CTOS 和 STOC 协议的二进制格式为:
8
+ ```
9
+ [length 2 bytes][identifier 1 byte][body]
10
+ ```
11
+
12
+ - `length`: 2 字节小端序,表示 identifier + body 的总长度
13
+ - `identifier`: 1 字节,协议类型标识符
14
+ - `body`: 正文内容(可变长度)
15
+
16
+ ## 实现架构
17
+
18
+ ### 基础类
19
+
20
+ #### `YGOProCtosBase` (`src/protos/ctos/base.ts`)
21
+ - 继承自 `PayloadBase`
22
+ - 只处理正文(body)部分
23
+ - 不包含 header 处理逻辑
24
+
25
+ #### `YGOProStocBase` (`src/protos/stoc/base.ts`)
26
+ - 继承自 `PayloadBase`
27
+ - 只处理正文(body)部分
28
+ - 不包含 header 处理逻辑
29
+
30
+ ### 注册器
31
+
32
+ #### `YGOProCtos` (`src/protos/ctos/registry.ts`)
33
+ ```typescript
34
+ export const YGOProCtos = new RegistryBase(YGOProCtosBase, {
35
+ identifierOffset: 2, // identifier 在字节 2
36
+ dataOffset: 3, // body 从字节 3 开始
37
+ });
38
+ ```
39
+
40
+ #### `YGOProStoc` (`src/protos/stoc/registry.ts`)
41
+ ```typescript
42
+ export const YGOProStoc = new RegistryBase(YGOProStocBase, {
43
+ identifierOffset: 2, // identifier 在字节 2
44
+ dataOffset: 3, // body 从字节 3 开始
45
+ });
46
+ ```
47
+
48
+ ### 公共数据结构
49
+
50
+ #### `HostInfo` (`src/protos/common/host-info.ts`)
51
+ 用于多个协议的房间信息结构(20 字节):
52
+ - `lflist`: uint32_t
53
+ - `rule`: uint8_t
54
+ - `mode`: uint8_t
55
+ - `duel_rule`: uint8_t
56
+ - `no_check_deck`: uint8_t
57
+ - `no_shuffle_deck`: uint8_t
58
+ - 3 字节 padding
59
+ - `start_lp`: int32_t
60
+ - `start_hand`: uint8_t
61
+ - `draw_count`: uint8_t
62
+ - `time_limit`: uint16_t
63
+
64
+ ## CTOS 协议实现
65
+
66
+ 实现了以下 CTOS 协议(共 19 个):
67
+
68
+ | 标识符 | 类名 | 说明 |
69
+ |--------|------|------|
70
+ | 0x01 | `YGOProCtosResponse` | 响应数据(字节数组) |
71
+ | 0x02 | `YGOProCtosUpdateDeck` | 更新卡组(使用 ygopro-deck-encode) |
72
+ | 0x03 | `YGOProCtosHandResult` | 猜拳结果 |
73
+ | 0x04 | `YGOProCtosTpResult` | 先后手选择结果 |
74
+ | 0x10 | `YGOProCtosPlayerInfo` | 玩家信息(名字) |
75
+ | 0x11 | `YGOProCtosCreateGame` | 创建房间 |
76
+ | 0x12 | `YGOProCtosJoinGame` | 加入房间 |
77
+ | 0x13 | `YGOProCtosLeaveGame` | 离开房间(无数据) |
78
+ | 0x14 | `YGOProCtosSurrender` | 认输(无数据) |
79
+ | 0x15 | `YGOProCtosTimeConfirm` | 时间确认(无数据) |
80
+ | 0x16 | `YGOProCtosChat` | 聊天消息 |
81
+ | 0x17 | `YGOProCtosExternalAddress` | 外部地址 |
82
+ | 0x20 | `YGOProCtosHsToDuelist` | 切换到决斗者(无数据) |
83
+ | 0x21 | `YGOProCtosHsToObserver` | 切换到观战者(无数据) |
84
+ | 0x22 | `YGOProCtosHsReady` | 准备(无数据) |
85
+ | 0x23 | `YGOProCtosHsNotReady` | 取消准备(无数据) |
86
+ | 0x24 | `YGOProCtosKick` | 踢人 |
87
+ | 0x25 | `YGOProCtosHsStart` | 开始决斗(无数据) |
88
+ | 0x30 | `YGOProCtosRequestField` | 请求场地信息(无数据) |
89
+
90
+ ### 特殊实现
91
+
92
+ #### `YGOProCtosUpdateDeck` (0x02)
93
+ - 使用 `ygopro-deck-encode` 库的 `YGOProDeck` 类
94
+ - `fromUpdateDeckPayload()` 和 `toUpdateDeckPayload()` 方法
95
+ - 成员:`deck: YGOProDeck`
96
+
97
+ #### `YGOProCtosResponse` (0x01)
98
+ - 直接存储原始字节数组
99
+ - 用于游戏响应数据
100
+
101
+ ## STOC 协议实现
102
+
103
+ 实现了以下 STOC 协议(共 24 个):
104
+
105
+ | 标识符 | 类名 | 说明 |
106
+ |--------|------|------|
107
+ | 0x01 | `YGOProStocGameMsg` | 游戏消息(使用 YGOProMessages) |
108
+ | 0x02 | `YGOProStocErrorMsg` | 错误消息 |
109
+ | 0x03 | `YGOProStocSelectHand` | 选择猜拳(无数据) |
110
+ | 0x04 | `YGOProStocSelectTp` | 选择先后手(无数据) |
111
+ | 0x05 | `YGOProStocHandResult` | 猜拳结果 |
112
+ | 0x06 | `YGOProStocTpResult` | 先后手结果(保留) |
113
+ | 0x07 | `YGOProStocChangeSide` | 换边(无数据) |
114
+ | 0x08 | `YGOProStocWaitingSide` | 等待换边(无数据) |
115
+ | 0x09 | `YGOProStocDeckCount` | 卡组数量 |
116
+ | 0x11 | `YGOProStocCreateGame` | 创建房间(保留) |
117
+ | 0x12 | `YGOProStocJoinGame` | 加入房间 |
118
+ | 0x13 | `YGOProStocTypeChange` | 类型变更 |
119
+ | 0x14 | `YGOProStocLeaveGame` | 离开房间(保留) |
120
+ | 0x15 | `YGOProStocDuelStart` | 决斗开始(无数据) |
121
+ | 0x16 | `YGOProStocDuelEnd` | 决斗结束(无数据) |
122
+ | 0x17 | `YGOProStocReplay` | 录像(使用 ygopro-yrp-encode) |
123
+ | 0x18 | `YGOProStocTimeLimit` | 时间限制 |
124
+ | 0x19 | `YGOProStocChat` | 聊天消息 |
125
+ | 0x20 | `YGOProStocHsPlayerEnter` | 玩家进入 |
126
+ | 0x21 | `YGOProStocHsPlayerChange` | 玩家状态变更 |
127
+ | 0x22 | `YGOProStocHsWatchChange` | 观战者数量变更 |
128
+ | 0x23 | `YGOProStocTeammateSurrender` | 队友认输(无数据) |
129
+ | 0x30 | `YGOProStocFieldFinish` | 场地同步完成(无数据) |
130
+ | 0x31 | `YGOProStocSrvproRoomlist` | 房间列表(SRVPro 服务器) |
131
+
132
+ ### 特殊实现
133
+
134
+ #### `YGOProStocSrvproRoomlist` (0x31)
135
+ - SRVPro 服务器特定协议,用于返回房间列表
136
+ - 结构:
137
+ ```typescript
138
+ export class SrvproRoomInfo {
139
+ roomname: string; // UTF-8, 64 bytes
140
+ room_status: number; // uint8_t (0=Waiting, 1=Dueling, 2=Siding)
141
+ room_duel_count: number; // int8_t
142
+ room_turn_count: number; // int8_t
143
+ player1: string; // UTF-8, 128 bytes
144
+ player1_score: number; // int8_t
145
+ player1_lp: number; // int32_t
146
+ player2: string; // UTF-8, 128 bytes
147
+ player2_score: number; // int8_t
148
+ player2_lp: number; // int32_t
149
+ }
150
+
151
+ export class YGOProStocSrvproRoomlist {
152
+ count: number; // uint16_t
153
+ rooms: SrvproRoomInfo[]; // 房间数组
154
+ }
155
+ ```
156
+ - 每个房间信息大小:333 字节(64 + 1 + 1 + 1 + 128 + 1 + 4 + 128 + 1 + 4)
157
+
158
+ #### `YGOProStocGameMsg` (0x01)
159
+ - 使用 `YGOProMessages` registry 解析 MSG 协议
160
+ - 成员:`msg: YGOProMsgBase | undefined`
161
+ - 自动解析和序列化 MSG 协议消息
162
+
163
+ #### `YGOProStocReplay` (0x17)
164
+ - 使用 `ygopro-yrp-encode` 库的 `YGOProYrp` 类
165
+ - `fromYrp()` 和 `toYrp()` 方法
166
+ - 成员:`replay: YGOProYrp`
167
+
168
+ ## 使用示例
169
+
170
+ ### CTOS 协议使用
171
+
172
+ ```typescript
173
+ import { YGOProCtos, YGOProCtosPlayerInfo } from 'ygopro-msg-encode';
174
+
175
+ // 创建协议对象
176
+ const playerInfo = new YGOProCtosPlayerInfo();
177
+ playerInfo.name = [0x0041, 0x0042, ...]; // UTF-16 字符数组
178
+
179
+ // 序列化
180
+ const payload = playerInfo.toPayload();
181
+
182
+ // 解析
183
+ const parsed = YGOProCtos.getInstanceFromPayload(payload);
184
+ ```
185
+
186
+ ### STOC 协议使用
187
+
188
+ ```typescript
189
+ import { YGOProStoc, YGOProStocGameMsg, YGOProMsgHint } from 'ygopro-msg-encode';
190
+
191
+ // 创建游戏消息
192
+ const gameMsg = new YGOProStocGameMsg();
193
+ const hint = new YGOProMsgHint();
194
+ hint.type = 1;
195
+ hint.player = 0;
196
+ hint.desc = 0x1234;
197
+ gameMsg.msg = hint;
198
+
199
+ // 序列化
200
+ const payload = gameMsg.toPayload();
201
+
202
+ // 解析
203
+ const parsed = YGOProStoc.getInstanceFromPayload(payload);
204
+ ```
205
+
206
+ ### UpdateDeck 使用
207
+
208
+ ```typescript
209
+ import { YGOProCtosUpdateDeck } from 'ygopro-msg-encode';
210
+ import YGOProDeck from 'ygopro-deck-encode';
211
+
212
+ // 创建
213
+ const updateDeck = new YGOProCtosUpdateDeck();
214
+ updateDeck.deck = new YGOProDeck({
215
+ main: [12345, 67890],
216
+ extra: [11111],
217
+ side: [22222],
218
+ });
219
+
220
+ // 序列化
221
+ const payload = updateDeck.toPayload();
222
+
223
+ // 解析
224
+ const parsed = new YGOProCtosUpdateDeck().fromPayload(payload.slice(3)); // 去掉 header
225
+ ```
226
+
227
+ ### Replay 使用
228
+
229
+ ```typescript
230
+ import { YGOProStocReplay } from 'ygopro-msg-encode';
231
+ import { YGOProYrp } from 'ygopro-yrp-encode';
232
+
233
+ // 创建
234
+ const replay = new YGOProStocReplay();
235
+ replay.replay = new YGOProYrp({
236
+ hostName: 'Player1',
237
+ clientName: 'Player2',
238
+ // ... 其他字段
239
+ });
240
+
241
+ // 序列化
242
+ const payload = replay.toPayload();
243
+ ```
244
+
245
+ ## 注意事项
246
+
247
+ ### Struct Padding
248
+ C++ 结构体的 padding 已经在各个类中正确处理:
249
+ - `HostInfo`: 字节 9-11 是 padding
250
+ - `CTOS_JoinGame`: 字节 2-3 是 padding
251
+ - `STOC_ErrorMsg`: 字节 1-3 是 padding
252
+ - `STOC_TimeLimit`: 字节 1 是 padding
253
+ - `STOC_HS_PlayerEnter`: 实际大小 41 字节(有 workaround)
254
+
255
+ ### 数组字段
256
+ - 固定长度数组使用 `@BinaryField('u16', offset, length)`
257
+ - UTF-16 字符串使用 `@BinaryField('utf16', offset, length)`
258
+ - 不使用 `u16[]` 这种写法
259
+
260
+ ### 无数据协议
261
+ 多个协议(如 `LEAVE_GAME`, `SURRENDER` 等)没有正文数据,只有 header。
262
+
263
+ ## 依赖库
264
+
265
+ - `ygopro-deck-encode`: 卡组编解码
266
+ - `ygopro-yrp-encode`: 录像编解码
267
+
268
+ ## 测试
269
+
270
+ 运行测试脚本:
271
+ ```bash
272
+ npm run build
273
+ node dist/test-ctos-stoc.cjs
274
+ ```
275
+
276
+ 或直接使用 TypeScript:
277
+ ```bash
278
+ npx tsx test-ctos-stoc.ts
279
+ ```