pokemon-io-core 0.0.82 → 0.0.84
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/core/battleState.d.ts +1 -1
- package/dist/core/engine.js +43 -7
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -1
- package/dist/engine/actions/forcedSwitch.d.ts +5 -0
- package/dist/engine/actions/forcedSwitch.js +44 -0
- package/dist/engine/actions/index.d.ts +5 -0
- package/dist/engine/actions/index.js +5 -0
- package/dist/engine/actions/item.d.ts +6 -0
- package/dist/engine/actions/item.js +61 -0
- package/dist/engine/actions/move.d.ts +6 -0
- package/dist/engine/actions/move.js +120 -0
- package/dist/engine/actions/priority.d.ts +6 -0
- package/dist/engine/actions/priority.js +30 -0
- package/dist/engine/actions/switch.d.ts +6 -0
- package/dist/engine/actions/switch.js +55 -0
- package/dist/engine/combat/crit.d.ts +2 -0
- package/dist/engine/combat/crit.js +4 -0
- package/dist/engine/combat/damage.d.ts +20 -0
- package/dist/engine/combat/damage.js +66 -0
- package/dist/engine/combat/heal.d.ts +6 -0
- package/dist/engine/combat/heal.js +25 -0
- package/dist/engine/combat/index.d.ts +5 -0
- package/dist/engine/combat/index.js +5 -0
- package/dist/engine/combat/typeEffectiveness.d.ts +3 -0
- package/dist/engine/combat/typeEffectiveness.js +7 -0
- package/dist/engine/combat/winner.d.ts +2 -0
- package/dist/engine/combat/winner.js +11 -0
- package/dist/engine/debug.d.ts +1 -0
- package/dist/engine/debug.js +6 -0
- package/dist/engine/effects/applyEffects.d.ts +7 -0
- package/dist/engine/effects/applyEffects.js +103 -0
- package/dist/engine/effects/index.d.ts +2 -0
- package/dist/engine/effects/index.js +2 -0
- package/dist/engine/effects/target.d.ts +6 -0
- package/dist/engine/effects/target.js +26 -0
- package/dist/engine/engine.d.ts +53 -0
- package/dist/engine/engine.js +1046 -0
- package/dist/engine/events.d.ts +4 -0
- package/dist/engine/events.js +12 -0
- package/dist/engine/fighters/fighter.d.ts +4 -0
- package/dist/engine/fighters/fighter.js +53 -0
- package/dist/engine/fighters/index.d.ts +3 -0
- package/dist/engine/fighters/index.js +3 -0
- package/dist/engine/fighters/selectors.d.ts +13 -0
- package/dist/engine/fighters/selectors.js +19 -0
- package/dist/engine/fighters/update.d.ts +3 -0
- package/dist/engine/fighters/update.js +30 -0
- package/dist/engine/index.d.ts +11 -1
- package/dist/engine/index.js +11 -1
- package/dist/engine/rng.d.ts +2 -0
- package/dist/engine/rng.js +8 -0
- package/dist/engine/rules.d.ts +44 -0
- package/dist/engine/rules.js +10 -0
- package/dist/engine/runtime.d.ts +7 -0
- package/dist/engine/runtime.js +49 -0
- package/dist/engine/status/apply.d.ts +6 -0
- package/dist/engine/status/apply.js +49 -0
- package/dist/engine/status/clear.d.ts +6 -0
- package/dist/engine/status/clear.js +47 -0
- package/dist/engine/status/endOfTurn.d.ts +6 -0
- package/dist/engine/status/endOfTurn.js +80 -0
- package/dist/engine/status/hardCc.d.ts +3 -0
- package/dist/engine/status/hardCc.js +4 -0
- package/dist/engine/status/index.d.ts +4 -0
- package/dist/engine/status/index.js +4 -0
- package/dist/engine/turn/index.d.ts +1 -0
- package/dist/engine/turn/index.js +1 -0
- package/dist/engine/turn/resolveTurn.d.ts +5 -0
- package/dist/engine/turn/resolveTurn.js +139 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/skins/CombatSkin.d.ts +1 -1
- package/dist/skins/cliches/clicheSkin.d.ts +2 -1
- package/dist/skins/pokemon/pokemonSkin.d.ts +2 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BattleRuntime } from "../
|
|
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 {
|
package/dist/core/engine.js
CHANGED
|
@@ -209,25 +209,26 @@ export const applyForcedSwitchChoice = (state, targetPlayer, newIndex) => {
|
|
|
209
209
|
if (forced.targetPlayer !== targetPlayer)
|
|
210
210
|
return { state, events: [] };
|
|
211
211
|
const self = targetPlayer === "player1" ? state.player1 : state.player2;
|
|
212
|
-
if (newIndex < 0 || newIndex >= self.fighterTeam.length)
|
|
212
|
+
if (newIndex < 0 || newIndex >= self.fighterTeam.length)
|
|
213
213
|
return { state, events: [] };
|
|
214
|
-
}
|
|
215
214
|
const fromIndex = self.activeIndex;
|
|
216
215
|
if (newIndex === fromIndex)
|
|
217
216
|
return { state, events: [] };
|
|
218
217
|
const toFighter = self.fighterTeam[newIndex];
|
|
219
|
-
if (!toFighter || !toFighter.isAlive || toFighter.currentHp <= 0)
|
|
218
|
+
if (!toFighter || !toFighter.isAlive || toFighter.currentHp <= 0)
|
|
220
219
|
return { state, events: [] };
|
|
221
|
-
}
|
|
222
220
|
const fromFighter = self.fighterTeam[fromIndex];
|
|
223
221
|
const events = [
|
|
224
222
|
{
|
|
225
|
-
...createBaseEvent(state.turnNumber, "fighter_switched", `El entrenador
|
|
223
|
+
...createBaseEvent(state.turnNumber, "fighter_switched", `El entrenador retira a ${fromFighter.fighterId} y manda a ${toFighter.fighterId}`),
|
|
226
224
|
fromFighterId: fromFighter.fighterId,
|
|
227
225
|
toFighterId: toFighter.fighterId,
|
|
228
226
|
},
|
|
227
|
+
{
|
|
228
|
+
...createBaseEvent(state.turnNumber, "turn_end", `Termina el turno ${state.turnNumber}`),
|
|
229
|
+
},
|
|
229
230
|
];
|
|
230
|
-
const
|
|
231
|
+
const switchedState = targetPlayer === "player1"
|
|
231
232
|
? {
|
|
232
233
|
...state,
|
|
233
234
|
forcedSwitch: null,
|
|
@@ -238,6 +239,10 @@ export const applyForcedSwitchChoice = (state, targetPlayer, newIndex) => {
|
|
|
238
239
|
forcedSwitch: null,
|
|
239
240
|
player2: { ...state.player2, activeIndex: newIndex },
|
|
240
241
|
};
|
|
242
|
+
const nextState = {
|
|
243
|
+
...switchedState,
|
|
244
|
+
turnNumber: switchedState.turnNumber + 1,
|
|
245
|
+
};
|
|
241
246
|
return { state: nextState, events };
|
|
242
247
|
};
|
|
243
248
|
let eventCounter = 0;
|
|
@@ -697,7 +702,32 @@ const resolveDamageMove = (state, playerKey, action) => {
|
|
|
697
702
|
// Todos los efectos (daño, aplicar estado, curas, etc.)
|
|
698
703
|
const { actor: finalSelf, target: finalOpp, events: extraEvents, } = applyEffectsOnTarget(state, updatedSelf, updatedOpponent, move.typeId, isCritical, move.effects);
|
|
699
704
|
events.push(...extraEvents);
|
|
700
|
-
|
|
705
|
+
let newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
|
|
706
|
+
// --- FORCED SWITCH (Roar) ---
|
|
707
|
+
const hasForceSwitch = move.effects.some((e) => e.kind === "force_switch");
|
|
708
|
+
if (hasForceSwitch) {
|
|
709
|
+
const targetPlayer = playerKey === "player1" ? "player2" : "player1";
|
|
710
|
+
const targetSide = targetPlayer === "player1" ? newState.player1 : newState.player2;
|
|
711
|
+
const activeIdx = targetSide.activeIndex;
|
|
712
|
+
const hasBenchAlive = targetSide.fighterTeam.some((f, idx) => {
|
|
713
|
+
if (idx === activeIdx)
|
|
714
|
+
return false;
|
|
715
|
+
return f.isAlive && f.currentHp > 0;
|
|
716
|
+
});
|
|
717
|
+
if (hasBenchAlive) {
|
|
718
|
+
newState = {
|
|
719
|
+
...newState,
|
|
720
|
+
forcedSwitch: {
|
|
721
|
+
targetPlayer,
|
|
722
|
+
reason: "roar",
|
|
723
|
+
},
|
|
724
|
+
};
|
|
725
|
+
events.push({
|
|
726
|
+
...createBaseEvent(state.turnNumber, "action_declared", `¡El entrenador rival debe cambiar de luchador!`),
|
|
727
|
+
actorId: self.fighterId,
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
701
731
|
return { state: newState, events };
|
|
702
732
|
};
|
|
703
733
|
// ------------------------------------------------------
|
|
@@ -970,6 +1000,12 @@ export const resolveTurn = (state, actions) => {
|
|
|
970
1000
|
// switch_fighter u otros no implementados aún
|
|
971
1001
|
continue;
|
|
972
1002
|
}
|
|
1003
|
+
if (currentState.forcedSwitch) {
|
|
1004
|
+
return {
|
|
1005
|
+
newState: currentState,
|
|
1006
|
+
events,
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
973
1009
|
const winnerAfterAction = checkWinner(currentState);
|
|
974
1010
|
if (winnerAfterAction !== "none") {
|
|
975
1011
|
events.push({
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -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,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,30 @@
|
|
|
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: 10, // prioridad especial (ajústalo si quieres)
|
|
22
|
+
speed: self.effectiveStats.speed,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// Items y switch: prioridad base 0 por ahora
|
|
26
|
+
return {
|
|
27
|
+
priority: 0,
|
|
28
|
+
speed: self.effectiveStats.speed,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -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,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,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;
|