@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.
- package/CHANGELOG.md +686 -0
- package/LICENSE +21 -0
- package/README.en.md +181 -0
- package/README.md +221 -0
- package/bin/dingtalk-connector.js +858 -0
- package/bin/wizard-config.mjs +110 -0
- package/dist/accounts-BAzdqkAV.mjs +268 -0
- package/dist/accounts-BQptOmgB.mjs +2 -0
- package/dist/chunk-upload-BBQgGtcZ.mjs +193 -0
- package/dist/chunk-upload-DaLXXZH3.mjs +2 -0
- package/dist/common-C8pYKU_y.mjs +2 -0
- package/dist/common-Dt9n6fQN.mjs +101 -0
- package/dist/connection-DHHFFNQJ.mjs +423 -0
- package/dist/entry-bundled.d.mts +16 -0
- package/dist/entry-bundled.mjs +31 -0
- package/dist/game-xiyou-CqHt-6Q1.mjs +4271 -0
- package/dist/gateway-methods-C4tcgI7P.mjs +771 -0
- package/dist/gateway-methods-Ci31A3vg.mjs +2 -0
- package/dist/http-client-CpnJHB89.mjs +2 -0
- package/dist/http-client-DFWZgO1n.mjs +33 -0
- package/dist/index.d.mts +193 -0
- package/dist/index.mjs +45 -0
- package/dist/logger-BmJkQkm1.mjs +2 -0
- package/dist/logger-mZ9OSbmD.mjs +58 -0
- package/dist/media-C_SVin7s.mjs +2 -0
- package/dist/media-cz72EVS3.mjs +509 -0
- package/dist/message-handler-DESzFFDc.mjs +1971 -0
- package/dist/messaging-B6l1sRvX.mjs +1044 -0
- package/dist/runtime-DUgpo5zC.mjs +1422 -0
- package/dist/session-DJ4jYqPv.mjs +114 -0
- package/dist/utils-Bjh4r_qS.mjs +4 -0
- package/dist/utils-CIfI_3Jh.mjs +63 -0
- package/dist/utils-legacy-CALCPP1t.mjs +230 -0
- package/dist/utils-legacy-CFYDBM4r.mjs +3 -0
- package/docs/DEAP_AGENT_GUIDE.en.md +115 -0
- package/docs/DEAP_AGENT_GUIDE.md +115 -0
- package/docs/DINGTALK_MANUAL_SETUP.md +50 -0
- package/docs/MULTI_AGENT_SETUP.md +306 -0
- package/docs/RELEASE_NOTES_V0.7.10.md +40 -0
- package/docs/RELEASE_NOTES_V0.7.2.md +143 -0
- package/docs/RELEASE_NOTES_V0.7.3.md +149 -0
- package/docs/RELEASE_NOTES_V0.7.4.md +206 -0
- package/docs/RELEASE_NOTES_V0.7.5.md +267 -0
- package/docs/RELEASE_NOTES_V0.7.6.md +219 -0
- package/docs/RELEASE_NOTES_V0.7.7.md +122 -0
- package/docs/RELEASE_NOTES_V0.7.8.md +101 -0
- package/docs/RELEASE_NOTES_V0.7.9.md +65 -0
- package/docs/RELEASE_NOTES_V0.8.0.md +53 -0
- package/docs/RELEASE_NOTES_V0.8.1.md +47 -0
- package/docs/RELEASE_NOTES_V0.8.10.md +49 -0
- package/docs/RELEASE_NOTES_V0.8.11.md +51 -0
- package/docs/RELEASE_NOTES_V0.8.12.md +63 -0
- package/docs/RELEASE_NOTES_V0.8.13-beta.0.md +69 -0
- package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
- package/docs/RELEASE_NOTES_V0.8.14.md +86 -0
- package/docs/RELEASE_NOTES_V0.8.16.md +40 -0
- package/docs/RELEASE_NOTES_V0.8.17.md +87 -0
- package/docs/RELEASE_NOTES_V0.8.18.md +64 -0
- package/docs/RELEASE_NOTES_V0.8.19.md +62 -0
- package/docs/RELEASE_NOTES_V0.8.2.md +55 -0
- package/docs/RELEASE_NOTES_V0.8.20.md +49 -0
- package/docs/RELEASE_NOTES_V0.8.3.md +63 -0
- package/docs/RELEASE_NOTES_V0.8.4.md +45 -0
- package/docs/RELEASE_NOTES_V0.8.7.md +49 -0
- package/docs/RELEASE_NOTES_V0.8.8.md +63 -0
- package/docs/RELEASE_NOTES_V0.8.9.md +81 -0
- package/docs/RELEASE_NOTES_v0.7.0.md +142 -0
- package/docs/RELEASE_NOTES_v0.7.1.md +74 -0
- package/docs/TROUBLESHOOTING.md +122 -0
- package/index.ts +77 -0
- package/openclaw.plugin.json +551 -0
- package/package.json +147 -0
- package/skills/dingtalk-channel-rules/SKILL.md +91 -0
- package/skills/dingtalk-troubleshoot/SKILL.md +93 -0
- package/skills/dws-cli/SKILL.md +129 -0
- package/skills/dws-cli/references/error-codes.md +95 -0
- package/skills/dws-cli/references/field-rules.md +105 -0
- package/skills/dws-cli/references/global-reference.md +104 -0
- package/skills/dws-cli/references/intent-guide.md +114 -0
- package/skills/dws-cli/references/products/aitable.md +452 -0
- package/skills/dws-cli/references/products/attendance.md +93 -0
- package/skills/dws-cli/references/products/calendar.md +217 -0
- package/skills/dws-cli/references/products/chat.md +292 -0
- package/skills/dws-cli/references/products/contact.md +108 -0
- package/skills/dws-cli/references/products/ding.md +57 -0
- package/skills/dws-cli/references/products/report.md +162 -0
- package/skills/dws-cli/references/products/simple.md +128 -0
- package/skills/dws-cli/references/products/todo.md +138 -0
- package/skills/dws-cli/references/products/workbench.md +39 -0
- package/skills/dws-cli/references/recovery-guide.md +94 -0
- package/src/channel.ts +588 -0
- package/src/config/accounts.ts +242 -0
- package/src/config/schema.ts +180 -0
- package/src/core/connection.ts +741 -0
- package/src/core/message-handler.ts +1788 -0
- package/src/core/provider.ts +111 -0
- package/src/core/state.ts +54 -0
- package/src/device-auth-config.ts +14 -0
- package/src/device-auth.ts +197 -0
- package/src/directory.ts +95 -0
- package/src/docs.ts +293 -0
- package/src/game-xiyou/achievement-engine.ts +252 -0
- package/src/game-xiyou/bounty-system.ts +315 -0
- package/src/game-xiyou/commands.ts +223 -0
- package/src/game-xiyou/drop-engine.ts +241 -0
- package/src/game-xiyou/encounter-system.ts +135 -0
- package/src/game-xiyou/escape-engine.ts +164 -0
- package/src/game-xiyou/exp-calculator.ts +139 -0
- package/src/game-xiyou/index.ts +479 -0
- package/src/game-xiyou/level-system.ts +91 -0
- package/src/game-xiyou/monster-pool.ts +180 -0
- package/src/game-xiyou/pity-counter.ts +114 -0
- package/src/game-xiyou/random-event-engine.ts +648 -0
- package/src/game-xiyou/renderer.ts +679 -0
- package/src/game-xiyou/storage.ts +218 -0
- package/src/game-xiyou/treasure-system.ts +105 -0
- package/src/game-xiyou/types.ts +582 -0
- package/src/game-xiyou/uid-resolver.ts +49 -0
- package/src/gateway-methods.ts +740 -0
- package/src/onboarding.ts +553 -0
- package/src/policy.ts +32 -0
- package/src/probe.ts +210 -0
- package/src/reply-dispatcher.ts +874 -0
- package/src/runtime.ts +32 -0
- package/src/sdk/helpers.ts +322 -0
- package/src/sdk/types.ts +519 -0
- package/src/secret-input.ts +19 -0
- package/src/services/media/audio.ts +54 -0
- package/src/services/media/chunk-upload.ts +296 -0
- package/src/services/media/common.ts +155 -0
- package/src/services/media/file.ts +75 -0
- package/src/services/media/image.ts +81 -0
- package/src/services/media/index.ts +10 -0
- package/src/services/media/video.ts +162 -0
- package/src/services/media.ts +1143 -0
- package/src/services/messaging/card.ts +604 -0
- package/src/services/messaging/index.ts +18 -0
- package/src/services/messaging/mentions.ts +267 -0
- package/src/services/messaging/send.ts +141 -0
- package/src/services/messaging.ts +1191 -0
- package/src/services/reply-markers.ts +55 -0
- package/src/targets.ts +45 -0
- package/src/types/index.ts +59 -0
- package/src/types/pdf-parse.d.ts +3 -0
- package/src/utils/agent.ts +63 -0
- package/src/utils/async.ts +51 -0
- package/src/utils/constants.ts +27 -0
- package/src/utils/http-client.ts +38 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +78 -0
- package/src/utils/session.ts +147 -0
- package/src/utils/token.ts +93 -0
- package/src/utils/utils-legacy.ts +454 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 西游妖魔榜养成系统 · 入口
|
|
3
|
+
*
|
|
4
|
+
* GamificationEngine 是养成系统的门面类,统一协调所有子系统。
|
|
5
|
+
* 对外暴露两个核心方法:
|
|
6
|
+
* - onDwsCommandResult(): 每次 dws CLI 命令执行后调用(成功或失败)
|
|
7
|
+
* - handleCommand(): 处理聊天命令(/修行 /图鉴 等)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import { resolveUid, getShortUid } from './uid-resolver.ts';
|
|
12
|
+
import { loadProfile, saveProfile, loadCollection, saveCollection, loadHistory, saveHistory } from './storage.ts';
|
|
13
|
+
import { calculateExp, updateSignInStatus } from './exp-calculator.ts';
|
|
14
|
+
import { checkLevelUp, applyLevelUp } from './level-system.ts';
|
|
15
|
+
import { executeDrop } from './drop-engine.ts';
|
|
16
|
+
import { checkEncounter, applyEncounterEffects } from './encounter-system.ts';
|
|
17
|
+
import { checkAchievements, triggerSpecialAchievement } from './achievement-engine.ts';
|
|
18
|
+
import { resolveEscape } from './escape-engine.ts';
|
|
19
|
+
import {
|
|
20
|
+
generateDailyBounties, updateBountyProgress, checkBountyDayReset,
|
|
21
|
+
type BountyUpdateContext,
|
|
22
|
+
} from './bounty-system.ts';
|
|
23
|
+
import {
|
|
24
|
+
checkRandomEvent, tickActiveEvents, tickDropEvents,
|
|
25
|
+
getActiveExpMultiplier, resolveDisasterEvent,
|
|
26
|
+
} from './random-event-engine.ts';
|
|
27
|
+
import {
|
|
28
|
+
renderDropResult, renderLevelUp, renderEncounter,
|
|
29
|
+
renderNewAchievements, renderBountyComplete, renderEventTrigger,
|
|
30
|
+
renderChallengeResult, renderDisasterResolved,
|
|
31
|
+
} from './renderer.ts';
|
|
32
|
+
import { isGamificationCommand, handleGamificationCommand } from './commands.ts';
|
|
33
|
+
import { getMonsterById } from './monster-pool.ts';
|
|
34
|
+
import type {
|
|
35
|
+
UserProfile, UserCollection, UserHistory,
|
|
36
|
+
GamificationOutput, HistoryRecord, CollectionEntry,
|
|
37
|
+
DropResult, Achievement, Bounty, RandomEvent, ChallengeEvent,
|
|
38
|
+
MonsterQuality,
|
|
39
|
+
} from './types.ts';
|
|
40
|
+
|
|
41
|
+
// ============ 单例 ============
|
|
42
|
+
|
|
43
|
+
let engineInstance: GamificationEngine | null = null;
|
|
44
|
+
|
|
45
|
+
export class GamificationEngine {
|
|
46
|
+
private profile: UserProfile;
|
|
47
|
+
private collection: UserCollection;
|
|
48
|
+
private history: UserHistory;
|
|
49
|
+
private uidHash: string;
|
|
50
|
+
|
|
51
|
+
private constructor(senderId?: string) {
|
|
52
|
+
this.uidHash = resolveUid(senderId);
|
|
53
|
+
this.profile = loadProfile(this.uidHash);
|
|
54
|
+
this.collection = loadCollection(this.uidHash);
|
|
55
|
+
this.history = loadHistory(this.uidHash);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 获取或创建引擎实例
|
|
60
|
+
*/
|
|
61
|
+
static getInstance(senderId?: string): GamificationEngine {
|
|
62
|
+
const uidHash = resolveUid(senderId);
|
|
63
|
+
|
|
64
|
+
// 如果 senderId 变了(不同用户),重新创建实例
|
|
65
|
+
if (!engineInstance || engineInstance.uidHash !== uidHash) {
|
|
66
|
+
engineInstance = new GamificationEngine(senderId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return engineInstance;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 强制重新加载数据(用于多用户场景)
|
|
74
|
+
*/
|
|
75
|
+
static getInstanceForUser(senderId: string): GamificationEngine {
|
|
76
|
+
return new GamificationEngine(senderId);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 检查养成系统是否启用
|
|
81
|
+
*/
|
|
82
|
+
isEnabled(): boolean {
|
|
83
|
+
return this.profile.settings.enabled;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 检查消息是否是养成系统命令
|
|
88
|
+
*/
|
|
89
|
+
isCommand(text: string): boolean {
|
|
90
|
+
return isGamificationCommand(text);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 处理聊天命令,返回 Markdown 响应
|
|
95
|
+
*/
|
|
96
|
+
handleCommand(text: string): string | null {
|
|
97
|
+
return handleGamificationCommand(
|
|
98
|
+
text,
|
|
99
|
+
this.profile,
|
|
100
|
+
this.collection,
|
|
101
|
+
() => this.save()
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* dws CLI 命令执行后调用(核心方法)
|
|
107
|
+
*
|
|
108
|
+
* v2: 集成逃跑机制、悬赏令、随机事件系统
|
|
109
|
+
*
|
|
110
|
+
* @param product - dws 产品名(如 "aitable"、"calendar")
|
|
111
|
+
* @param success - 命令是否成功
|
|
112
|
+
* @param commandStr - 原始命令字符串(用于生成 hash)
|
|
113
|
+
* @param isRecovery - 是否为 recovery 成功
|
|
114
|
+
* @returns Markdown 字符串(追加到 agent 回复末尾),或空字符串
|
|
115
|
+
*/
|
|
116
|
+
onDwsCommandResult(
|
|
117
|
+
product: string,
|
|
118
|
+
success: boolean,
|
|
119
|
+
commandStr: string = '',
|
|
120
|
+
isRecovery: boolean = false
|
|
121
|
+
): string {
|
|
122
|
+
if (!this.isEnabled()) return '';
|
|
123
|
+
|
|
124
|
+
const commandHash = createHash('sha256').update(commandStr).digest('hex').slice(0, 16);
|
|
125
|
+
|
|
126
|
+
// v2: 每日悬赏令刷新 & 连续全清检查
|
|
127
|
+
checkBountyDayReset(this.profile);
|
|
128
|
+
generateDailyBounties(this.profile);
|
|
129
|
+
|
|
130
|
+
// 处理失败情况
|
|
131
|
+
if (!success) {
|
|
132
|
+
this.profile.currentCombo = 0;
|
|
133
|
+
this.profile.consecutiveFailures += 1;
|
|
134
|
+
|
|
135
|
+
// v2: 失败也要更新挑战事件进度
|
|
136
|
+
const eventResults = tickActiveEvents(this.profile, false, product, false);
|
|
137
|
+
|
|
138
|
+
this.save();
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ===== 以下为成功执行的处理 =====
|
|
143
|
+
|
|
144
|
+
// 1. 更新基础统计
|
|
145
|
+
this.profile.totalOperations += 1;
|
|
146
|
+
this.profile.currentCombo += 1;
|
|
147
|
+
if (this.profile.currentCombo > this.profile.maxCombo) {
|
|
148
|
+
this.profile.maxCombo = this.profile.currentCombo;
|
|
149
|
+
}
|
|
150
|
+
this.profile.productUsage[product] = (this.profile.productUsage[product] ?? 0) + 1;
|
|
151
|
+
|
|
152
|
+
if (isRecovery) {
|
|
153
|
+
this.profile.totalRecoveries += 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 2. 更新签到状态
|
|
157
|
+
updateSignInStatus(this.profile);
|
|
158
|
+
|
|
159
|
+
// 3. 计算修行值
|
|
160
|
+
const expResult = calculateExp(product, this.profile);
|
|
161
|
+
|
|
162
|
+
// v2: 应用事件修行值倍率(蟠桃大会 ×3 / 紧箍咒发作 ×0.5)
|
|
163
|
+
const eventExpMultiplier = getActiveExpMultiplier(this.profile);
|
|
164
|
+
expResult.totalExp = Math.floor(expResult.totalExp * eventExpMultiplier);
|
|
165
|
+
|
|
166
|
+
// 4. 检查升级
|
|
167
|
+
const levelUp = checkLevelUp(this.profile, expResult.totalExp);
|
|
168
|
+
|
|
169
|
+
// 5. 应用修行值
|
|
170
|
+
this.profile.totalExp += expResult.totalExp;
|
|
171
|
+
applyLevelUp(this.profile);
|
|
172
|
+
|
|
173
|
+
// 6. 执行掉落
|
|
174
|
+
let dropResult = executeDrop(product, this.profile, this.collection);
|
|
175
|
+
dropResult.expGained = expResult.totalExp;
|
|
176
|
+
|
|
177
|
+
// v2: 逃跑判定(仅对有效掉落)
|
|
178
|
+
if (dropResult.monster.id) {
|
|
179
|
+
dropResult = resolveEscape(dropResult, this.profile, product);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// v2: 递减 drop_count 类型事件
|
|
183
|
+
if (dropResult.monster.id) {
|
|
184
|
+
tickDropEvents(this.profile);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 7. 更新图鉴(v2: 仅在未逃跑时更新)
|
|
188
|
+
if (dropResult.monster.id && !dropResult.escaped) {
|
|
189
|
+
this.updateCollection(dropResult.monster.id, dropResult.isShiny, commandHash);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 逃跑时给安慰奖修行值
|
|
193
|
+
if (dropResult.escaped) {
|
|
194
|
+
this.profile.totalExp += 2;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 8. 检查机缘
|
|
198
|
+
const encounter = checkEncounter(this.profile);
|
|
199
|
+
if (encounter) {
|
|
200
|
+
applyEncounterEffects(this.profile, encounter);
|
|
201
|
+
|
|
202
|
+
// v2: 机缘可以化解灾厄事件
|
|
203
|
+
const resolvedEvent = resolveDisasterEvent(this.profile, 'trigger_encounter');
|
|
204
|
+
if (resolvedEvent) {
|
|
205
|
+
// 渲染时会展示化解通知
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// v2: 触发随机事件
|
|
210
|
+
const triggeredEvent = checkRandomEvent(this.profile);
|
|
211
|
+
|
|
212
|
+
// v2: 处理即时事件效果
|
|
213
|
+
let extraDropResult: DropResult | null = null;
|
|
214
|
+
if (triggeredEvent) {
|
|
215
|
+
// EV003 龙宫寻宝:额外掉落
|
|
216
|
+
if (triggeredEvent.effect.type === 'extra_drop') {
|
|
217
|
+
extraDropResult = executeDrop(product, this.profile, this.collection);
|
|
218
|
+
if (extraDropResult.monster.id && !extraDropResult.escaped) {
|
|
219
|
+
this.updateCollection(extraDropResult.monster.id, extraDropResult.isShiny, commandHash);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// EV202 金蝉脱壳:移除一只已收服的精良妖怪
|
|
224
|
+
if (triggeredEvent.id === 'EV202') {
|
|
225
|
+
const fineEntries = this.collection.entries.filter(e => {
|
|
226
|
+
const monster = getMonsterById(e.monsterId);
|
|
227
|
+
return monster?.quality === 'fine' && !e.isShiny;
|
|
228
|
+
});
|
|
229
|
+
if (fineEntries.length > 0) {
|
|
230
|
+
const randomIndex = Math.floor(Math.random() * fineEntries.length);
|
|
231
|
+
const removedEntry = fineEntries[randomIndex];
|
|
232
|
+
this.collection.entries = this.collection.entries.filter(e => e !== removedEntry);
|
|
233
|
+
// 接下来 10 次内再次遇到该妖怪概率 ×5(通过 escapeHistory 追踪)
|
|
234
|
+
this.profile.escapeHistory[removedEntry.monsterId] = 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// v2: 更新活跃事件持续时间
|
|
240
|
+
const capturedMonster = dropResult.monster.id !== '' && !dropResult.escaped;
|
|
241
|
+
const eventResults = tickActiveEvents(this.profile, true, product, capturedMonster);
|
|
242
|
+
|
|
243
|
+
// v2: 检查"否极泰来"成就(灾厄结束后立即触发增益)
|
|
244
|
+
const hasDisasterExpired = eventResults.some(
|
|
245
|
+
r => (r.event.category === 'disaster') && (r.outcome === 'expired' || r.outcome === 'resolved')
|
|
246
|
+
);
|
|
247
|
+
if (hasDisasterExpired && triggeredEvent?.category === 'blessing') {
|
|
248
|
+
const blessingAchievement = triggerSpecialAchievement(this.profile, 'A412');
|
|
249
|
+
// 成就奖励在下面统一处理
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 9. 获取今日记录用于成就判定
|
|
253
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
254
|
+
const todayRecords = this.history.records.filter(r => {
|
|
255
|
+
const recordDate = new Date(r.timestamp).toISOString().slice(0, 10);
|
|
256
|
+
return recordDate === today;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// v2: 构建悬赏令更新上下文
|
|
260
|
+
const todayProducts = new Set<string>();
|
|
261
|
+
const todayQualities = new Set<MonsterQuality>();
|
|
262
|
+
for (const record of todayRecords) {
|
|
263
|
+
if (record.success) todayProducts.add(record.product);
|
|
264
|
+
if (record.monsterId && !record.escaped) {
|
|
265
|
+
const monster = getMonsterById(record.monsterId);
|
|
266
|
+
if (monster) todayQualities.add(monster.quality);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
todayProducts.add(product);
|
|
270
|
+
if (capturedMonster) todayQualities.add(dropResult.monster.quality);
|
|
271
|
+
|
|
272
|
+
const bountyContext: BountyUpdateContext = {
|
|
273
|
+
commandSuccess: true,
|
|
274
|
+
product,
|
|
275
|
+
dropResult: capturedMonster ? dropResult : undefined,
|
|
276
|
+
encounterTriggered: encounter !== null,
|
|
277
|
+
encounterType: encounter?.type,
|
|
278
|
+
currentCombo: this.profile.currentCombo,
|
|
279
|
+
todayProducts,
|
|
280
|
+
todayQualities,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// v2: 更新悬赏令进度
|
|
284
|
+
const completedBounties = updateBountyProgress(this.profile, bountyContext);
|
|
285
|
+
|
|
286
|
+
// v2: 完成悬赏令可以化解灾厄事件
|
|
287
|
+
if (completedBounties.length > 0) {
|
|
288
|
+
resolveDisasterEvent(this.profile, 'complete_bounty');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 10. 检查成就
|
|
292
|
+
const newAchievements = checkAchievements(this.profile, this.collection, todayRecords);
|
|
293
|
+
|
|
294
|
+
// 特殊成就:保底触发
|
|
295
|
+
if (dropResult.isPityTriggered) {
|
|
296
|
+
const pityAchievement = triggerSpecialAchievement(this.profile, 'A302');
|
|
297
|
+
if (pityAchievement) {
|
|
298
|
+
newAchievements.push(pityAchievement);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 特殊成就:屡败屡战
|
|
303
|
+
if (this.profile.consecutiveFailures >= 10) {
|
|
304
|
+
const failAchievement = triggerSpecialAchievement(this.profile, 'A303');
|
|
305
|
+
if (failAchievement) {
|
|
306
|
+
newAchievements.push(failAchievement);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// v2 特殊成就:走火入魔后存活
|
|
311
|
+
if (triggeredEvent?.id === 'EV206' && this.profile.totalExp > 0) {
|
|
312
|
+
const madnessAchievement = triggerSpecialAchievement(this.profile, 'A408');
|
|
313
|
+
if (madnessAchievement) {
|
|
314
|
+
newAchievements.push(madnessAchievement);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 成就修行值奖励
|
|
319
|
+
for (const achievement of newAchievements) {
|
|
320
|
+
this.profile.totalExp += achievement.expReward;
|
|
321
|
+
}
|
|
322
|
+
applyLevelUp(this.profile);
|
|
323
|
+
|
|
324
|
+
// 重置连续失败计数
|
|
325
|
+
this.profile.consecutiveFailures = 0;
|
|
326
|
+
|
|
327
|
+
// 11. 记录历史
|
|
328
|
+
const historyRecord: HistoryRecord = {
|
|
329
|
+
timestamp: Date.now(),
|
|
330
|
+
product,
|
|
331
|
+
commandHash,
|
|
332
|
+
success: true,
|
|
333
|
+
expGained: expResult.totalExp,
|
|
334
|
+
monsterId: dropResult.monster.id || undefined,
|
|
335
|
+
isShiny: dropResult.isShiny || undefined,
|
|
336
|
+
escaped: dropResult.escaped || undefined,
|
|
337
|
+
encounterId: encounter?.immortalId,
|
|
338
|
+
achievementIds: newAchievements.length > 0 ? newAchievements.map(a => a.id) : undefined,
|
|
339
|
+
eventId: triggeredEvent?.id,
|
|
340
|
+
completedBountyIds: completedBounties.length > 0 ? completedBounties.map(b => b.id) : undefined,
|
|
341
|
+
};
|
|
342
|
+
this.history.records.push(historyRecord);
|
|
343
|
+
|
|
344
|
+
// 12. 持久化
|
|
345
|
+
this.save();
|
|
346
|
+
|
|
347
|
+
// 13. 渲染输出
|
|
348
|
+
return this.renderOutput(
|
|
349
|
+
dropResult, expResult, levelUp, encounter, newAchievements,
|
|
350
|
+
completedBounties, triggeredEvent ?? null, eventResults
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 更新图鉴
|
|
356
|
+
*/
|
|
357
|
+
private updateCollection(monsterId: string, isShiny: boolean, commandHash: string): void {
|
|
358
|
+
const existingEntry = this.collection.entries.find(
|
|
359
|
+
e => e.monsterId === monsterId && e.isShiny === isShiny
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
if (existingEntry) {
|
|
363
|
+
existingEntry.captureCount += 1;
|
|
364
|
+
} else {
|
|
365
|
+
const newEntry: CollectionEntry = {
|
|
366
|
+
monsterId,
|
|
367
|
+
firstCapturedAt: Date.now(),
|
|
368
|
+
captureCount: 1,
|
|
369
|
+
isShiny,
|
|
370
|
+
};
|
|
371
|
+
this.collection.entries.push(newEntry);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 渲染完整输出(追加到 agent 回复末尾的 Markdown)
|
|
377
|
+
*
|
|
378
|
+
* v3: 紧凑化 — 降妖结果 + 附属事件合并为双列表格,大幅减少纵向空间
|
|
379
|
+
*
|
|
380
|
+
* 布局策略:
|
|
381
|
+
* - 只有降妖结果时:直接单行输出
|
|
382
|
+
* - 降妖 + 附属事件时:用表格 左列=降妖 | 右列=附属事件列表
|
|
383
|
+
* - 只有附属事件时:紧凑列表输出
|
|
384
|
+
*/
|
|
385
|
+
private renderOutput(
|
|
386
|
+
dropResult: DropResult,
|
|
387
|
+
expResult: any,
|
|
388
|
+
levelUp: any,
|
|
389
|
+
encounter: any,
|
|
390
|
+
newAchievements: Achievement[],
|
|
391
|
+
completedBounties: Bounty[],
|
|
392
|
+
triggeredEvent: RandomEvent | ChallengeEvent | null,
|
|
393
|
+
eventResults: Array<{ event: RandomEvent | ChallengeEvent; outcome: string }>
|
|
394
|
+
): string {
|
|
395
|
+
// 收集降妖结果(左列)
|
|
396
|
+
const shouldShowDrop = dropResult.monster.id && !(
|
|
397
|
+
this.profile.settings.muteNormalDrops &&
|
|
398
|
+
dropResult.monster.quality === 'normal' &&
|
|
399
|
+
!dropResult.isNew &&
|
|
400
|
+
!dropResult.isShiny &&
|
|
401
|
+
!dropResult.escaped
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
const dropLine = shouldShowDrop
|
|
405
|
+
? renderDropResult(dropResult, expResult, this.collection).trim()
|
|
406
|
+
: '';
|
|
407
|
+
|
|
408
|
+
// 收集附属事件(右列)
|
|
409
|
+
const sideEvents: string[] = [];
|
|
410
|
+
|
|
411
|
+
if (levelUp) {
|
|
412
|
+
sideEvents.push(renderLevelUp(levelUp).trim());
|
|
413
|
+
}
|
|
414
|
+
if (encounter) {
|
|
415
|
+
sideEvents.push(renderEncounter(encounter).trim());
|
|
416
|
+
}
|
|
417
|
+
if (triggeredEvent) {
|
|
418
|
+
sideEvents.push(renderEventTrigger(triggeredEvent).trim());
|
|
419
|
+
}
|
|
420
|
+
for (const result of eventResults) {
|
|
421
|
+
if (result.event.category === 'challenge') {
|
|
422
|
+
if (result.outcome === 'success' || result.outcome === 'failure') {
|
|
423
|
+
sideEvents.push(renderChallengeResult(
|
|
424
|
+
result.event as ChallengeEvent,
|
|
425
|
+
result.outcome === 'success'
|
|
426
|
+
).trim());
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (result.event.category === 'disaster' && result.outcome === 'resolved') {
|
|
430
|
+
sideEvents.push(renderDisasterResolved(result.event).trim());
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
for (const bounty of completedBounties) {
|
|
434
|
+
sideEvents.push(renderBountyComplete(bounty).trim());
|
|
435
|
+
}
|
|
436
|
+
if (newAchievements.length > 0) {
|
|
437
|
+
sideEvents.push(renderNewAchievements(newAchievements).trim());
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 组装输出
|
|
441
|
+
if (!dropLine && sideEvents.length === 0) {
|
|
442
|
+
return '';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 有降妖 + 有附属事件:用表格合并展示
|
|
446
|
+
if (dropLine && sideEvents.length > 0) {
|
|
447
|
+
const sideContent = sideEvents.join(' · ');
|
|
448
|
+
return [
|
|
449
|
+
'',
|
|
450
|
+
'---',
|
|
451
|
+
`| 🗡️ 降妖 | 📋 事件 |`,
|
|
452
|
+
`|---|---|`,
|
|
453
|
+
`| ${dropLine} | ${sideContent} |`,
|
|
454
|
+
].join('\n');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// 只有降妖结果
|
|
458
|
+
if (dropLine) {
|
|
459
|
+
return `\n---\n${dropLine}`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 只有附属事件
|
|
463
|
+
return `\n---\n${sideEvents.join('\n')}`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* 持久化所有数据
|
|
468
|
+
*/
|
|
469
|
+
private save(): void {
|
|
470
|
+
saveProfile(this.profile);
|
|
471
|
+
saveCollection(this.collection);
|
|
472
|
+
saveHistory(this.history);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ============ 便捷导出 ============
|
|
477
|
+
|
|
478
|
+
export { isGamificationCommand } from './commands.ts';
|
|
479
|
+
export type { GamificationOutput } from './types.ts';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 等级判定与升级事件
|
|
3
|
+
*
|
|
4
|
+
* 根据累计修行值判定当前等级,检测升级事件。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { UserProfile, LevelUpResult, LevelDefinition } from './types.ts';
|
|
8
|
+
import { LEVEL_DEFINITIONS } from './types.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 根据累计修行值计算当前等级
|
|
12
|
+
*/
|
|
13
|
+
export function calculateLevel(totalExp: number): LevelDefinition {
|
|
14
|
+
let currentLevel = LEVEL_DEFINITIONS[0];
|
|
15
|
+
for (const levelDef of LEVEL_DEFINITIONS) {
|
|
16
|
+
if (totalExp >= levelDef.requiredExp) {
|
|
17
|
+
currentLevel = levelDef;
|
|
18
|
+
} else {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return currentLevel;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 获取下一级的定义(如果已满级则返回 null)
|
|
27
|
+
*/
|
|
28
|
+
export function getNextLevel(currentLevel: number): LevelDefinition | null {
|
|
29
|
+
const nextIndex = LEVEL_DEFINITIONS.findIndex(l => l.level === currentLevel) + 1;
|
|
30
|
+
if (nextIndex >= LEVEL_DEFINITIONS.length) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return LEVEL_DEFINITIONS[nextIndex];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 计算距离下一级还需要多少修行值
|
|
38
|
+
*/
|
|
39
|
+
export function getExpToNextLevel(totalExp: number): number | null {
|
|
40
|
+
const currentLevel = calculateLevel(totalExp);
|
|
41
|
+
const nextLevel = getNextLevel(currentLevel.level);
|
|
42
|
+
if (!nextLevel) {
|
|
43
|
+
return null; // 已满级
|
|
44
|
+
}
|
|
45
|
+
return nextLevel.requiredExp - totalExp;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 获取当前等级的进度百分比
|
|
50
|
+
*/
|
|
51
|
+
export function getLevelProgress(totalExp: number): number {
|
|
52
|
+
const currentLevel = calculateLevel(totalExp);
|
|
53
|
+
const nextLevel = getNextLevel(currentLevel.level);
|
|
54
|
+
|
|
55
|
+
if (!nextLevel) {
|
|
56
|
+
return 100; // 已满级
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const levelRange = nextLevel.requiredExp - currentLevel.requiredExp;
|
|
60
|
+
const currentProgress = totalExp - currentLevel.requiredExp;
|
|
61
|
+
return Math.floor((currentProgress / levelRange) * 100);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 检查是否发生升级,返回升级结果
|
|
66
|
+
*/
|
|
67
|
+
export function checkLevelUp(profile: UserProfile, expGained: number): LevelUpResult | null {
|
|
68
|
+
const previousLevel = calculateLevel(profile.totalExp);
|
|
69
|
+
const newLevel = calculateLevel(profile.totalExp + expGained);
|
|
70
|
+
|
|
71
|
+
if (newLevel.level <= previousLevel.level) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
previousLevel: previousLevel.level,
|
|
77
|
+
previousTitle: previousLevel.title,
|
|
78
|
+
newLevel: newLevel.level,
|
|
79
|
+
newTitle: newLevel.title,
|
|
80
|
+
unlockDescription: newLevel.unlockDescription,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 应用升级到 profile
|
|
86
|
+
*/
|
|
87
|
+
export function applyLevelUp(profile: UserProfile): void {
|
|
88
|
+
const levelDef = calculateLevel(profile.totalExp);
|
|
89
|
+
profile.level = levelDef.level;
|
|
90
|
+
profile.title = levelDef.title;
|
|
91
|
+
}
|