@umang-boss/claudemon 2.1.2 → 2.2.0
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/dist/award-xp.mjs +129 -58
- package/dist/award-xp.mjs.map +3 -3
- package/dist/server.mjs +492 -99
- package/dist/server.mjs.map +2 -2
- package/package.json +1 -1
- package/src/engine/encounter-pool.ts +51 -6
- package/src/engine/encounters.ts +122 -128
- package/src/gamification/legendary-quests.ts +515 -91
- package/src/hooks/award-xp.ts +3 -5
- package/statusline/buddy-status.sh +15 -11
|
@@ -1,36 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Legendary quest chain definitions for Claudemon.
|
|
3
3
|
* Each legendary Pokemon requires completing a multi-step quest
|
|
4
|
-
* tied to sustained coding activity.
|
|
4
|
+
* tied to sustained coding activity. Designed for corporate developers —
|
|
5
|
+
* tough but achievable with consistent daily work.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import type { AchievementCondition, LegendaryQuest, PlayerState } from "../engine/types.js";
|
|
9
|
+
import { POKEMON_BY_ID } from "../engine/pokemon-data.js";
|
|
8
10
|
import { isConditionMet } from "./achievements.js";
|
|
9
11
|
|
|
10
12
|
// ── Quest Definitions ────────────────────────────────────────
|
|
11
13
|
|
|
12
14
|
export const LEGENDARY_QUESTS: readonly LegendaryQuest[] = [
|
|
15
|
+
// ═══════════════════════════════════════════════════════════
|
|
16
|
+
// GEN 1 — Kanto Legends
|
|
17
|
+
// ═══════════════════════════════════════════════════════════
|
|
18
|
+
|
|
13
19
|
// ── Articuno (#144) — "The Ice Bird of Endurance" ────────
|
|
14
20
|
{
|
|
15
21
|
pokemonId: 144,
|
|
16
22
|
name: "The Ice Bird of Endurance",
|
|
17
23
|
steps: [
|
|
18
24
|
{
|
|
19
|
-
description: "Code for
|
|
20
|
-
condition: { type: "streak", minDays:
|
|
25
|
+
description: "Code for 14 days (weekends off OK)",
|
|
26
|
+
condition: { type: "streak", minDays: 14 },
|
|
21
27
|
},
|
|
22
28
|
{
|
|
23
29
|
description: "Catch 3 Water or Ice type Pokemon",
|
|
24
|
-
// Approximated as pokedex gate; real type-check in getQuestProgress.
|
|
25
30
|
condition: { type: "pokedex", minCaught: 3 },
|
|
26
31
|
},
|
|
27
32
|
{
|
|
28
|
-
description: "Code for
|
|
29
|
-
condition: { type: "streak", minDays:
|
|
33
|
+
description: "Code for 30 days (weekends off OK)",
|
|
34
|
+
condition: { type: "streak", minDays: 30 },
|
|
30
35
|
},
|
|
31
36
|
{
|
|
32
|
-
description: "Reach level
|
|
33
|
-
condition: { type: "level", minLevel:
|
|
37
|
+
description: "Reach level 30 on your active Pokemon",
|
|
38
|
+
condition: { type: "level", minLevel: 30 },
|
|
34
39
|
},
|
|
35
40
|
],
|
|
36
41
|
},
|
|
@@ -41,20 +46,20 @@ export const LEGENDARY_QUESTS: readonly LegendaryQuest[] = [
|
|
|
41
46
|
name: "The Thunder of Testing",
|
|
42
47
|
steps: [
|
|
43
48
|
{
|
|
44
|
-
description: "Pass
|
|
45
|
-
condition: { type: "counter", counter: "tests_passed", threshold:
|
|
49
|
+
description: "Pass 50 tests",
|
|
50
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 50 },
|
|
46
51
|
},
|
|
47
52
|
{
|
|
48
|
-
description: "Write
|
|
49
|
-
condition: { type: "counter", counter: "tests_written", threshold:
|
|
53
|
+
description: "Write 20 test files",
|
|
54
|
+
condition: { type: "counter", counter: "tests_written", threshold: 20 },
|
|
50
55
|
},
|
|
51
56
|
{
|
|
52
|
-
description: "Pass
|
|
53
|
-
condition: { type: "counter", counter: "tests_passed", threshold:
|
|
57
|
+
description: "Pass 200 tests",
|
|
58
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 200 },
|
|
54
59
|
},
|
|
55
60
|
{
|
|
56
|
-
description: "Pass
|
|
57
|
-
condition: { type: "counter", counter: "tests_passed", threshold:
|
|
61
|
+
description: "Pass 500 tests lifetime",
|
|
62
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 500 },
|
|
58
63
|
},
|
|
59
64
|
],
|
|
60
65
|
},
|
|
@@ -65,20 +70,20 @@ export const LEGENDARY_QUESTS: readonly LegendaryQuest[] = [
|
|
|
65
70
|
name: "The Flame of Debugging",
|
|
66
71
|
steps: [
|
|
67
72
|
{
|
|
68
|
-
description: "Fix
|
|
69
|
-
condition: { type: "counter", counter: "bugs_fixed", threshold:
|
|
73
|
+
description: "Fix 25 bugs",
|
|
74
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 25 },
|
|
70
75
|
},
|
|
71
76
|
{
|
|
72
77
|
description: "Earn the Blaze Badge",
|
|
73
78
|
condition: { type: "badge", badge: "blaze" },
|
|
74
79
|
},
|
|
75
80
|
{
|
|
76
|
-
description: "Fix
|
|
77
|
-
condition: { type: "counter", counter: "bugs_fixed", threshold:
|
|
81
|
+
description: "Fix 100 bugs",
|
|
82
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 100 },
|
|
78
83
|
},
|
|
79
84
|
{
|
|
80
|
-
description: "Fix
|
|
81
|
-
condition: { type: "counter", counter: "bugs_fixed", threshold:
|
|
85
|
+
description: "Fix 250 bugs lifetime",
|
|
86
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 250 },
|
|
82
87
|
},
|
|
83
88
|
],
|
|
84
89
|
},
|
|
@@ -89,20 +94,20 @@ export const LEGENDARY_QUESTS: readonly LegendaryQuest[] = [
|
|
|
89
94
|
name: "The Ultimate Creation",
|
|
90
95
|
steps: [
|
|
91
96
|
{
|
|
92
|
-
description: "Catch
|
|
93
|
-
condition: { type: "pokedex", minCaught:
|
|
97
|
+
description: "Catch 30 unique Pokemon",
|
|
98
|
+
condition: { type: "pokedex", minCaught: 30 },
|
|
94
99
|
},
|
|
95
100
|
{
|
|
96
|
-
description: "Reach level
|
|
97
|
-
condition: { type: "level", minLevel:
|
|
101
|
+
description: "Reach level 40 on any Pokemon",
|
|
102
|
+
condition: { type: "level", minLevel: 40 },
|
|
98
103
|
},
|
|
99
104
|
{
|
|
100
|
-
description: "Catch
|
|
101
|
-
condition: { type: "pokedex", minCaught:
|
|
105
|
+
description: "Catch 75 unique Pokemon",
|
|
106
|
+
condition: { type: "pokedex", minCaught: 75 },
|
|
102
107
|
},
|
|
103
108
|
{
|
|
104
|
-
description: "Catch
|
|
105
|
-
condition: { type: "pokedex", minCaught:
|
|
109
|
+
description: "Catch 120 unique Pokemon",
|
|
110
|
+
condition: { type: "pokedex", minCaught: 120 },
|
|
106
111
|
},
|
|
107
112
|
],
|
|
108
113
|
},
|
|
@@ -113,24 +118,456 @@ export const LEGENDARY_QUESTS: readonly LegendaryQuest[] = [
|
|
|
113
118
|
name: "The Myth",
|
|
114
119
|
steps: [
|
|
115
120
|
{
|
|
116
|
-
description: "Code for
|
|
117
|
-
condition: { type: "streak", minDays:
|
|
121
|
+
description: "Code for 60 days (weekends off OK)",
|
|
122
|
+
condition: { type: "streak", minDays: 60 },
|
|
118
123
|
},
|
|
119
124
|
{
|
|
120
125
|
description: "Catch all 3 legendary birds (Articuno, Zapdos, Moltres)",
|
|
121
|
-
|
|
122
|
-
// Real check in getQuestProgress verifies specific IDs.
|
|
123
|
-
condition: { type: "pokedex", minCaught: 148 },
|
|
126
|
+
condition: { type: "pokedex", minCaught: 50 },
|
|
124
127
|
},
|
|
125
128
|
{
|
|
126
129
|
description: "Catch Mewtwo",
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
condition: { type: "pokedex", minCaught: 60 },
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
description: "Code for 180 days (weekends off OK)",
|
|
134
|
+
condition: { type: "streak", minDays: 180 },
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// ═══════════════════════════════════════════════════════════
|
|
140
|
+
// GEN 2 — Johto Legends
|
|
141
|
+
// ═══════════════════════════════════════════════════════════
|
|
142
|
+
|
|
143
|
+
// ── Lugia (#249) — "The Guardian of the Deep" ───────────
|
|
144
|
+
{
|
|
145
|
+
pokemonId: 249,
|
|
146
|
+
name: "The Guardian of the Deep",
|
|
147
|
+
steps: [
|
|
148
|
+
{
|
|
149
|
+
description: "Pass 100 tests",
|
|
150
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 100 },
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
description: "Build successfully 50 times",
|
|
154
|
+
condition: { type: "counter", counter: "builds_succeeded", threshold: 50 },
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
description: "Catch 40 unique Pokemon",
|
|
158
|
+
condition: { type: "pokedex", minCaught: 40 },
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
description: "Code for 45 days (weekends off OK)",
|
|
162
|
+
condition: { type: "streak", minDays: 45 },
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// ── Ho-Oh (#250) — "The Rainbow Phoenix" ────────────────
|
|
168
|
+
{
|
|
169
|
+
pokemonId: 250,
|
|
170
|
+
name: "The Rainbow Phoenix",
|
|
171
|
+
steps: [
|
|
172
|
+
{
|
|
173
|
+
description: "Catch Pokemon of 10 different types",
|
|
174
|
+
condition: { type: "pokedex", minCaught: 20 },
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
description: "Make 100 commits",
|
|
178
|
+
condition: { type: "counter", counter: "commits", threshold: 100 },
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
description: "Reach level 35 on any Pokemon",
|
|
182
|
+
condition: { type: "level", minLevel: 35 },
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
description: "Code for 60 days (weekends off OK)",
|
|
186
|
+
condition: { type: "streak", minDays: 60 },
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// ═══════════════════════════════════════════════════════════
|
|
192
|
+
// GEN 3 — Hoenn Legends
|
|
193
|
+
// ═══════════════════════════════════════════════════════════
|
|
194
|
+
|
|
195
|
+
// ── Kyogre (#382) — "The Ocean Architect" ───────────────
|
|
196
|
+
{
|
|
197
|
+
pokemonId: 382,
|
|
198
|
+
name: "The Ocean Architect",
|
|
199
|
+
steps: [
|
|
200
|
+
{
|
|
201
|
+
description: "Create 50 files",
|
|
202
|
+
condition: { type: "counter", counter: "files_created", threshold: 50 },
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
description: "Pass 150 tests",
|
|
206
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 150 },
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
description: "Earn the Flow Badge",
|
|
210
|
+
condition: { type: "badge", badge: "flow" },
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
description: "Catch 50 unique Pokemon",
|
|
214
|
+
condition: { type: "pokedex", minCaught: 50 },
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// ── Groudon (#383) — "The Continent Builder" ────────────
|
|
220
|
+
{
|
|
221
|
+
pokemonId: 383,
|
|
222
|
+
name: "The Continent Builder",
|
|
223
|
+
steps: [
|
|
224
|
+
{
|
|
225
|
+
description: "Edit 200 files",
|
|
226
|
+
condition: { type: "counter", counter: "files_edited", threshold: 200 },
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
description: "Build successfully 75 times",
|
|
230
|
+
condition: { type: "counter", counter: "builds_succeeded", threshold: 75 },
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
description: "Fix 75 bugs",
|
|
234
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 75 },
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
description: "Code for 45 days (weekends off OK)",
|
|
238
|
+
condition: { type: "streak", minDays: 45 },
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// ── Rayquaza (#384) — "The Sky Sovereign" ───────────────
|
|
244
|
+
{
|
|
245
|
+
pokemonId: 384,
|
|
246
|
+
name: "The Sky Sovereign",
|
|
247
|
+
steps: [
|
|
248
|
+
{
|
|
249
|
+
description: "Catch both Kyogre and Groudon",
|
|
250
|
+
condition: { type: "pokedex", minCaught: 60 },
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
description: "Make 200 commits",
|
|
254
|
+
condition: { type: "counter", counter: "commits", threshold: 200 },
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
description: "Reach level 45 on any Pokemon",
|
|
258
|
+
condition: { type: "level", minLevel: 45 },
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
description: "Code for 90 days (weekends off OK)",
|
|
262
|
+
condition: { type: "streak", minDays: 90 },
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// ═══════════════════════════════════════════════════════════
|
|
268
|
+
// GEN 4 — Sinnoh Legends
|
|
269
|
+
// ═══════════════════════════════════════════════════════════
|
|
270
|
+
|
|
271
|
+
// ── Dialga (#483) — "The Temporal Coder" ────────────────
|
|
272
|
+
{
|
|
273
|
+
pokemonId: 483,
|
|
274
|
+
name: "The Temporal Coder",
|
|
275
|
+
steps: [
|
|
276
|
+
{
|
|
277
|
+
description: "Code for 30 days (weekends off OK)",
|
|
278
|
+
condition: { type: "streak", minDays: 30 },
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
description: "Make 150 commits",
|
|
282
|
+
condition: { type: "counter", counter: "commits", threshold: 150 },
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
description: "Catch 60 unique Pokemon",
|
|
286
|
+
condition: { type: "pokedex", minCaught: 60 },
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
description: "Earn the Spark Badge",
|
|
290
|
+
condition: { type: "badge", badge: "spark" },
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
// ── Palkia (#484) — "The Spatial Engineer" ──────────────
|
|
296
|
+
{
|
|
297
|
+
pokemonId: 484,
|
|
298
|
+
name: "The Spatial Engineer",
|
|
299
|
+
steps: [
|
|
300
|
+
{
|
|
301
|
+
description: "Create 75 files",
|
|
302
|
+
condition: { type: "counter", counter: "files_created", threshold: 75 },
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
description: "Edit 300 files",
|
|
306
|
+
condition: { type: "counter", counter: "files_edited", threshold: 300 },
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
description: "Pass 300 tests",
|
|
310
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 300 },
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
description: "Code for 60 days (weekends off OK)",
|
|
314
|
+
condition: { type: "streak", minDays: 60 },
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// ── Giratina (#487) — "The Shadow Refactorer" ───────────
|
|
320
|
+
{
|
|
321
|
+
pokemonId: 487,
|
|
322
|
+
name: "The Shadow Refactorer",
|
|
323
|
+
steps: [
|
|
324
|
+
{
|
|
325
|
+
description: "Catch both Dialga and Palkia",
|
|
326
|
+
condition: { type: "pokedex", minCaught: 70 },
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
description: "Fix 150 bugs",
|
|
330
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 150 },
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
description: "Perform 50 lint fixes",
|
|
334
|
+
condition: { type: "counter", counter: "lint_fixes", threshold: 50 },
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
description: "Code for 90 days (weekends off OK)",
|
|
338
|
+
condition: { type: "streak", minDays: 90 },
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
// ═══════════════════════════════════════════════════════════
|
|
344
|
+
// GEN 5 — Unova Legends
|
|
345
|
+
// ═══════════════════════════════════════════════════════════
|
|
346
|
+
|
|
347
|
+
// ── Reshiram (#643) — "The Flame of Truth" ──────────────
|
|
348
|
+
{
|
|
349
|
+
pokemonId: 643,
|
|
350
|
+
name: "The Flame of Truth",
|
|
351
|
+
steps: [
|
|
352
|
+
{
|
|
353
|
+
description: "Write 30 test files",
|
|
354
|
+
condition: { type: "counter", counter: "tests_written", threshold: 30 },
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
description: "Pass 250 tests",
|
|
358
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 250 },
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
description: "Reach level 40 on any Pokemon",
|
|
362
|
+
condition: { type: "level", minLevel: 40 },
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
description: "Code for 60 days (weekends off OK)",
|
|
366
|
+
condition: { type: "streak", minDays: 60 },
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
// ── Zekrom (#644) — "The Bolt of Ideals" ────────────────
|
|
372
|
+
{
|
|
373
|
+
pokemonId: 644,
|
|
374
|
+
name: "The Bolt of Ideals",
|
|
375
|
+
steps: [
|
|
376
|
+
{
|
|
377
|
+
description: "Make 200 commits",
|
|
378
|
+
condition: { type: "counter", counter: "commits", threshold: 200 },
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
description: "Build successfully 100 times",
|
|
382
|
+
condition: { type: "counter", counter: "builds_succeeded", threshold: 100 },
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
description: "Catch 80 unique Pokemon",
|
|
386
|
+
condition: { type: "pokedex", minCaught: 80 },
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
description: "Code for 60 days (weekends off OK)",
|
|
390
|
+
condition: { type: "streak", minDays: 60 },
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
// ── Kyurem (#646) — "The Frozen Merger" ─────────────────
|
|
396
|
+
{
|
|
397
|
+
pokemonId: 646,
|
|
398
|
+
name: "The Frozen Merger",
|
|
399
|
+
steps: [
|
|
400
|
+
{
|
|
401
|
+
description: "Catch both Reshiram and Zekrom",
|
|
402
|
+
condition: { type: "pokedex", minCaught: 90 },
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
description: "Earn the Lunar Badge",
|
|
406
|
+
condition: { type: "badge", badge: "lunar" },
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
description: "Reach level 50 on any Pokemon",
|
|
410
|
+
condition: { type: "level", minLevel: 50 },
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
description: "Code for 120 days (weekends off OK)",
|
|
414
|
+
condition: { type: "streak", minDays: 120 },
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
// ═══════════════════════════════════════════════════════════
|
|
420
|
+
// GEN 6 — Kalos Legends
|
|
421
|
+
// ═══════════════════════════════════════════════════════════
|
|
422
|
+
|
|
423
|
+
// ── Xerneas (#716) — "The Tree of Life" ─────────────────
|
|
424
|
+
{
|
|
425
|
+
pokemonId: 716,
|
|
426
|
+
name: "The Tree of Life",
|
|
427
|
+
steps: [
|
|
428
|
+
{
|
|
429
|
+
description: "Create 100 files",
|
|
430
|
+
condition: { type: "counter", counter: "files_created", threshold: 100 },
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
description: "Earn the Growth Badge",
|
|
434
|
+
condition: { type: "badge", badge: "growth" },
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
description: "Catch 70 unique Pokemon",
|
|
438
|
+
condition: { type: "pokedex", minCaught: 70 },
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
description: "Code for 75 days (weekends off OK)",
|
|
442
|
+
condition: { type: "streak", minDays: 75 },
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
// ── Yveltal (#717) — "The Destruction Debugger" ─────────
|
|
448
|
+
{
|
|
449
|
+
pokemonId: 717,
|
|
450
|
+
name: "The Destruction Debugger",
|
|
451
|
+
steps: [
|
|
452
|
+
{
|
|
453
|
+
description: "Fix 100 bugs",
|
|
454
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 100 },
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
description: "Pass 400 tests",
|
|
458
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 400 },
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
description: "Make 250 commits",
|
|
462
|
+
condition: { type: "counter", counter: "commits", threshold: 250 },
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
description: "Code for 75 days (weekends off OK)",
|
|
466
|
+
condition: { type: "streak", minDays: 75 },
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
// ═══════════════════════════════════════════════════════════
|
|
472
|
+
// GEN 7 — Alola Legends
|
|
473
|
+
// ═══════════════════════════════════════════════════════════
|
|
474
|
+
|
|
475
|
+
// ── Solgaleo (#791) — "The Sunlit Deployer" ─────────────
|
|
476
|
+
{
|
|
477
|
+
pokemonId: 791,
|
|
478
|
+
name: "The Sunlit Deployer",
|
|
479
|
+
steps: [
|
|
480
|
+
{
|
|
481
|
+
description: "Build successfully 100 times",
|
|
482
|
+
condition: { type: "counter", counter: "builds_succeeded", threshold: 100 },
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
description: "Make 300 commits",
|
|
486
|
+
condition: { type: "counter", counter: "commits", threshold: 300 },
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
description: "Catch 100 unique Pokemon",
|
|
490
|
+
condition: { type: "pokedex", minCaught: 100 },
|
|
130
491
|
},
|
|
131
492
|
{
|
|
132
|
-
description: "Code for
|
|
133
|
-
condition: { type: "streak", minDays:
|
|
493
|
+
description: "Code for 90 days (weekends off OK)",
|
|
494
|
+
condition: { type: "streak", minDays: 90 },
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
// ── Lunala (#792) — "The Moonlit Reviewer" ──────────────
|
|
500
|
+
{
|
|
501
|
+
pokemonId: 792,
|
|
502
|
+
name: "The Moonlit Reviewer",
|
|
503
|
+
steps: [
|
|
504
|
+
{
|
|
505
|
+
description: "Perform 1000 searches",
|
|
506
|
+
condition: { type: "counter", counter: "searches", threshold: 1000 },
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
description: "Write 50 test files",
|
|
510
|
+
condition: { type: "counter", counter: "tests_written", threshold: 50 },
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
description: "Reach level 50 on any Pokemon",
|
|
514
|
+
condition: { type: "level", minLevel: 50 },
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
description: "Code for 90 days (weekends off OK)",
|
|
518
|
+
condition: { type: "streak", minDays: 90 },
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
},
|
|
522
|
+
|
|
523
|
+
// ═══════════════════════════════════════════════════════════
|
|
524
|
+
// GEN 8 — Galar Legends
|
|
525
|
+
// ═══════════════════════════════════════════════════════════
|
|
526
|
+
|
|
527
|
+
// ── Zacian (#888) — "The Crowned Shipper" ───────────────
|
|
528
|
+
{
|
|
529
|
+
pokemonId: 888,
|
|
530
|
+
name: "The Crowned Shipper",
|
|
531
|
+
steps: [
|
|
532
|
+
{
|
|
533
|
+
description: "Make 250 commits",
|
|
534
|
+
condition: { type: "counter", counter: "commits", threshold: 250 },
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
description: "Build successfully 150 times",
|
|
538
|
+
condition: { type: "counter", counter: "builds_succeeded", threshold: 150 },
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
description: "Catch 120 unique Pokemon",
|
|
542
|
+
condition: { type: "pokedex", minCaught: 120 },
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
description: "Code for 120 days (weekends off OK)",
|
|
546
|
+
condition: { type: "streak", minDays: 120 },
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
// ── Zamazenta (#889) — "The Shield of Stability" ────────
|
|
552
|
+
{
|
|
553
|
+
pokemonId: 889,
|
|
554
|
+
name: "The Shield of Stability",
|
|
555
|
+
steps: [
|
|
556
|
+
{
|
|
557
|
+
description: "Pass 500 tests",
|
|
558
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 500 },
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
description: "Fix 200 bugs",
|
|
562
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 200 },
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
description: "Edit 500 files",
|
|
566
|
+
condition: { type: "counter", counter: "files_edited", threshold: 500 },
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
description: "Code for 120 days (weekends off OK)",
|
|
570
|
+
condition: { type: "streak", minDays: 120 },
|
|
134
571
|
},
|
|
135
572
|
],
|
|
136
573
|
},
|
|
@@ -138,45 +575,12 @@ export const LEGENDARY_QUESTS: readonly LegendaryQuest[] = [
|
|
|
138
575
|
|
|
139
576
|
// ── Quest Progress ───────────────────────────────────────────
|
|
140
577
|
|
|
141
|
-
/** IDs of Water and Ice type Pokemon
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
54,
|
|
148
|
-
55, // Psyduck, Golduck
|
|
149
|
-
60,
|
|
150
|
-
61,
|
|
151
|
-
62, // Poliwag line
|
|
152
|
-
72,
|
|
153
|
-
73, // Tentacool, Tentacruel
|
|
154
|
-
79,
|
|
155
|
-
80, // Slowpoke, Slowbro
|
|
156
|
-
86,
|
|
157
|
-
87, // Seel, Dewgong (also Ice)
|
|
158
|
-
90,
|
|
159
|
-
91, // Shellder, Cloyster (also Ice)
|
|
160
|
-
98,
|
|
161
|
-
99, // Krabby, Kingler
|
|
162
|
-
116,
|
|
163
|
-
117, // Horsea, Seadra
|
|
164
|
-
118,
|
|
165
|
-
119, // Goldeen, Seaking
|
|
166
|
-
120,
|
|
167
|
-
121, // Staryu, Starmie
|
|
168
|
-
129,
|
|
169
|
-
130, // Magikarp, Gyarados
|
|
170
|
-
131, // Lapras (Water/Ice)
|
|
171
|
-
134, // Vaporeon
|
|
172
|
-
138,
|
|
173
|
-
139, // Omanyte, Omastar
|
|
174
|
-
140,
|
|
175
|
-
141, // Kabuto, Kabutops
|
|
176
|
-
// Ice types (not already listed)
|
|
177
|
-
124, // Jynx (Ice/Psychic)
|
|
178
|
-
144, // Articuno (Ice/Flying)
|
|
179
|
-
]);
|
|
578
|
+
/** IDs of Water and Ice type Pokemon (all gens, not just Gen 1). */
|
|
579
|
+
function isWaterOrIceType(pokemonId: number): boolean {
|
|
580
|
+
const pokemon = POKEMON_BY_ID.get(pokemonId);
|
|
581
|
+
if (!pokemon) return false;
|
|
582
|
+
return pokemon.types.some((t) => t === "Water" || t === "Ice");
|
|
583
|
+
}
|
|
180
584
|
|
|
181
585
|
/** Legendary bird Pokedex IDs. */
|
|
182
586
|
const LEGENDARY_BIRD_IDS: readonly number[] = [144, 145, 146];
|
|
@@ -184,6 +588,18 @@ const LEGENDARY_BIRD_IDS: readonly number[] = [144, 145, 146];
|
|
|
184
588
|
/** Mewtwo Pokedex ID. */
|
|
185
589
|
const MEWTWO_ID = 150;
|
|
186
590
|
|
|
591
|
+
/** Kyogre and Groudon IDs. */
|
|
592
|
+
const KYOGRE_ID = 382;
|
|
593
|
+
const GROUDON_ID = 383;
|
|
594
|
+
|
|
595
|
+
/** Dialga and Palkia IDs. */
|
|
596
|
+
const DIALGA_ID = 483;
|
|
597
|
+
const PALKIA_ID = 484;
|
|
598
|
+
|
|
599
|
+
/** Reshiram and Zekrom IDs. */
|
|
600
|
+
const RESHIRAM_ID = 643;
|
|
601
|
+
const ZEKROM_ID = 644;
|
|
602
|
+
|
|
187
603
|
export interface QuestProgress {
|
|
188
604
|
readonly quest: LegendaryQuest;
|
|
189
605
|
readonly stepsCompleted: number;
|
|
@@ -203,7 +619,7 @@ export function getQuestProgress(state: PlayerState): QuestProgress[] {
|
|
|
203
619
|
if (isStepComplete(quest.pokemonId, step.description, step.condition, state)) {
|
|
204
620
|
stepsCompleted++;
|
|
205
621
|
} else {
|
|
206
|
-
break;
|
|
622
|
+
break;
|
|
207
623
|
}
|
|
208
624
|
}
|
|
209
625
|
|
|
@@ -217,11 +633,6 @@ export function getQuestProgress(state: PlayerState): QuestProgress[] {
|
|
|
217
633
|
|
|
218
634
|
// ── Internal Helpers ─────────────────────────────────────────
|
|
219
635
|
|
|
220
|
-
/**
|
|
221
|
-
* Check whether a quest step is complete.
|
|
222
|
-
* Uses isConditionMet for standard conditions, with overrides
|
|
223
|
-
* for steps that require specific-Pokemon or type-based checks.
|
|
224
|
-
*/
|
|
225
636
|
function isStepComplete(
|
|
226
637
|
questPokemonId: number,
|
|
227
638
|
stepDescription: string,
|
|
@@ -230,7 +641,7 @@ function isStepComplete(
|
|
|
230
641
|
): boolean {
|
|
231
642
|
// Articuno step 2: catch 3 Water/Ice type Pokemon
|
|
232
643
|
if (questPokemonId === 144 && stepDescription.includes("Water or Ice")) {
|
|
233
|
-
return
|
|
644
|
+
return countCaughtByType(state, isWaterOrIceType) >= 3;
|
|
234
645
|
}
|
|
235
646
|
|
|
236
647
|
// Mew step 2: catch all 3 legendary birds
|
|
@@ -243,21 +654,34 @@ function isStepComplete(
|
|
|
243
654
|
return isPokemonCaught(state, MEWTWO_ID);
|
|
244
655
|
}
|
|
245
656
|
|
|
657
|
+
// Rayquaza step 1: catch both Kyogre and Groudon
|
|
658
|
+
if (questPokemonId === 384 && stepDescription.includes("Kyogre and Groudon")) {
|
|
659
|
+
return isPokemonCaught(state, KYOGRE_ID) && isPokemonCaught(state, GROUDON_ID);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Giratina step 1: catch both Dialga and Palkia
|
|
663
|
+
if (questPokemonId === 487 && stepDescription.includes("Dialga and Palkia")) {
|
|
664
|
+
return isPokemonCaught(state, DIALGA_ID) && isPokemonCaught(state, PALKIA_ID);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Kyurem step 1: catch both Reshiram and Zekrom
|
|
668
|
+
if (questPokemonId === 646 && stepDescription.includes("Reshiram and Zekrom")) {
|
|
669
|
+
return isPokemonCaught(state, RESHIRAM_ID) && isPokemonCaught(state, ZEKROM_ID);
|
|
670
|
+
}
|
|
671
|
+
|
|
246
672
|
// Default: delegate to the standard condition checker.
|
|
247
673
|
return isConditionMet(condition, state);
|
|
248
674
|
}
|
|
249
675
|
|
|
250
|
-
/** Check whether a specific Pokemon ID has been caught in the Pokedex. */
|
|
251
676
|
function isPokemonCaught(state: PlayerState, pokemonId: number): boolean {
|
|
252
677
|
const entry = state.pokedex.entries[pokemonId];
|
|
253
678
|
return entry !== undefined && entry.caught;
|
|
254
679
|
}
|
|
255
680
|
|
|
256
|
-
|
|
257
|
-
function countCaughtByIds(state: PlayerState, ids: ReadonlySet<number>): number {
|
|
681
|
+
function countCaughtByType(state: PlayerState, typePredicate: (id: number) => boolean): number {
|
|
258
682
|
let count = 0;
|
|
259
|
-
for (const
|
|
260
|
-
if (
|
|
683
|
+
for (const [idStr, entry] of Object.entries(state.pokedex.entries)) {
|
|
684
|
+
if (entry.caught && typePredicate(Number(idStr))) {
|
|
261
685
|
count++;
|
|
262
686
|
}
|
|
263
687
|
}
|