koishi-plugin-rocom 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 (157) hide show
  1. package/lib/client.d.ts +53 -0
  2. package/lib/client.js +473 -0
  3. package/lib/commands/account.d.ts +5 -0
  4. package/lib/commands/account.js +205 -0
  5. package/lib/commands/admin.d.ts +2 -0
  6. package/lib/commands/admin.js +117 -0
  7. package/lib/commands/egg.d.ts +2 -0
  8. package/lib/commands/egg.js +196 -0
  9. package/lib/commands/merchant.d.ts +2 -0
  10. package/lib/commands/merchant.js +242 -0
  11. package/lib/commands/query.d.ts +2 -0
  12. package/lib/commands/query.js +1264 -0
  13. package/lib/commands/wiki.d.ts +2 -0
  14. package/lib/commands/wiki.js +11 -0
  15. package/lib/egg-service.d.ts +229 -0
  16. package/lib/egg-service.js +705 -0
  17. package/lib/index.d.ts +24 -0
  18. package/lib/index.js +3746 -0
  19. package/lib/render-templates/bind-list/index.html +51 -0
  20. package/lib/render-templates/bind-list/style.css +178 -0
  21. package/lib/render-templates/exchange-hall/css/_@astro-renderers.0KDkAyVb.css +1 -0
  22. package/lib/render-templates/exchange-hall/css/index.B3tv56V6.css +1 -0
  23. package/lib/render-templates/exchange-hall/css/index.D2LGPudy.css +1 -0
  24. package/lib/render-templates/exchange-hall/extracted.css +393 -0
  25. package/lib/render-templates/exchange-hall/index.html +99 -0
  26. package/lib/render-templates/exchange-hall/style.css +267 -0
  27. package/lib/render-templates/friendship/index.html +58 -0
  28. package/lib/render-templates/friendship/style.css +182 -0
  29. package/lib/render-templates/home/data/home_item_list.json +122 -0
  30. package/lib/render-templates/home/img/home_icon/100604.png +0 -0
  31. package/lib/render-templates/home/img/home_icon/100604_1.png +0 -0
  32. package/lib/render-templates/home/img/home_icon/100604_2.png +0 -0
  33. package/lib/render-templates/home/img/home_icon/100605.png +0 -0
  34. package/lib/render-templates/home/img/home_icon/100605_1.png +0 -0
  35. package/lib/render-templates/home/img/home_icon/100605_2.png +0 -0
  36. package/lib/render-templates/home/img/home_icon/100606.png +0 -0
  37. package/lib/render-templates/home/img/home_icon/100606_1.png +0 -0
  38. package/lib/render-templates/home/img/home_icon/100606_2.png +0 -0
  39. package/lib/render-templates/home/img/home_icon/100607.png +0 -0
  40. package/lib/render-templates/home/img/home_icon/100607_1.png +0 -0
  41. package/lib/render-templates/home/img/home_icon/100607_2.png +0 -0
  42. package/lib/render-templates/home/img/home_icon/100608.png +0 -0
  43. package/lib/render-templates/home/img/home_icon/100608_1.png +0 -0
  44. package/lib/render-templates/home/img/home_icon/100608_2.png +0 -0
  45. package/lib/render-templates/home/img/home_icon/100622.png +0 -0
  46. package/lib/render-templates/home/img/home_icon/100622_1.png +0 -0
  47. package/lib/render-templates/home/img/home_icon/100622_2.png +0 -0
  48. package/lib/render-templates/home/img/home_icon/100623.png +0 -0
  49. package/lib/render-templates/home/img/home_icon/100623_1.png +0 -0
  50. package/lib/render-templates/home/img/home_icon/100623_2.png +0 -0
  51. package/lib/render-templates/home/img/home_icon/100624.png +0 -0
  52. package/lib/render-templates/home/img/home_icon/100624_1.png +0 -0
  53. package/lib/render-templates/home/img/home_icon/100624_2.png +0 -0
  54. package/lib/render-templates/home/img/home_icon/100627.png +0 -0
  55. package/lib/render-templates/home/img/home_icon/100627_1.png +0 -0
  56. package/lib/render-templates/home/img/home_icon/100627_2.png +0 -0
  57. package/lib/render-templates/home/img/home_icon/100684.png +0 -0
  58. package/lib/render-templates/home/img/home_icon/100684_1.png +0 -0
  59. package/lib/render-templates/home/img/home_icon/100684_2.png +0 -0
  60. package/lib/render-templates/home/img/home_icon/100686.png +0 -0
  61. package/lib/render-templates/home/img/home_icon/100686_1.png +0 -0
  62. package/lib/render-templates/home/img/home_icon/100686_2.png +0 -0
  63. package/lib/render-templates/home/img/home_icon/100687.png +0 -0
  64. package/lib/render-templates/home/img/home_icon/100687_1.png +0 -0
  65. package/lib/render-templates/home/img/home_icon/100687_2.png +0 -0
  66. package/lib/render-templates/home/img/home_icon/100689.png +0 -0
  67. package/lib/render-templates/home/img/home_icon/100689_1.png +0 -0
  68. package/lib/render-templates/home/img/home_icon/100689_2.png +0 -0
  69. package/lib/render-templates/home/img/home_icon/100690.png +0 -0
  70. package/lib/render-templates/home/img/home_icon/100690_1.png +0 -0
  71. package/lib/render-templates/home/img/home_icon/100690_2.png +0 -0
  72. package/lib/render-templates/home/img/home_icon/100691.png +0 -0
  73. package/lib/render-templates/home/img/home_icon/100691_1.png +0 -0
  74. package/lib/render-templates/home/img/home_icon/100691_2.png +0 -0
  75. package/lib/render-templates/home/img/home_icon/100692.png +0 -0
  76. package/lib/render-templates/home/img/home_icon/100692_1.png +0 -0
  77. package/lib/render-templates/home/img/home_icon/100692_2.png +0 -0
  78. package/lib/render-templates/home/img/home_icon/100693.png +0 -0
  79. package/lib/render-templates/home/img/home_icon/100693_1.png +0 -0
  80. package/lib/render-templates/home/img/home_icon/100693_2.png +0 -0
  81. package/lib/render-templates/home/img/home_icon/100694.png +0 -0
  82. package/lib/render-templates/home/img/home_icon/100694_1.png +0 -0
  83. package/lib/render-templates/home/img/home_icon/100694_2.png +0 -0
  84. package/lib/render-templates/home/img/home_icon/100706.png +0 -0
  85. package/lib/render-templates/home/img/home_icon/100706_1.png +0 -0
  86. package/lib/render-templates/home/img/home_icon/100706_2.png +0 -0
  87. package/lib/render-templates/home/img/home_icon/100751.png +0 -0
  88. package/lib/render-templates/home/img/home_icon/100751_1.png +0 -0
  89. package/lib/render-templates/home/img/home_icon/100751_2.png +0 -0
  90. package/lib/render-templates/home/img/home_icon/100755.png +0 -0
  91. package/lib/render-templates/home/img/home_icon/100755_1.png +0 -0
  92. package/lib/render-templates/home/img/home_icon/100755_2.png +0 -0
  93. package/lib/render-templates/home/img/home_icon/100762.png +0 -0
  94. package/lib/render-templates/home/img/home_icon/100762_1.png +0 -0
  95. package/lib/render-templates/home/img/home_icon/100762_2.png +0 -0
  96. package/lib/render-templates/home/img/home_icon/100764.png +0 -0
  97. package/lib/render-templates/home/img/home_icon/100764_1.png +0 -0
  98. package/lib/render-templates/home/img/home_icon/100764_2.png +0 -0
  99. package/lib/render-templates/home/img/home_icon/100869.png +0 -0
  100. package/lib/render-templates/home/img/home_icon/100869_1.png +0 -0
  101. package/lib/render-templates/home/img/home_icon/100869_2.png +0 -0
  102. package/lib/render-templates/home/img/img_HomeVisit_Icon1.png +0 -0
  103. package/lib/render-templates/home/img/img_LevelReward_Bg2.png +0 -0
  104. package/lib/render-templates/home/index.html +139 -0
  105. package/lib/render-templates/home/style.css +537 -0
  106. package/lib/render-templates/ingame-shop/index.html +87 -0
  107. package/lib/render-templates/ingame-shop/style.css +220 -0
  108. package/lib/render-templates/inspect/index.html +47 -0
  109. package/lib/render-templates/inspect/style.css +149 -0
  110. package/lib/render-templates/lineup/index.html +77 -0
  111. package/lib/render-templates/lineup/style.css +255 -0
  112. package/lib/render-templates/lineup-detail/index.html +63 -0
  113. package/lib/render-templates/lineup-detail/style.css +218 -0
  114. package/lib/render-templates/menu/index.html +36 -0
  115. package/lib/render-templates/menu/style.css +126 -0
  116. package/lib/render-templates/package/index.html +115 -0
  117. package/lib/render-templates/package/style.css +352 -0
  118. package/lib/render-templates/personal-card/index.html +292 -0
  119. package/lib/render-templates/personal-card/style.css +2114 -0
  120. package/lib/render-templates/pet-wiki/index.html +118 -0
  121. package/lib/render-templates/pet-wiki/style.css +382 -0
  122. package/lib/render-templates/player-search/index.html +60 -0
  123. package/lib/render-templates/player-search/style.css +192 -0
  124. package/lib/render-templates/record/index.html +86 -0
  125. package/lib/render-templates/record/style.css +322 -0
  126. package/lib/render-templates/searcheggs/Pets.json +104328 -0
  127. package/lib/render-templates/searcheggs/candidates.html +52 -0
  128. package/lib/render-templates/searcheggs/eggs.py +599 -0
  129. package/lib/render-templates/searcheggs/index.html +198 -0
  130. package/lib/render-templates/searcheggs/pair.html +81 -0
  131. package/lib/render-templates/searcheggs/size.html +82 -0
  132. package/lib/render-templates/searcheggs/style.css +586 -0
  133. package/lib/render-templates/searcheggs/want.html +63 -0
  134. package/lib/render-templates/skill-wiki/index.html +68 -0
  135. package/lib/render-templates/skill-wiki/style.css +182 -0
  136. package/lib/render-templates/student/index.html +95 -0
  137. package/lib/render-templates/student/style.css +255 -0
  138. package/lib/render-templates/student-perks/index.html +78 -0
  139. package/lib/render-templates/student-perks/style.css +238 -0
  140. package/lib/render-templates/student-state/index.html +52 -0
  141. package/lib/render-templates/student-state/style.css +157 -0
  142. package/lib/render-templates/yuanxing-shangren/index.html +371 -0
  143. package/lib/render-templates/yuanxing-shangren/style.css +371 -0
  144. package/lib/render.d.ts +11 -0
  145. package/lib/render.js +226 -0
  146. package/lib/role-token.d.ts +27 -0
  147. package/lib/role-token.js +137 -0
  148. package/lib/send-image.d.ts +3 -0
  149. package/lib/send-image.js +135 -0
  150. package/lib/subscription-send.d.ts +8 -0
  151. package/lib/subscription-send.js +48 -0
  152. package/lib/types.d.ts +32 -0
  153. package/lib/types.js +2 -0
  154. package/lib/user.d.ts +67 -0
  155. package/lib/user.js +176 -0
  156. package/package.json +58 -0
  157. package/readme.md +575 -0
@@ -0,0 +1,705 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EggService = exports.EGG_GROUP_META = void 0;
7
+ exports.getEggGroupLabel = getEggGroupLabel;
8
+ exports.formatEggGroups = formatEggGroups;
9
+ const koishi_1 = require("koishi");
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const logger = new koishi_1.Logger('rocom-egg');
13
+ exports.EGG_GROUP_META = {
14
+ 1: { label: '未发现', desc: '不能和任何精灵生蛋' },
15
+ 2: { label: '怪兽', desc: '像怪兽一样的动物' },
16
+ 3: { label: '两栖', desc: '两栖动物和水边生活的多栖动物' },
17
+ 4: { label: '虫', desc: '看起来像虫子的精灵' },
18
+ 5: { label: '飞行', desc: '会飞的精灵' },
19
+ 6: { label: '陆上', desc: '生活在陆地上的精灵' },
20
+ 7: { label: '妖精', desc: '可爱的小动物' },
21
+ 8: { label: '植物', desc: '看起来像植物的精灵' },
22
+ 9: { label: '人型', desc: '看起来像人的精灵' },
23
+ 10: { label: '软体', desc: '看起来软软的精灵' },
24
+ 11: { label: '矿物', desc: '身体由矿物组成的精灵' },
25
+ 12: { label: '不定形', desc: '没有固定形态的精灵' },
26
+ 13: { label: '鱼', desc: '看起来像鱼的精灵' },
27
+ 14: { label: '龙', desc: '看起来像龙的精灵' },
28
+ 15: { label: '机械', desc: '身体由机械组成的精灵' },
29
+ };
30
+ function getEggGroupLabel(id) {
31
+ return exports.EGG_GROUP_META[id]?.label || `蛋组${id}`;
32
+ }
33
+ function formatEggGroups(ids) {
34
+ if (!ids?.length)
35
+ return '暂无蛋组数据';
36
+ return ids.map(getEggGroupLabel).join(' / ');
37
+ }
38
+ function petName(p) {
39
+ return p?.localized?.zh?.name || p?.name || '???';
40
+ }
41
+ function petType(p) {
42
+ const parts = [];
43
+ const mt = p?.main_type?.localized?.zh;
44
+ if (mt)
45
+ parts.push(mt);
46
+ const st = p?.sub_type?.localized?.zh;
47
+ if (st)
48
+ parts.push(st);
49
+ return parts.join(' / ') || '未知';
50
+ }
51
+ function fmtDur(s) {
52
+ if (!s || s <= 0)
53
+ return '暂无数据';
54
+ if (s % 86400 === 0)
55
+ return `${s / 86400} 天`;
56
+ const h = s / 3600;
57
+ return h === Math.floor(h) ? `${h} 小时` : `${h.toFixed(1)} 小时`;
58
+ }
59
+ function wt(v) {
60
+ return v != null ? Math.round(v / 1000 * 10) / 10 : null;
61
+ }
62
+ function ht(v) {
63
+ return v != null ? Math.round(v) / 100 : null;
64
+ }
65
+ function fmtRange(lo, hi, u) {
66
+ if (lo == null && hi == null)
67
+ return '暂无数据';
68
+ if (lo != null && hi != null)
69
+ return lo === hi ? `${lo}${u}` : `${lo}-${hi}${u}`;
70
+ return `${lo ?? hi}${u}`;
71
+ }
72
+ function num(value) {
73
+ const parsed = Number(value);
74
+ return Number.isFinite(parsed) ? parsed : null;
75
+ }
76
+ function formatNumber(value, digits = 2) {
77
+ const parsed = num(value);
78
+ if (parsed == null)
79
+ return '';
80
+ return String(Number(parsed.toFixed(digits)));
81
+ }
82
+ function assetPetId(petId) {
83
+ const n = Number(petId);
84
+ if (isNaN(n))
85
+ return null;
86
+ return n >= 3000 ? n : n + 3000;
87
+ }
88
+ function petIconUrl(petId) {
89
+ const aid = assetPetId(petId);
90
+ return aid ? `https://game.gtimg.cn/images/rocom/rocodata/jingling/${aid}/icon.png` : '';
91
+ }
92
+ function petImageUrl(petId) {
93
+ const aid = assetPetId(petId);
94
+ return aid ? `https://game.gtimg.cn/images/rocom/rocodata/jingling/${aid}/image.png` : '';
95
+ }
96
+ class EggService {
97
+ pets = [];
98
+ byId = new Map();
99
+ byZh = new Map();
100
+ byEn = new Map();
101
+ constructor(dataDir) {
102
+ this.load(dataDir);
103
+ }
104
+ load(dataDir) {
105
+ const filePath = node_path_1.default.join(dataDir, 'Pets.json');
106
+ if (!node_fs_1.default.existsSync(filePath)) {
107
+ logger.error(`Pets.json 不存在: ${filePath}`);
108
+ return;
109
+ }
110
+ try {
111
+ const raw = JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf-8'));
112
+ this.pets = Array.isArray(raw) ? raw : [];
113
+ for (const p of this.pets) {
114
+ this.byId.set(p.id, p);
115
+ const zh = p.localized?.zh?.name;
116
+ if (zh)
117
+ this.byZh.set(zh, p);
118
+ const en = p.name?.toLowerCase();
119
+ if (en)
120
+ this.byEn.set(en, p);
121
+ }
122
+ logger.info(`加载 ${this.pets.length} 只精灵`);
123
+ }
124
+ catch (e) {
125
+ logger.error(`加载 Pets.json 失败: ${e}`);
126
+ }
127
+ }
128
+ getEggGroups(pet) {
129
+ return pet?.breeding_profile?.egg_groups || [];
130
+ }
131
+ search(keyword) {
132
+ const kw = keyword.trim();
133
+ if (!kw)
134
+ return { matchType: 'not_found' };
135
+ if (this.byZh.has(kw))
136
+ return { matchType: 'exact', pet: this.byZh.get(kw) };
137
+ const pid = Number(kw);
138
+ if (!isNaN(pid) && this.byId.has(pid))
139
+ return { matchType: 'exact', pet: this.byId.get(pid) };
140
+ if (this.byEn.has(kw.toLowerCase()))
141
+ return { matchType: 'exact', pet: this.byEn.get(kw.toLowerCase()) };
142
+ const kwLower = kw.toLowerCase();
143
+ const hits = this.pets.filter(p => {
144
+ const zh = p.localized?.zh?.name || '';
145
+ const en = p.name || '';
146
+ return zh.toLowerCase().includes(kwLower) || en.toLowerCase().includes(kwLower);
147
+ });
148
+ if (hits.length === 1)
149
+ return { matchType: 'fuzzy', pet: hits[0] };
150
+ if (hits.length > 1)
151
+ return { matchType: 'multi', candidates: hits.slice(0, 20) };
152
+ return { matchType: 'not_found' };
153
+ }
154
+ searchBySize(height, weight) {
155
+ const perfect = [], range = [];
156
+ for (const p of this.pets) {
157
+ const br = p.breeding || {};
158
+ let hMatch = null, wMatch = null;
159
+ if (height != null) {
160
+ const hLo = br.height_low, hHi = br.height_high;
161
+ if (hLo != null && hHi != null) {
162
+ if (hLo <= height && height <= hHi)
163
+ hMatch = 'perfect';
164
+ else if (hLo * 0.85 <= height && height <= hHi * 1.15)
165
+ hMatch = 'range';
166
+ else
167
+ hMatch = 'none';
168
+ }
169
+ else
170
+ hMatch = 'none';
171
+ }
172
+ if (weight != null) {
173
+ const wLo = br.weight_low, wHi = br.weight_high;
174
+ if (wLo != null && wHi != null) {
175
+ const wKgLo = wLo / 1000, wKgHi = wHi / 1000;
176
+ if (wKgLo <= weight && weight <= wKgHi)
177
+ wMatch = 'perfect';
178
+ else if (wKgLo * 0.85 <= weight && weight <= wKgHi * 1.15)
179
+ wMatch = 'range';
180
+ else
181
+ wMatch = 'none';
182
+ }
183
+ else
184
+ wMatch = 'none';
185
+ }
186
+ if (height != null && weight != null) {
187
+ if (hMatch === 'perfect' && wMatch === 'perfect')
188
+ perfect.push(p);
189
+ else if (hMatch !== 'none' && wMatch !== 'none')
190
+ range.push(p);
191
+ }
192
+ else if (height != null) {
193
+ if (hMatch === 'perfect')
194
+ perfect.push(p);
195
+ else if (hMatch === 'range')
196
+ range.push(p);
197
+ }
198
+ else if (weight != null) {
199
+ if (wMatch === 'perfect')
200
+ perfect.push(p);
201
+ else if (wMatch === 'range')
202
+ range.push(p);
203
+ }
204
+ }
205
+ return { perfect: perfect.slice(0, 20), range: range.slice(0, 20) };
206
+ }
207
+ formatMatchSummary(probability, matchCount) {
208
+ const parts = [];
209
+ if (probability != null)
210
+ parts.push(`匹配率 ${formatNumber(probability)}%`);
211
+ if (matchCount != null)
212
+ parts.push(`命中次数 ${formatNumber(matchCount, 0)}`);
213
+ return parts.join(' / ');
214
+ }
215
+ calcLocalMatchInfo(queryHeight, queryWeight, heightMin, heightMax, weightMin, weightMax) {
216
+ const scores = [];
217
+ if (queryHeight != null) {
218
+ const score = this.rangeMatchScore(ht(queryHeight), heightMin, heightMax);
219
+ if (score != null)
220
+ scores.push(score);
221
+ }
222
+ if (queryWeight != null) {
223
+ const score = this.rangeMatchScore(queryWeight, weightMin, weightMax);
224
+ if (score != null)
225
+ scores.push(score);
226
+ }
227
+ if (!scores.length)
228
+ return { probability: null, matchCount: null };
229
+ return {
230
+ probability: scores.reduce((sum, score) => sum + score, 0) / scores.length,
231
+ matchCount: scores.length,
232
+ };
233
+ }
234
+ rangeMatchScore(value, low, high) {
235
+ const valueNum = num(value);
236
+ const lowNum = num(low);
237
+ const highNum = num(high);
238
+ if (valueNum == null || lowNum == null || highNum == null)
239
+ return null;
240
+ if (lowNum <= valueNum && valueNum <= highNum)
241
+ return 100;
242
+ let tolerance;
243
+ let distance;
244
+ if (valueNum < lowNum) {
245
+ tolerance = Math.max(lowNum * 0.15, 0.0001);
246
+ distance = lowNum - valueNum;
247
+ }
248
+ else {
249
+ tolerance = Math.max(highNum * 0.15, 0.0001);
250
+ distance = valueNum - highNum;
251
+ }
252
+ if (distance > tolerance)
253
+ return 0;
254
+ return Math.max(0, 100 * (1 - distance / tolerance));
255
+ }
256
+ formatPetCard(pet, queryHeight, queryWeight) {
257
+ const br = pet?.breeding || {};
258
+ const eggGroups = this.getEggGroups(pet);
259
+ const heightMin = ht(br.height_low);
260
+ const heightMax = ht(br.height_high);
261
+ const weightMin = wt(br.weight_low);
262
+ const weightMax = wt(br.weight_high);
263
+ const { probability, matchCount } = this.calcLocalMatchInfo(queryHeight, queryWeight, heightMin, heightMax, weightMin, weightMax);
264
+ return {
265
+ id: pet?.id,
266
+ name: petName(pet),
267
+ icon: petIconUrl(pet?.id),
268
+ image: petImageUrl(pet?.id),
269
+ type_label: petType(pet),
270
+ egg_group_ids: eggGroups,
271
+ egg_groups_label: formatEggGroups(eggGroups),
272
+ height_min: heightMin,
273
+ height_max: heightMax,
274
+ height_label: fmtRange(heightMin, heightMax, 'm'),
275
+ weight_min: weightMin,
276
+ weight_max: weightMax,
277
+ weight_label: fmtRange(weightMin, weightMax, 'kg'),
278
+ probability,
279
+ match_count: matchCount,
280
+ match_info_label: this.formatMatchSummary(probability, matchCount),
281
+ };
282
+ }
283
+ formatSizeApiCard(item) {
284
+ const probability = num(item?.probability);
285
+ const matchCount = num(item?.matchCount);
286
+ const heightMin = num(item?.diameterMin);
287
+ const heightMax = num(item?.diameterMax);
288
+ const weightMin = num(item?.weightMin);
289
+ const weightMax = num(item?.weightMax);
290
+ return {
291
+ id: item?.petId || '-',
292
+ name: item?.pet || '未知精灵',
293
+ icon: item?.petIcon || petIconUrl(item?.petId),
294
+ image: item?.petImage || petImageUrl(item?.petId),
295
+ type_label: '后端未提供',
296
+ egg_group_ids: [],
297
+ egg_groups_label: '后端未提供',
298
+ height_min: heightMin,
299
+ height_max: heightMax,
300
+ height_label: fmtRange(heightMin, heightMax, 'm'),
301
+ weight_min: weightMin,
302
+ weight_max: weightMax,
303
+ weight_label: fmtRange(weightMin, weightMax, 'kg'),
304
+ probability,
305
+ match_count: matchCount,
306
+ match_info_label: this.formatMatchSummary(probability, matchCount),
307
+ };
308
+ }
309
+ mergeCardsByName(perfect, ranged) {
310
+ const perfectMap = new Map();
311
+ const rangedMap = new Map();
312
+ const keyOf = (item) => String(item?.name || item?.id || '').replace(/\s+/g, '');
313
+ const add = (target, item) => {
314
+ const key = keyOf(item);
315
+ if (!key)
316
+ return;
317
+ target.set(key, target.has(key) ? this.mergeSizeCard(target.get(key), item) : item);
318
+ };
319
+ for (const item of perfect)
320
+ add(perfectMap, item);
321
+ for (const item of ranged) {
322
+ const key = keyOf(item);
323
+ if (key && perfectMap.has(key)) {
324
+ perfectMap.set(key, this.mergeSizeCard(perfectMap.get(key), item));
325
+ }
326
+ else {
327
+ add(rangedMap, item);
328
+ }
329
+ }
330
+ return [[...perfectMap.values()], [...rangedMap.values()]];
331
+ }
332
+ mergeSizeCard(left, right) {
333
+ const unique = (values) => [...new Set(values.filter(value => value != null && value !== '').map(value => String(value)))];
334
+ const ids = unique([...String(left?.id || '').split('/'), ...String(right?.id || '').split('/')]
335
+ .map(value => value.trim().replace(/^#/, '')));
336
+ const eggGroupIds = unique([...(left?.egg_group_ids || []), ...(right?.egg_group_ids || [])]).map(Number);
337
+ const probabilityValues = [num(left?.probability), num(right?.probability)].filter(value => value != null);
338
+ const matchCountValues = [num(left?.match_count), num(right?.match_count)].filter(value => value != null);
339
+ const heightMin = this.minValue(left?.height_min, right?.height_min);
340
+ const heightMax = this.maxValue(left?.height_max, right?.height_max);
341
+ const weightMin = this.minValue(left?.weight_min, right?.weight_min);
342
+ const weightMax = this.maxValue(left?.weight_max, right?.weight_max);
343
+ const probability = probabilityValues.length ? probabilityValues.reduce((sum, value) => sum + value, 0) : null;
344
+ const matchCount = matchCountValues.length ? matchCountValues.reduce((sum, value) => sum + value, 0) : null;
345
+ return {
346
+ ...left,
347
+ id: ids.join('/'),
348
+ egg_group_ids: eggGroupIds,
349
+ egg_groups_label: eggGroupIds.length ? formatEggGroups(eggGroupIds) : unique([left?.egg_groups_label, right?.egg_groups_label]).join(' / ') || left?.egg_groups_label,
350
+ probability,
351
+ match_count: matchCount,
352
+ match_info_label: this.formatMatchSummary(probability, matchCount),
353
+ height_min: heightMin,
354
+ height_max: heightMax,
355
+ height_label: fmtRange(heightMin, heightMax, 'm'),
356
+ weight_min: weightMin,
357
+ weight_max: weightMax,
358
+ weight_label: fmtRange(weightMin, weightMax, 'kg'),
359
+ };
360
+ }
361
+ minValue(...values) {
362
+ const numbers = values.map(num).filter(value => value != null);
363
+ return numbers.length ? Math.min(...numbers) : null;
364
+ }
365
+ maxValue(...values) {
366
+ const numbers = values.map(num).filter(value => value != null);
367
+ return numbers.length ? Math.max(...numbers) : null;
368
+ }
369
+ getCompatiblePets(pet) {
370
+ const groups = new Set(this.getEggGroups(pet));
371
+ if (!groups.size || groups.has(1))
372
+ return [];
373
+ return this.pets.filter(o => {
374
+ if (o.id === pet.id)
375
+ return false;
376
+ const og = new Set(this.getEggGroups(o));
377
+ if (!og.size || og.has(1))
378
+ return false;
379
+ for (const g of groups)
380
+ if (og.has(g))
381
+ return true;
382
+ return false;
383
+ });
384
+ }
385
+ getBreedingParents(pet) {
386
+ const eggGroups = new Set(this.getEggGroups(pet));
387
+ if (!eggGroups.size || eggGroups.has(1))
388
+ return [];
389
+ return this.pets.filter(o => {
390
+ if (o.id === pet.id)
391
+ return false;
392
+ const og = new Set(this.getEggGroups(o));
393
+ if (!og.size || og.has(1))
394
+ return false;
395
+ for (const g of eggGroups)
396
+ if (og.has(g))
397
+ return true;
398
+ return false;
399
+ });
400
+ }
401
+ evaluatePair(a, b) {
402
+ const ga = new Set(this.getEggGroups(a)), gb = new Set(this.getEggGroups(b));
403
+ const shared = [...ga].filter(g => gb.has(g)).sort();
404
+ const reasons = [];
405
+ if (!ga.size)
406
+ reasons.push(`${petName(a)} 暂无蛋组数据`);
407
+ if (!gb.size)
408
+ reasons.push(`${petName(b)} 暂无蛋组数据`);
409
+ if (ga.has(1))
410
+ reasons.push(`${petName(a)} 属于「未发现」蛋组`);
411
+ if (gb.has(1))
412
+ reasons.push(`${petName(b)} 属于「未发现」蛋组`);
413
+ if (!shared.length && !reasons.length)
414
+ reasons.push('蛋组不相同,无法配种');
415
+ const br = a.breeding || {};
416
+ return {
417
+ compatible: !reasons.length && shared.length > 0,
418
+ reasons,
419
+ shared_egg_groups: shared,
420
+ shared_egg_group_labels: shared.map(getEggGroupLabel),
421
+ hatch_label: fmtDur(br.hatch_data),
422
+ weight_label: fmtRange(wt(br.weight_low), wt(br.weight_high), 'kg'),
423
+ height_label: fmtRange(br.height_low, br.height_high, 'cm'),
424
+ };
425
+ }
426
+ // ─── 文本构建 ───
427
+ buildSizeSearchText(height, weight, results, heightDisplay) {
428
+ const cond = [];
429
+ if (height != null)
430
+ cond.push(`身高=${heightDisplay || fmtRange(ht(height), ht(height), 'm')}`);
431
+ if (weight != null)
432
+ cond.push(`体重=${weight}kg`);
433
+ const condStr = cond.join(' + ');
434
+ if (!results || (!results.perfect.length && !results.range.length))
435
+ return `❌ 未找到符合 ${condStr} 的精灵。`;
436
+ const lines = [];
437
+ if (results.perfect.length) {
438
+ lines.push(`✅ 完美匹配 ${condStr} 的精灵(共 ${results.perfect.length} 只):`);
439
+ results.perfect.slice(0, 10).forEach((p, i) => {
440
+ const br = p.breeding || {};
441
+ lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${fmtRange(ht(br.height_low), ht(br.height_high), 'm')} / ${fmtRange(wt(br.weight_low), wt(br.weight_high), 'kg')} · ${formatEggGroups(this.getEggGroups(p))}`);
442
+ });
443
+ }
444
+ if (results.range.length) {
445
+ if (lines.length)
446
+ lines.push('');
447
+ lines.push(`🔍 范围匹配 ${condStr} 的精灵(共 ${results.range.length} 只,容差±15%):`);
448
+ results.range.slice(0, 10).forEach((p, i) => {
449
+ const br = p.breeding || {};
450
+ lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${fmtRange(ht(br.height_low), ht(br.height_high), 'm')} / ${fmtRange(wt(br.weight_low), wt(br.weight_high), 'kg')} · ${formatEggGroups(this.getEggGroups(p))}`);
451
+ });
452
+ }
453
+ return lines.join('\n');
454
+ }
455
+ buildSizeSearchTextFromApi(height, weight, results, heightDisplay) {
456
+ const cond = [];
457
+ if (height != null)
458
+ cond.push(`身高=${heightDisplay || fmtRange(ht(height), ht(height), 'm')}`);
459
+ if (weight != null)
460
+ cond.push(`体重=${weight}kg`);
461
+ const condStr = cond.join(' + ') || '当前条件';
462
+ const exact = results?.exactResults || [];
463
+ const candidates = results?.candidates || [];
464
+ if (!exact.length && !candidates.length)
465
+ return `❌ 未找到符合 ${condStr} 的精灵。`;
466
+ const lines = [];
467
+ if (exact.length) {
468
+ lines.push(`✅ 完美匹配 ${condStr} 的精灵(共 ${exact.length} 只):`);
469
+ exact.slice(0, 10).forEach((item, i) => {
470
+ const card = this.formatSizeApiCard(item);
471
+ lines.push(` ${i + 1}. ${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label}`);
472
+ });
473
+ }
474
+ if (candidates.length) {
475
+ if (lines.length)
476
+ lines.push('');
477
+ lines.push(`🔍 范围匹配 ${condStr} 的精灵(共 ${candidates.length} 只):`);
478
+ candidates.slice(0, 10).forEach((item, i) => {
479
+ const card = this.formatSizeApiCard(item);
480
+ lines.push(` ${i + 1}. ${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label}`);
481
+ });
482
+ }
483
+ return lines.join('\n');
484
+ }
485
+ buildSearchText(pet) {
486
+ const egs = this.getEggGroups(pet);
487
+ const compat = this.getCompatiblePets(pet);
488
+ const lines = [
489
+ `🥚 ${petName(pet)} (#${pet.id})`,
490
+ `属性:${petType(pet)}`,
491
+ `蛋组:${formatEggGroups(egs)}`,
492
+ `可配种精灵数:${compat.length}`,
493
+ ];
494
+ if (egs.includes(1))
495
+ lines.push('⚠️ 该精灵属于「未发现」蛋组,无法配种。');
496
+ return lines.join('\n');
497
+ }
498
+ buildCandidatesText(keyword, candidates) {
499
+ const lines = [`🔍 「${keyword}」匹配到 ${candidates.length} 只精灵,请精确输入:`];
500
+ candidates.slice(0, 10).forEach((p, i) => {
501
+ lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${petType(p)} · ${formatEggGroups(this.getEggGroups(p))}`);
502
+ });
503
+ if (candidates.length > 10)
504
+ lines.push(` ... 还有 ${candidates.length - 10} 个结果`);
505
+ return lines.join('\n');
506
+ }
507
+ buildWantPetText(pet) {
508
+ const zh = petName(pet);
509
+ const egs = this.getEggGroups(pet);
510
+ const lines = [`🥚 想要孵出「${zh}」:`, `蛋组:${formatEggGroups(egs)}`];
511
+ if (egs.includes(1)) {
512
+ lines.push('⚠️ 该精灵属于「未发现」蛋组,无法通过配种获得。');
513
+ return lines.join('\n');
514
+ }
515
+ lines.push(`\n📌 母体必须是「${zh}」(孵蛋结果跟随母体)`);
516
+ const fathers = this.getBreedingParents(pet);
517
+ if (fathers.length) {
518
+ lines.push(`\n🔗 可选父体(共 ${fathers.length} 只):`);
519
+ fathers.slice(0, 15).forEach((f, i) => {
520
+ lines.push(` ${i + 1}. ${petName(f)} — ${formatEggGroups(this.getEggGroups(f))}`);
521
+ });
522
+ if (fathers.length > 15)
523
+ lines.push(` ... 还有 ${fathers.length - 15} 只`);
524
+ }
525
+ else {
526
+ lines.push('\n❌ 未找到可配种的父体精灵。');
527
+ }
528
+ return lines.join('\n');
529
+ }
530
+ buildPairText(a, b) {
531
+ const ev = this.evaluatePair(a, b);
532
+ const ma = petName(a), fa = petName(b);
533
+ if (ev.compatible) {
534
+ return `✅ 父体 ${fa} × 母体 ${ma} 可以配种!\n共享蛋组:${ev.shared_egg_group_labels.join(' / ')}\n孵出结果:${ma}(跟随母体)\n孵化时长:${ev.hatch_label}`;
535
+ }
536
+ return `❌ ${fa} × ${ma} 无法配种。\n原因:${ev.reasons.join(';')}`;
537
+ }
538
+ // ─── 渲染数据构建 ───
539
+ buildSearchData(pet) {
540
+ const egs = this.getEggGroups(pet);
541
+ const compat = this.getCompatiblePets(pet);
542
+ const gmap = {};
543
+ for (const gid of egs)
544
+ if (gid !== 1)
545
+ gmap[gid] = [];
546
+ for (const c of compat) {
547
+ for (const gid of egs) {
548
+ if (gid !== 1 && this.getEggGroups(c).includes(gid)) {
549
+ gmap[gid] = gmap[gid] || [];
550
+ gmap[gid].push(c);
551
+ }
552
+ }
553
+ }
554
+ const sections = egs.map(gid => {
555
+ const meta = exports.EGG_GROUP_META[gid];
556
+ const members = gmap[gid] || [];
557
+ return {
558
+ id: gid,
559
+ label: meta?.label || `蛋组${gid}`,
560
+ desc: meta?.desc || '',
561
+ count: members.length,
562
+ members: members.slice(0, 30).map(m => ({
563
+ name: petName(m), id: m.id,
564
+ type_label: petType(m),
565
+ egg_groups_label: formatEggGroups(this.getEggGroups(m)),
566
+ })),
567
+ has_more: members.length > 30,
568
+ total: members.length,
569
+ };
570
+ });
571
+ const br = pet.breeding || {};
572
+ const bp = pet.breeding_profile || {};
573
+ const eggDetails = this.buildEggDetails(br);
574
+ return {
575
+ pet_name: petName(pet), pet_id: pet.id,
576
+ pet_icon: petIconUrl(pet.id), pet_image: petImageUrl(pet.id),
577
+ type_label: petType(pet),
578
+ egg_groups_label: formatEggGroups(egs),
579
+ egg_groups: egs,
580
+ egg_group_labels: Object.fromEntries(egs.map(gid => [gid, getEggGroupLabel(gid)])),
581
+ male_rate: bp.male_rate ?? null,
582
+ female_rate: bp.female_rate ?? null,
583
+ hatch_label: fmtDur(br.hatch_data),
584
+ weight_label: fmtRange(wt(br.weight_low), wt(br.weight_high), 'kg'),
585
+ height_label: fmtRange(br.height_low, br.height_high, 'cm'),
586
+ total_compatible: compat.length,
587
+ is_undiscovered: egs.includes(1),
588
+ egg_group_sections: sections,
589
+ total_stats: ['base_hp', 'base_phy_atk', 'base_mag_atk', 'base_phy_def', 'base_mag_def', 'base_spd']
590
+ .reduce((sum, k) => sum + (pet[k] || 0), 0),
591
+ egg_details: eggDetails,
592
+ commandHint: '洛克查蛋 <名称> | 洛克查蛋 身高25 体重1.5 | 洛克配种 <父> <母>',
593
+ copyright: 'Koishi & WeGame 洛克王国插件',
594
+ };
595
+ }
596
+ buildPairData(a, b) {
597
+ const ev = this.evaluatePair(a, b);
598
+ const makePetCard = (p) => ({
599
+ name: petName(p), id: p.id,
600
+ type_label: petType(p),
601
+ egg_groups_label: formatEggGroups(this.getEggGroups(p)),
602
+ });
603
+ return {
604
+ mother: makePetCard(a),
605
+ father: makePetCard(b),
606
+ ...ev,
607
+ commandHint: '默认前父后母,孵蛋结果跟随母体 | 洛克配种 <精灵名> 查怎么孵',
608
+ copyright: 'Koishi & WeGame 洛克王国插件',
609
+ };
610
+ }
611
+ buildWantPetData(pet) {
612
+ const fathers = this.getBreedingParents(pet);
613
+ const bp = pet.breeding_profile || {};
614
+ const eggGroups = this.getEggGroups(pet);
615
+ return {
616
+ target: this.formatPetCard(pet),
617
+ egg_groups_label: formatEggGroups(eggGroups),
618
+ female_rate: bp.female_rate ?? null,
619
+ male_rate: bp.male_rate ?? null,
620
+ is_undiscovered: eggGroups.includes(1),
621
+ fathers: fathers.slice(0, 30).map(p => this.formatPetCard(p)),
622
+ father_count: fathers.length,
623
+ commandHint: '洛克配种 <父体> <母体> 查看详细结果',
624
+ copyright: 'Koishi & WeGame 洛克王国插件',
625
+ };
626
+ }
627
+ buildCandidatesRenderData(keyword, candidates) {
628
+ return {
629
+ keyword,
630
+ count: candidates.length,
631
+ candidates: candidates.map(p => this.formatPetCard(p)),
632
+ commandHint: '请使用更精确的名称重新查询',
633
+ copyright: 'Koishi & WeGame 洛克王国插件',
634
+ };
635
+ }
636
+ buildSizeSearchData(height, weight, results, heightDisplay) {
637
+ const conditions = [];
638
+ if (height != null)
639
+ conditions.push(`身高 ${heightDisplay || fmtRange(ht(height), ht(height), 'm')}`);
640
+ if (weight != null)
641
+ conditions.push(`体重 ${weight} kg`);
642
+ const [perfect, ranged] = this.mergeCardsByName((results?.perfect || []).map(p => this.formatPetCard(p, height, weight)), (results?.range || []).map(p => this.formatPetCard(p, height, weight)));
643
+ return {
644
+ query_label: conditions.join(' / ') || '尺寸反查',
645
+ perfect_matches: perfect,
646
+ range_matches: ranged,
647
+ total_count: perfect.length + ranged.length,
648
+ has_results: !!(perfect.length || ranged.length),
649
+ commandHint: '洛克查蛋 <精灵名> | 洛克查蛋 身高25 体重1.5',
650
+ copyright: 'Koishi & WeGame 洛克王国插件',
651
+ };
652
+ }
653
+ buildSizeSearchDataFromApi(height, weight, results, heightDisplay) {
654
+ const conditions = [];
655
+ if (height != null)
656
+ conditions.push(`身高 ${heightDisplay || fmtRange(ht(height), ht(height), 'm')}`);
657
+ if (weight != null)
658
+ conditions.push(`体重 ${weight} kg`);
659
+ const [perfect, ranged] = this.mergeCardsByName((results?.exactResults || []).map((item) => this.formatSizeApiCard(item)), (results?.candidates || []).map((item) => this.formatSizeApiCard(item)));
660
+ const searchMode = results?.searchMode || '';
661
+ const queryLabel = `${conditions.join(' / ') || '尺寸反查'}${searchMode ? ` · 模式 ${searchMode}` : ''}`;
662
+ return {
663
+ query_label: queryLabel,
664
+ perfect_matches: perfect,
665
+ range_matches: ranged,
666
+ total_count: perfect.length + ranged.length,
667
+ has_results: !!(perfect.length || ranged.length),
668
+ commandHint: '洛克查蛋 <精灵名> | 洛克查蛋 0.18m 1.5kg | 洛克查蛋 0.18',
669
+ copyright: 'Koishi & WeGame 洛克王国插件',
670
+ };
671
+ }
672
+ buildEggDetails(breeding) {
673
+ if (!breeding)
674
+ return { has_data: false };
675
+ const baseProb = breeding.egg_base_glass_prob_array;
676
+ const addProb = breeding.egg_add_glass_prob_array;
677
+ const preciousMap = {
678
+ 1: '迪莫蛋', 2: '星辰蛋', 3: '彩虹蛋', 4: '梦幻蛋', 5: '传说蛋', 6: '神秘蛋', 7: '特殊蛋',
679
+ };
680
+ const variants = (breeding.variants || []).map((v) => ({
681
+ id: v.id, name: v.name || '',
682
+ hatch_label: fmtDur(v.hatch_data),
683
+ weight_label: fmtRange(wt(v.weight_low), wt(v.weight_high), 'kg'),
684
+ height_label: fmtRange(v.height_low, v.height_high, 'cm'),
685
+ precious_egg_type: v.precious_egg_type,
686
+ precious_egg_label: preciousMap[v.precious_egg_type] || '普通蛋',
687
+ base_prob_str: v.egg_base_glass_prob_array?.length === 2
688
+ ? `${v.egg_base_glass_prob_array[0]}/${v.egg_base_glass_prob_array[1]}` : '暂无',
689
+ }));
690
+ return {
691
+ has_data: true,
692
+ base_prob_str: baseProb?.length === 2 ? `${baseProb[0]}/${baseProb[1]}` : '暂无数据',
693
+ base_prob_pct: baseProb?.length === 2 ? (baseProb[0] / baseProb[1]) * 100 : null,
694
+ add_prob_str: addProb?.length === 2 ? `${addProb[0]}/${addProb[1]}` : '暂无数据',
695
+ add_prob_pct: addProb?.length === 2 ? (addProb[0] / addProb[1]) * 100 : null,
696
+ is_contact_add_glass: breeding.is_contact_add_glass_prob,
697
+ is_contact_add_shining: breeding.is_contact_add_shining_prob,
698
+ precious_egg_type: breeding.precious_egg_type,
699
+ precious_egg_label: preciousMap[breeding.precious_egg_type] || '普通蛋',
700
+ variants,
701
+ variant_count: variants.length,
702
+ };
703
+ }
704
+ }
705
+ exports.EggService = EggService;