pokemon-io-core 0.0.83 → 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/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/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;
|
|
@@ -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
|
+
};
|