@jeik/dingtalk-connector 0.8.21-fix1

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 (154) hide show
  1. package/CHANGELOG.md +686 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +181 -0
  4. package/README.md +221 -0
  5. package/bin/dingtalk-connector.js +858 -0
  6. package/bin/wizard-config.mjs +110 -0
  7. package/dist/accounts-BAzdqkAV.mjs +268 -0
  8. package/dist/accounts-BQptOmgB.mjs +2 -0
  9. package/dist/chunk-upload-BBQgGtcZ.mjs +193 -0
  10. package/dist/chunk-upload-DaLXXZH3.mjs +2 -0
  11. package/dist/common-C8pYKU_y.mjs +2 -0
  12. package/dist/common-Dt9n6fQN.mjs +101 -0
  13. package/dist/connection-DHHFFNQJ.mjs +423 -0
  14. package/dist/entry-bundled.d.mts +16 -0
  15. package/dist/entry-bundled.mjs +31 -0
  16. package/dist/game-xiyou-CqHt-6Q1.mjs +4271 -0
  17. package/dist/gateway-methods-C4tcgI7P.mjs +771 -0
  18. package/dist/gateway-methods-Ci31A3vg.mjs +2 -0
  19. package/dist/http-client-CpnJHB89.mjs +2 -0
  20. package/dist/http-client-DFWZgO1n.mjs +33 -0
  21. package/dist/index.d.mts +193 -0
  22. package/dist/index.mjs +45 -0
  23. package/dist/logger-BmJkQkm1.mjs +2 -0
  24. package/dist/logger-mZ9OSbmD.mjs +58 -0
  25. package/dist/media-C_SVin7s.mjs +2 -0
  26. package/dist/media-cz72EVS3.mjs +509 -0
  27. package/dist/message-handler-DESzFFDc.mjs +1971 -0
  28. package/dist/messaging-B6l1sRvX.mjs +1044 -0
  29. package/dist/runtime-DUgpo5zC.mjs +1422 -0
  30. package/dist/session-DJ4jYqPv.mjs +114 -0
  31. package/dist/utils-Bjh4r_qS.mjs +4 -0
  32. package/dist/utils-CIfI_3Jh.mjs +63 -0
  33. package/dist/utils-legacy-CALCPP1t.mjs +230 -0
  34. package/dist/utils-legacy-CFYDBM4r.mjs +3 -0
  35. package/docs/DEAP_AGENT_GUIDE.en.md +115 -0
  36. package/docs/DEAP_AGENT_GUIDE.md +115 -0
  37. package/docs/DINGTALK_MANUAL_SETUP.md +50 -0
  38. package/docs/MULTI_AGENT_SETUP.md +306 -0
  39. package/docs/RELEASE_NOTES_V0.7.10.md +40 -0
  40. package/docs/RELEASE_NOTES_V0.7.2.md +143 -0
  41. package/docs/RELEASE_NOTES_V0.7.3.md +149 -0
  42. package/docs/RELEASE_NOTES_V0.7.4.md +206 -0
  43. package/docs/RELEASE_NOTES_V0.7.5.md +267 -0
  44. package/docs/RELEASE_NOTES_V0.7.6.md +219 -0
  45. package/docs/RELEASE_NOTES_V0.7.7.md +122 -0
  46. package/docs/RELEASE_NOTES_V0.7.8.md +101 -0
  47. package/docs/RELEASE_NOTES_V0.7.9.md +65 -0
  48. package/docs/RELEASE_NOTES_V0.8.0.md +53 -0
  49. package/docs/RELEASE_NOTES_V0.8.1.md +47 -0
  50. package/docs/RELEASE_NOTES_V0.8.10.md +49 -0
  51. package/docs/RELEASE_NOTES_V0.8.11.md +51 -0
  52. package/docs/RELEASE_NOTES_V0.8.12.md +63 -0
  53. package/docs/RELEASE_NOTES_V0.8.13-beta.0.md +69 -0
  54. package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
  55. package/docs/RELEASE_NOTES_V0.8.14.md +86 -0
  56. package/docs/RELEASE_NOTES_V0.8.16.md +40 -0
  57. package/docs/RELEASE_NOTES_V0.8.17.md +87 -0
  58. package/docs/RELEASE_NOTES_V0.8.18.md +64 -0
  59. package/docs/RELEASE_NOTES_V0.8.19.md +62 -0
  60. package/docs/RELEASE_NOTES_V0.8.2.md +55 -0
  61. package/docs/RELEASE_NOTES_V0.8.20.md +49 -0
  62. package/docs/RELEASE_NOTES_V0.8.3.md +63 -0
  63. package/docs/RELEASE_NOTES_V0.8.4.md +45 -0
  64. package/docs/RELEASE_NOTES_V0.8.7.md +49 -0
  65. package/docs/RELEASE_NOTES_V0.8.8.md +63 -0
  66. package/docs/RELEASE_NOTES_V0.8.9.md +81 -0
  67. package/docs/RELEASE_NOTES_v0.7.0.md +142 -0
  68. package/docs/RELEASE_NOTES_v0.7.1.md +74 -0
  69. package/docs/TROUBLESHOOTING.md +122 -0
  70. package/index.ts +77 -0
  71. package/openclaw.plugin.json +551 -0
  72. package/package.json +147 -0
  73. package/skills/dingtalk-channel-rules/SKILL.md +91 -0
  74. package/skills/dingtalk-troubleshoot/SKILL.md +93 -0
  75. package/skills/dws-cli/SKILL.md +129 -0
  76. package/skills/dws-cli/references/error-codes.md +95 -0
  77. package/skills/dws-cli/references/field-rules.md +105 -0
  78. package/skills/dws-cli/references/global-reference.md +104 -0
  79. package/skills/dws-cli/references/intent-guide.md +114 -0
  80. package/skills/dws-cli/references/products/aitable.md +452 -0
  81. package/skills/dws-cli/references/products/attendance.md +93 -0
  82. package/skills/dws-cli/references/products/calendar.md +217 -0
  83. package/skills/dws-cli/references/products/chat.md +292 -0
  84. package/skills/dws-cli/references/products/contact.md +108 -0
  85. package/skills/dws-cli/references/products/ding.md +57 -0
  86. package/skills/dws-cli/references/products/report.md +162 -0
  87. package/skills/dws-cli/references/products/simple.md +128 -0
  88. package/skills/dws-cli/references/products/todo.md +138 -0
  89. package/skills/dws-cli/references/products/workbench.md +39 -0
  90. package/skills/dws-cli/references/recovery-guide.md +94 -0
  91. package/src/channel.ts +588 -0
  92. package/src/config/accounts.ts +242 -0
  93. package/src/config/schema.ts +180 -0
  94. package/src/core/connection.ts +741 -0
  95. package/src/core/message-handler.ts +1788 -0
  96. package/src/core/provider.ts +111 -0
  97. package/src/core/state.ts +54 -0
  98. package/src/device-auth-config.ts +14 -0
  99. package/src/device-auth.ts +197 -0
  100. package/src/directory.ts +95 -0
  101. package/src/docs.ts +293 -0
  102. package/src/game-xiyou/achievement-engine.ts +252 -0
  103. package/src/game-xiyou/bounty-system.ts +315 -0
  104. package/src/game-xiyou/commands.ts +223 -0
  105. package/src/game-xiyou/drop-engine.ts +241 -0
  106. package/src/game-xiyou/encounter-system.ts +135 -0
  107. package/src/game-xiyou/escape-engine.ts +164 -0
  108. package/src/game-xiyou/exp-calculator.ts +139 -0
  109. package/src/game-xiyou/index.ts +479 -0
  110. package/src/game-xiyou/level-system.ts +91 -0
  111. package/src/game-xiyou/monster-pool.ts +180 -0
  112. package/src/game-xiyou/pity-counter.ts +114 -0
  113. package/src/game-xiyou/random-event-engine.ts +648 -0
  114. package/src/game-xiyou/renderer.ts +679 -0
  115. package/src/game-xiyou/storage.ts +218 -0
  116. package/src/game-xiyou/treasure-system.ts +105 -0
  117. package/src/game-xiyou/types.ts +582 -0
  118. package/src/game-xiyou/uid-resolver.ts +49 -0
  119. package/src/gateway-methods.ts +740 -0
  120. package/src/onboarding.ts +553 -0
  121. package/src/policy.ts +32 -0
  122. package/src/probe.ts +210 -0
  123. package/src/reply-dispatcher.ts +874 -0
  124. package/src/runtime.ts +32 -0
  125. package/src/sdk/helpers.ts +322 -0
  126. package/src/sdk/types.ts +519 -0
  127. package/src/secret-input.ts +19 -0
  128. package/src/services/media/audio.ts +54 -0
  129. package/src/services/media/chunk-upload.ts +296 -0
  130. package/src/services/media/common.ts +155 -0
  131. package/src/services/media/file.ts +75 -0
  132. package/src/services/media/image.ts +81 -0
  133. package/src/services/media/index.ts +10 -0
  134. package/src/services/media/video.ts +162 -0
  135. package/src/services/media.ts +1143 -0
  136. package/src/services/messaging/card.ts +604 -0
  137. package/src/services/messaging/index.ts +18 -0
  138. package/src/services/messaging/mentions.ts +267 -0
  139. package/src/services/messaging/send.ts +141 -0
  140. package/src/services/messaging.ts +1191 -0
  141. package/src/services/reply-markers.ts +55 -0
  142. package/src/targets.ts +45 -0
  143. package/src/types/index.ts +59 -0
  144. package/src/types/pdf-parse.d.ts +3 -0
  145. package/src/utils/agent.ts +63 -0
  146. package/src/utils/async.ts +51 -0
  147. package/src/utils/constants.ts +27 -0
  148. package/src/utils/http-client.ts +38 -0
  149. package/src/utils/index.ts +8 -0
  150. package/src/utils/logger.ts +78 -0
  151. package/src/utils/session.ts +147 -0
  152. package/src/utils/token.ts +93 -0
  153. package/src/utils/utils-legacy.ts +454 -0
  154. package/tsconfig.json +20 -0
@@ -0,0 +1,648 @@
1
+ /**
2
+ * 随机事件系统 (v2)
3
+ *
4
+ * 低概率触发的特殊剧情事件,打破日常节奏,制造惊喜和紧张感。
5
+ * 事件分为三类:增益(8%)、挑战(5%)、灾厄(3%)。
6
+ * 独立于掉落和机缘触发,同一事件 24 小时内不重复。
7
+ */
8
+
9
+ import { randomBytes } from 'crypto';
10
+ import type {
11
+ RandomEvent, ChallengeEvent, EventCategory, EventEffect, EventDuration,
12
+ EventResolution, ActiveEventState, EventHistoryEntry, EventStats,
13
+ ChallengeCondition, ChallengeProgress, EventReward, EventPenalty,
14
+ UserProfile, MonsterQuality,
15
+ } from './types.ts';
16
+
17
+ function cryptoRandom(): number {
18
+ const buffer = randomBytes(4);
19
+ return buffer.readUInt32BE(0) / 0xFFFFFFFF;
20
+ }
21
+
22
+ // ============ 事件数据定义 ============
23
+
24
+ const BLESSING_EVENTS: RandomEvent[] = [
25
+ {
26
+ id: 'EV001', name: '蟠桃大会', category: 'blessing', triggerRate: 0.015,
27
+ description: '接下来 10 次操作修行值 ×3',
28
+ flavorText: '王母娘娘设宴,蟠桃大会开席!修行值大涨!',
29
+ effect: { type: 'exp_multiplier', value: 3, targetCount: 10 },
30
+ duration: { type: 'operation_count', total: 10, remaining: 10 },
31
+ },
32
+ {
33
+ id: 'EV002', name: '月光宝盒', category: 'blessing', triggerRate: 0.010,
34
+ description: '下一次掉落品质强制提升一级',
35
+ flavorText: '紫霞仙子留下的月光宝盒,时光倒流,命运改写!',
36
+ effect: { type: 'quality_boost', value: 1 },
37
+ duration: { type: 'drop_count', total: 1, remaining: 1 },
38
+ },
39
+ {
40
+ id: 'EV003', name: '龙宫寻宝', category: 'blessing', triggerRate: 0.015,
41
+ description: '立即额外触发一次掉落',
42
+ flavorText: '东海龙宫大门洞开,宝物任你挑选!',
43
+ effect: { type: 'extra_drop', value: 1 },
44
+ duration: { type: 'instant', total: 1, remaining: 0 },
45
+ },
46
+ {
47
+ id: 'EV004', name: '仙人指路', category: 'blessing', triggerRate: 0.020,
48
+ description: '下 5 次操作的产品关联权重 ×5',
49
+ flavorText: '南极仙翁路过,指了指前方:"那边有好东西。"',
50
+ effect: { type: 'product_weight', value: 5, targetCount: 5 },
51
+ duration: { type: 'operation_count', total: 5, remaining: 5 },
52
+ },
53
+ {
54
+ id: 'EV005', name: '蟠桃熟了', category: 'blessing', triggerRate: 0.010,
55
+ description: '立即获得 30-100 随机修行值',
56
+ flavorText: '三千年一熟的蟠桃,今日恰好落入你手。',
57
+ effect: { type: 'exp_flat', value: 0 }, // value 在触发时随机
58
+ duration: { type: 'instant', total: 1, remaining: 0 },
59
+ },
60
+ {
61
+ id: 'EV006', name: '土地公的宝箱', category: 'blessing', triggerRate: 0.010,
62
+ description: '随机获得一件一次性法宝',
63
+ flavorText: '土地公从地下冒出来:"大圣,这个给你!"',
64
+ effect: { type: 'treasure_grant', value: 1 },
65
+ duration: { type: 'instant', total: 1, remaining: 0 },
66
+ },
67
+ ];
68
+
69
+ const CHALLENGE_EVENTS_DATA: Array<Omit<ChallengeEvent, 'progress'>> = [
70
+ {
71
+ id: 'EV101', name: '妖王入侵', category: 'challenge', triggerRate: 0.015,
72
+ description: '接下来连续成功 5 次',
73
+ flavorText: '一股强大的妖气从远方袭来——"哈哈哈,齐天大圣不过如此!"',
74
+ effect: { type: 'exp_flat', value: 0 },
75
+ duration: { type: 'operation_count', total: 8, remaining: 8 },
76
+ challengeCondition: { type: 'consecutive_success', target: 5, current: 0 },
77
+ successReward: { exp: 80, pityBonus: 5 },
78
+ failurePenalty: { expLoss: 30 },
79
+ operationLimit: 8,
80
+ },
81
+ {
82
+ id: 'EV102', name: '火焰山', category: 'challenge', triggerRate: 0.010,
83
+ description: '接下来 10 次操作中使用 ≥3 种不同产品',
84
+ flavorText: '火焰山烈焰滔天,唯有多方尝试方能通过!',
85
+ effect: { type: 'exp_flat', value: 0 },
86
+ duration: { type: 'operation_count', total: 10, remaining: 10 },
87
+ challengeCondition: { type: 'product_variety', target: 3, current: 0 },
88
+ successReward: { exp: 60, escapeRateMod: -0.05 },
89
+ failurePenalty: { expLoss: 0, comboReset: true },
90
+ operationLimit: 10,
91
+ },
92
+ {
93
+ id: 'EV103', name: '通天河阻路', category: 'challenge', triggerRate: 0.010,
94
+ description: '接下来连续成功 3 次且收服 ≥1 只妖怪',
95
+ flavorText: '通天河水势汹涌,需要勇气和实力才能渡过!',
96
+ effect: { type: 'exp_flat', value: 0 },
97
+ duration: { type: 'operation_count', total: 5, remaining: 5 },
98
+ challengeCondition: { type: 'consecutive_success', target: 3, current: 0 },
99
+ successReward: { exp: 100, extraDrop: true },
100
+ failurePenalty: { expLoss: 20 },
101
+ operationLimit: 5,
102
+ },
103
+ {
104
+ id: 'EV104', name: '真假美猴王', category: 'challenge', triggerRate: 0.005,
105
+ description: '接下来 3 次掉落中选出"真"的那只',
106
+ flavorText: '六耳猕猴现身,真假难辨!',
107
+ effect: { type: 'exp_flat', value: 0 },
108
+ duration: { type: 'drop_count', total: 3, remaining: 3 },
109
+ challengeCondition: { type: 'pick_correct', target: 1, current: 0 },
110
+ successReward: { exp: 100 },
111
+ failurePenalty: { expLoss: 0 },
112
+ operationLimit: 3,
113
+ },
114
+ {
115
+ id: 'EV105', name: '盘丝洞迷阵', category: 'challenge', triggerRate: 0.005,
116
+ description: '接下来 5 次操作必须使用 5 种不同产品',
117
+ flavorText: '蜘蛛精的丝网密布,每一步都不能重复!',
118
+ effect: { type: 'exp_flat', value: 0 },
119
+ duration: { type: 'operation_count', total: 5, remaining: 5 },
120
+ challengeCondition: { type: 'product_variety', target: 5, current: 0 },
121
+ successReward: { exp: 120, monster: { qualityMin: 'rare' } },
122
+ failurePenalty: { expLoss: 50 },
123
+ operationLimit: 5,
124
+ },
125
+ {
126
+ id: 'EV106', name: '比丘国救童', category: 'challenge', triggerRate: 0.005,
127
+ description: '接下来 8 次操作中成功率 ≥80%',
128
+ flavorText: '比丘国的孩子们需要你的帮助!',
129
+ effect: { type: 'exp_flat', value: 0 },
130
+ duration: { type: 'operation_count', total: 8, remaining: 8 },
131
+ challengeCondition: { type: 'success_rate', target: 7, current: 0 },
132
+ successReward: { exp: 150, treasureFragment: 'random' },
133
+ failurePenalty: { expLoss: 40 },
134
+ operationLimit: 8,
135
+ },
136
+ ];
137
+
138
+ const DISASTER_EVENTS: RandomEvent[] = [
139
+ {
140
+ id: 'EV201', name: '黑风来袭', category: 'disaster', triggerRate: 0.010,
141
+ description: '接下来 5 次掉落逃跑率 +20%',
142
+ flavorText: '黑风山的妖气蔓延而来——"嘿嘿嘿,让你的妖怪都跑光!"',
143
+ effect: { type: 'escape_rate_mod', value: 0.20 },
144
+ duration: { type: 'drop_count', total: 5, remaining: 5 },
145
+ resolution: { type: 'consecutive_success', count: 3, description: '连续成功 3 次可提前解除' },
146
+ },
147
+ {
148
+ id: 'EV202', name: '金蝉脱壳', category: 'disaster', triggerRate: 0.005,
149
+ description: '随机一只已收服的精良妖怪逃跑',
150
+ flavorText: '一阵妖风吹过,你的妖怪图鉴闪了一下……',
151
+ effect: { type: 'monster_escape', value: 1 },
152
+ duration: { type: 'instant', total: 1, remaining: 0 },
153
+ },
154
+ {
155
+ id: 'EV203', name: '妖雾弥漫', category: 'disaster', triggerRate: 0.008,
156
+ description: '接下来 3 次掉落品质上限降为精良',
157
+ flavorText: '浓雾笼罩,视线模糊,只能看到近处的小妖……',
158
+ effect: { type: 'quality_cap', value: 1 }, // 1 = fine
159
+ duration: { type: 'drop_count', total: 3, remaining: 3 },
160
+ resolution: { type: 'use_treasure', treasureId: 'zhaoyaojing', description: '使用法宝"照妖镜"可立即解除' },
161
+ },
162
+ {
163
+ id: 'EV204', name: '紧箍咒发作', category: 'disaster', triggerRate: 0.004,
164
+ description: '接下来 5 次操作修行值减半',
165
+ flavorText: '头痛欲裂!唐僧又念紧箍咒了!',
166
+ effect: { type: 'exp_halve', value: 0.5 },
167
+ duration: { type: 'operation_count', total: 5, remaining: 5 },
168
+ resolution: { type: 'complete_bounty', description: '完成 1 张悬赏令可提前解除' },
169
+ },
170
+ {
171
+ id: 'EV205', name: '五行山镇压', category: 'disaster', triggerRate: 0.002,
172
+ description: '连击归零 + 接下来 3 次操作无掉落',
173
+ flavorText: '五行山从天而降,压得你动弹不得!',
174
+ effect: { type: 'no_drop', value: 3 },
175
+ duration: { type: 'operation_count', total: 3, remaining: 3 },
176
+ resolution: { type: 'trigger_encounter', description: '触发 1 次神仙机缘可提前解除' },
177
+ },
178
+ {
179
+ id: 'EV206', name: '走火入魔', category: 'disaster', triggerRate: 0.001,
180
+ description: '修行值 -100 + 保底计数器全部 -10',
181
+ flavorText: '修炼走火入魔,功力大损!',
182
+ effect: { type: 'exp_loss', value: 100 },
183
+ duration: { type: 'instant', total: 1, remaining: 0 },
184
+ },
185
+ ];
186
+
187
+ // ============ 核心逻辑 ============
188
+
189
+ /**
190
+ * 创建默认的活跃事件状态
191
+ */
192
+ export function createDefaultActiveEventState(): ActiveEventState {
193
+ return {
194
+ currentEvents: [],
195
+ activeChallenge: null,
196
+ lastEventTriggerTime: {},
197
+ };
198
+ }
199
+
200
+ /**
201
+ * 创建默认的事件统计
202
+ */
203
+ export function createDefaultEventStats(): EventStats {
204
+ return {
205
+ totalTriggered: 0,
206
+ challengesCompleted: 0,
207
+ challengesFailed: 0,
208
+ disastersResolved: 0,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * 检查并触发随机事件
214
+ *
215
+ * @returns 触发的事件,或 null
216
+ */
217
+ export function checkRandomEvent(profile: UserProfile): RandomEvent | ChallengeEvent | null {
218
+ const now = Date.now();
219
+ const oneDayMs = 24 * 60 * 60 * 1000;
220
+
221
+ // 收集所有候选事件
222
+ const candidates: Array<RandomEvent | Omit<ChallengeEvent, 'progress'>> = [
223
+ ...BLESSING_EVENTS,
224
+ ...CHALLENGE_EVENTS_DATA,
225
+ ...DISASTER_EVENTS,
226
+ ];
227
+
228
+ for (const candidate of candidates) {
229
+ // 24 小时内不重复
230
+ const lastTrigger = profile.activeEvents.lastEventTriggerTime[candidate.id] ?? 0;
231
+ if (now - lastTrigger < oneDayMs) continue;
232
+
233
+ // 挑战事件:同时最多 1 个进行中
234
+ if (candidate.category === 'challenge' && profile.activeEvents.activeChallenge) continue;
235
+
236
+ // 概率判定
237
+ if (cryptoRandom() < candidate.triggerRate) {
238
+ profile.activeEvents.lastEventTriggerTime[candidate.id] = now;
239
+ profile.eventStats.totalTriggered += 1;
240
+
241
+ // 实例化事件
242
+ const event = instantiateEvent(candidate, profile);
243
+
244
+ // 添加到活跃事件
245
+ if (event.category === 'challenge') {
246
+ profile.activeEvents.activeChallenge = event as ChallengeEvent;
247
+ } else if (event.duration.type !== 'instant') {
248
+ profile.activeEvents.currentEvents.push(event);
249
+ }
250
+
251
+ // 记录历史
252
+ profile.eventHistory.push({
253
+ eventId: event.id,
254
+ triggeredAt: now,
255
+ });
256
+
257
+ return event;
258
+ }
259
+ }
260
+
261
+ return null;
262
+ }
263
+
264
+ /** 一次性法宝 ID 池(用于 EV006 土地公的宝箱) */
265
+ const CONSUMABLE_TREASURE_IDS = ['pantao', 'renshenguo'];
266
+
267
+ /**
268
+ * 实例化事件(处理随机值、即时效果等)
269
+ *
270
+ * 即时事件的效果在此函数中直接应用到 profile。
271
+ * 持续性事件仅初始化状态,效果由 tickActiveEvents / 查询函数处理。
272
+ */
273
+ function instantiateEvent(
274
+ template: RandomEvent | Omit<ChallengeEvent, 'progress'>,
275
+ profile: UserProfile
276
+ ): RandomEvent | ChallengeEvent {
277
+ const event: RandomEvent | ChallengeEvent = JSON.parse(JSON.stringify(template));
278
+
279
+ switch (event.id) {
280
+ // ---- 增益即时事件 ----
281
+
282
+ case 'EV005': {
283
+ // 蟠桃熟了:随机 30-100 修行值,立即发放
284
+ const expGain = Math.floor(30 + cryptoRandom() * 71);
285
+ event.effect.value = expGain;
286
+ profile.totalExp += expGain;
287
+ break;
288
+ }
289
+
290
+ case 'EV006': {
291
+ // 土地公的宝箱:随机赐予一件一次性法宝
292
+ const treasureId = CONSUMABLE_TREASURE_IDS[
293
+ Math.floor(cryptoRandom() * CONSUMABLE_TREASURE_IDS.length)
294
+ ];
295
+ event.effect.value = 1;
296
+ // 添加法宝到背包(如果已有则跳过,不重复添加)
297
+ if (!profile.treasures.includes(treasureId)) {
298
+ profile.treasures.push(treasureId);
299
+ }
300
+ // 将赐予的法宝 ID 记录在 description 中供渲染使用
301
+ event.description = `获得了一次性法宝:${treasureId === 'pantao' ? '蟠桃' : '人参果'}`;
302
+ break;
303
+ }
304
+
305
+ // EV003 龙宫寻宝:额外掉落标记,由主引擎 (index.ts) 在 checkRandomEvent 返回后检查
306
+ // event.effect.type === 'extra_drop' 时触发额外一次 executeDrop
307
+
308
+ // ---- 灾厄即时事件 ----
309
+
310
+ case 'EV202': {
311
+ // 金蝉脱壳:随机移除一只已收服的精良妖怪
312
+ // 实际的图鉴移除由主引擎处理(需要访问 collection),
313
+ // 这里标记 effect.value 为需要移除的品质等级(1 = fine)
314
+ event.effect.value = 1;
315
+ break;
316
+ }
317
+
318
+ case 'EV205': {
319
+ // 五行山镇压:连击归零 + 3 次无掉落
320
+ profile.currentCombo = 0;
321
+ break;
322
+ }
323
+
324
+ case 'EV206': {
325
+ // 走火入魔:修行值 -100 + 保底计数器全部 -10
326
+ profile.totalExp = Math.max(0, profile.totalExp - 100);
327
+ profile.pityCounters.sinceLastRare = Math.max(0, profile.pityCounters.sinceLastRare - 10);
328
+ profile.pityCounters.sinceLastEpic = Math.max(0, profile.pityCounters.sinceLastEpic - 10);
329
+ profile.pityCounters.sinceLastLegendary = Math.max(0, profile.pityCounters.sinceLastLegendary - 10);
330
+ profile.pityCounters.totalDropsWithoutShiny = Math.max(0, profile.pityCounters.totalDropsWithoutShiny - 10);
331
+ break;
332
+ }
333
+ }
334
+
335
+ // 挑战事件:初始化 progress 和 usedProducts 追踪
336
+ if (event.category === 'challenge') {
337
+ const challengeEvent = event as ChallengeEvent;
338
+ challengeEvent.progress = {
339
+ operationsUsed: 0,
340
+ operationLimit: challengeEvent.operationLimit,
341
+ conditionMet: false,
342
+ usedProducts: [],
343
+ };
344
+ }
345
+
346
+ return event;
347
+ }
348
+
349
+ /**
350
+ * 每次操作后更新活跃事件的持续时间和状态
351
+ *
352
+ * @returns 本次过期/完成的事件列表
353
+ */
354
+ export function tickActiveEvents(
355
+ profile: UserProfile,
356
+ operationSuccess: boolean,
357
+ product: string,
358
+ capturedMonster: boolean
359
+ ): ExpiredEventResult[] {
360
+ const results: ExpiredEventResult[] = [];
361
+
362
+ // 更新持续性事件
363
+ const remainingEvents: RandomEvent[] = [];
364
+ for (const event of profile.activeEvents.currentEvents) {
365
+ if (event.duration.type === 'operation_count') {
366
+ event.duration.remaining -= 1;
367
+ }
368
+ // drop_count 类型在掉落时递减(由 drop-engine 调用 tickDropEvent)
369
+
370
+ // 检查灾厄事件的化解条件
371
+ if (event.category === 'disaster' && event.resolution) {
372
+ if (checkResolution(event.resolution, profile, operationSuccess)) {
373
+ results.push({ event, outcome: 'resolved' });
374
+ profile.eventStats.disastersResolved += 1;
375
+ updateEventHistory(profile, event.id, 'resolved');
376
+ continue;
377
+ }
378
+ }
379
+
380
+ if (event.duration.remaining <= 0) {
381
+ results.push({ event, outcome: 'expired' });
382
+ updateEventHistory(profile, event.id, 'expired');
383
+ } else {
384
+ remainingEvents.push(event);
385
+ }
386
+ }
387
+ profile.activeEvents.currentEvents = remainingEvents;
388
+
389
+ // 更新挑战事件
390
+ const challenge = profile.activeEvents.activeChallenge;
391
+ if (challenge) {
392
+ challenge.progress.operationsUsed += 1;
393
+ challenge.duration.remaining -= 1;
394
+
395
+ // 更新挑战条件进度
396
+ updateChallengeProgress(challenge, operationSuccess, product, capturedMonster);
397
+
398
+ // 检查挑战是否完成
399
+ if (challenge.progress.conditionMet) {
400
+ results.push({ event: challenge, outcome: 'success' });
401
+ applyChallengeReward(profile, challenge);
402
+ profile.eventStats.challengesCompleted += 1;
403
+ updateEventHistory(profile, challenge.id, 'success');
404
+ profile.activeEvents.activeChallenge = null;
405
+ } else if (challenge.progress.operationsUsed >= challenge.progress.operationLimit) {
406
+ results.push({ event: challenge, outcome: 'failure' });
407
+ applyChallengePenalty(profile, challenge);
408
+ profile.eventStats.challengesFailed += 1;
409
+ updateEventHistory(profile, challenge.id, 'failure');
410
+ profile.activeEvents.activeChallenge = null;
411
+ }
412
+ }
413
+
414
+ return results;
415
+ }
416
+
417
+ export interface ExpiredEventResult {
418
+ event: RandomEvent | ChallengeEvent;
419
+ outcome: 'expired' | 'resolved' | 'success' | 'failure';
420
+ }
421
+
422
+ /**
423
+ * 掉落时递减 drop_count 类型事件的剩余次数
424
+ */
425
+ export function tickDropEvents(profile: UserProfile): void {
426
+ for (const event of profile.activeEvents.currentEvents) {
427
+ if (event.duration.type === 'drop_count') {
428
+ event.duration.remaining -= 1;
429
+ }
430
+ }
431
+ }
432
+
433
+ // ============ 事件效果查询 ============
434
+
435
+ /**
436
+ * 获取当前活跃的修行值倍率修正
437
+ */
438
+ export function getActiveExpMultiplier(profile: UserProfile): number {
439
+ let multiplier = 1;
440
+ for (const event of profile.activeEvents.currentEvents) {
441
+ if (event.effect.type === 'exp_multiplier') {
442
+ multiplier *= event.effect.value;
443
+ }
444
+ if (event.effect.type === 'exp_halve') {
445
+ multiplier *= event.effect.value;
446
+ }
447
+ }
448
+ return multiplier;
449
+ }
450
+
451
+ /**
452
+ * 检查是否有品质提升事件
453
+ */
454
+ export function getQualityBoost(profile: UserProfile): number {
455
+ for (const event of profile.activeEvents.currentEvents) {
456
+ if (event.effect.type === 'quality_boost' && event.duration.remaining > 0) {
457
+ return event.effect.value;
458
+ }
459
+ }
460
+ return 0;
461
+ }
462
+
463
+ /**
464
+ * 检查是否有品质上限事件
465
+ */
466
+ export function getQualityCap(profile: UserProfile): MonsterQuality | null {
467
+ for (const event of profile.activeEvents.currentEvents) {
468
+ if (event.effect.type === 'quality_cap' && event.duration.remaining > 0) {
469
+ return 'fine'; // quality_cap value=1 表示上限为 fine
470
+ }
471
+ }
472
+ return null;
473
+ }
474
+
475
+ /**
476
+ * 检查是否有禁止掉落事件
477
+ */
478
+ export function isDropSuppressed(profile: UserProfile): boolean {
479
+ return profile.activeEvents.currentEvents.some(
480
+ e => e.effect.type === 'no_drop' && e.duration.remaining > 0
481
+ );
482
+ }
483
+
484
+ /**
485
+ * 获取产品关联权重倍率修正
486
+ */
487
+ export function getProductWeightBoost(profile: UserProfile): number {
488
+ for (const event of profile.activeEvents.currentEvents) {
489
+ if (event.effect.type === 'product_weight' && event.duration.remaining > 0) {
490
+ return event.effect.value;
491
+ }
492
+ }
493
+ return 1;
494
+ }
495
+
496
+ // ============ 内部辅助 ============
497
+
498
+ function checkResolution(
499
+ resolution: EventResolution,
500
+ profile: UserProfile,
501
+ operationSuccess: boolean
502
+ ): boolean {
503
+ switch (resolution.type) {
504
+ case 'consecutive_success':
505
+ return profile.currentCombo >= (resolution.count ?? 3);
506
+
507
+ case 'use_treasure':
508
+ // 由 commands.ts 在使用法宝时检查并调用 resolveDisasterEvent
509
+ return false;
510
+
511
+ case 'complete_bounty':
512
+ // 由 bounty-system 在完成悬赏时检查
513
+ return false;
514
+
515
+ case 'trigger_encounter':
516
+ // 由 encounter-system 在触发机缘时检查
517
+ return false;
518
+
519
+ default:
520
+ return false;
521
+ }
522
+ }
523
+
524
+ /**
525
+ * 手动解除灾厄事件(由外部系统调用)
526
+ */
527
+ export function resolveDisasterEvent(
528
+ profile: UserProfile,
529
+ resolutionType: EventResolution['type']
530
+ ): RandomEvent | null {
531
+ const index = profile.activeEvents.currentEvents.findIndex(
532
+ e => e.category === 'disaster' && e.resolution?.type === resolutionType
533
+ );
534
+
535
+ if (index === -1) return null;
536
+
537
+ const event = profile.activeEvents.currentEvents[index];
538
+ profile.activeEvents.currentEvents.splice(index, 1);
539
+ profile.eventStats.disastersResolved += 1;
540
+ updateEventHistory(profile, event.id, 'resolved');
541
+ return event;
542
+ }
543
+
544
+ function updateChallengeProgress(
545
+ challenge: ChallengeEvent,
546
+ operationSuccess: boolean,
547
+ product: string,
548
+ capturedMonster: boolean
549
+ ): void {
550
+ const usedProducts = challenge.progress.usedProducts ?? [];
551
+
552
+ switch (challenge.challengeCondition.type) {
553
+ case 'consecutive_success':
554
+ if (operationSuccess) {
555
+ challenge.challengeCondition.current += 1;
556
+ } else {
557
+ challenge.challengeCondition.current = 0;
558
+ }
559
+ break;
560
+
561
+ case 'product_variety':
562
+ if (!usedProducts.includes(product)) {
563
+ usedProducts.push(product);
564
+ challenge.progress.usedProducts = usedProducts;
565
+ }
566
+ challenge.challengeCondition.current = usedProducts.length;
567
+ break;
568
+
569
+ case 'success_rate':
570
+ if (operationSuccess) {
571
+ challenge.challengeCondition.current += 1;
572
+ }
573
+ break;
574
+
575
+ case 'capture_count':
576
+ if (capturedMonster) {
577
+ challenge.challengeCondition.current += 1;
578
+ }
579
+ break;
580
+
581
+ case 'pick_correct':
582
+ // "真假美猴王":每次掉落随机判定是否选中"真"的(1/3 概率)
583
+ if (cryptoRandom() < 1 / 3) {
584
+ challenge.challengeCondition.current += 1;
585
+ }
586
+ break;
587
+ }
588
+
589
+ if (challenge.challengeCondition.current >= challenge.challengeCondition.target) {
590
+ challenge.progress.conditionMet = true;
591
+ }
592
+ }
593
+
594
+ function applyChallengeReward(profile: UserProfile, challenge: ChallengeEvent): void {
595
+ const reward = challenge.successReward;
596
+ profile.totalExp += reward.exp;
597
+
598
+ if (reward.pityBonus) {
599
+ profile.pityCounters.sinceLastRare += reward.pityBonus;
600
+ profile.pityCounters.sinceLastEpic += reward.pityBonus;
601
+ profile.pityCounters.sinceLastLegendary += reward.pityBonus;
602
+ }
603
+ }
604
+
605
+ function applyChallengePenalty(profile: UserProfile, challenge: ChallengeEvent): void {
606
+ const penalty = challenge.failurePenalty;
607
+ profile.totalExp = Math.max(0, profile.totalExp - penalty.expLoss);
608
+
609
+ if (penalty.comboReset) {
610
+ profile.currentCombo = 0;
611
+ }
612
+
613
+ if (penalty.pityLoss) {
614
+ profile.pityCounters.sinceLastRare = Math.max(0, profile.pityCounters.sinceLastRare - penalty.pityLoss);
615
+ profile.pityCounters.sinceLastEpic = Math.max(0, profile.pityCounters.sinceLastEpic - penalty.pityLoss);
616
+ profile.pityCounters.sinceLastLegendary = Math.max(0, profile.pityCounters.sinceLastLegendary - penalty.pityLoss);
617
+ }
618
+ }
619
+
620
+ function updateEventHistory(
621
+ profile: UserProfile,
622
+ eventId: string,
623
+ outcome: EventHistoryEntry['outcome']
624
+ ): void {
625
+ const entry = profile.eventHistory.find(
626
+ e => e.eventId === eventId && !e.outcome
627
+ );
628
+ if (entry) {
629
+ entry.outcome = outcome;
630
+ }
631
+ }
632
+
633
+ /**
634
+ * 获取所有事件定义(用于成就判定等)
635
+ */
636
+ export function getAllEventDefinitions(): RandomEvent[] {
637
+ return [
638
+ ...BLESSING_EVENTS,
639
+ ...DISASTER_EVENTS,
640
+ ];
641
+ }
642
+
643
+ /**
644
+ * 获取所有挑战事件定义
645
+ */
646
+ export function getAllChallengeDefinitions(): Array<Omit<ChallengeEvent, 'progress'>> {
647
+ return CHALLENGE_EVENTS_DATA;
648
+ }