pokemon-io-core 0.0.89 → 0.0.90

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.
@@ -13,7 +13,7 @@ export interface BattlePlayerView {
13
13
  activeIndex: number;
14
14
  }
15
15
  export type BattleStatus = "ongoing" | "awaiting_forced_switch" | "finished";
16
- export type ForcedSwitchReason = "roar";
16
+ export type ForcedSwitchReason = "roar" | "faint";
17
17
  export interface ForcedSwitchView {
18
18
  targetPlayerId: string;
19
19
  sourcePlayerId: string;
@@ -37,6 +37,9 @@ export type ForcedSwitchState = null | {
37
37
  kind: "move";
38
38
  moveId: string;
39
39
  actorFighterId: string;
40
+ } | {
41
+ kind: "faint";
42
+ actorFighterId: string;
40
43
  };
41
44
  };
42
45
  export interface BattleState {
@@ -1 +1,2 @@
1
+ // pokemon-io-core/src/core/battleState.ts
1
2
  export {};
@@ -1,5 +1,4 @@
1
- // ------------------------------------------------------
2
- // Bucle principal de turno
1
+ // turn/resolveTurn.ts (o donde lo tengas)
3
2
  import { resolveItemUse } from "../actions/item.js";
4
3
  import { resolveDamageMove } from "../actions/move.js";
5
4
  import { getMovePriorityAndSpeed } from "../actions/priority.js";
@@ -12,6 +11,42 @@ import { getOpponentAndSelf } from "../fighters/selectors.js";
12
11
  import { applyEndOfTurnStatuses } from "../status/endOfTurn.js";
13
12
  import { hasHardCc } from "../status/hardCc.js";
14
13
  // ------------------------------------------------------
14
+ // Helpers KO -> forced switch (estilo Pokémon real)
15
+ // ------------------------------------------------------
16
+ const isActiveFainted = (player) => {
17
+ const active = player.fighterTeam[player.activeIndex];
18
+ if (!active)
19
+ return true;
20
+ return !active.isAlive || active.currentHp <= 0;
21
+ };
22
+ const hasAliveBench = (player) => player.fighterTeam.some((f, idx) => idx !== player.activeIndex && f.isAlive && f.currentHp > 0);
23
+ /**
24
+ * Si hay un activo KO con banca viva, pedimos forced switch.
25
+ * Regla:
26
+ * - primero forzamos al oponente del que acaba de actuar (lo normal)
27
+ * - si no, forzamos al propio (recoil/status raro)
28
+ */
29
+ const computeForcedSwitchFromFaint = (state, lastActingPlayerKey, actorFighterId) => {
30
+ if (state.forcedSwitch)
31
+ return null;
32
+ const oppKey = lastActingPlayerKey === "player1" ? "player2" : "player1";
33
+ const oppPlayer = oppKey === "player1" ? state.player1 : state.player2;
34
+ if (isActiveFainted(oppPlayer) && hasAliveBench(oppPlayer)) {
35
+ return {
36
+ targetPlayer: oppKey,
37
+ reason: { kind: "faint", actorFighterId },
38
+ };
39
+ }
40
+ const selfPlayer = lastActingPlayerKey === "player1" ? state.player1 : state.player2;
41
+ if (isActiveFainted(selfPlayer) && hasAliveBench(selfPlayer)) {
42
+ return {
43
+ targetPlayer: lastActingPlayerKey,
44
+ reason: { kind: "faint", actorFighterId },
45
+ };
46
+ }
47
+ return null;
48
+ };
49
+ // ------------------------------------------------------
15
50
  export const resolveTurn = (state, actions) => {
16
51
  const runtimeState = state;
17
52
  let currentState = runtimeState;
@@ -19,6 +54,7 @@ export const resolveTurn = (state, actions) => {
19
54
  const team = player.fighterTeam.map((f) => recomputeEffectiveStatsForFighter(runtimeState, f));
20
55
  return { ...player, fighterTeam: team };
21
56
  };
57
+ // Recalcular stats efectivos antes del turno
22
58
  currentState = {
23
59
  ...currentState,
24
60
  player1: recalcForPlayer(currentState.player1),
@@ -45,13 +81,10 @@ export const resolveTurn = (state, actions) => {
45
81
  },
46
82
  ];
47
83
  entries.sort((a, b) => {
48
- if (b.priority !== a.priority) {
84
+ if (b.priority !== a.priority)
49
85
  return b.priority - a.priority;
50
- }
51
- if (b.speed !== a.speed) {
86
+ if (b.speed !== a.speed)
52
87
  return b.speed - a.speed;
53
- }
54
- // empate total → coin flip
55
88
  return Math.random() < 0.5 ? -1 : 1;
56
89
  });
57
90
  dbg(`TURN ${runtimeState.turnNumber} order`, entries.map((e) => ({
@@ -64,6 +97,30 @@ export const resolveTurn = (state, actions) => {
64
97
  const { playerKey, action } = entry;
65
98
  if (action.kind === "no_action")
66
99
  continue;
100
+ // ✅ CLAVE: permitir SWITCH incluso si el activo está KO (evita soft-locks)
101
+ if (action.kind === "switch_fighter") {
102
+ const result = resolveSwitchFighter(currentState, playerKey, action);
103
+ currentState = result.state;
104
+ events.push(...result.events);
105
+ // Si el engine ya trae forcedSwitch (por roar u otro), paramos aquí
106
+ if (currentState.forcedSwitch) {
107
+ return { newState: currentState, events };
108
+ }
109
+ const winnerAfterSwitch = checkWinner(currentState);
110
+ if (winnerAfterSwitch !== "none") {
111
+ events.push({
112
+ ...createBaseEvent(currentState.turnNumber, "battle_end", `La batalla termina: ${winnerAfterSwitch}`),
113
+ winner: winnerAfterSwitch,
114
+ });
115
+ const finalState = {
116
+ ...currentState,
117
+ turnNumber: currentState.turnNumber + 1,
118
+ };
119
+ return { newState: finalState, events };
120
+ }
121
+ continue;
122
+ }
123
+ // A partir de aquí: moves/items solo si el activo está vivo
67
124
  const { self } = getOpponentAndSelf(currentState, playerKey);
68
125
  if (!self.isAlive || self.currentHp <= 0)
69
126
  continue;
@@ -74,30 +131,31 @@ export const resolveTurn = (state, actions) => {
74
131
  });
75
132
  continue;
76
133
  }
77
- if (action.kind === "switch_fighter") {
78
- const result = resolveSwitchFighter(currentState, playerKey, action);
79
- currentState = result.state;
80
- events.push(...result.events);
81
- }
82
134
  if (action.kind === "use_move") {
83
135
  const result = resolveDamageMove(currentState, playerKey, action);
84
136
  currentState = result.state;
85
137
  events.push(...result.events);
86
138
  }
87
- if (action.kind === "use_item") {
139
+ else if (action.kind === "use_item") {
88
140
  const result = resolveItemUse(currentState, playerKey, action);
89
141
  currentState = result.state;
90
142
  events.push(...result.events);
91
143
  }
92
144
  else {
93
- // switch_fighter u otros no implementados aún
94
145
  continue;
95
146
  }
147
+ // ✅ forcedSwitch existente (roar, etc.)
96
148
  if (currentState.forcedSwitch) {
97
- return {
98
- newState: currentState,
99
- events,
149
+ return { newState: currentState, events };
150
+ }
151
+ // ✅ NUEVO: forced switch automático por KO (sin consumir turno)
152
+ const faintForced = computeForcedSwitchFromFaint(currentState, playerKey, self.fighterId);
153
+ if (faintForced) {
154
+ currentState = {
155
+ ...currentState,
156
+ forcedSwitch: faintForced,
100
157
  };
158
+ return { newState: currentState, events };
101
159
  }
102
160
  const winnerAfterAction = checkWinner(currentState);
103
161
  if (winnerAfterAction !== "none") {
@@ -109,15 +167,38 @@ export const resolveTurn = (state, actions) => {
109
167
  ...currentState,
110
168
  turnNumber: currentState.turnNumber + 1,
111
169
  };
112
- return {
113
- newState: finalState,
114
- events,
115
- };
170
+ return { newState: finalState, events };
116
171
  }
117
172
  }
173
+ // Fin de turno: estados (DoT, etc.)
118
174
  const statusResult = applyEndOfTurnStatuses(currentState);
119
175
  currentState = statusResult.state;
120
176
  events.push(...statusResult.events);
177
+ // ✅ NUEVO: KO por estados al final del turno también fuerza cambio
178
+ if (!currentState.forcedSwitch) {
179
+ const p1Needs = isActiveFainted(currentState.player1) && hasAliveBench(currentState.player1);
180
+ const p2Needs = isActiveFainted(currentState.player2) && hasAliveBench(currentState.player2);
181
+ if (p1Needs) {
182
+ currentState = {
183
+ ...currentState,
184
+ forcedSwitch: {
185
+ targetPlayer: "player1",
186
+ reason: { kind: "faint", actorFighterId: "status" },
187
+ },
188
+ };
189
+ return { newState: currentState, events };
190
+ }
191
+ if (p2Needs) {
192
+ currentState = {
193
+ ...currentState,
194
+ forcedSwitch: {
195
+ targetPlayer: "player2",
196
+ reason: { kind: "faint", actorFighterId: "status" },
197
+ },
198
+ };
199
+ return { newState: currentState, events };
200
+ }
201
+ }
121
202
  const winnerAtEnd = checkWinner(currentState);
122
203
  if (winnerAtEnd !== "none") {
123
204
  events.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pokemon-io-core",
3
- "version": "0.0.89",
3
+ "version": "0.0.90",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",