pokemon-io-core 0.0.83 → 0.0.85

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 (75) hide show
  1. package/dist/core/battleState.d.ts +1 -1
  2. package/dist/core/index.d.ts +0 -1
  3. package/dist/core/index.js +0 -1
  4. package/dist/engine/actions/forcedSwitch.d.ts +5 -0
  5. package/dist/engine/actions/forcedSwitch.js +44 -0
  6. package/dist/engine/actions/index.d.ts +5 -0
  7. package/dist/engine/actions/index.js +5 -0
  8. package/dist/engine/actions/item.d.ts +6 -0
  9. package/dist/engine/actions/item.js +61 -0
  10. package/dist/engine/actions/move.d.ts +6 -0
  11. package/dist/engine/actions/move.js +120 -0
  12. package/dist/engine/actions/priority.d.ts +6 -0
  13. package/dist/engine/actions/priority.js +36 -0
  14. package/dist/engine/actions/switch.d.ts +6 -0
  15. package/dist/engine/actions/switch.js +55 -0
  16. package/dist/engine/combat/crit.d.ts +2 -0
  17. package/dist/engine/combat/crit.js +4 -0
  18. package/dist/engine/combat/damage.d.ts +20 -0
  19. package/dist/engine/combat/damage.js +66 -0
  20. package/dist/engine/combat/heal.d.ts +6 -0
  21. package/dist/engine/combat/heal.js +25 -0
  22. package/dist/engine/combat/index.d.ts +5 -0
  23. package/dist/engine/combat/index.js +5 -0
  24. package/dist/engine/combat/typeEffectiveness.d.ts +3 -0
  25. package/dist/engine/combat/typeEffectiveness.js +7 -0
  26. package/dist/engine/combat/winner.d.ts +2 -0
  27. package/dist/engine/combat/winner.js +11 -0
  28. package/dist/engine/debug.d.ts +1 -0
  29. package/dist/engine/debug.js +6 -0
  30. package/dist/engine/effects/applyEffects.d.ts +7 -0
  31. package/dist/engine/effects/applyEffects.js +103 -0
  32. package/dist/engine/effects/index.d.ts +2 -0
  33. package/dist/engine/effects/index.js +2 -0
  34. package/dist/engine/effects/target.d.ts +6 -0
  35. package/dist/engine/effects/target.js +26 -0
  36. package/dist/engine/engine.d.ts +53 -0
  37. package/dist/engine/engine.js +1046 -0
  38. package/dist/engine/events.d.ts +4 -0
  39. package/dist/engine/events.js +12 -0
  40. package/dist/engine/fighters/fighter.d.ts +4 -0
  41. package/dist/engine/fighters/fighter.js +53 -0
  42. package/dist/engine/fighters/index.d.ts +3 -0
  43. package/dist/engine/fighters/index.js +3 -0
  44. package/dist/engine/fighters/selectors.d.ts +13 -0
  45. package/dist/engine/fighters/selectors.js +19 -0
  46. package/dist/engine/fighters/update.d.ts +3 -0
  47. package/dist/engine/fighters/update.js +30 -0
  48. package/dist/engine/index.d.ts +11 -1
  49. package/dist/engine/index.js +11 -1
  50. package/dist/engine/rng.d.ts +2 -0
  51. package/dist/engine/rng.js +8 -0
  52. package/dist/engine/rules.d.ts +44 -0
  53. package/dist/engine/rules.js +10 -0
  54. package/dist/engine/runtime.d.ts +7 -0
  55. package/dist/engine/runtime.js +49 -0
  56. package/dist/engine/status/apply.d.ts +6 -0
  57. package/dist/engine/status/apply.js +49 -0
  58. package/dist/engine/status/clear.d.ts +6 -0
  59. package/dist/engine/status/clear.js +47 -0
  60. package/dist/engine/status/endOfTurn.d.ts +6 -0
  61. package/dist/engine/status/endOfTurn.js +80 -0
  62. package/dist/engine/status/hardCc.d.ts +3 -0
  63. package/dist/engine/status/hardCc.js +4 -0
  64. package/dist/engine/status/index.d.ts +4 -0
  65. package/dist/engine/status/index.js +4 -0
  66. package/dist/engine/turn/index.d.ts +1 -0
  67. package/dist/engine/turn/index.js +1 -0
  68. package/dist/engine/turn/resolveTurn.d.ts +5 -0
  69. package/dist/engine/turn/resolveTurn.js +139 -0
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.js +1 -0
  72. package/dist/skins/CombatSkin.d.ts +1 -1
  73. package/dist/skins/cliches/clicheSkin.d.ts +2 -1
  74. package/dist/skins/pokemon/pokemonSkin.d.ts +2 -1
  75. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { BattleRuntime } from "../core/engine.js";
1
+ import { BattleRuntime } from "../engine/rules.js";
2
2
  import type { FighterId, TypeId, MoveId, ItemId, AmuletId, StatusId } from "./ids";
3
3
  import type { FighterStats } from "./types.js";
4
4
  export interface EquippedMove {
@@ -1,7 +1,6 @@
1
1
  export * from "./actions.js";
2
2
  export * from "./amulets.js";
3
3
  export * from "./battleState.js";
4
- export * from "./engine.js";
5
4
  export * from "./events.js";
6
5
  export * from "./fighters.js";
7
6
  export * from "./ids.js";
@@ -1,7 +1,6 @@
1
1
  export * from "./actions.js";
2
2
  export * from "./amulets.js";
3
3
  export * from "./battleState.js";
4
- export * from "./engine.js";
5
4
  export * from "./events.js";
6
5
  export * from "./fighters.js";
7
6
  export * from "./ids.js";
@@ -0,0 +1,5 @@
1
+ import { BattleEvent, BattleState } from "../../core";
2
+ export declare const applyForcedSwitchChoice: (state: BattleState, targetPlayer: "player1" | "player2", newIndex: number) => {
3
+ state: BattleState;
4
+ events: BattleEvent[];
5
+ };
@@ -0,0 +1,44 @@
1
+ import { createBaseEvent } from "../events";
2
+ export const applyForcedSwitchChoice = (state, targetPlayer, newIndex) => {
3
+ const forced = state.forcedSwitch;
4
+ if (!forced)
5
+ return { state, events: [] };
6
+ if (forced.targetPlayer !== targetPlayer)
7
+ return { state, events: [] };
8
+ const self = targetPlayer === "player1" ? state.player1 : state.player2;
9
+ if (newIndex < 0 || newIndex >= self.fighterTeam.length)
10
+ return { state, events: [] };
11
+ const fromIndex = self.activeIndex;
12
+ if (newIndex === fromIndex)
13
+ return { state, events: [] };
14
+ const toFighter = self.fighterTeam[newIndex];
15
+ if (!toFighter || !toFighter.isAlive || toFighter.currentHp <= 0)
16
+ return { state, events: [] };
17
+ const fromFighter = self.fighterTeam[fromIndex];
18
+ const events = [
19
+ {
20
+ ...createBaseEvent(state.turnNumber, "fighter_switched", `El entrenador retira a ${fromFighter.fighterId} y manda a ${toFighter.fighterId}`),
21
+ fromFighterId: fromFighter.fighterId,
22
+ toFighterId: toFighter.fighterId,
23
+ },
24
+ {
25
+ ...createBaseEvent(state.turnNumber, "turn_end", `Termina el turno ${state.turnNumber}`),
26
+ },
27
+ ];
28
+ const switchedState = targetPlayer === "player1"
29
+ ? {
30
+ ...state,
31
+ forcedSwitch: null,
32
+ player1: { ...state.player1, activeIndex: newIndex },
33
+ }
34
+ : {
35
+ ...state,
36
+ forcedSwitch: null,
37
+ player2: { ...state.player2, activeIndex: newIndex },
38
+ };
39
+ const nextState = {
40
+ ...switchedState,
41
+ turnNumber: switchedState.turnNumber + 1,
42
+ };
43
+ return { state: nextState, events };
44
+ };
@@ -0,0 +1,5 @@
1
+ export * from './forcedSwitch.js';
2
+ export * from './item.js';
3
+ export * from './move.js';
4
+ export * from './priority.js';
5
+ export * from './switch.js';
@@ -0,0 +1,5 @@
1
+ export * from './forcedSwitch.js';
2
+ export * from './item.js';
3
+ export * from './move.js';
4
+ export * from './priority.js';
5
+ export * from './switch.js';
@@ -0,0 +1,6 @@
1
+ import { BattleEvent, PlayerAction } from "../../core";
2
+ import { RuntimeBattleState } from "../rules";
3
+ export declare const resolveItemUse: (state: RuntimeBattleState, playerKey: "player1" | "player2", action: PlayerAction) => {
4
+ state: RuntimeBattleState;
5
+ events: BattleEvent[];
6
+ };
@@ -0,0 +1,61 @@
1
+ import { dbg } from "../debug";
2
+ import { applyEffectsOnTarget } from "../effects/applyEffects";
3
+ import { createBaseEvent } from "../events";
4
+ import { getPlayersAndActives } from "../fighters/selectors";
5
+ import { updateFightersInState } from "../fighters/update";
6
+ export const resolveItemUse = (state, playerKey, action) => {
7
+ if (action.kind !== "use_item") {
8
+ return { state, events: [] };
9
+ }
10
+ const { selfPlayer, oppPlayer, self, opponent } = getPlayersAndActives(state, playerKey);
11
+ const events = [];
12
+ const itemSlot = selfPlayer.inventory[action.itemIndex];
13
+ if (!itemSlot || itemSlot.usesRemaining <= 0) {
14
+ dbg("use_item: no charges", {
15
+ playerKey,
16
+ itemIndex: action.itemIndex,
17
+ });
18
+ return { state, events };
19
+ }
20
+ const itemDef = state.runtime.itemsById[itemSlot.itemId];
21
+ if (!itemDef) {
22
+ dbg("use_item: item definition not found", {
23
+ playerKey,
24
+ itemId: itemSlot.itemId,
25
+ });
26
+ return { state, events };
27
+ }
28
+ const updatedInventory = selfPlayer.inventory.map((it, idx) => idx === action.itemIndex
29
+ ? { ...it, usesRemaining: Math.max(0, it.usesRemaining - 1) }
30
+ : it);
31
+ let updatedSelf = { ...self };
32
+ let updatedOpponent = { ...opponent };
33
+ events.push({
34
+ ...createBaseEvent(state.turnNumber, "action_declared", `${self.fighterId} usó ${itemDef.name}`),
35
+ actorId: self.fighterId,
36
+ });
37
+ const { actor: finalSelf, target: finalOpp, events: extraEvents, } = applyEffectsOnTarget(state, updatedSelf, updatedOpponent, null, // los items no usan fórmula de tipo por ahora
38
+ false, itemDef.effects);
39
+ events.push(...extraEvents);
40
+ let newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
41
+ // persistimos el inventario gastado en el jugador que actuó
42
+ if (playerKey === "player1") {
43
+ newState = {
44
+ ...newState,
45
+ player1: {
46
+ ...newState.player1,
47
+ inventory: updatedInventory,
48
+ },
49
+ };
50
+ }
51
+ else {
52
+ newState = {
53
+ ...newState,
54
+ player2: {
55
+ ...newState.player2,
56
+ inventory: updatedInventory,
57
+ },
58
+ };
59
+ }
60
+ return { state: newState, events };
61
+ };
@@ -0,0 +1,6 @@
1
+ import { BattleEvent, PlayerAction } from "../../core";
2
+ import { RuntimeBattleState } from "../rules";
3
+ export declare const resolveDamageMove: (state: RuntimeBattleState, playerKey: "player1" | "player2", action: PlayerAction) => {
4
+ state: RuntimeBattleState;
5
+ events: BattleEvent[];
6
+ };
@@ -0,0 +1,120 @@
1
+ import { computeCritChance } from "../combat/crit";
2
+ import { getTypeEffectiveness } from "../combat/typeEffectiveness";
3
+ import { dbg } from "../debug";
4
+ import { applyEffectsOnTarget } from "../effects/applyEffects";
5
+ import { createBaseEvent } from "../events";
6
+ import { getOpponentAndSelf } from "../fighters/selectors";
7
+ import { updateFightersInState } from "../fighters/update";
8
+ import { chance, randomInRange } from "../rng";
9
+ export const resolveDamageMove = (state, playerKey, action) => {
10
+ if (action.kind !== "use_move") {
11
+ return { state, events: [] };
12
+ }
13
+ const { self, opponent } = getOpponentAndSelf(state, playerKey);
14
+ const events = [];
15
+ const moveSlot = self.moves[action.moveIndex];
16
+ dbg("use_move: slot", moveSlot);
17
+ if (!moveSlot) {
18
+ dbg("use_move: empty slot", { playerKey, moveIndex: action.moveIndex });
19
+ return { state, events };
20
+ }
21
+ const move = state.runtime.movesById[moveSlot.moveId];
22
+ if (!move) {
23
+ dbg("use_move: move definition not found", {
24
+ playerKey,
25
+ moveId: moveSlot.moveId,
26
+ });
27
+ return { state, events };
28
+ }
29
+ if (moveSlot.currentPP <= 0) {
30
+ dbg("use_move: no PP", {
31
+ playerKey,
32
+ fighterId: self.fighterId,
33
+ moveId: move.id,
34
+ });
35
+ return { state, events };
36
+ }
37
+ events.push({
38
+ ...createBaseEvent(state.turnNumber, "action_declared", `${self.fighterId} usó ${move.name}`),
39
+ actorId: self.fighterId,
40
+ });
41
+ const accuracy = move.accuracy ?? 100;
42
+ const hitRoll = randomInRange(0, 100);
43
+ const hit = hitRoll < accuracy;
44
+ const updatedMoves = self.moves.map((m, idx) => idx === action.moveIndex
45
+ ? { ...m, currentPP: Math.max(0, m.currentPP - 1) }
46
+ : m);
47
+ let updatedSelf = { ...self, moves: updatedMoves };
48
+ let updatedOpponent = { ...opponent };
49
+ if (!hit) {
50
+ dbg("MISS", {
51
+ fighterId: self.fighterId,
52
+ moveId: move.id,
53
+ moveName: move.name,
54
+ hitRoll,
55
+ accuracy,
56
+ });
57
+ events.push({
58
+ ...createBaseEvent(state.turnNumber, "move_miss", `${self.fighterId} falla ${move.name}`),
59
+ actorId: self.fighterId,
60
+ moveId: move.id,
61
+ targetId: opponent.fighterId,
62
+ });
63
+ const newState = updateFightersInState(state, playerKey, updatedSelf, updatedOpponent);
64
+ return { state: newState, events };
65
+ }
66
+ const critChance = computeCritChance(state.runtime.rules, updatedSelf.effectiveStats.crit);
67
+ const isCritical = chance(critChance);
68
+ const effectiveness = getTypeEffectiveness(state, move.typeId, updatedOpponent.classId);
69
+ dbg("HIT", {
70
+ fighterId: self.fighterId,
71
+ moveId: move.id,
72
+ moveName: move.name,
73
+ isCritical,
74
+ effectiveness,
75
+ defenderBeforeHp: opponent.currentHp,
76
+ });
77
+ events.push({
78
+ ...createBaseEvent(state.turnNumber, "move_hit", `${self.fighterId} acierta ${move.name}`),
79
+ actorId: self.fighterId,
80
+ moveId: move.id,
81
+ targetId: opponent.fighterId,
82
+ isCritical,
83
+ effectiveness,
84
+ });
85
+ dbg("MOVE_EFFECTS", {
86
+ moveId: move.id,
87
+ moveName: move.name,
88
+ effects: move.effects,
89
+ });
90
+ // Todos los efectos (daño, aplicar estado, curas, etc.)
91
+ const { actor: finalSelf, target: finalOpp, events: extraEvents, } = applyEffectsOnTarget(state, updatedSelf, updatedOpponent, move.typeId, isCritical, move.effects);
92
+ events.push(...extraEvents);
93
+ let newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
94
+ // --- FORCED SWITCH (Roar) ---
95
+ const hasForceSwitch = move.effects.some((e) => e.kind === "force_switch");
96
+ if (hasForceSwitch) {
97
+ const targetPlayer = playerKey === "player1" ? "player2" : "player1";
98
+ const targetSide = targetPlayer === "player1" ? newState.player1 : newState.player2;
99
+ const activeIdx = targetSide.activeIndex;
100
+ const hasBenchAlive = targetSide.fighterTeam.some((f, idx) => {
101
+ if (idx === activeIdx)
102
+ return false;
103
+ return f.isAlive && f.currentHp > 0;
104
+ });
105
+ if (hasBenchAlive) {
106
+ newState = {
107
+ ...newState,
108
+ forcedSwitch: {
109
+ targetPlayer,
110
+ reason: "roar",
111
+ },
112
+ };
113
+ events.push({
114
+ ...createBaseEvent(state.turnNumber, "action_declared", `¡El entrenador rival debe cambiar de luchador!`),
115
+ actorId: self.fighterId,
116
+ });
117
+ }
118
+ }
119
+ return { state: newState, events };
120
+ };
@@ -0,0 +1,6 @@
1
+ import { PlayerAction } from "../../core";
2
+ import { RuntimeBattleState } from "../rules";
3
+ export declare const getMovePriorityAndSpeed: (state: RuntimeBattleState, playerKey: "player1" | "player2", action: PlayerAction) => {
4
+ priority: number;
5
+ speed: number;
6
+ };
@@ -0,0 +1,36 @@
1
+ import { getOpponentAndSelf } from "../fighters/selectors";
2
+ export const getMovePriorityAndSpeed = (state, playerKey, action) => {
3
+ const { self } = getOpponentAndSelf(state, playerKey);
4
+ if (action.kind === "no_action") {
5
+ return { priority: 0, speed: 0 };
6
+ }
7
+ if (action.kind === "use_move") {
8
+ const moveEntry = self.moves[action.moveIndex];
9
+ if (!moveEntry) {
10
+ return { priority: 0, speed: self.effectiveStats.speed };
11
+ }
12
+ const moveDef = state.runtime.movesById[moveEntry.moveId];
13
+ const priority = moveDef?.priority ?? 0;
14
+ return {
15
+ priority,
16
+ speed: self.effectiveStats.speed,
17
+ };
18
+ }
19
+ if (action.kind === "switch_fighter") {
20
+ return {
21
+ priority: 99999, // prioridad especial (ajústalo si quieres)
22
+ speed: self.effectiveStats.speed,
23
+ };
24
+ }
25
+ if (action.kind === "use_item") {
26
+ return {
27
+ priority: 10,
28
+ speed: self.effectiveStats.speed,
29
+ };
30
+ }
31
+ // Items y switch: prioridad base 0 por ahora
32
+ return {
33
+ priority: 0,
34
+ speed: self.effectiveStats.speed,
35
+ };
36
+ };
@@ -0,0 +1,6 @@
1
+ import { BattleEvent, PlayerAction } from "../../core";
2
+ import { RuntimeBattleState } from "../rules";
3
+ export declare const resolveSwitchFighter: (state: RuntimeBattleState, playerKey: "player1" | "player2", action: PlayerAction) => {
4
+ state: RuntimeBattleState;
5
+ events: BattleEvent[];
6
+ };
@@ -0,0 +1,55 @@
1
+ import { createBaseEvent } from "../events";
2
+ export const resolveSwitchFighter = (state, playerKey, action) => {
3
+ if (action.kind !== "switch_fighter")
4
+ return { state, events: [] };
5
+ const events = [];
6
+ const selfPlayer = playerKey === "player1" ? state.player1 : state.player2;
7
+ const fromIndex = selfPlayer.activeIndex;
8
+ const toIndex = action.newIndex;
9
+ // validaciones
10
+ if (toIndex < 0 || toIndex >= selfPlayer.fighterTeam.length) {
11
+ return { state, events };
12
+ }
13
+ if (toIndex === fromIndex) {
14
+ return { state, events };
15
+ }
16
+ const target = selfPlayer.fighterTeam[toIndex];
17
+ if (!target || !target.isAlive || target.currentHp <= 0) {
18
+ return { state, events };
19
+ }
20
+ const fromFighter = selfPlayer.fighterTeam[fromIndex];
21
+ events.push({
22
+ ...createBaseEvent(state.turnNumber, "fighter_switched", `El entrenador retira a ${fromFighter.fighterId} y manda a ${target.fighterId}`),
23
+ fromFighterId: fromFighter.fighterId,
24
+ toFighterId: target.fighterId,
25
+ toHp: target.currentHp,
26
+ toStatuses: (target.statuses ?? []).map((st) => ({
27
+ statusId: st.statusId,
28
+ stacks: st.stacks,
29
+ remainingTurns: st.remainingTurns,
30
+ })),
31
+ });
32
+ // actualizar activeIndex
33
+ if (playerKey === "player1") {
34
+ return {
35
+ state: {
36
+ ...state,
37
+ player1: {
38
+ ...state.player1,
39
+ activeIndex: toIndex,
40
+ },
41
+ },
42
+ events,
43
+ };
44
+ }
45
+ return {
46
+ state: {
47
+ ...state,
48
+ player2: {
49
+ ...state.player2,
50
+ activeIndex: toIndex,
51
+ },
52
+ },
53
+ events,
54
+ };
55
+ };
@@ -0,0 +1,2 @@
1
+ import { BattleRules } from "../rules";
2
+ export declare const computeCritChance: (rules: BattleRules, critStat: number) => number;
@@ -0,0 +1,4 @@
1
+ export const computeCritChance = (rules, critStat) => {
2
+ const raw = rules.baseCritChance + critStat * rules.critPerStat;
3
+ return Math.max(0, Math.min(1, raw));
4
+ };
@@ -0,0 +1,20 @@
1
+ import { BattleEvent, BattleFighter, FighterId, TypeId } from "../../core";
2
+ import { RuntimeBattleState } from "../rules";
3
+ interface DamageComputationInput {
4
+ state: RuntimeBattleState;
5
+ attacker: BattleFighter;
6
+ defender: BattleFighter;
7
+ moveTypeId: TypeId;
8
+ basePower: number;
9
+ isCritical: boolean;
10
+ }
11
+ export declare const computeDamage: (input: DamageComputationInput) => {
12
+ damage: number;
13
+ effectiveness: number;
14
+ };
15
+ interface DamageApplyResult {
16
+ updatedDefender: BattleFighter;
17
+ events: BattleEvent[];
18
+ }
19
+ export declare const applyDamageToFighter: (state: RuntimeBattleState, defender: BattleFighter, amount: number, actorId: FighterId, isCritical: boolean) => DamageApplyResult;
20
+ export {};
@@ -0,0 +1,66 @@
1
+ import { dbg } from "../debug";
2
+ import { createBaseEvent } from "../events";
3
+ import { randomInRange } from "../rng";
4
+ import { getTypeEffectiveness } from "./typeEffectiveness";
5
+ export const computeDamage = (input) => {
6
+ dbg("🧨 computeDamage", { input });
7
+ const { state, attacker, defender, moveTypeId, basePower, isCritical } = input;
8
+ const rules = state.runtime.rules;
9
+ const attackerOff = attacker.effectiveStats.offense;
10
+ const defenderDef = defender.effectiveStats.defense;
11
+ const typeEffectiveness = getTypeEffectiveness(state, moveTypeId, defender.classId);
12
+ if (typeEffectiveness === 0) {
13
+ return {
14
+ damage: 0,
15
+ effectiveness: 0,
16
+ };
17
+ }
18
+ const stabMultiplier = attacker.classId === moveTypeId ? rules.stabMultiplier : 1;
19
+ const critMultiplier = isCritical ? rules.critMultiplier : 1;
20
+ const randomFactor = randomInRange(rules.randomMinDamageFactor, rules.randomMaxDamageFactor);
21
+ const rawDamage = basePower *
22
+ (attackerOff / Math.max(1, defenderDef)) *
23
+ typeEffectiveness *
24
+ stabMultiplier *
25
+ critMultiplier *
26
+ randomFactor;
27
+ dbg("🧨 computeDamage", `
28
+ basePower(${basePower}) *
29
+ (attackerOff(${attackerOff}) / Math.max(1, defenderDef(${defenderDef}))) = ${attackerOff / Math.max(1, defenderDef)}*
30
+ typeEffectiveness(${typeEffectiveness}) *
31
+ stabMultiplier(${stabMultiplier}) *
32
+ critMultiplier(${critMultiplier}) *
33
+ randomFactor(${randomFactor}) = ${rawDamage}
34
+ `);
35
+ const finalDamage = Math.max(1, Math.floor(rawDamage));
36
+ dbg("🧨 computeDamage", `
37
+ finalDamage(${finalDamage}) = Math.max(1, Math.floor(rawDamage))
38
+ `);
39
+ return {
40
+ damage: finalDamage,
41
+ effectiveness: typeEffectiveness,
42
+ };
43
+ };
44
+ export const applyDamageToFighter = (state, defender, amount, actorId, isCritical) => {
45
+ const events = [];
46
+ const newHp = Math.max(0, defender.currentHp - amount);
47
+ const updatedDefender = {
48
+ ...defender,
49
+ currentHp: newHp,
50
+ isAlive: newHp > 0,
51
+ };
52
+ events.push({
53
+ ...createBaseEvent(state.turnNumber, "damage", `${actorId} inflige ${amount} de daño a ${defender.fighterId}`),
54
+ actorId,
55
+ targetId: defender.fighterId,
56
+ amount,
57
+ isCritical,
58
+ });
59
+ if (!updatedDefender.isAlive) {
60
+ events.push({
61
+ ...createBaseEvent(state.turnNumber, "fighter_fainted", `${defender.fighterId} ha caído`),
62
+ fighterId: defender.fighterId,
63
+ });
64
+ }
65
+ return { updatedDefender, events };
66
+ };
@@ -0,0 +1,6 @@
1
+ import { BattleEvent, BattleFighter, FighterId } from "../../core";
2
+ import { RuntimeBattleState } from "../rules";
3
+ export declare const applyHealToFighter: (state: RuntimeBattleState, target: BattleFighter, amount: number, sourceId: FighterId) => {
4
+ updated: BattleFighter;
5
+ events: BattleEvent[];
6
+ };
@@ -0,0 +1,25 @@
1
+ import { dbg } from "../debug";
2
+ import { createBaseEvent } from "../events";
3
+ export const applyHealToFighter = (state, target, amount, sourceId) => {
4
+ const newHp = Math.min(target.maxHp, target.currentHp + amount);
5
+ const healed = newHp - target.currentHp;
6
+ const updated = {
7
+ ...target,
8
+ currentHp: newHp,
9
+ isAlive: newHp > 0,
10
+ };
11
+ const events = [];
12
+ dbg("HEAL", {
13
+ sourceId,
14
+ targetId: target.fighterId,
15
+ healed,
16
+ newHp,
17
+ });
18
+ events.push({
19
+ ...createBaseEvent(state.turnNumber, "heal", `${sourceId} cura ${healed} a ${target.fighterId}`),
20
+ actorId: sourceId,
21
+ targetId: target.fighterId,
22
+ amount: healed,
23
+ });
24
+ return { updated, events };
25
+ };
@@ -0,0 +1,5 @@
1
+ export * from './crit.js';
2
+ export * from './damage.js';
3
+ export * from './heal.js';
4
+ export * from './typeEffectiveness.js';
5
+ export * from './winner.js';
@@ -0,0 +1,5 @@
1
+ export * from './crit.js';
2
+ export * from './damage.js';
3
+ export * from './heal.js';
4
+ export * from './typeEffectiveness.js';
5
+ export * from './winner.js';
@@ -0,0 +1,3 @@
1
+ import { TypeId } from "../../core";
2
+ import { RuntimeBattleState } from "../rules";
3
+ export declare const getTypeEffectiveness: (state: RuntimeBattleState, attackerTypeId: TypeId, defenderTypeId: TypeId) => number;
@@ -0,0 +1,7 @@
1
+ export const getTypeEffectiveness = (state, attackerTypeId, defenderTypeId) => {
2
+ const matrix = state.runtime.typeEffectiveness;
3
+ const row = matrix[attackerTypeId];
4
+ if (!row)
5
+ return 1;
6
+ return row[defenderTypeId] ?? 1;
7
+ };
@@ -0,0 +1,2 @@
1
+ import { RuntimeBattleState } from "../rules";
2
+ export declare const checkWinner: (state: RuntimeBattleState) => "none" | "player1" | "player2" | "draw";
@@ -0,0 +1,11 @@
1
+ export const checkWinner = (state) => {
2
+ const p1Alive = state.player1.fighterTeam.some((f) => f.isAlive && f.currentHp > 0);
3
+ const p2Alive = state.player2.fighterTeam.some((f) => f.isAlive && f.currentHp > 0);
4
+ if (p1Alive && !p2Alive)
5
+ return "player1";
6
+ if (!p1Alive && p2Alive)
7
+ return "player2";
8
+ if (!p1Alive && !p2Alive)
9
+ return "draw";
10
+ return "none";
11
+ };
@@ -0,0 +1 @@
1
+ export declare const dbg: (...args: unknown[]) => void;
@@ -0,0 +1,6 @@
1
+ const DEBUG_BATTLE = true;
2
+ export const dbg = (...args) => {
3
+ if (!DEBUG_BATTLE)
4
+ return;
5
+ console.log("[BATTLE]", ...args);
6
+ };
@@ -0,0 +1,7 @@
1
+ import { BattleEvent, BattleFighter, EffectDefinition, TypeId } from "../../core";
2
+ import { RuntimeBattleState } from "../rules";
3
+ export declare const applyEffectsOnTarget: (state: RuntimeBattleState, actor: BattleFighter, target: BattleFighter, moveTypeId: TypeId | null, isCritical: boolean, effects: EffectDefinition[]) => {
4
+ actor: BattleFighter;
5
+ target: BattleFighter;
6
+ events: BattleEvent[];
7
+ };