pokemon-io-core 0.0.97 → 0.0.98
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 +3 -2
- package/dist/core/battleState.d.ts +7 -1
- package/dist/core/events.d.ts +2 -2
- package/dist/core/moves.d.ts +5 -0
- package/dist/engine/actions/move.js +76 -8
- package/dist/engine/combat/damage.js +12 -1
- package/dist/engine/effects/applyEffects.js +3 -0
- package/dist/engine/fighters/fighter.js +10 -0
- package/dist/engine/status/endOfTurn.js +13 -3
- package/dist/engine/turn/resolveTurn.js +49 -7
- package/dist/skins/pokemon/items.js +62 -0
- package/dist/skins/pokemon/moves.js +212 -580
- package/dist/skins/pokemon/statuses.js +22 -53
- package/package.json +1 -1
package/dist/api/battle.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BattleEvent, StatusId } from "../core/index.js";
|
|
1
|
+
import { BattleEvent, StatusId, VolatileStatus } from "../core/index.js";
|
|
2
2
|
export interface BattlePlayerStatusView {
|
|
3
3
|
statusId: StatusId;
|
|
4
|
-
stacks:
|
|
4
|
+
stacks: number;
|
|
5
5
|
remainingTurns: number;
|
|
6
6
|
}
|
|
7
7
|
export interface BattlePlayerMoveView {
|
|
@@ -25,6 +25,7 @@ export interface BattlePlayerView {
|
|
|
25
25
|
nickname: string;
|
|
26
26
|
hp: number;
|
|
27
27
|
statuses: BattlePlayerStatusView[];
|
|
28
|
+
volatileStatus: VolatileStatus;
|
|
28
29
|
activeFighterId: string | null;
|
|
29
30
|
activeIndex: number;
|
|
30
31
|
activeMoves: BattlePlayerMoveView[];
|
|
@@ -11,9 +11,14 @@ export interface EquippedItem {
|
|
|
11
11
|
}
|
|
12
12
|
export interface ActiveStatus {
|
|
13
13
|
statusId: StatusId;
|
|
14
|
-
stacks:
|
|
14
|
+
stacks: number;
|
|
15
15
|
remainingTurns: number;
|
|
16
16
|
}
|
|
17
|
+
export type VolatileStatus = {
|
|
18
|
+
kind: "charging";
|
|
19
|
+
moveId: string;
|
|
20
|
+
invulnerability?: string;
|
|
21
|
+
} | null;
|
|
17
22
|
export interface BattleFighter {
|
|
18
23
|
fighterId: FighterId;
|
|
19
24
|
classId: TypeId;
|
|
@@ -22,6 +27,7 @@ export interface BattleFighter {
|
|
|
22
27
|
baseStats: FighterStats;
|
|
23
28
|
effectiveStats: FighterStats;
|
|
24
29
|
statStages: StatStages;
|
|
30
|
+
volatileStatus: VolatileStatus;
|
|
25
31
|
moves: EquippedMove[];
|
|
26
32
|
amuletId: AmuletId | null;
|
|
27
33
|
statuses: ActiveStatus[];
|
package/dist/core/events.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface StatusAppliedEvent extends BaseBattleEvent {
|
|
|
60
60
|
kind: "status_applied";
|
|
61
61
|
targetId: FighterId;
|
|
62
62
|
statusId: StatusId;
|
|
63
|
-
stacks:
|
|
63
|
+
stacks: number;
|
|
64
64
|
durationTurns: number;
|
|
65
65
|
}
|
|
66
66
|
export interface FighterSwitchedEvent extends BaseBattleEvent {
|
|
@@ -70,7 +70,7 @@ export interface FighterSwitchedEvent extends BaseBattleEvent {
|
|
|
70
70
|
toHp: number;
|
|
71
71
|
toStatuses: {
|
|
72
72
|
statusId: string;
|
|
73
|
-
stacks:
|
|
73
|
+
stacks: number;
|
|
74
74
|
remainingTurns: number;
|
|
75
75
|
}[];
|
|
76
76
|
}
|
package/dist/core/moves.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type EffectKind = "damage" | "heal" | "shield" | "modify_stats" | "apply_
|
|
|
4
4
|
export type TargetKind = EffectTarget;
|
|
5
5
|
interface BaseEffect {
|
|
6
6
|
target?: EffectTarget;
|
|
7
|
+
chance?: number;
|
|
7
8
|
}
|
|
8
9
|
export interface DamageEffect extends BaseEffect {
|
|
9
10
|
kind: "damage";
|
|
@@ -76,6 +77,10 @@ export interface MoveDefinition {
|
|
|
76
77
|
accuracy?: number;
|
|
77
78
|
maxPP: number;
|
|
78
79
|
priority?: number;
|
|
80
|
+
charge?: {
|
|
81
|
+
invulnerability?: string;
|
|
82
|
+
};
|
|
83
|
+
bypassInvulnerability?: string[];
|
|
79
84
|
effects: EffectDefinition[];
|
|
80
85
|
}
|
|
81
86
|
export {};
|
|
@@ -39,18 +39,83 @@ export const resolveDamageMove = (state, playerKey, action) => {
|
|
|
39
39
|
...createBaseEvent(state.turnNumber, "action_declared", `${self.fighterId} usó ${move.name}`),
|
|
40
40
|
actorId: self.fighterId,
|
|
41
41
|
});
|
|
42
|
-
//
|
|
43
|
-
|
|
42
|
+
// Prepare mutable copies for updates through the function
|
|
43
|
+
let updatedSelf = { ...self };
|
|
44
|
+
let updatedOpponent = { ...opponent };
|
|
45
|
+
// -------------------------------------------------------------
|
|
46
|
+
// 1. Invulnerability Check (Target is Charging/Flying/Digging)
|
|
47
|
+
// -------------------------------------------------------------
|
|
48
|
+
if (updatedOpponent.volatileStatus?.kind === "charging" &&
|
|
49
|
+
updatedOpponent.volatileStatus.invulnerability) {
|
|
50
|
+
const isBypassed = move.bypassInvulnerability?.includes(updatedOpponent.volatileStatus.invulnerability);
|
|
51
|
+
if (!isBypassed) {
|
|
52
|
+
dbg("MISS_INVULNERABLE", {
|
|
53
|
+
attacker: self.fighterId,
|
|
54
|
+
defender: opponent.fighterId,
|
|
55
|
+
invulnerability: updatedOpponent.volatileStatus.invulnerability,
|
|
56
|
+
});
|
|
57
|
+
events.push({
|
|
58
|
+
...createBaseEvent(state.turnNumber, "move_miss", `${self.fighterId} no alcanza a ${opponent.fighterId}`),
|
|
59
|
+
actorId: self.fighterId,
|
|
60
|
+
moveId: move.id,
|
|
61
|
+
targetId: opponent.fighterId,
|
|
62
|
+
});
|
|
63
|
+
// Still consume PP? Normally yes, you missed.
|
|
64
|
+
const updatedMoves = updatedSelf.moves.map((m, idx) => idx === action.moveIndex
|
|
65
|
+
? { ...m, currentPP: Math.max(0, m.currentPP - 1) }
|
|
66
|
+
: m);
|
|
67
|
+
updatedSelf.moves = updatedMoves;
|
|
68
|
+
const newState = updateFightersInState(state, playerKey, updatedSelf, updatedOpponent);
|
|
69
|
+
return { state: newState, events };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// -------------------------------------------------------------
|
|
73
|
+
// 2. Charging Logic (Self)
|
|
74
|
+
// -------------------------------------------------------------
|
|
75
|
+
const isCharging = updatedSelf.volatileStatus?.kind === "charging";
|
|
76
|
+
// A) Start Charging (Turn 1)
|
|
77
|
+
if (!isCharging && move.charge) {
|
|
78
|
+
dbg("START_CHARGING", { moveId: move.id });
|
|
79
|
+
// Set status
|
|
80
|
+
updatedSelf.volatileStatus = {
|
|
81
|
+
kind: "charging",
|
|
82
|
+
moveId: move.id,
|
|
83
|
+
invulnerability: move.charge.invulnerability,
|
|
84
|
+
};
|
|
85
|
+
// Updates message
|
|
86
|
+
events[events.length - 1].message = `${self.fighterId} prepara ${move.name}...`;
|
|
87
|
+
// Deduct PP on charge turn
|
|
88
|
+
const updatedMoves = updatedSelf.moves.map((m, idx) => idx === action.moveIndex
|
|
89
|
+
? { ...m, currentPP: Math.max(0, m.currentPP - 1) }
|
|
90
|
+
: m);
|
|
91
|
+
updatedSelf.moves = updatedMoves;
|
|
92
|
+
const newState = updateFightersInState(state, playerKey, updatedSelf, updatedOpponent);
|
|
93
|
+
return { state: newState, events };
|
|
94
|
+
}
|
|
95
|
+
// B) Execute Charge (Turn 2)
|
|
96
|
+
if (isCharging) {
|
|
97
|
+
// Clear status
|
|
98
|
+
updatedSelf.volatileStatus = null;
|
|
99
|
+
// Do NOT deduct PP (paid in turn 1)
|
|
100
|
+
// We do nothing to moves here.
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Normal Move (not charging move, and not currently charging)
|
|
104
|
+
// Deduct PP
|
|
105
|
+
const updatedMoves = updatedSelf.moves.map((m, idx) => idx === action.moveIndex
|
|
106
|
+
? { ...m, currentPP: Math.max(0, m.currentPP - 1) }
|
|
107
|
+
: m);
|
|
108
|
+
updatedSelf.moves = updatedMoves;
|
|
109
|
+
}
|
|
110
|
+
// -------------------------------------------------------------
|
|
111
|
+
// 3. Accuracy Calculation
|
|
112
|
+
// -------------------------------------------------------------
|
|
113
|
+
const accStage = updatedSelf.statStages.accuracy - updatedOpponent.statStages.evasion;
|
|
44
114
|
const accMult = getAccuracyMultiplier(accStage);
|
|
45
115
|
const baseAcc = move.accuracy ?? 100;
|
|
46
116
|
const finalAccuracy = baseAcc * accMult;
|
|
47
117
|
const hitRoll = randomInRange(0, 100);
|
|
48
118
|
const hit = hitRoll < finalAccuracy;
|
|
49
|
-
const updatedMoves = self.moves.map((m, idx) => idx === action.moveIndex
|
|
50
|
-
? { ...m, currentPP: Math.max(0, m.currentPP - 1) }
|
|
51
|
-
: m);
|
|
52
|
-
let updatedSelf = { ...self, moves: updatedMoves };
|
|
53
|
-
let updatedOpponent = { ...opponent };
|
|
54
119
|
if (!hit) {
|
|
55
120
|
dbg("MISS", {
|
|
56
121
|
fighterId: self.fighterId,
|
|
@@ -68,6 +133,9 @@ export const resolveDamageMove = (state, playerKey, action) => {
|
|
|
68
133
|
const newState = updateFightersInState(state, playerKey, updatedSelf, updatedOpponent);
|
|
69
134
|
return { state: newState, events };
|
|
70
135
|
}
|
|
136
|
+
// -------------------------------------------------------------
|
|
137
|
+
// 4. Hit / Crit / Damage / Effects
|
|
138
|
+
// -------------------------------------------------------------
|
|
71
139
|
const critChance = computeCritChance(state.runtime.rules, updatedSelf.effectiveStats.crit);
|
|
72
140
|
const isCritical = chance(critChance);
|
|
73
141
|
const effectiveness = getTypeEffectiveness(state, move.typeId, updatedOpponent.classId);
|
|
@@ -92,7 +160,7 @@ export const resolveDamageMove = (state, playerKey, action) => {
|
|
|
92
160
|
moveName: move.name,
|
|
93
161
|
effects: move.effects,
|
|
94
162
|
});
|
|
95
|
-
//
|
|
163
|
+
// Apply Effects
|
|
96
164
|
const { actor: finalSelf, target: finalOpp, events: extraEvents, } = applyEffectsOnTarget(state, updatedSelf, updatedOpponent, move.typeId, isCritical, move.effects);
|
|
97
165
|
events.push(...extraEvents);
|
|
98
166
|
let newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
|
|
@@ -44,11 +44,22 @@ export const computeDamage = (input) => {
|
|
|
44
44
|
export const applyDamageToFighter = (state, defender, amount, actorId, isCritical) => {
|
|
45
45
|
const events = [];
|
|
46
46
|
const newHp = Math.max(0, defender.currentHp - amount);
|
|
47
|
-
|
|
47
|
+
let updatedDefender = {
|
|
48
48
|
...defender,
|
|
49
49
|
currentHp: newHp,
|
|
50
50
|
isAlive: newHp > 0,
|
|
51
51
|
};
|
|
52
|
+
// ✅ Sleep Wake check (30%)
|
|
53
|
+
if (updatedDefender.isAlive &&
|
|
54
|
+
updatedDefender.statuses.some((s) => s.statusId === "sleep") &&
|
|
55
|
+
Math.random() < 0.3) {
|
|
56
|
+
updatedDefender.statuses = updatedDefender.statuses.filter((s) => s.statusId !== "sleep");
|
|
57
|
+
events.push({
|
|
58
|
+
...createBaseEvent(state.turnNumber, "status_cleared", `${defender.fighterId} se despierta por el golpe`),
|
|
59
|
+
targetId: defender.fighterId,
|
|
60
|
+
statusId: "sleep",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
52
63
|
events.push({
|
|
53
64
|
...createBaseEvent(state.turnNumber, "damage", `${actorId} inflige ${amount} de daño a ${defender.fighterId}`),
|
|
54
65
|
actorId,
|
|
@@ -25,6 +25,9 @@ export const applyEffectsOnTarget = (state, actor, target, moveTypeId, isCritica
|
|
|
25
25
|
effectsCount: effects?.length ?? 0,
|
|
26
26
|
});
|
|
27
27
|
for (const eff of effects) {
|
|
28
|
+
if (eff.chance !== undefined && Math.random() > eff.chance) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
28
31
|
dbg("EFFECT_LOOP", eff);
|
|
29
32
|
switch (eff.kind) {
|
|
30
33
|
case "heal": {
|
|
@@ -25,6 +25,7 @@ export const createBattleFighter = (cfg) => {
|
|
|
25
25
|
})),
|
|
26
26
|
amuletId: cfg.amulet ? cfg.amulet.id : null,
|
|
27
27
|
statuses: [],
|
|
28
|
+
volatileStatus: null,
|
|
28
29
|
isAlive: true,
|
|
29
30
|
};
|
|
30
31
|
};
|
|
@@ -63,6 +64,15 @@ export const recomputeEffectiveStatsForFighter = (state, fighter) => {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
// 3) Aquí podrías aplicar amuletos, buffs temporales o lo que quieras
|
|
67
|
+
// 3) Hardcoded Status Multipliers (Balance Overhaul)
|
|
68
|
+
const hasBurn = fighter.statuses.some((s) => s.statusId === "burn");
|
|
69
|
+
const hasParalysis = fighter.statuses.some((s) => s.statusId === "paralysis");
|
|
70
|
+
if (hasBurn) {
|
|
71
|
+
eff.offense = Math.floor(eff.offense * 0.5);
|
|
72
|
+
}
|
|
73
|
+
if (hasParalysis) {
|
|
74
|
+
eff.speed = Math.floor(eff.speed * 0.5);
|
|
75
|
+
}
|
|
66
76
|
return {
|
|
67
77
|
...fighter,
|
|
68
78
|
effectiveStats: eff,
|
|
@@ -24,7 +24,7 @@ export const applyEndOfTurnStatuses = (state) => {
|
|
|
24
24
|
let damageFromStatus = 0;
|
|
25
25
|
def.effectsPerStack.forEach((eff) => {
|
|
26
26
|
if (eff.kind === "damage") {
|
|
27
|
-
const stacksMultiplier = st.stacks
|
|
27
|
+
const stacksMultiplier = st.stacks;
|
|
28
28
|
const base = typeof eff.flatAmount === "number"
|
|
29
29
|
? eff.flatAmount
|
|
30
30
|
: typeof eff.basePower === "number"
|
|
@@ -45,8 +45,18 @@ export const applyEndOfTurnStatuses = (state) => {
|
|
|
45
45
|
}
|
|
46
46
|
// solo decrementamos si sigue vivo
|
|
47
47
|
const remaining = st.remainingTurns - 1;
|
|
48
|
-
if (remaining > 0)
|
|
49
|
-
|
|
48
|
+
if (remaining > 0) {
|
|
49
|
+
let nextStacks = st.stacks;
|
|
50
|
+
// ✅ Toxic Scaling
|
|
51
|
+
if (st.statusId === "poison") {
|
|
52
|
+
nextStacks += 1;
|
|
53
|
+
}
|
|
54
|
+
updatedStatuses.push({
|
|
55
|
+
...st,
|
|
56
|
+
remainingTurns: remaining,
|
|
57
|
+
stacks: nextStacks,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
50
60
|
else {
|
|
51
61
|
events.push({
|
|
52
62
|
...createBaseEvent(state.turnNumber, "status_expired", `El estado ${st.statusId} expira en ${updated.fighterId}`),
|
|
@@ -61,9 +61,29 @@ export const resolveTurn = (state, actions) => {
|
|
|
61
61
|
player2: recalcForPlayer(currentState.player2),
|
|
62
62
|
};
|
|
63
63
|
const events = [];
|
|
64
|
+
// -----------------------------------------------------------------------
|
|
65
|
+
// 1. Force Actions for Charging Fighters (Fly, Dig, etc.)
|
|
66
|
+
// -----------------------------------------------------------------------
|
|
67
|
+
const overrideAction = (player, originalAction) => {
|
|
68
|
+
const active = player.fighterTeam[player.activeIndex];
|
|
69
|
+
if (active && active.isAlive && active.volatileStatus?.kind === "charging") {
|
|
70
|
+
const moveId = active.volatileStatus.moveId;
|
|
71
|
+
const moveIndex = active.moves.findIndex((m) => m.moveId === moveId);
|
|
72
|
+
if (moveIndex >= 0) {
|
|
73
|
+
return { kind: "use_move", moveIndex };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return originalAction;
|
|
77
|
+
};
|
|
78
|
+
const finalActions = {
|
|
79
|
+
player1: overrideAction(runtimeState.player1, actions.player1),
|
|
80
|
+
player2: overrideAction(runtimeState.player2, actions.player2),
|
|
81
|
+
};
|
|
64
82
|
dbg(`TURN ${runtimeState.turnNumber} start`, {
|
|
65
|
-
p1Action:
|
|
66
|
-
p2Action:
|
|
83
|
+
p1Action: finalActions.player1.kind,
|
|
84
|
+
p2Action: finalActions.player2.kind,
|
|
85
|
+
originalP1: actions.player1.kind,
|
|
86
|
+
originalP2: actions.player2.kind,
|
|
67
87
|
});
|
|
68
88
|
events.push({
|
|
69
89
|
...createBaseEvent(runtimeState.turnNumber, "turn_start", `Comienza el turno ${runtimeState.turnNumber}`),
|
|
@@ -71,13 +91,13 @@ export const resolveTurn = (state, actions) => {
|
|
|
71
91
|
const entries = [
|
|
72
92
|
{
|
|
73
93
|
playerKey: "player1",
|
|
74
|
-
action:
|
|
75
|
-
...getMovePriorityAndSpeed(runtimeState, "player1",
|
|
94
|
+
action: finalActions.player1,
|
|
95
|
+
...getMovePriorityAndSpeed(runtimeState, "player1", finalActions.player1),
|
|
76
96
|
},
|
|
77
97
|
{
|
|
78
98
|
playerKey: "player2",
|
|
79
|
-
action:
|
|
80
|
-
...getMovePriorityAndSpeed(runtimeState, "player2",
|
|
99
|
+
action: finalActions.player2,
|
|
100
|
+
...getMovePriorityAndSpeed(runtimeState, "player2", finalActions.player2),
|
|
81
101
|
},
|
|
82
102
|
];
|
|
83
103
|
entries.sort((a, b) => {
|
|
@@ -124,9 +144,31 @@ export const resolveTurn = (state, actions) => {
|
|
|
124
144
|
const { self } = getOpponentAndSelf(currentState, playerKey);
|
|
125
145
|
if (!self.isAlive || self.currentHp <= 0)
|
|
126
146
|
continue;
|
|
147
|
+
// ✅ Paralysis check (25% chance to skip)
|
|
148
|
+
if (self.statuses.some((s) => s.statusId === "paralysis") &&
|
|
149
|
+
Math.random() < 0.25) {
|
|
150
|
+
events.push({
|
|
151
|
+
...createBaseEvent(currentState.turnNumber, "action_skipped_hard_cc", `${self.fighterId} está paralizado y no puede moverse`),
|
|
152
|
+
actorId: self.fighterId,
|
|
153
|
+
});
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
127
156
|
if (hasHardCc(currentState, self)) {
|
|
157
|
+
let interruptionSuffix = "";
|
|
158
|
+
// Si estaba cargando y es interrumpido, pierde la carga (Fly/Dig fallan)
|
|
159
|
+
if (self.volatileStatus?.kind === "charging") {
|
|
160
|
+
const newFighter = { ...self, volatileStatus: null };
|
|
161
|
+
currentState = {
|
|
162
|
+
...currentState,
|
|
163
|
+
[playerKey]: {
|
|
164
|
+
...currentState[playerKey],
|
|
165
|
+
fighterTeam: currentState[playerKey].fighterTeam.map((f) => f.fighterId === self.fighterId ? newFighter : f),
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
interruptionSuffix = " (Concentración perdida)";
|
|
169
|
+
}
|
|
128
170
|
events.push({
|
|
129
|
-
...createBaseEvent(currentState.turnNumber, "action_skipped_hard_cc", `${self.fighterId} no puede actuar por control total`),
|
|
171
|
+
...createBaseEvent(currentState.turnNumber, "action_skipped_hard_cc", `${self.fighterId} no puede actuar por control total${interruptionSuffix}`),
|
|
130
172
|
actorId: self.fighterId,
|
|
131
173
|
});
|
|
132
174
|
continue;
|
|
@@ -140,4 +140,66 @@ export const POKEMON_ITEMS = [
|
|
|
140
140
|
],
|
|
141
141
|
image: "green-berry",
|
|
142
142
|
},
|
|
143
|
+
{
|
|
144
|
+
id: "attack_potion",
|
|
145
|
+
name: "Poción Ofensiva",
|
|
146
|
+
category: "status_buff",
|
|
147
|
+
maxUses: 2,
|
|
148
|
+
target: "self",
|
|
149
|
+
effects: [
|
|
150
|
+
{ kind: "heal", amount: 30 },
|
|
151
|
+
{ kind: "modify_stats", target: "self", offenseDelta: 1 },
|
|
152
|
+
],
|
|
153
|
+
image: "potion_red", // Placeholder
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "defense_potion",
|
|
157
|
+
name: "Poción Defensiva",
|
|
158
|
+
category: "status_buff",
|
|
159
|
+
maxUses: 2,
|
|
160
|
+
target: "self",
|
|
161
|
+
effects: [
|
|
162
|
+
{ kind: "heal", amount: 30 },
|
|
163
|
+
{ kind: "modify_stats", target: "self", defenseDelta: 1 },
|
|
164
|
+
],
|
|
165
|
+
image: "potion_blue", // Placeholder
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "speed_elixir",
|
|
169
|
+
name: "Elixir de Velocidad",
|
|
170
|
+
category: "status_buff",
|
|
171
|
+
maxUses: 2,
|
|
172
|
+
target: "self",
|
|
173
|
+
effects: [
|
|
174
|
+
{ kind: "heal", amount: 30 },
|
|
175
|
+
{ kind: "modify_stats", target: "self", speedDelta: 1 },
|
|
176
|
+
],
|
|
177
|
+
image: "potion_yellow", // Placeholder
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "crit_injection",
|
|
181
|
+
name: "Inyección Crítica",
|
|
182
|
+
category: "status_buff",
|
|
183
|
+
maxUses: 2,
|
|
184
|
+
target: "self",
|
|
185
|
+
effects: [
|
|
186
|
+
{ kind: "heal", amount: 30 },
|
|
187
|
+
{ kind: "modify_crit_chance", target: "self", delta: 1 },
|
|
188
|
+
],
|
|
189
|
+
image: "potion_purple", // Placeholder
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "chesto_berry",
|
|
193
|
+
name: "Baya Atania",
|
|
194
|
+
category: "status_buff",
|
|
195
|
+
maxUses: 1,
|
|
196
|
+
target: "self",
|
|
197
|
+
effects: [
|
|
198
|
+
{
|
|
199
|
+
kind: "clear_status",
|
|
200
|
+
statusIds: ["sleep"],
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
image: "chesto-berry", // Placeholder
|
|
204
|
+
},
|
|
143
205
|
];
|