| name | add-move |
| description | Add new moves to the battle system. Use when implementing attack, status, or support moves for Pokemon. |
Adding Moves to Pokestar
Quick Reference
Files to modify:
src/enums/battleEnums.js - Add moveIdEnum.YOUR_MOVE entry (ONLY if not exists)
src/battle/data/moves.js - Add the move implementation as a new entry in the movesToRegisterRaw object (NOT sketchMoves or the frozen movesToRegister re-export)
DO NOT modify other moves or battleConfig.js unless explicitly asked.
Move Structure
The move name and tier should be provided as inputs to this skill. For all other fields, they are optional, and if unprovided, you can infer them yourself.
[moveIdEnum.MOVE_NAME]: new Move({
id: moveIdEnum.MOVE_NAME,
name: "Move Name",
type: pokemonTypes.TYPE,
power: 80,
accuracy: 100,
cooldown: 3,
targetType: targetTypes.ENEMY,
targetPosition: targetPositions.FRONT,
targetPattern: targetPatterns.SINGLE,
tier: moveTiers.POWER,
damageType: damageTypes.PHYSICAL,
description: "Move description",
execute() { },
}),
Execute Context
NOTE: The execute function binds this to the MoveInstance.js MoveInstance class, with properties such as:
Example Properties:
this.source - User of the move
this.primaryTarget - Main target selected
this.allTargets - All affected targets (based on pattern)
this.missedTargets - Targets that were missed
this.battle - Battle instance
this.power, this.type, this.id - Move properties
Example Generic Methods
All generic methods are available in MoveInstance.js. These are some common examples:
this.genericDealAllDamage();
this.genericDealAllDamage({
calculateDamageFunction: (args) =>
this.source.calculateMoveDamage(args) * 1.5,
offTargetDamageMultiplier: 0.5,
backTargetDamageMultiplier: 0.7,
attackOverride: this.source.getStat("def"),
});
this.genericApplyAllStatus({
statusId: statusConditions.BURN,
probability: 0.3,
});
this.genericApplyAllEffects({
effectId: "atkUp",
duration: 3,
probability: 0.5,
});
this.genericApplySingleEffect({
target: this.primaryTarget,
effectId: "shield",
duration: 3,
initialArgs: { shield: 100 },
});
this.genericChangeAllCombatReadiness({ amount: 25, action: "boost" });
this.genericHealAllTargets({ healPercent: 25 });
this.genericHealSingleTarget({ target, healAmount: 100 });
Move Tags
Optional tags array for ability interactions:
"punch" - Boosted by Iron Fist ability
"slice" - Boosted by Sharpness ability
"charge" - Two-turn move
Dynamic Move Properties
Use overrideFields to change properties based on context:
overrideFields: (options) => {
if (options.source?.speciesId === pokemonIdEnum.SPECIAL_FORM) {
return { power: 120, description: "Enhanced version" };
}
},
Useful BattlePokemon Methods
pokemon.getStat("atk") - Get effective stat
pokemon.hasType(pokemonTypes.FIRE) - Check type
pokemon.applyEffect(id, duration, source, initialArgs)
pokemon.dispellEffect(effectId) - Remove effect
pokemon.removeStatus() - Clear status condition
pokemon.dealDamage(amount, target, info) - Deal damage
pokemon.giveHeal(amount, target, info) - Heal
pokemon.boostCombatReadiness(source, amount)
pokemon.getPartyPokemon() - Get ally array
pokemon.isFainted - Check if fainted
Testing
After implementing a move, run the move test suite to validate the implementation:
npm test -- src/battle/data/__tests__/moves.test.js
This runs an e2e test that verifies all moves can execute without throwing errors. If your new move causes a test failure, fix the implementation and re-run until tests pass.
When to Write Move-Specific Tests
Default: write at least one assertion test for the move. The e2e test in moves.test.js only verifies that execute() does not throw — a move with the wrong damage, wrong target, missing status application, or silently-broken side effect will pass the e2e test but still be wrong. So unless the move is purely a thin wrapper around a single generic method, write a focused test.
Skip move-specific tests only when ALL of these are true:
- The
execute() body is one (or zero) generic method calls (genericDealAllDamage, genericApplyAllEffects, genericApplyAllStatus, genericHealAllTargets, genericChangeAllCombatReadiness, etc.) with no extra logic before or after.
- There are no conditionals (
if, ternary, &&/|| short-circuits used for control flow).
- There is no
overrideFields, no tags interactions you want to verify, and no charge/two-turn behavior.
- There are no probabilistic secondary effects worth pinning down (status chance, effect chance).
If any non-generic call appears in execute() (a custom damage calculation, a manual pokemon.applyEffect, a pokemon.dealDamage, a pokemon.removeStatus, a custom target loop, etc.), or any of the conditions above is false, write at least one assertion test. Examples of what counts as "non-generic" and warrants a test:
- Conditional branches or multi-step flows in
execute()
- Long execute functions with multiple interacting operations
- Charge / two-turn move mechanics
- Species-specific or form-specific behavior
- Non-standard damage calculations or targeting (custom
calculateDamageFunction, custom multipliers)
- Item / effect manipulation (stealing, swapping, dispelling)
- Probabilistic effects whose probability you want to lock in via
describeStatusProbability / describeEffectProbability
- Direct calls to
BattlePokemon methods (applyEffect, dispellEffect, dealDamage, giveHeal, boostCombatReadiness, etc.) outside of the generic helpers
- Any
tags you want to confirm interact correctly with abilities (e.g. "punch" + Iron Fist)
The bar is "non-generic call → one assertion test", not "complex logic → one assertion test". Prefer one short, focused test that asserts the unique behavior over no test at all.
How to Write Move Tests
Add tests to src/battle/data/__tests__/moves.test.js. See the test-move skill for the full testing infrastructure, including custom matchers, test utilities, and test patterns.
References
references/target-patterns.md - Visual guide to target patterns
references/effects.md - List of all buff/debuff effect IDs
references/pattern-*.md - Code examples for common move types