pokemon-io-core 0.0.101 → 0.0.102

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.
@@ -65,5 +65,17 @@ export interface BattleView {
65
65
  player1: boolean;
66
66
  player2: boolean;
67
67
  };
68
+ pendingRandomEvent?: {
69
+ eventName: string;
70
+ eventDescription: string;
71
+ imageUrl?: string;
72
+ effects: Array<{
73
+ effectType: string;
74
+ target: string;
75
+ percentValue?: number;
76
+ statType?: string;
77
+ statChange?: number;
78
+ }>;
79
+ } | null;
68
80
  forcedSwitch: ForcedSwitchView | null;
69
81
  }
@@ -0,0 +1,46 @@
1
+ import { AmuletDefinition } from "./amulets.js";
2
+ import { FighterDefinition } from "./fighters.js";
3
+ import { ItemDefinition } from "./items.js";
4
+ import { MoveDefinition } from "./moves.js";
5
+ import { StatusDefinition } from "./status.js";
6
+ import { TypeDefinition, TypeEffectivenessMatrix } from "./types.js";
7
+ import { ItemId, MoveId, StatusId, TypeId } from "./ids.js";
8
+ export interface BattleRules {
9
+ baseCritChance: number;
10
+ critPerStat: number;
11
+ critMultiplier: number;
12
+ stabMultiplier: number;
13
+ randomMinDamageFactor: number;
14
+ randomMaxDamageFactor: number;
15
+ }
16
+ export interface BattleRuntime {
17
+ rules: BattleRules;
18
+ typesById: Record<TypeId, TypeDefinition>;
19
+ movesById: Record<MoveId, MoveDefinition>;
20
+ itemsById: Record<ItemId, ItemDefinition>;
21
+ amuletsById: Record<string, AmuletDefinition>;
22
+ statusesById: Record<StatusId, StatusDefinition>;
23
+ typeEffectiveness: TypeEffectivenessMatrix;
24
+ }
25
+ export interface PlayerFighterBattleConfig {
26
+ fighter: FighterDefinition;
27
+ maxHp: number;
28
+ moves: MoveDefinition[];
29
+ amulet: AmuletDefinition | null;
30
+ }
31
+ export interface PlayerBattleConfig {
32
+ fighters: PlayerFighterBattleConfig[];
33
+ items: ItemDefinition[];
34
+ amulet: AmuletDefinition | null;
35
+ }
36
+ export interface BattleConfig {
37
+ types: TypeDefinition[];
38
+ typeEffectiveness: TypeEffectivenessMatrix;
39
+ moves: MoveDefinition[];
40
+ items: ItemDefinition[];
41
+ amulets: AmuletDefinition[];
42
+ statuses: StatusDefinition[];
43
+ player1: PlayerBattleConfig;
44
+ player2: PlayerBattleConfig;
45
+ rules?: Partial<BattleRules>;
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { BattleRuntime } from "../engine/rules.js";
1
+ import { BattleRuntime } from "./battleRuntime.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 {
@@ -62,5 +62,5 @@ export interface BattleState {
62
62
  player1: PlayerBattleState;
63
63
  player2: PlayerBattleState;
64
64
  runtime: BattleRuntime;
65
- forcedSwitch: ForcedSwitchState;
65
+ forcedSwitch?: ForcedSwitchState;
66
66
  }
@@ -18,6 +18,7 @@ export interface StatsChangedEvent extends BaseBattleEvent {
18
18
  accuracy?: number;
19
19
  evasion?: number;
20
20
  };
21
+ source?: "move" | "status" | "item" | "random_event";
21
22
  }
22
23
  export interface ActionDeclaredEvent extends BaseBattleEvent {
23
24
  kind: "action_declared";
@@ -49,12 +50,14 @@ export interface DamageEvent extends BaseBattleEvent {
49
50
  targetId: FighterId;
50
51
  amount: number;
51
52
  isCritical: boolean;
53
+ source?: "move" | "status" | "item" | "random_event";
52
54
  }
53
55
  export interface HealEvent extends BaseBattleEvent {
54
56
  kind: "heal";
55
57
  actorId: FighterId;
56
58
  targetId: FighterId;
57
59
  amount: number;
60
+ source?: "move" | "status" | "item" | "random_event";
58
61
  }
59
62
  export interface StatusAppliedEvent extends BaseBattleEvent {
60
63
  kind: "status_applied";
@@ -98,6 +101,15 @@ export interface RandomEventTriggeredEvent extends BaseBattleEvent {
98
101
  eventId: string;
99
102
  eventName: string;
100
103
  eventDescription: string;
104
+ eventRarity: "common" | "rare" | "epic" | "legendary";
105
+ imageUrl?: string;
106
+ effects: Array<{
107
+ effectType: string;
108
+ target: string;
109
+ percentValue?: number;
110
+ statType?: string;
111
+ statChange?: number;
112
+ }>;
101
113
  }
102
114
  export interface RandomEventEffectEvent extends BaseBattleEvent {
103
115
  kind: "random_event_effect";
@@ -1,13 +1,16 @@
1
1
  export * from "./actions.js";
2
- export * from "./amulets.js";
3
- export * from "./battleState.js";
2
+ export * from "./randomEvents.js";
3
+ export { CORE_RANDOM_EVENTS, POKEMON_RANDOM_EVENTS } from "./randomEventsList.js";
4
4
  export * from "./emotes.js";
5
5
  export * from "./events.js";
6
+ export * from "./events.js";
6
7
  export * from "./fighters.js";
7
8
  export * from "./ids.js";
8
9
  export * from "./items.js";
9
10
  export * from "./moves.js";
10
11
  export * from "./status.js";
11
- export * from "./types.js";
12
- export * from "./randomEvents.js";
13
12
  export * from "./randomEventsList.js";
13
+ export * from "./battleRuntime.js";
14
+ export * from "./battleState.js";
15
+ export * from "./amulets.js";
16
+ export * from "./types.js";
@@ -1,13 +1,16 @@
1
1
  export * from "./actions.js";
2
- export * from "./amulets.js";
3
- export * from "./battleState.js";
2
+ export * from "./randomEvents.js";
3
+ export { CORE_RANDOM_EVENTS, POKEMON_RANDOM_EVENTS } from "./randomEventsList.js";
4
4
  export * from "./emotes.js";
5
5
  export * from "./events.js";
6
+ export * from "./events.js";
6
7
  export * from "./fighters.js";
7
8
  export * from "./ids.js";
8
9
  export * from "./items.js";
9
10
  export * from "./moves.js";
10
11
  export * from "./status.js";
11
- export * from "./types.js";
12
- export * from "./randomEvents.js";
13
12
  export * from "./randomEventsList.js";
13
+ export * from "./battleRuntime.js";
14
+ export * from "./battleState.js";
15
+ export * from "./amulets.js";
16
+ export * from "./types.js";
@@ -1,6 +1,11 @@
1
1
  import type { RandomEventDefinition } from "./randomEvents.js";
2
2
  /**
3
3
  * CORE Random Events - Generic events that work with any skin
4
- * Phase 1: Initial 4 events for MVP
4
+ * Phase 1 + Phase 2: 9 total CORE events
5
5
  */
6
6
  export declare const CORE_RANDOM_EVENTS: RandomEventDefinition[];
7
+ /**
8
+ * POKEMON-SPECIFIC Random Events
9
+ * These only appear when using the Pokemon skin
10
+ */
11
+ export declare const POKEMON_RANDOM_EVENTS: RandomEventDefinition[];
@@ -1,10 +1,10 @@
1
1
  // @pokemon-io/combat-core/src/core/randomEventsList.ts
2
2
  /**
3
3
  * CORE Random Events - Generic events that work with any skin
4
- * Phase 1: Initial 4 events for MVP
4
+ * Phase 1 + Phase 2: 9 total CORE events
5
5
  */
6
6
  export const CORE_RANDOM_EVENTS = [
7
- // ==================== COMMON ====================
7
+ // ==================== COMMON (4 events) ====================
8
8
  {
9
9
  id: "fatigue",
10
10
  name: "💤 Cansancio General",
@@ -32,7 +32,35 @@ export const CORE_RANDOM_EVENTS = [
32
32
  },
33
33
  ],
34
34
  },
35
- // ==================== RARE ====================
35
+ {
36
+ id: "adrenaline_rush",
37
+ name: "⚡ Adrenalina",
38
+ description: "Una inyección de adrenalina aumenta la velocidad de ambos.",
39
+ rarity: "common",
40
+ effects: [
41
+ {
42
+ effectType: "stat_change",
43
+ target: "both",
44
+ statType: "speed",
45
+ statChange: 1,
46
+ },
47
+ ],
48
+ },
49
+ {
50
+ id: "intimidation",
51
+ name: "😨 Intimidación Mutua",
52
+ description: "Ambos luchadores se sienten intimidados.",
53
+ rarity: "common",
54
+ effects: [
55
+ {
56
+ effectType: "stat_change",
57
+ target: "both",
58
+ statType: "offense",
59
+ statChange: -1,
60
+ },
61
+ ],
62
+ },
63
+ // ==================== RARE (3 events) ====================
36
64
  {
37
65
  id: "police_raid",
38
66
  name: "🚨 Redada Policial",
@@ -50,7 +78,34 @@ export const CORE_RANDOM_EVENTS = [
50
78
  },
51
79
  ],
52
80
  },
53
- // ==================== EPIC ====================
81
+ {
82
+ id: "meteor_shower",
83
+ name: "☄️ Lluvia de Meteoritos",
84
+ description: "¡Meteoritos caen del cielo dañando a ambos!",
85
+ rarity: "rare",
86
+ effects: [
87
+ {
88
+ effectType: "damage_percent",
89
+ target: "both",
90
+ percentValue: 10,
91
+ },
92
+ ],
93
+ },
94
+ {
95
+ id: "lucky_charm",
96
+ name: "🍀 Amuleto de la Suerte",
97
+ description: "Un luchador aleatorio se siente afortunado.",
98
+ rarity: "rare",
99
+ effects: [
100
+ {
101
+ effectType: "stat_change",
102
+ target: "random_one",
103
+ statType: "crit",
104
+ statChange: 2,
105
+ },
106
+ ],
107
+ },
108
+ // ==================== EPIC (2 events) ====================
54
109
  {
55
110
  id: "casino",
56
111
  name: "🎰 Casino",
@@ -77,4 +132,171 @@ export const CORE_RANDOM_EVENTS = [
77
132
  },
78
133
  ],
79
134
  },
135
+ {
136
+ id: "reset_button",
137
+ name: "🔄 Botón de Reinicio",
138
+ description: "Todas las estadísticas vuelven a la normalidad.",
139
+ rarity: "epic",
140
+ effects: [
141
+ {
142
+ effectType: "reset_stats",
143
+ target: "both",
144
+ },
145
+ ],
146
+ },
147
+ ];
148
+ /**
149
+ * POKEMON-SPECIFIC Random Events
150
+ * These only appear when using the Pokemon skin
151
+ */
152
+ export const POKEMON_RANDOM_EVENTS = [
153
+ // ==================== COMMON (3 events) ====================
154
+ {
155
+ id: "pokemon_center",
156
+ name: "🏥 Centro Pokémon Móvil",
157
+ description: "Una enfermera Joy aparece y cura a ambos Pokémon.",
158
+ rarity: "common",
159
+ effects: [
160
+ {
161
+ effectType: "heal_percent",
162
+ target: "both",
163
+ percentValue: 15,
164
+ },
165
+ ],
166
+ },
167
+ {
168
+ id: "team_rocket",
169
+ name: "🚀 ¡Equipo Rocket!",
170
+ description: "¡Preparaos para los problemas! El ataque de ambos baja.",
171
+ rarity: "common",
172
+ effects: [
173
+ {
174
+ effectType: "stat_change",
175
+ target: "both",
176
+ statType: "offense",
177
+ statChange: -1,
178
+ },
179
+ ],
180
+ },
181
+ {
182
+ id: "rare_candy",
183
+ name: "🍬 Caramelo Raro",
184
+ description: "Un Pokémon aleatorio encuentra un caramelo y se fortalece.",
185
+ rarity: "common",
186
+ effects: [
187
+ {
188
+ effectType: "stat_change",
189
+ target: "random_one",
190
+ statType: "offense",
191
+ statChange: 1,
192
+ },
193
+ {
194
+ effectType: "stat_change",
195
+ target: "random_one",
196
+ statType: "defense",
197
+ statChange: 1,
198
+ },
199
+ ],
200
+ },
201
+ // ==================== RARE (4 events) ====================
202
+ {
203
+ id: "sandstorm",
204
+ name: "🌪️ Tormenta de Arena",
205
+ description: "Una tormenta de arena daña a todos los Pokémon.",
206
+ rarity: "rare",
207
+ effects: [
208
+ {
209
+ effectType: "damage_percent",
210
+ target: "both",
211
+ percentValue: 8,
212
+ },
213
+ ],
214
+ },
215
+ {
216
+ id: "professor_oak",
217
+ name: "👴 Profesor Oak",
218
+ description: "¡El Profesor Oak anima a tu Pokémon!",
219
+ rarity: "rare",
220
+ effects: [
221
+ {
222
+ effectType: "stat_change",
223
+ target: "random_one",
224
+ statType: "offense",
225
+ statChange: 2,
226
+ },
227
+ {
228
+ effectType: "stat_change",
229
+ target: "random_one",
230
+ statType: "speed",
231
+ statChange: 1,
232
+ },
233
+ ],
234
+ },
235
+ {
236
+ id: "dawn_stone",
237
+ name: "💎 Piedra Alba",
238
+ description: "Una piedra evolutiva aumenta la defensa de un Pokémon.",
239
+ rarity: "rare",
240
+ effects: [
241
+ {
242
+ effectType: "stat_change",
243
+ target: "random_one",
244
+ statType: "defense",
245
+ statChange: 3,
246
+ },
247
+ ],
248
+ },
249
+ {
250
+ id: "potion_spray",
251
+ name: "💊 Lluvia de Pociones",
252
+ description: "Pociones caen del cielo curando a ambos Pokémon.",
253
+ rarity: "rare",
254
+ effects: [
255
+ {
256
+ effectType: "heal_percent",
257
+ target: "both",
258
+ percentValue: 12,
259
+ },
260
+ ],
261
+ },
262
+ // ==================== EPIC (2 events) ====================
263
+ {
264
+ id: "legendary_appearance",
265
+ name: "✨ Aparición Legendaria",
266
+ description: "Un Pokémon legendario otorga poder a un luchador aleatorio.",
267
+ rarity: "epic",
268
+ effects: [
269
+ {
270
+ effectType: "stat_change",
271
+ target: "random_one",
272
+ statType: "offense",
273
+ statChange: 3,
274
+ },
275
+ {
276
+ effectType: "stat_change",
277
+ target: "random_one",
278
+ statType: "defense",
279
+ statChange: 2,
280
+ },
281
+ {
282
+ effectType: "stat_change",
283
+ target: "random_one",
284
+ statType: "speed",
285
+ statChange: 2,
286
+ },
287
+ ],
288
+ },
289
+ {
290
+ id: "max_revive",
291
+ name: "⚡ Revivir Máximo",
292
+ description: "El Pokémon con menos vida recupera un 30% de HP.",
293
+ rarity: "epic",
294
+ effects: [
295
+ {
296
+ effectType: "heal_percent",
297
+ target: "random_one", // TODO: Cambiar a "lowest_hp" cuando se implemente
298
+ percentValue: 30,
299
+ },
300
+ ],
301
+ },
80
302
  ];
@@ -1,13 +1,16 @@
1
- import type { RandomEventDefinition } from "../../core/randomEvents.js";
1
+ import { type RandomEventDefinition } from "../../core/randomEvents.js";
2
2
  /**
3
- * Check if a random event should trigger
4
- * @param turnNumber Current turn number
5
- * @returns true if event should trigger (50% chance every 2 turns)
3
+ * Determina si debe dispararse un evento aleatorio en este turno
4
+ *
5
+ * @param turnNumber - Número del turno actual
6
+ * @returns true si debe ocurrir un evento
6
7
  */
7
- export declare const shouldTriggerRandomEvent: (turnNumber: number) => boolean;
8
+ export declare function shouldTriggerRandomEvent(turnNumber: number): boolean;
8
9
  /**
9
- * Select a random event from available events based on rarity weights
10
- * @param availableEvents List of available events
11
- * @returns Selected event
10
+ * Selecciona un evento aleatorio basado en los pesos de rareza
11
+ * Usa "weighted random selection" para dar más probabilidad a eventos comunes
12
+ *
13
+ * @param events - Array de eventos disponibles
14
+ * @returns Evento seleccionado
12
15
  */
13
- export declare const selectRandomEvent: (availableEvents: RandomEventDefinition[]) => RandomEventDefinition;
16
+ export declare function selectRandomEvent(events: RandomEventDefinition[]): RandomEventDefinition;
@@ -1,35 +1,67 @@
1
1
  // @pokemon-io/combat-core/src/engine/events/randomEventTrigger.ts
2
- import { RARITY_WEIGHTS } from "../../core/randomEvents.js";
2
+ import { RARITY_WEIGHTS, } from "../../core/randomEvents.js";
3
3
  /**
4
- * Check if a random event should trigger
5
- * @param turnNumber Current turn number
6
- * @returns true if event should trigger (50% chance every 2 turns)
4
+ * 🎲 LÓGICA DE PROBABILIDAD DE EVENTOS ALEATORIOS
5
+ *
6
+ * 1. TRIGGER (¿Ocurre un evento?):
7
+ * - Se revisa cada 2 turnos (turno 2, 4, 6, etc.)
8
+ * - Probabilidad: 50% (0.5) en producción
9
+ * - DEBUG MODE: 100% para pruebas (línea 27)
10
+ *
11
+ * 2. SELECCIÓN (¿Qué evento ocurre?):
12
+ * - Se usa "weighted random selection" basado en rareza
13
+ * - Pesos definidos en RARITY_WEIGHTS:
14
+ * • Common: 60% de probabilidad
15
+ * • Rare: 25% de probabilidad
16
+ * • Epic: 12% de probabilidad
17
+ * • Legendary: 3% de probabilidad
7
18
  */
8
- export const shouldTriggerRandomEvent = (turnNumber) => {
9
- // Only check on even turns
19
+ // 🔧 DEBUG: Cambia esto a false para volver a 50% de probabilidad
20
+ const DEBUG_MODE = true;
21
+ const TRIGGER_CHANCE = DEBUG_MODE ? 1.0 : 0.5; // 100% en debug, 50% en producción
22
+ /**
23
+ * Determina si debe dispararse un evento aleatorio en este turno
24
+ *
25
+ * @param turnNumber - Número del turno actual
26
+ * @returns true si debe ocurrir un evento
27
+ */
28
+ export function shouldTriggerRandomEvent(turnNumber) {
29
+ // Solo en turnos pares (2, 4, 6, ...)
10
30
  if (turnNumber % 2 !== 0) {
31
+ console.log(`[Random Events] Turno ${turnNumber}: NO (turno impar)`);
11
32
  return false;
12
33
  }
13
- // 50% chance
14
- return Math.random() < 0.5;
15
- };
34
+ // Probabilidad de trigger
35
+ const roll = Math.random();
36
+ const triggered = roll < TRIGGER_CHANCE;
37
+ console.log(`[Random Events] Turno ${turnNumber}: ${triggered ? '✅ SÍ' : '❌ NO'} ` +
38
+ `(roll=${roll.toFixed(3)}, threshold=${TRIGGER_CHANCE})`);
39
+ return triggered;
40
+ }
16
41
  /**
17
- * Select a random event from available events based on rarity weights
18
- * @param availableEvents List of available events
19
- * @returns Selected event
42
+ * Selecciona un evento aleatorio basado en los pesos de rareza
43
+ * Usa "weighted random selection" para dar más probabilidad a eventos comunes
44
+ *
45
+ * @param events - Array de eventos disponibles
46
+ * @returns Evento seleccionado
20
47
  */
21
- export const selectRandomEvent = (availableEvents) => {
22
- // Calculate total weight
23
- const totalWeight = availableEvents.reduce((sum, event) => sum + RARITY_WEIGHTS[event.rarity], 0);
24
- // Roll random number
25
- let roll = Math.random() * totalWeight;
26
- // Select event based on weight
27
- for (const event of availableEvents) {
28
- roll -= RARITY_WEIGHTS[event.rarity];
29
- if (roll <= 0) {
48
+ export function selectRandomEvent(events) {
49
+ // Calcular peso total
50
+ let totalWeight = 0;
51
+ for (const event of events) {
52
+ totalWeight += RARITY_WEIGHTS[event.rarity];
53
+ }
54
+ // Selección ponderada
55
+ let random = Math.random() * totalWeight;
56
+ console.log(`[Random Events] Seleccionando evento (totalWeight=${totalWeight}, roll=${random.toFixed(2)})`);
57
+ for (const event of events) {
58
+ random -= RARITY_WEIGHTS[event.rarity];
59
+ if (random <= 0) {
60
+ console.log(`[Random Events] Evento seleccionado: "${event.name}" (${event.rarity})`);
30
61
  return event;
31
62
  }
32
63
  }
33
- // Fallback (shouldn't happen)
34
- return availableEvents[0];
35
- };
64
+ // Fallback (no debería ocurrir)
65
+ console.warn("[Random Events] Fallback al primer evento");
66
+ return events[0];
67
+ }
@@ -1,43 +1,5 @@
1
- import { AmuletDefinition, BattleState, FighterDefinition, ItemDefinition, ItemId, MoveDefinition, MoveId, StatusDefinition, StatusId, TypeDefinition, TypeEffectivenessMatrix, TypeId } from "../core/index.js";
2
- export interface BattleRules {
3
- baseCritChance: number;
4
- critPerStat: number;
5
- critMultiplier: number;
6
- stabMultiplier: number;
7
- randomMinDamageFactor: number;
8
- randomMaxDamageFactor: number;
9
- }
10
- export interface BattleRuntime {
11
- rules: BattleRules;
12
- typesById: Record<TypeId, TypeDefinition>;
13
- movesById: Record<MoveId, MoveDefinition>;
14
- itemsById: Record<ItemId, ItemDefinition>;
15
- amuletsById: Record<string, AmuletDefinition>;
16
- statusesById: Record<StatusId, StatusDefinition>;
17
- typeEffectiveness: TypeEffectivenessMatrix;
18
- }
19
- export interface PlayerFighterBattleConfig {
20
- fighter: FighterDefinition;
21
- maxHp: number;
22
- moves: MoveDefinition[];
23
- amulet: AmuletDefinition | null;
24
- }
25
- export interface PlayerBattleConfig {
26
- fighters: PlayerFighterBattleConfig[];
27
- items: ItemDefinition[];
28
- amulet: AmuletDefinition | null;
29
- }
30
- export interface BattleConfig {
31
- types: TypeDefinition[];
32
- typeEffectiveness: TypeEffectivenessMatrix;
33
- moves: MoveDefinition[];
34
- items: ItemDefinition[];
35
- amulets: AmuletDefinition[];
36
- statuses: StatusDefinition[];
37
- player1: PlayerBattleConfig;
38
- player2: PlayerBattleConfig;
39
- rules?: Partial<BattleRules>;
40
- }
1
+ import { BattleState, BattleRules, BattleRuntime } from "../core/index.js";
2
+ export type { BattleRules, BattleRuntime, BattleConfig, PlayerBattleConfig, PlayerFighterBattleConfig, } from "../core/index.js";
41
3
  export interface RuntimeBattleState extends BattleState {
42
4
  runtime: BattleRuntime;
43
5
  }
@@ -10,6 +10,9 @@ import { recomputeEffectiveStatsForFighter } from "../fighters/fighter.js";
10
10
  import { getOpponentAndSelf } from "../fighters/selectors.js";
11
11
  import { applyEndOfTurnStatuses } from "../status/endOfTurn.js";
12
12
  import { hasHardCc } from "../status/hardCc.js";
13
+ import { shouldTriggerRandomEvent, selectRandomEvent } from "../events/randomEventTrigger.js";
14
+ import { applyRandomEvent } from "../events/applyRandomEvent.js";
15
+ import { CORE_RANDOM_EVENTS } from "../../core/index.js";
13
16
  // ------------------------------------------------------
14
17
  // Helpers KO -> forced switch (estilo Pokémon real)
15
18
  // ------------------------------------------------------
@@ -267,6 +270,46 @@ export const resolveTurn = (state, actions) => {
267
270
  winner: winnerAtEnd,
268
271
  });
269
272
  }
273
+ // ✅ RANDOM EVENTS - Check and apply after forced switch and before turn_end
274
+ const shouldCheckRandomEvent = !currentState.forcedSwitch && // Skip if forced switch pending
275
+ winnerAtEnd === "none"; // Skip if battle ended
276
+ if (shouldCheckRandomEvent && shouldTriggerRandomEvent(currentState.turnNumber)) {
277
+ // Select random event
278
+ const availableEvents = [
279
+ ...CORE_RANDOM_EVENTS,
280
+ // TODO: Add skin-specific events (POKEMON_RANDOM_EVENTS)
281
+ ];
282
+ const randomEvent = selectRandomEvent(availableEvents);
283
+ // Emit banner event
284
+ events.push({
285
+ ...createBaseEvent(currentState.turnNumber, "random_event_triggered", randomEvent.description),
286
+ eventId: randomEvent.id,
287
+ eventName: randomEvent.name,
288
+ eventDescription: randomEvent.description,
289
+ eventRarity: randomEvent.rarity,
290
+ imageUrl: randomEvent.imageUrl,
291
+ effects: randomEvent.effects.map((effect) => ({
292
+ effectType: effect.effectType,
293
+ target: effect.target,
294
+ percentValue: effect.percentValue,
295
+ statType: effect.statType,
296
+ statChange: effect.statChange,
297
+ })),
298
+ });
299
+ // Apply random event effects
300
+ const effectsResult = applyRandomEvent(currentState, randomEvent);
301
+ currentState = effectsResult.state;
302
+ // Add effect events with source="random_event" marker
303
+ const markedEvents = effectsResult.events.map((event) => {
304
+ if (event.kind === "damage" ||
305
+ event.kind === "heal" ||
306
+ event.kind === "stats_changed") {
307
+ return { ...event, source: "random_event" };
308
+ }
309
+ return event;
310
+ });
311
+ events.push(...markedEvents);
312
+ }
270
313
  events.push({
271
314
  ...createBaseEvent(currentState.turnNumber, "turn_end", `Termina el turno ${currentState.turnNumber}`),
272
315
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pokemon-io-core",
3
- "version": "0.0.101",
3
+ "version": "0.0.102",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",