pokemon-io-core 0.0.10 → 0.0.12

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.
@@ -175,6 +175,89 @@ const applyDamageToFighter = (state, defender, amount, actorId, isCritical) => {
175
175
  }
176
176
  return { updatedDefender, events };
177
177
  };
178
+ const applyStatusToFighter = (state, target, statusId) => {
179
+ const def = state.runtime.statusesById[statusId];
180
+ if (!def)
181
+ return { updated: target, events: [] };
182
+ const events = [];
183
+ const existing = target.statuses.find((s) => s.statusId === statusId);
184
+ const duration = Math.floor(randomInRange(def.minDurationTurns, def.maxDurationTurns + 1)) ||
185
+ def.minDurationTurns;
186
+ let newStatuses = [...target.statuses];
187
+ if (!existing) {
188
+ newStatuses.push({
189
+ statusId,
190
+ stacks: 1,
191
+ remainingTurns: duration,
192
+ });
193
+ }
194
+ else if (existing.stacks === 1) {
195
+ newStatuses = newStatuses.map((s) => s.statusId === statusId
196
+ ? { ...s, stacks: 2, remainingTurns: duration }
197
+ : s);
198
+ }
199
+ else {
200
+ newStatuses = newStatuses.map((s) => s.statusId === statusId ? { ...s, remainingTurns: duration } : s);
201
+ }
202
+ events.push({
203
+ ...createBaseEvent(state.turnNumber, "status_applied", `Se aplica ${statusId} a ${target.fighterId}`),
204
+ targetId: target.fighterId,
205
+ statusId,
206
+ });
207
+ return {
208
+ updated: { ...target, statuses: newStatuses },
209
+ events,
210
+ };
211
+ };
212
+ const applyHealToFighter = (state, target, amount, sourceId) => {
213
+ const newHp = Math.min(target.maxHp, target.currentHp + amount);
214
+ const healed = newHp - target.currentHp;
215
+ const updated = {
216
+ ...target,
217
+ currentHp: newHp,
218
+ isAlive: newHp > 0,
219
+ };
220
+ const events = [];
221
+ events.push({
222
+ ...createBaseEvent(state.turnNumber, "heal", `${sourceId} cura ${healed} a ${target.fighterId}`),
223
+ actorId: sourceId,
224
+ targetId: target.fighterId,
225
+ amount: healed,
226
+ });
227
+ return { updated, events };
228
+ };
229
+ const applyEffectsOnTarget = (state, actor, target, effects) => {
230
+ let currentActor = actor;
231
+ let currentTarget = target;
232
+ const events = [];
233
+ for (const eff of effects) {
234
+ switch (eff.kind) {
235
+ case "heal": {
236
+ const res = applyHealToFighter(state, currentActor, eff.amount, actor.fighterId);
237
+ currentActor = res.updated;
238
+ events.push(...res.events);
239
+ break;
240
+ }
241
+ case "apply_status": {
242
+ const res = applyStatusToFighter(state, currentTarget, eff.statusId);
243
+ currentTarget = res.updated;
244
+ events.push(...res.events);
245
+ break;
246
+ }
247
+ case "damage": {
248
+ if (typeof eff.amount === "number") {
249
+ const res = applyDamageToFighter(state, currentTarget, eff.amount, actor.fighterId, false);
250
+ currentTarget = res.updatedDefender;
251
+ events.push(...res.events);
252
+ }
253
+ break;
254
+ }
255
+ default:
256
+ break;
257
+ }
258
+ }
259
+ return { actor: currentActor, target: currentTarget, events };
260
+ };
178
261
  const getMovePriorityAndSpeed = (state, playerKey, action) => {
179
262
  const { self } = getOpponentAndSelf(state, playerKey);
180
263
  if (action.kind === "no_action") {
@@ -200,43 +283,51 @@ const getMovePriorityAndSpeed = (state, playerKey, action) => {
200
283
  };
201
284
  const resolveDamageMove = (state, playerKey, action) => {
202
285
  if (action.kind !== "use_move") {
203
- // por ahora ignoramos otros tipos aquí
204
286
  return { state, events: [] };
205
287
  }
206
288
  const { self, opponent } = getOpponentAndSelf(state, playerKey);
207
289
  const events = [];
208
290
  const moveSlot = self.moves[action.moveIndex];
209
291
  if (!moveSlot) {
210
- // movimiento vacío → no hace nada
211
292
  return { state, events };
212
293
  }
213
294
  const move = state.runtime.movesById[moveSlot.moveId];
214
295
  if (!move) {
215
296
  return { state, events };
216
297
  }
217
- // Sin PP → no hace nada (podrías emitir evento de "sin PP" si quieres)
298
+ // Sin PP → no hace nada
218
299
  if (moveSlot.currentPP <= 0) {
219
300
  return { state, events };
220
301
  }
221
- // Movimientos de estado/curación aún no implementados en detalle
222
- if (move.kind === "status" || move.kind === "heal") {
223
- // stub: no hacemos nada por ahora
224
- return { state, events };
302
+ // Movimiento solo de curación no calculamos daño base
303
+ if (move.kind === "heal") {
304
+ events.push({
305
+ ...createBaseEvent(state.turnNumber, "action_declared", `${self.fighterId} usa ${move.name}`),
306
+ actorId: self.fighterId,
307
+ });
308
+ const updatedMoves = self.moves.map((m, idx) => idx === action.moveIndex
309
+ ? { ...m, currentPP: Math.max(0, m.currentPP - 1) }
310
+ : m);
311
+ let updatedSelf = { ...self, moves: updatedMoves };
312
+ let updatedOpponent = { ...opponent };
313
+ const { actor: finalSelf, target: finalOpp, events: extraEvents, } = applyEffectsOnTarget(state, updatedSelf, updatedOpponent, move.effects);
314
+ events.push(...extraEvents);
315
+ const newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
316
+ return { state: newState, events };
225
317
  }
226
- // Acción declarada (para narrador avanzado)
318
+ // Movimiento de daño/hybrid
227
319
  events.push({
228
320
  ...createBaseEvent(state.turnNumber, "action_declared", `${self.fighterId} usa ${move.name}`),
229
321
  actorId: self.fighterId,
230
322
  });
231
- // Comprobar accuracy
232
323
  const accuracy = move.accuracy ?? 100;
233
324
  const hitRoll = randomInRange(0, 100);
234
325
  const hit = hitRoll < accuracy;
235
- // Reducir PP
236
326
  const updatedMoves = self.moves.map((m, idx) => idx === action.moveIndex
237
327
  ? { ...m, currentPP: Math.max(0, m.currentPP - 1) }
238
328
  : m);
239
329
  let updatedSelf = { ...self, moves: updatedMoves };
330
+ let updatedOpponent = { ...opponent };
240
331
  if (!hit) {
241
332
  events.push({
242
333
  ...createBaseEvent(state.turnNumber, "move_miss", `${self.fighterId} falla ${move.name}`),
@@ -244,16 +335,16 @@ const resolveDamageMove = (state, playerKey, action) => {
244
335
  moveId: move.id,
245
336
  targetId: opponent.fighterId,
246
337
  });
247
- const newState = updateFightersInState(state, playerKey, updatedSelf, opponent);
338
+ const newState = updateFightersInState(state, playerKey, updatedSelf, updatedOpponent);
248
339
  return { state: newState, events };
249
340
  }
250
- // Crítico
341
+ // Crítico y daño base
251
342
  const critChance = computeCritChance(state.runtime.rules, updatedSelf.effectiveStats.crit);
252
343
  const isCritical = chance(critChance);
253
344
  const { damage, effectiveness } = computeDamage({
254
345
  state,
255
346
  attacker: updatedSelf,
256
- defender: opponent,
347
+ defender: updatedOpponent,
257
348
  move,
258
349
  isCritical,
259
350
  });
@@ -265,9 +356,42 @@ const resolveDamageMove = (state, playerKey, action) => {
265
356
  isCritical,
266
357
  effectiveness,
267
358
  });
268
- const damageResult = applyDamageToFighter(state, opponent, damage, updatedSelf.fighterId, isCritical);
269
- events.push(...damageResult.events);
270
- const newState = updateFightersInState(state, playerKey, updatedSelf, damageResult.updatedDefender);
359
+ const damageRes = applyDamageToFighter(state, updatedOpponent, damage, updatedSelf.fighterId, isCritical);
360
+ updatedOpponent = damageRes.updatedDefender;
361
+ events.push(...damageRes.events);
362
+ // Efectos secundarios del movimiento (quemado, buffs, daño extra fijo, etc.)
363
+ const { actor: finalSelf, target: finalOpp, events: extraEvents, } = applyEffectsOnTarget(state, updatedSelf, updatedOpponent, move.effects);
364
+ events.push(...extraEvents);
365
+ const newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
366
+ return { state: newState, events };
367
+ };
368
+ const resolveItemUse = (state, playerKey, action) => {
369
+ if (action.kind !== "use_item") {
370
+ return { state, events: [] };
371
+ }
372
+ const { self, opponent } = getOpponentAndSelf(state, playerKey);
373
+ const events = [];
374
+ const itemSlot = self.items[action.itemIndex];
375
+ if (!itemSlot || itemSlot.usesRemaining <= 0) {
376
+ return { state, events };
377
+ }
378
+ const itemDef = state.runtime.itemsById[itemSlot.itemId];
379
+ if (!itemDef) {
380
+ return { state, events };
381
+ }
382
+ // Reducir usos
383
+ const updatedItems = self.items.map((it, idx) => idx === action.itemIndex
384
+ ? { ...it, usesRemaining: Math.max(0, it.usesRemaining - 1) }
385
+ : it);
386
+ let updatedSelf = { ...self, items: updatedItems };
387
+ let updatedOpponent = { ...opponent };
388
+ events.push({
389
+ ...createBaseEvent(state.turnNumber, "action_declared", `${self.fighterId} usa objeto ${itemDef.name}`),
390
+ actorId: self.fighterId,
391
+ });
392
+ const { actor: finalSelf, target: finalOpp, events: extraEvents, } = applyEffectsOnTarget(state, updatedSelf, updatedOpponent, itemDef.effects);
393
+ events.push(...extraEvents);
394
+ const newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
271
395
  return { state: newState, events };
272
396
  };
273
397
  const updateFightersInState = (state, actingPlayerKey, updatedSelf, updatedOpponent) => {
@@ -433,8 +557,13 @@ export const resolveTurn = (state, actions) => {
433
557
  currentState = result.state;
434
558
  events.push(...result.events);
435
559
  }
560
+ else if (action.kind === "use_item") {
561
+ const result = resolveItemUse(currentState, playerKey, action);
562
+ currentState = result.state;
563
+ events.push(...result.events);
564
+ }
436
565
  else {
437
- // MVP: ignoramos use_item y switch_fighter de momento
566
+ // switch_fighter y otros aún no implementados
438
567
  continue;
439
568
  }
440
569
  // Comprobar victoria inmediata tras cada acción
@@ -4,6 +4,7 @@ export type TargetKind = "self" | "enemy";
4
4
  export type EffectKind = "damage" | "heal" | "shield" | "modify_stats" | "apply_status" | "modify_hit_chance" | "modify_crit_chance" | "modify_priority" | "modify_effective_speed";
5
5
  export interface DamageEffect {
6
6
  kind: "damage";
7
+ amount: number;
7
8
  powerMultiplier?: number;
8
9
  flatBonus?: number;
9
10
  }
@@ -2,3 +2,5 @@ export * from "./types.js";
2
2
  export * from "./moves.js";
3
3
  export * from "./fighters.js";
4
4
  export * from "./pokemonSkin.js";
5
+ export * from "./items.js";
6
+ export * from "./statuses.js";
@@ -2,3 +2,5 @@ export * from "./types.js";
2
2
  export * from "./moves.js";
3
3
  export * from "./fighters.js";
4
4
  export * from "./pokemonSkin.js";
5
+ export * from "./items.js";
6
+ export * from "./statuses.js";
@@ -0,0 +1,2 @@
1
+ import { ItemDefinition } from "../../core";
2
+ export declare const POKEMON_ITEMS: ItemDefinition[];
@@ -0,0 +1,26 @@
1
+ export const POKEMON_ITEMS = [
2
+ {
3
+ id: "potion",
4
+ name: "Poción",
5
+ category: "heal_shield",
6
+ maxUses: 2,
7
+ effects: [{ kind: "heal", amount: 30 }]
8
+ },
9
+ {
10
+ id: "super_potion",
11
+ name: "Super Poción",
12
+ category: "heal_shield",
13
+ maxUses: 1,
14
+ effects: [{ kind: "heal", amount: 60 }]
15
+ },
16
+ {
17
+ id: "burn_bomb",
18
+ name: "Bomba de Fuego",
19
+ category: "damage",
20
+ maxUses: 1,
21
+ effects: [
22
+ { kind: "damage", amount: 20 },
23
+ { kind: "apply_status", statusId: "burn" }
24
+ ]
25
+ }
26
+ ];
@@ -12,9 +12,10 @@ export const POKEMON_MOVES = [
12
12
  target: "enemy",
13
13
  effects: [
14
14
  {
15
- kind: "damage"
16
- }
17
- ]
15
+ kind: "damage",
16
+ amount: 20,
17
+ },
18
+ ],
18
19
  },
19
20
  {
20
21
  id: "ember",
@@ -28,9 +29,10 @@ export const POKEMON_MOVES = [
28
29
  target: "enemy",
29
30
  effects: [
30
31
  {
31
- kind: "damage"
32
- }
33
- ]
32
+ kind: "apply_status",
33
+ statusId: "burn",
34
+ },
35
+ ],
34
36
  },
35
37
  {
36
38
  id: "water_gun",
@@ -44,9 +46,10 @@ export const POKEMON_MOVES = [
44
46
  target: "enemy",
45
47
  effects: [
46
48
  {
47
- kind: "damage"
48
- }
49
- ]
49
+ kind: "damage",
50
+ amount: 20,
51
+ },
52
+ ],
50
53
  },
51
54
  {
52
55
  id: "vine_whip",
@@ -60,9 +63,10 @@ export const POKEMON_MOVES = [
60
63
  target: "enemy",
61
64
  effects: [
62
65
  {
63
- kind: "damage"
64
- }
65
- ]
66
+ kind: "damage",
67
+ amount: 20,
68
+ },
69
+ ],
66
70
  },
67
71
  {
68
72
  id: "thunder_shock",
@@ -76,8 +80,24 @@ export const POKEMON_MOVES = [
76
80
  target: "enemy",
77
81
  effects: [
78
82
  {
79
- kind: "damage"
80
- }
81
- ]
82
- }
83
+ kind: "damage",
84
+ amount: 20,
85
+ },
86
+ ],
87
+ },
88
+ {
89
+ id: "fire_fang",
90
+ name: "Colmillo Ígneo",
91
+ typeId: "fire",
92
+ kind: "damage",
93
+ basePower: 65,
94
+ accuracy: 95,
95
+ maxPP: 15,
96
+ priority: 0,
97
+ target: "enemy",
98
+ effects: [
99
+ { kind: "damage", amount: 10 },
100
+ { kind: "apply_status", statusId: "burn" },
101
+ ],
102
+ },
83
103
  ];
@@ -1,14 +1,13 @@
1
+ import { ItemDefinition, MoveDefinition, PlayerBattleConfig } from "../../core";
1
2
  import type { CombatSkin, FighterLoadout } from "../CombatSkin";
2
- import type { MoveDefinition, ItemDefinition, AmuletDefinition, StatusDefinition } from "../../core";
3
- import type { PlayerBattleConfig } from "../../core/engine";
4
3
  export declare class PokemonSkin implements CombatSkin {
5
4
  readonly id = "pokemon";
6
5
  getTypes(): import("../..").TypeDefinition[];
7
6
  getTypeEffectiveness(): import("../..").TypeEffectivenessMatrix;
8
7
  getMoves(): MoveDefinition[];
9
8
  getItems(): ItemDefinition[];
10
- getAmulets(): AmuletDefinition[];
11
- getStatuses(): StatusDefinition[];
9
+ getAmulets(): never[];
10
+ getStatuses(): never[];
12
11
  buildPlayerConfig(loadout: FighterLoadout): PlayerBattleConfig;
13
12
  }
14
13
  export declare const createPokemonSkin: () => CombatSkin;
@@ -1,7 +1,7 @@
1
- // src/skins/pokemon/pokemonSkin.ts
2
- import { POKEMON_TYPES, POKEMON_TYPE_MATRIX, POKEMON_MOVES, POKEMON_FIGHTERS } from "./index";
1
+ // pokemon-io-core/src/skins/pokemon/pokemonSkin.ts
2
+ import { POKEMON_TYPES, POKEMON_TYPE_MATRIX, POKEMON_MOVES, POKEMON_FIGHTERS, POKEMON_ITEMS } from "./index";
3
3
  const MAX_HP_DEFAULT = 100;
4
- // Helpers locales
4
+ // --- helpers ---
5
5
  const findPokemonById = (id) => {
6
6
  const lower = id.toLowerCase();
7
7
  return (POKEMON_FIGHTERS.find((f) => f.id.toLowerCase() === lower || f.name.toLowerCase() === lower) ?? null);
@@ -9,19 +9,19 @@ const findPokemonById = (id) => {
9
9
  const findMoveById = (id) => {
10
10
  return POKEMON_MOVES.find((m) => m.id === id) ?? null;
11
11
  };
12
+ const findItemById = (id) => {
13
+ return POKEMON_ITEMS.find((i) => i.id === id) ?? null;
14
+ };
12
15
  const FILLER_MOVE_ID = "tackle";
13
16
  const buildMovesFromLoadout = (fighter, loadout) => {
14
- // 1) si el jugador ha elegido moveIds, intentamos respetarlos
15
17
  const selectedMoveIds = loadout.moveIds ?? [];
16
18
  const selectedMoves = selectedMoveIds
17
19
  .map((id) => findMoveById(id))
18
20
  .filter((m) => m !== null);
19
- // 2) si ya hay 4 válidos → nos quedamos con los primeros 4
20
21
  if (selectedMoves.length >= 4) {
21
22
  return selectedMoves.slice(0, 4);
22
23
  }
23
24
  const result = [...selectedMoves];
24
- // 3) completamos con recommendedMoves del fighter
25
25
  const recommendedIds = fighter.recommendedMoves ?? [];
26
26
  for (const recId of recommendedIds) {
27
27
  if (result.length >= 4)
@@ -31,22 +31,27 @@ const buildMovesFromLoadout = (fighter, loadout) => {
31
31
  result.push(move);
32
32
  }
33
33
  }
34
- // 4) si sigue faltando, rellenamos con "tackle"
35
34
  const filler = findMoveById(FILLER_MOVE_ID);
36
35
  while (result.length < 4 && filler) {
37
36
  result.push(filler);
38
37
  }
39
- // Garantizamos que haya al menos 1 movimiento
40
38
  if (result.length === 0 && filler) {
41
39
  result.push(filler);
42
40
  }
43
- // Cortamos por si acaso
44
41
  return result.slice(0, 4);
45
42
  };
46
- // De momento no tenemos objetos / amuletos / estados modelados para Pokémon
47
- const POKEMON_ITEMS = [];
48
- const POKEMON_AMULETS = [];
49
- const POKEMON_STATUSES = [];
43
+ const buildItemsFromLoadout = (loadout) => {
44
+ const selectedItemIds = loadout.itemIds ?? [];
45
+ const selectedItems = selectedItemIds
46
+ .map((id) => findItemById(id))
47
+ .filter((i) => i !== null);
48
+ // Si tu motor sigue exigiendo 4 items, puedes rellenar aquí:
49
+ // while (selectedItems.length < 4 && POKEMON_ITEMS[selectedItems.length]) {
50
+ // selectedItems.push(POKEMON_ITEMS[selectedItems.length]);
51
+ // }
52
+ return selectedItems;
53
+ };
54
+ // --- skin ---
50
55
  export class PokemonSkin {
51
56
  id = "pokemon";
52
57
  getTypes() {
@@ -62,10 +67,10 @@ export class PokemonSkin {
62
67
  return POKEMON_ITEMS;
63
68
  }
64
69
  getAmulets() {
65
- return POKEMON_AMULETS;
70
+ return []; // más adelante
66
71
  }
67
72
  getStatuses() {
68
- return POKEMON_STATUSES;
73
+ return []; // más adelante
69
74
  }
70
75
  buildPlayerConfig(loadout) {
71
76
  if (!loadout.fighterId) {
@@ -76,17 +81,16 @@ export class PokemonSkin {
76
81
  throw new Error(`Unknown fighterId for Pokemon skin: ${loadout.fighterId}`);
77
82
  }
78
83
  const moves = buildMovesFromLoadout(fighter, loadout);
79
- // Importante: aquí aún no aplicamos items/amuletos, pero dejamos el contrato listo
84
+ const items = buildItemsFromLoadout(loadout);
80
85
  return {
81
86
  fighter,
82
87
  maxHp: MAX_HP_DEFAULT,
83
88
  moves,
84
- items: [], // Más adelante: mapear loadout.itemIds -> ItemDefinition[]
85
- amulet: null // Más adelante: loadout.amuletId -> AmuletDefinition
89
+ items,
90
+ amulet: null
86
91
  };
87
92
  }
88
93
  }
89
- // Helper para crear la skin (así importas siempre una factoría)
90
94
  export const createPokemonSkin = () => {
91
95
  return new PokemonSkin();
92
96
  };
@@ -0,0 +1,2 @@
1
+ import { StatusDefinition } from "../../core";
2
+ export declare const POKEMON_STATUSES: StatusDefinition[];
@@ -0,0 +1,21 @@
1
+ export const POKEMON_STATUSES = [
2
+ {
3
+ id: "burn",
4
+ name: "Quemado",
5
+ kind: "soft",
6
+ minDurationTurns: 2,
7
+ maxDurationTurns: 4,
8
+ effectsPerStack: [
9
+ // DoT ligero
10
+ { kind: "damage", amount: 10 }
11
+ ]
12
+ },
13
+ {
14
+ id: "stun",
15
+ name: "Aturdido",
16
+ kind: "hard_cc",
17
+ minDurationTurns: 1,
18
+ maxDurationTurns: 1,
19
+ effectsPerStack: []
20
+ }
21
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pokemon-io-core",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",