@umang-boss/claudemon 1.0.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/README.md +164 -0
- package/bin/claudemon.js +52 -0
- package/bunfig.toml +2 -0
- package/cli/doctor.ts +334 -0
- package/cli/index.ts +42 -0
- package/cli/install.ts +248 -0
- package/cli/shared.ts +102 -0
- package/cli/uninstall.ts +155 -0
- package/cli/update.ts +318 -0
- package/hooks/post-tool-use.sh +127 -0
- package/hooks/stop.sh +49 -0
- package/hooks/user-prompt-submit.sh +73 -0
- package/package.json +68 -0
- package/scripts/download-colorscripts.ts +311 -0
- package/skills/buddy/SKILL.md +47 -0
- package/sprites/colorscripts/small/1-bulbasaur.txt +11 -0
- package/sprites/colorscripts/small/10-caterpie.txt +9 -0
- package/sprites/colorscripts/small/100-voltorb.txt +8 -0
- package/sprites/colorscripts/small/101-electrode.txt +9 -0
- package/sprites/colorscripts/small/102-exeggcute.txt +10 -0
- package/sprites/colorscripts/small/103-exeggutor.txt +23 -0
- package/sprites/colorscripts/small/104-cubone.txt +11 -0
- package/sprites/colorscripts/small/105-marowak.txt +16 -0
- package/sprites/colorscripts/small/106-hitmonlee.txt +16 -0
- package/sprites/colorscripts/small/107-hitmonchan.txt +19 -0
- package/sprites/colorscripts/small/108-lickitung.txt +10 -0
- package/sprites/colorscripts/small/109-koffing.txt +14 -0
- package/sprites/colorscripts/small/11-metapod.txt +10 -0
- package/sprites/colorscripts/small/110-weezing.txt +23 -0
- package/sprites/colorscripts/small/111-rhyhorn.txt +11 -0
- package/sprites/colorscripts/small/112-rhydon.txt +20 -0
- package/sprites/colorscripts/small/113-chansey.txt +11 -0
- package/sprites/colorscripts/small/114-tangela.txt +10 -0
- package/sprites/colorscripts/small/115-kangaskhan.txt +18 -0
- package/sprites/colorscripts/small/116-horsea.txt +10 -0
- package/sprites/colorscripts/small/117-seadra.txt +11 -0
- package/sprites/colorscripts/small/118-goldeen.txt +11 -0
- package/sprites/colorscripts/small/119-seaking.txt +16 -0
- package/sprites/colorscripts/small/12-butterfree.txt +20 -0
- package/sprites/colorscripts/small/120-staryu.txt +10 -0
- package/sprites/colorscripts/small/121-starmie.txt +17 -0
- package/sprites/colorscripts/small/122-mr-mime.txt +18 -0
- package/sprites/colorscripts/small/123-scyther.txt +21 -0
- package/sprites/colorscripts/small/124-jynx.txt +18 -0
- package/sprites/colorscripts/small/125-electabuzz.txt +19 -0
- package/sprites/colorscripts/small/126-magmar.txt +19 -0
- package/sprites/colorscripts/small/127-pinsir.txt +19 -0
- package/sprites/colorscripts/small/128-tauros.txt +20 -0
- package/sprites/colorscripts/small/129-magikarp.txt +13 -0
- package/sprites/colorscripts/small/13-weedle.txt +10 -0
- package/sprites/colorscripts/small/130-gyarados.txt +21 -0
- package/sprites/colorscripts/small/131-lapras.txt +19 -0
- package/sprites/colorscripts/small/132-ditto.txt +8 -0
- package/sprites/colorscripts/small/133-eevee.txt +10 -0
- package/sprites/colorscripts/small/134-vaporeon.txt +16 -0
- package/sprites/colorscripts/small/135-jolteon.txt +17 -0
- package/sprites/colorscripts/small/136-flareon.txt +18 -0
- package/sprites/colorscripts/small/137-porygon.txt +10 -0
- package/sprites/colorscripts/small/138-omanyte.txt +10 -0
- package/sprites/colorscripts/small/139-omastar.txt +18 -0
- package/sprites/colorscripts/small/14-kakuna.txt +10 -0
- package/sprites/colorscripts/small/140-kabuto.txt +8 -0
- package/sprites/colorscripts/small/141-kabutops.txt +17 -0
- package/sprites/colorscripts/small/142-aerodactyl.txt +17 -0
- package/sprites/colorscripts/small/143-snorlax.txt +21 -0
- package/sprites/colorscripts/small/144-articuno.txt +24 -0
- package/sprites/colorscripts/small/145-zapdos.txt +20 -0
- package/sprites/colorscripts/small/146-moltres.txt +23 -0
- package/sprites/colorscripts/small/147-dratini.txt +10 -0
- package/sprites/colorscripts/small/148-dragonair.txt +12 -0
- package/sprites/colorscripts/small/149-dragonite.txt +21 -0
- package/sprites/colorscripts/small/15-beedrill.txt +13 -0
- package/sprites/colorscripts/small/150-mewtwo.txt +22 -0
- package/sprites/colorscripts/small/151-mew.txt +14 -0
- package/sprites/colorscripts/small/16-pidgey.txt +10 -0
- package/sprites/colorscripts/small/17-pidgeotto.txt +11 -0
- package/sprites/colorscripts/small/18-pidgeot.txt +18 -0
- package/sprites/colorscripts/small/19-rattata.txt +12 -0
- package/sprites/colorscripts/small/2-ivysaur.txt +11 -0
- package/sprites/colorscripts/small/20-raticate.txt +12 -0
- package/sprites/colorscripts/small/21-spearow.txt +9 -0
- package/sprites/colorscripts/small/22-fearow.txt +12 -0
- package/sprites/colorscripts/small/23-ekans.txt +12 -0
- package/sprites/colorscripts/small/24-arbok.txt +16 -0
- package/sprites/colorscripts/small/25-pikachu.txt +11 -0
- package/sprites/colorscripts/small/26-raichu.txt +19 -0
- package/sprites/colorscripts/small/27-sandshrew.txt +10 -0
- package/sprites/colorscripts/small/28-sandslash.txt +16 -0
- package/sprites/colorscripts/small/29-nidoran-f.txt +11 -0
- package/sprites/colorscripts/small/3-venusaur.txt +21 -0
- package/sprites/colorscripts/small/30-nidorina.txt +12 -0
- package/sprites/colorscripts/small/31-nidoqueen.txt +19 -0
- package/sprites/colorscripts/small/32-nidoran-m.txt +11 -0
- package/sprites/colorscripts/small/33-nidorino.txt +12 -0
- package/sprites/colorscripts/small/34-nidoking.txt +18 -0
- package/sprites/colorscripts/small/35-clefairy.txt +11 -0
- package/sprites/colorscripts/small/36-clefable.txt +17 -0
- package/sprites/colorscripts/small/37-vulpix.txt +11 -0
- package/sprites/colorscripts/small/38-ninetales.txt +18 -0
- package/sprites/colorscripts/small/39-jigglypuff.txt +11 -0
- package/sprites/colorscripts/small/4-charmander.txt +11 -0
- package/sprites/colorscripts/small/40-wigglytuff.txt +20 -0
- package/sprites/colorscripts/small/41-zubat.txt +11 -0
- package/sprites/colorscripts/small/42-golbat.txt +18 -0
- package/sprites/colorscripts/small/43-oddish.txt +11 -0
- package/sprites/colorscripts/small/44-gloom.txt +12 -0
- package/sprites/colorscripts/small/45-vileplume.txt +17 -0
- package/sprites/colorscripts/small/46-paras.txt +11 -0
- package/sprites/colorscripts/small/47-parasect.txt +12 -0
- package/sprites/colorscripts/small/48-venonat.txt +14 -0
- package/sprites/colorscripts/small/49-venomoth.txt +19 -0
- package/sprites/colorscripts/small/5-charmeleon.txt +13 -0
- package/sprites/colorscripts/small/50-diglett.txt +8 -0
- package/sprites/colorscripts/small/51-dugtrio.txt +18 -0
- package/sprites/colorscripts/small/52-meowth.txt +12 -0
- package/sprites/colorscripts/small/53-persian.txt +20 -0
- package/sprites/colorscripts/small/54-psyduck.txt +12 -0
- package/sprites/colorscripts/small/55-golduck.txt +17 -0
- package/sprites/colorscripts/small/56-mankey.txt +11 -0
- package/sprites/colorscripts/small/57-primeape.txt +13 -0
- package/sprites/colorscripts/small/58-growlithe.txt +12 -0
- package/sprites/colorscripts/small/59-arcanine.txt +20 -0
- package/sprites/colorscripts/small/6-charizard.txt +21 -0
- package/sprites/colorscripts/small/60-poliwag.txt +9 -0
- package/sprites/colorscripts/small/61-poliwhirl.txt +11 -0
- package/sprites/colorscripts/small/62-poliwrath.txt +17 -0
- package/sprites/colorscripts/small/63-abra.txt +12 -0
- package/sprites/colorscripts/small/64-kadabra.txt +14 -0
- package/sprites/colorscripts/small/65-alakazam.txt +19 -0
- package/sprites/colorscripts/small/66-machop.txt +11 -0
- package/sprites/colorscripts/small/67-machoke.txt +12 -0
- package/sprites/colorscripts/small/68-machamp.txt +19 -0
- package/sprites/colorscripts/small/69-bellsprout.txt +9 -0
- package/sprites/colorscripts/small/7-squirtle.txt +10 -0
- package/sprites/colorscripts/small/70-weepinbell.txt +11 -0
- package/sprites/colorscripts/small/71-victreebel.txt +17 -0
- package/sprites/colorscripts/small/72-tentacool.txt +12 -0
- package/sprites/colorscripts/small/73-tentacruel.txt +20 -0
- package/sprites/colorscripts/small/74-geodude.txt +9 -0
- package/sprites/colorscripts/small/75-graveler.txt +12 -0
- package/sprites/colorscripts/small/76-golem.txt +18 -0
- package/sprites/colorscripts/small/77-ponyta.txt +13 -0
- package/sprites/colorscripts/small/78-rapidash.txt +18 -0
- package/sprites/colorscripts/small/79-slowpoke.txt +12 -0
- package/sprites/colorscripts/small/8-wartortle.txt +12 -0
- package/sprites/colorscripts/small/80-slowbro.txt +18 -0
- package/sprites/colorscripts/small/81-magnemite.txt +9 -0
- package/sprites/colorscripts/small/82-magneton.txt +18 -0
- package/sprites/colorscripts/small/83-farfetchd.txt +12 -0
- package/sprites/colorscripts/small/84-doduo.txt +10 -0
- package/sprites/colorscripts/small/85-dodrio.txt +17 -0
- package/sprites/colorscripts/small/86-seel.txt +13 -0
- package/sprites/colorscripts/small/87-dewgong.txt +20 -0
- package/sprites/colorscripts/small/88-grimer.txt +10 -0
- package/sprites/colorscripts/small/89-muk.txt +14 -0
- package/sprites/colorscripts/small/9-blastoise.txt +20 -0
- package/sprites/colorscripts/small/90-shellder.txt +10 -0
- package/sprites/colorscripts/small/91-cloyster.txt +18 -0
- package/sprites/colorscripts/small/92-gastly.txt +12 -0
- package/sprites/colorscripts/small/93-haunter.txt +14 -0
- package/sprites/colorscripts/small/94-gengar.txt +19 -0
- package/sprites/colorscripts/small/95-onix.txt +22 -0
- package/sprites/colorscripts/small/96-drowzee.txt +12 -0
- package/sprites/colorscripts/small/97-hypno.txt +19 -0
- package/sprites/colorscripts/small/98-krabby.txt +12 -0
- package/sprites/colorscripts/small/99-kingler.txt +20 -0
- package/src/engine/constants.ts +121 -0
- package/src/engine/encounter-pool.ts +71 -0
- package/src/engine/encounters.ts +308 -0
- package/src/engine/evolution-data.ts +535 -0
- package/src/engine/evolution.ts +310 -0
- package/src/engine/pokemon-data.ts +1838 -0
- package/src/engine/reactions.ts +877 -0
- package/src/engine/starter-pool.ts +47 -0
- package/src/engine/stats.ts +97 -0
- package/src/engine/types.ts +312 -0
- package/src/engine/xp.ts +135 -0
- package/src/gamification/achievements.ts +204 -0
- package/src/gamification/legendary-quests.ts +265 -0
- package/src/gamification/milestones.ts +86 -0
- package/src/hooks/award-xp.ts +131 -0
- package/src/hooks/increment-counter.ts +27 -0
- package/src/server/index.ts +78 -0
- package/src/server/instructions.ts +194 -0
- package/src/server/tools/achievements.ts +118 -0
- package/src/server/tools/catch.ts +295 -0
- package/src/server/tools/display-helpers.ts +35 -0
- package/src/server/tools/evolve.ts +236 -0
- package/src/server/tools/legendary.ts +78 -0
- package/src/server/tools/party.ts +251 -0
- package/src/server/tools/pet.ts +124 -0
- package/src/server/tools/pokedex.ts +286 -0
- package/src/server/tools/rename.ts +63 -0
- package/src/server/tools/show.ts +136 -0
- package/src/server/tools/starter.ts +175 -0
- package/src/server/tools/stats.ts +123 -0
- package/src/server/tools/visibility.ts +65 -0
- package/src/sprites/index.ts +45 -0
- package/src/state/io.ts +91 -0
- package/src/state/schemas.ts +131 -0
- package/src/state/state-manager.ts +321 -0
- package/statusline/buddy-status.sh +233 -0
- package/tsconfig.json +37 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evolution engine for Claudemon.
|
|
3
|
+
* Handles evolution eligibility checks, stat-based branching (Eevee),
|
|
4
|
+
* badge earning, and the transformation of owned Pokemon.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
EvolutionChain,
|
|
9
|
+
EvolutionLink,
|
|
10
|
+
EvolutionMethod,
|
|
11
|
+
OwnedPokemon,
|
|
12
|
+
PlayerState,
|
|
13
|
+
BadgeType,
|
|
14
|
+
BadgeCondition,
|
|
15
|
+
CodingStat,
|
|
16
|
+
CodingStats,
|
|
17
|
+
BaseStats,
|
|
18
|
+
} from "./types.js";
|
|
19
|
+
import { BASE_STAT_TO_CODING } from "./types.js";
|
|
20
|
+
import { EVOLUTION_CHAINS } from "./evolution-data.js";
|
|
21
|
+
import { POKEMON_BY_ID } from "./pokemon-data.js";
|
|
22
|
+
import { BADGES } from "./constants.js";
|
|
23
|
+
|
|
24
|
+
// ── Eevee Branching Constants ─────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/** Minimum level required for Eevee stat-based evolution. */
|
|
27
|
+
const EEVEE_MIN_LEVEL = 25;
|
|
28
|
+
|
|
29
|
+
/** Pokemon ID for Eevee. */
|
|
30
|
+
const EEVEE_ID = 133;
|
|
31
|
+
|
|
32
|
+
/** Maps a dominant coding stat to the corresponding Eeveelution ID. */
|
|
33
|
+
const EEVEE_STAT_MAP: Readonly<Record<string, number>> = {
|
|
34
|
+
debugging: 136, // Flareon
|
|
35
|
+
stability: 134, // Vaporeon
|
|
36
|
+
velocity: 135, // Jolteon
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
/** The three stats used for Eevee branching. */
|
|
40
|
+
const EEVEE_BRANCH_STATS: readonly CodingStat[] = ["debugging", "stability", "velocity"];
|
|
41
|
+
|
|
42
|
+
// ── Precomputed Lookup Index ──────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Maps every Pokemon ID that appears in an evolution chain
|
|
46
|
+
* to the chain that contains it. Built once at module load.
|
|
47
|
+
*/
|
|
48
|
+
const chainByPokemonId: ReadonlyMap<number, EvolutionChain> = buildChainIndex();
|
|
49
|
+
|
|
50
|
+
function buildChainIndex(): Map<number, EvolutionChain> {
|
|
51
|
+
const index = new Map<number, EvolutionChain>();
|
|
52
|
+
for (const chain of EVOLUTION_CHAINS) {
|
|
53
|
+
for (const link of chain.links) {
|
|
54
|
+
index.set(link.from, chain);
|
|
55
|
+
index.set(link.to, chain);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return index;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Public API ────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Find the evolution chain that contains a given Pokemon.
|
|
65
|
+
* Returns null if the Pokemon has no evolution chain or the chain has no links.
|
|
66
|
+
*/
|
|
67
|
+
export function findEvolutionChain(pokemonId: number): EvolutionChain | null {
|
|
68
|
+
return chainByPokemonId.get(pokemonId) ?? null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get all possible evolution links FROM a given Pokemon.
|
|
73
|
+
* A Pokemon might have multiple paths (e.g., Eevee has 3).
|
|
74
|
+
* Returns an empty array if there are no outgoing links.
|
|
75
|
+
*/
|
|
76
|
+
export function getEvolutionLinks(pokemonId: number): readonly EvolutionLink[] {
|
|
77
|
+
const chain = findEvolutionChain(pokemonId);
|
|
78
|
+
if (!chain) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
return chain.links.filter((link) => link.from === pokemonId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if a Pokemon is eligible to evolve right now.
|
|
86
|
+
* Evaluates the evolution method against current state.
|
|
87
|
+
* Returns the first eligible EvolutionLink, or null.
|
|
88
|
+
*
|
|
89
|
+
* For Eevee (stat-based): returns the link matching the dominant stat.
|
|
90
|
+
* If stats are tied, returns null (user must boost one stat to break the tie).
|
|
91
|
+
*/
|
|
92
|
+
export function checkEvolution(pokemon: OwnedPokemon, state: PlayerState): EvolutionLink | null {
|
|
93
|
+
const links = getEvolutionLinks(pokemon.pokemonId);
|
|
94
|
+
if (links.length === 0) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Special handling for Eevee: stat-based branching with tie-breaking
|
|
99
|
+
if (pokemon.pokemonId === EEVEE_ID) {
|
|
100
|
+
return checkEeveeEvolution(pokemon, state);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Standard path: return the first satisfied link
|
|
104
|
+
for (const link of links) {
|
|
105
|
+
if (isMethodSatisfied(link.method, pokemon, state)) {
|
|
106
|
+
return link;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check a specific evolution method against current state.
|
|
115
|
+
*/
|
|
116
|
+
function isMethodSatisfied(
|
|
117
|
+
method: EvolutionMethod,
|
|
118
|
+
pokemon: OwnedPokemon,
|
|
119
|
+
state: PlayerState,
|
|
120
|
+
): boolean {
|
|
121
|
+
switch (method.type) {
|
|
122
|
+
case "level":
|
|
123
|
+
return pokemon.level >= method.level;
|
|
124
|
+
|
|
125
|
+
case "badge":
|
|
126
|
+
return state.badges.includes(method.badge);
|
|
127
|
+
|
|
128
|
+
case "collaboration":
|
|
129
|
+
return state.counters.prs_merged >= 10;
|
|
130
|
+
|
|
131
|
+
case "stat":
|
|
132
|
+
return (
|
|
133
|
+
pokemon.level >= EEVEE_MIN_LEVEL && pokemon.codingStats[method.stat] >= method.minValue
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Apply an evolution: transform the Pokemon to its new species.
|
|
140
|
+
* - Updates pokemonId to the target species
|
|
141
|
+
* - Recalculates coding stats with new base stats (preserves activity bonuses)
|
|
142
|
+
* - Sets evolvedAt timestamp
|
|
143
|
+
* - Returns the new species name and types for display
|
|
144
|
+
*
|
|
145
|
+
* Does not change: nickname, happiness, totalXp, currentXp, level, shiny, isStarter, id.
|
|
146
|
+
*/
|
|
147
|
+
export function applyEvolution(
|
|
148
|
+
pokemon: OwnedPokemon,
|
|
149
|
+
targetId: number,
|
|
150
|
+
): { newName: string; newTypes: readonly [string, string?] } {
|
|
151
|
+
const oldSpecies = POKEMON_BY_ID.get(pokemon.pokemonId);
|
|
152
|
+
const newSpecies = POKEMON_BY_ID.get(targetId);
|
|
153
|
+
|
|
154
|
+
if (!oldSpecies) {
|
|
155
|
+
throw new Error(`Unknown source Pokemon ID: ${pokemon.pokemonId}`);
|
|
156
|
+
}
|
|
157
|
+
if (!newSpecies) {
|
|
158
|
+
throw new Error(`Unknown target Pokemon ID: ${targetId}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Preserve activity bonuses: subtract old base contribution, add new base contribution
|
|
162
|
+
const updatedStats = recalculateStats(
|
|
163
|
+
pokemon.codingStats,
|
|
164
|
+
oldSpecies.baseStats,
|
|
165
|
+
newSpecies.baseStats,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
pokemon.pokemonId = targetId;
|
|
169
|
+
pokemon.codingStats = updatedStats;
|
|
170
|
+
pokemon.evolvedAt = new Date().toISOString();
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
newName: newSpecies.name,
|
|
174
|
+
newTypes: newSpecies.types,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the dominant coding stat for Eevee branching.
|
|
180
|
+
* Only considers debugging, stability, and velocity.
|
|
181
|
+
* Returns null if there is a tie between the top stats.
|
|
182
|
+
*/
|
|
183
|
+
export function getDominantStat(stats: CodingStats): CodingStat | null {
|
|
184
|
+
let maxValue = -Infinity;
|
|
185
|
+
let dominant: CodingStat | null = null;
|
|
186
|
+
let tied = false;
|
|
187
|
+
|
|
188
|
+
for (const stat of EEVEE_BRANCH_STATS) {
|
|
189
|
+
const value = stats[stat];
|
|
190
|
+
if (value > maxValue) {
|
|
191
|
+
maxValue = value;
|
|
192
|
+
dominant = stat;
|
|
193
|
+
tied = false;
|
|
194
|
+
} else if (value === maxValue) {
|
|
195
|
+
tied = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return tied ? null : dominant;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Check if a specific badge has been earned based on player state. */
|
|
203
|
+
export function isBadgeEarned(badge: BadgeType, state: PlayerState): boolean {
|
|
204
|
+
const badgeDef = BADGES.find((b) => b.type === badge);
|
|
205
|
+
if (!badgeDef) return false;
|
|
206
|
+
return isBadgeConditionMet(badgeDef.condition, state);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get all badges the player has earned but not yet collected.
|
|
211
|
+
* Compares earned conditions against state.badges array.
|
|
212
|
+
*/
|
|
213
|
+
export function getNewlyEarnedBadges(state: PlayerState): BadgeType[] {
|
|
214
|
+
const newBadges: BadgeType[] = [];
|
|
215
|
+
|
|
216
|
+
for (const badgeDef of BADGES) {
|
|
217
|
+
// Skip badges the player already has
|
|
218
|
+
if (state.badges.includes(badgeDef.type)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (isBadgeConditionMet(badgeDef.condition, state)) {
|
|
222
|
+
newBadges.push(badgeDef.type);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return newBadges;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── Internal Helpers ──────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Eevee-specific evolution check.
|
|
233
|
+
* Requires level >= 25 and a clear dominant stat (no ties).
|
|
234
|
+
*/
|
|
235
|
+
function checkEeveeEvolution(pokemon: OwnedPokemon, state: PlayerState): EvolutionLink | null {
|
|
236
|
+
if (pokemon.level < EEVEE_MIN_LEVEL) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const dominant = getDominantStat(pokemon.codingStats);
|
|
241
|
+
if (dominant === null) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const targetId = EEVEE_STAT_MAP[dominant];
|
|
246
|
+
if (targetId === undefined) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Find the matching link and verify the stat threshold is met
|
|
251
|
+
const links = getEvolutionLinks(pokemon.pokemonId);
|
|
252
|
+
const matchingLink = links.find((link) => link.to === targetId);
|
|
253
|
+
if (!matchingLink) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (isMethodSatisfied(matchingLink.method, pokemon, state)) {
|
|
258
|
+
return matchingLink;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Evaluate a badge condition against player state.
|
|
266
|
+
*/
|
|
267
|
+
function isBadgeConditionMet(condition: BadgeCondition, state: PlayerState): boolean {
|
|
268
|
+
switch (condition.type) {
|
|
269
|
+
case "counter":
|
|
270
|
+
return state.counters[condition.counter] >= condition.threshold;
|
|
271
|
+
case "streak":
|
|
272
|
+
return state.streak.currentStreak >= condition.minDays;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Recalculate coding stats when evolving to a new species.
|
|
278
|
+
* Extracts activity bonuses from current stats by removing the old base
|
|
279
|
+
* contribution, then applies the new base contribution.
|
|
280
|
+
*
|
|
281
|
+
* Formula per stat:
|
|
282
|
+
* activityBonus = currentStat - floor(oldBaseStat * 0.5)
|
|
283
|
+
* newStat = floor(newBaseStat * 0.5) + activityBonus
|
|
284
|
+
*
|
|
285
|
+
* Activity bonuses are floored at 0 to prevent negative values.
|
|
286
|
+
*/
|
|
287
|
+
function recalculateStats(
|
|
288
|
+
currentStats: CodingStats,
|
|
289
|
+
oldBase: BaseStats,
|
|
290
|
+
newBase: BaseStats,
|
|
291
|
+
): CodingStats {
|
|
292
|
+
const result: CodingStats = {
|
|
293
|
+
stamina: 0,
|
|
294
|
+
debugging: 0,
|
|
295
|
+
stability: 0,
|
|
296
|
+
velocity: 0,
|
|
297
|
+
wisdom: 0,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
for (const [baseStat, codingStat] of Object.entries(BASE_STAT_TO_CODING) as Array<
|
|
301
|
+
[keyof BaseStats, CodingStat]
|
|
302
|
+
>) {
|
|
303
|
+
const oldBaseContribution = Math.floor(oldBase[baseStat] * 0.5);
|
|
304
|
+
const activityBonus = Math.max(0, currentStats[codingStat] - oldBaseContribution);
|
|
305
|
+
const newBaseContribution = Math.floor(newBase[baseStat] * 0.5);
|
|
306
|
+
result[codingStat] = newBaseContribution + activityBonus;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return result;
|
|
310
|
+
}
|