npm package: @objectifthunes/pokejs-battle
Description: Pure Pokémon battle logic — stats, damage, accuracy, critical hits, status, weather, abilities, items, EVs, experience, and full turn-based battle simulation.
Latest version: 1.0.2
Deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

# @objectifthunes/pokejs-battle

Pure Pokémon battle logic for TypeScript — stats, damage, accuracy, critical hits, status conditions, weather, abilities, items, leveling, evolution, EVs, friendship. All Gen V+ formulas, zero dependencies beyond [`@objectifthunes/pokejs-data`](https://www.npmjs.com/package/@objectifthunes/pokejs-data).

> **This is the logic layer.** If you want a full game with timing, animations, menus, and AI — use [`@objectifthunes/pokejs-engine`](https://www.npmjs.com/package/@objectifthunes/pokejs-engine) which builds on top of this.

```
npm install @objectifthunes/pokejs-battle
```

---

## Quick Start

### Create a Pokemon

```ts
import { createPokemon } from "@objectifthunes/pokejs-battle";

const garchomp = createPokemon({
  species: "garchomp",
  level: 50,
  nature: "jolly",
  ability: "rough-skin",
  item: "choice-scarf",
  ivs: { hp: 31, attack: 31, defense: 31, specialAttack: 0, specialDefense: 31, speed: 31 },
  evs: { attack: 252, speed: 252, hp: 4 },
  moves: ["earthquake", "outrage", "stone-edge", "fire-fang"],
});

garchomp.stats;      // { hp: 184, attack: 182, defense: 115, ... }
garchomp.currentHP;  // 184
```

### Simulate a Battle

```ts
import { createPokemon, Battle } from "@objectifthunes/pokejs-battle";

const battle = new Battle({
  team1: [
    createPokemon({ species: "garchomp", level: 50, moves: ["earthquake", "outrage"] }),
    createPokemon({ species: "rotom-wash", level: 50, moves: ["hydro-pump", "thunderbolt"] }),
  ],
  team2: [
    createPokemon({ species: "tyranitar", level: 50, moves: ["stone-edge", "crunch"] }),
    createPokemon({ species: "skarmory", level: 50, moves: ["brave-bird", "iron-head"] }),
  ],
});

// Both sides pick actions
battle.setAction(1, { type: "move", moveIndex: 0 });
battle.setAction(2, { type: "move", moveIndex: 0 });

// Execute the turn — returns a stream of events
const events = battle.executeTurn();

for (const e of events) {
  console.log(e.message);
}
// "Garchomp used Earthquake!"
// "It's super effective!"
// "Tyranitar took 212 damage!"
// "Tyranitar fainted!"

// Check state
battle.state.side2.active;  // Tyranitar's battle state
battle.state.ended;         // false
battle.state.winner;        // null

// Switch after faint
if (battle.needsSwitch(2)) {
  battle.forceSwitch(2, 1);  // send in Skarmory
}
```

---

## API Reference

### Factory

```ts
createPokemon(opts)         // Create a PokemonInstance with fully calculated stats
createRandomPokemon(level)  // Random species, random IVs/nature
createRandomTeam(n, level)  // n random Pokemon
recalcStats(pokemon)        // Recalculate after level up / EV change
```

**CreatePokemonOptions:**
```ts
{
  species: "garchomp",       // required — species slug
  level: 50,                 // default: 50
  nature: "jolly",           // default: random
  ability: "rough-skin",     // default: first ability
  item: "choice-scarf",      // default: null
  ivs: { attack: 31, ... },  // default: all 31
  evs: { attack: 252, ... }, // default: all 0
  moves: ["earthquake"],     // default: auto-fill from learnset
  nickname: "Chompy",        // default: null
  friendship: 70,            // default: 70
}
```

### Battle

```ts
const battle = new Battle({ team1, team2, weather?, terrain? });

battle.setAction(side, action)    // Set action for side 1 or 2
battle.ready                      // true when both sides have submitted actions
battle.executeTurn()              // Execute turn → BattleEvent[]
battle.getAvailableActions(side)  // { moves: number[], switches: number[] }
battle.needsSwitch(side)          // true if active fainted with team remaining
battle.forceSwitch(side, index)   // Force switch after faint → BattleEvent[]
battle.state                      // Full BattleState (read-only)
```

**Actions:**
```ts
{ type: "move", moveIndex: 0 }       // Use move at index 0
{ type: "switch", pokemonIndex: 1 }  // Switch to team member at index 1
```

**BattleEvent** — every meaningful thing that happens in a turn:
```ts
interface BattleEvent {
  type: BattleEventType;  // see below
  side: 1 | 2;
  pokemon?: string;
  damage?: number;
  heal?: number;
  stat?: string;
  stages?: number;
  status?: string;
  weather?: string;
  effectiveness?: number;
  message: string;        // human-readable description
}
```

**All event types:**
```
switch_in          switch_out         move_used          move_missed
move_failed        damage             heal               faint
critical_hit       super_effective    not_very_effective immune
no_effect          status_inflicted   status_cured       status_damage
stat_change        weather_set        weather_damage     weather_ended
ability_activated  item_activated     item_consumed      flinched
fully_paralyzed    fast_asleep        frozen_solid       confused_hit_self
thawed             woke_up            recoil             drain
ohko_prevented     multi_hit          battle_end
```

---

### Core Calculations

#### Stats (Gen III+)

```ts
import { calcStat, calcAllStats, effectiveStat, stageMultiplier } from "@objectifthunes/pokejs-battle";

// HP  = floor((2*base + iv + floor(ev/4)) * level/100) + level + 10
// Other = floor((floor((2*base + iv + floor(ev/4)) * level/100) + 5) * nature)
calcStat("hp", 108, 31, 252, 50)           // Garchomp HP at lv50
calcStat("attack", 130, 31, 252, 50, 1.1)  // with +nature

calcAllStats(baseStats, 50, ivs, evs, "jolly")

stageMultiplier(2)     // +2 → 2.0x
stageMultiplier(-1)    // -1 → 0.667x
effectiveStat(182, 2)  // 182 at +2 → 364
```

#### Damage (Gen V+)

```ts
import { calcDamage, rollDamage } from "@objectifthunes/pokejs-battle";

// floor(((2*level/5 + 2) * power * A/D) / 50 + 2) * modifiers
const roll = calcDamage({
  level: 50, power: 100, attack: 182, defense: 100,
  moveType: "ground",
  attackerTypes: ["dragon", "ground"],
  defenderTypes: ["rock", "dark"],
  isPhysical: true, critical: false, weather: null,
  stab: true, burned: false, hasGuts: false,
  targets: 1, extraMultiplier: 1,
});

roll.min;           // minimum damage (85% roll)
roll.max;           // maximum damage (100% roll)
roll.effectiveness; // 2 (super effective)

const damage = rollDamage(roll);  // pick a random roll
```

#### Accuracy

```ts
import { checkAccuracy, effectiveAccuracy } from "@objectifthunes/pokejs-battle";

checkAccuracy(95, 0, 0)      // 95% → true/false
checkAccuracy(null, 0, 0)    // null = always hits (Swift, Aerial Ace)
checkAccuracy(85, 1, -1)     // 85% with +1 acc / -1 eva stages
effectiveAccuracy(85, 1, -1) // → effective hit % for display
```

#### Critical Hits (Gen VI+)

```ts
import { checkCritical, critRate, totalCritStage } from "@objectifthunes/pokejs-battle";

critRate(0)  // stage 0 → 1/24 (4.17%)
critRate(1)  // stage 1 → 1/8  (12.5%)
critRate(2)  // stage 2 → 1/2  (50%)
critRate(3)  // stage 3 → 1/1  (100%)

const stage = totalCritStage(
  true,  // high crit-rate move  (+1)
  true,  // Focus Energy         (+2)
  1,     // Super Luck ability   (+1)
  0,     // no item bonus
);        // → stage 4, always crits

checkCritical(stage)  // → boolean
```

#### Experience & Leveling

```ts
import { totalExpForLevel, expToNextLevel, levelFromExp, calcExpGain } from "@objectifthunes/pokejs-battle";

// 6 growth rates: "erratic" | "fast" | "medium-fast" | "medium-slow" | "slow" | "fluctuating"
totalExpForLevel(50, "medium-fast")  // total XP to reach lv50
expToNextLevel(49, "medium-fast")    // XP gap from lv49 to lv50
levelFromExp(125000, "medium-fast")  // → 50

calcExpGain({
  baseExpYield: 270,       // Garchomp's yield
  defeatedLevel: 50,
  gainerLevel: 45,
  isTrainerBattle: true,   // ×1.5
  luckyEgg: true,          // ×1.5
})
```

---

### Mechanics

#### Status Conditions

```ts
import { inflictStatus, cureStatus, isImmuneToStatus, canAct, applyStatusDamage } from "@objectifthunes/pokejs-battle";

inflictStatus(bp, "burn")              // false if already statused or immune
cureStatus(bp)                          // clears status
isImmuneToStatus("burn", ["fire"], "")  // true — Fire types can't burn
canAct(bp)                              // checks sleep/freeze/para/confusion/flinch
applyStatusDamage(bp)                   // burn/poison/toxic tick → { event, fainted }
```

| Status | Effect |
|---|---|
| Burn | 1/16 HP/turn, physical damage halved (unless Guts) |
| Paralysis | 50% speed, 25% full paralysis chance |
| Sleep | 1–3 turns asleep, 33% wake chance |
| Freeze | Can't move, 20% thaw chance |
| Poison | 1/8 HP/turn |
| Toxic | 1/16 → 2/16 → 3/16... escalating each turn |

#### Weather

```ts
import { abilityWeather, applyWeatherDamage, takesWeatherDamage } from "@objectifthunes/pokejs-battle";

abilityWeather("drizzle")                  // "rain"
abilityWeather("drought")                  // "sun"
takesWeatherDamage(bp, "sandstorm")        // false for Rock/Ground/Steel
applyWeatherDamage(bp, "sandstorm", side)  // returns BattleEvent | null
```

| Weather | Damage | Power boost | Speed boost |
|---|---|---|---|
| Sun | — | Fire ×1.5, Water ×0.5 | Chlorophyll |
| Rain | — | Water ×1.5, Fire ×0.5 | Swift Swim |
| Sandstorm | 1/16 (not Rock/Ground/Steel) | Rock SpDef ×1.5 | Sand Rush |
| Hail | 1/16 (not Ice) | — | — |
| Snow | 1/16 (not Ice) | Ice Def ×1.5 | Slush Rush |

#### Abilities

```ts
import { abilityTypeInteraction, attackerAbilityMultiplier, sturdyCheck, onSwitchIn } from "@objectifthunes/pokejs-battle";

abilityTypeInteraction("levitate", "ground")    // "immune"
abilityTypeInteraction("flash-fire", "fire")     // "boost"
abilityTypeInteraction("water-absorb", "water")  // "absorb"
sturdyCheck("sturdy", currentHP, maxHP, damage)  // true → prevent OHKO
onSwitchIn(bp, opponent, side)                   // Intimidate, etc. → BattleEvent[]
```

Supported: Intimidate, Levitate, Flash Fire, Water/Volt/Storm Drain Absorb, Thick Fat, Guts, Marvel Scale, Speed Boost, Drizzle, Drought, Sand Stream, Snow Warning, Huge Power, Pure Power, Multiscale, Sturdy, Mold Breaker, Magic Guard, Technician, Adaptability, Regenerator, Natural Cure, Serene Grace, Sheer Force, Compound Eyes, Prankster, Poison Heal, Skill Link, and more.

#### Items

```ts
import { choiceItemMultiplier, lifeOrbRecoil, focusSashCheck, applyEndOfTurnItem } from "@objectifthunes/pokejs-battle";

choiceItemMultiplier("choice-band")            // { attack: 1.5, specialAttack: 1, ... }
lifeOrbRecoil("life-orb", 200)                 // 20 (10% maxHP)
focusSashCheck("focus-sash", hp, maxHP, dmg)   // true → survive with 1 HP
applyEndOfTurnItem(bp, side)                   // Leftovers/Black Sludge heal → event | null
```

Supported: Choice Band/Specs/Scarf, Life Orb, Leftovers, Focus Sash, Eviolite, Assault Vest, Rocky Helmet, Sitrus Berry, Flame Orb, Toxic Orb, Black Sludge, Expert Belt, Air Balloon, Heavy-Duty Boots, Big Root, type-boosting items, resist berries, Scope Lens, Razor Claw.

#### Evolution

```ts
import { isFullyEvolved, isBaseForm, evolutionStages, canEvolveByLevel } from "@objectifthunes/pokejs-battle";

isFullyEvolved("charizard")   // true
isBaseForm("charmander")      // true
evolutionStages("charmeleon") // 3
canEvolveByLevel(pokemon)     // → string[] of possible evolutions
```

#### Friendship

```ts
import { applyFriendshipEvent, canEvolveByFriendship, friendshipLevel } from "@objectifthunes/pokejs-battle";

applyFriendshipEvent(pokemon, "level-up", hasSootheBell)
canEvolveByFriendship(pokemon)   // true if friendship >= 160
friendshipLevel(220)              // "Best friends"
```

#### EV Training

```ts
import { addEVs, totalEVs, applyEVGain, competitiveSpread, useEVBerry } from "@objectifthunes/pokejs-battle";

addEVs(pokemon.evs, "attack", 4)      // returns actual EVs added (cap-aware)
totalEVs(pokemon.evs)                 // total (max 510)
competitiveSpread("attack", "speed")  // { attack: 252, speed: 252, hp: 4, ... }
useEVBerry(pokemon, "pomeg-berry")    // reduce HP EVs by 10
applyEVGain(pokemon, [{ stat: "attack", amount: 2 }], hasPokerus, powerItem)
```

### Turn Order

```ts
import { sortActions, getEffectiveSpeed, getMovePriority } from "@objectifthunes/pokejs-battle";

// Effective speed = base stat + stages + paralysis + Scarf + Tailwind + Unburden
getEffectiveSpeed(bp, hasTailwind)

// Priority: +4 (Protect) → +1 (Quick Attack) → 0 (most) → -6 (Trick Room)
// Prankster: +1 for status moves
getMovePriority(movePriority, ability, damageClass)

// Sort by priority, then speed (reversed under Trick Room), ties broken randomly
sortActions(actions, trickRoom)
```

---

## TypeScript

Full type definitions included. Key types:

```ts
import type {
  PokemonInstance,       // Pokemon with IVs, EVs, moves, item, stats, currentHP
  CreatePokemonOptions,
  BattleState,           // Full snapshot of battle state
  BattleSide,            // One side: team, active, conditions
  BattlePokemon,         // Active Pokemon: instance + stages + volatile state
  BattleEvent,           // A single event from executeTurn()
  BattleEventType,
  Action,                // MoveAction | SwitchAction
  Weather, Terrain,
  StatStages,
  VolatileState,
  NonVolatileStatus,     // "burn" | "paralysis" | "sleep" | "freeze" | "poison" | "toxic"
  GrowthRate,
  DamageParams, DamageRoll,
} from "@objectifthunes/pokejs-battle";
```

---

## License

MIT