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.
package/dist/api/battle.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/battleState.js
CHANGED
|
@@ -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
|
-
|
|
99
|
-
|
|
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({
|