pokemon-io-core 0.0.11 → 0.0.13

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.
@@ -0,0 +1,46 @@
1
+ import { BattleView } from "./battle";
2
+ import { RoomView } from "./room";
3
+ export interface ClientToServerEvents {
4
+ "room:create": (payload: {
5
+ nickname: string;
6
+ }) => void;
7
+ "room:join": (payload: {
8
+ roomId: string;
9
+ nickname: string;
10
+ }) => void;
11
+ "room:leave": (payload: {
12
+ roomId: string;
13
+ }) => void;
14
+ "room:setLoadout": (payload: {
15
+ roomId: string;
16
+ fighterId: string;
17
+ moveIds: string[];
18
+ itemIds: string[];
19
+ amuletId?: string | null;
20
+ }) => void;
21
+ "room:startBattleRequest": (payload: {
22
+ roomId: string;
23
+ }) => void;
24
+ "battle:useMove": (payload: {
25
+ roomId: string;
26
+ moveIndex: number;
27
+ }) => void;
28
+ "battle:useItem": (payload: {
29
+ roomId: string;
30
+ itemIndex: number;
31
+ }) => void;
32
+ "ping": () => void;
33
+ }
34
+ export interface ServerToClientEvents {
35
+ "connection:state": (payload: {
36
+ isConnected: boolean;
37
+ socketId: string | null;
38
+ }) => void;
39
+ "room:state": (payload: RoomView) => void;
40
+ "room:error": (payload: {
41
+ code: string;
42
+ message: string;
43
+ }) => void;
44
+ "battle:state": (payload: BattleView) => void;
45
+ "pong": (msg: string) => void;
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -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
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./core/index.js";
2
2
  export * from "./api/battle.js";
3
3
  export * from "./api/room.js";
4
+ export * from "./api/socketTypes.js";
4
5
  export * from "./skins/CombatSkin.js";
5
6
  export * from "./engine/pokemonBattleService.js";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./core/index.js";
2
2
  export * from "./api/battle.js";
3
3
  export * from "./api/room.js";
4
+ export * from "./api/socketTypes.js";
4
5
  export * from "./skins/CombatSkin.js";
5
6
  export * from "./engine/pokemonBattleService.js";
@@ -3,3 +3,4 @@ export * from "./moves.js";
3
3
  export * from "./fighters.js";
4
4
  export * from "./pokemonSkin.js";
5
5
  export * from "./items.js";
6
+ export * from "./statuses.js";
@@ -3,3 +3,4 @@ export * from "./moves.js";
3
3
  export * from "./fighters.js";
4
4
  export * from "./pokemonSkin.js";
5
5
  export * from "./items.js";
6
+ export * from "./statuses.js";
@@ -1,51 +1,26 @@
1
- // Para MVP: 4 objetos simples
2
1
  export const POKEMON_ITEMS = [
3
2
  {
4
3
  id: "potion",
5
4
  name: "Poción",
6
5
  category: "heal_shield",
7
6
  maxUses: 2,
8
- effects: [
9
- {
10
- kind: "heal", // tu EffectDefinition debe tener esto
11
- amount: 30
12
- }
13
- ]
7
+ effects: [{ kind: "heal", amount: 30 }]
14
8
  },
15
9
  {
16
10
  id: "super_potion",
17
11
  name: "Super Poción",
18
12
  category: "heal_shield",
19
13
  maxUses: 1,
20
- effects: [
21
- {
22
- kind: "heal",
23
- amount: 60
24
- }
25
- ]
14
+ effects: [{ kind: "heal", amount: 60 }]
26
15
  },
27
16
  {
28
- id: "bomb",
29
- name: "Bomba",
17
+ id: "burn_bomb",
18
+ name: "Bomba de Fuego",
30
19
  category: "damage",
31
20
  maxUses: 1,
32
21
  effects: [
33
- {
34
- kind: "damage",
35
- powerMultiplier: 1.5
36
- }
37
- ]
38
- },
39
- {
40
- id: "guard_charm",
41
- name: "Amuleto de Guardia",
42
- category: "status_buff",
43
- maxUses: 1,
44
- effects: [
45
- {
46
- kind: "apply_status",
47
- statusId: "guard"
48
- }
22
+ { kind: "damage", amount: 20 },
23
+ { kind: "apply_status", statusId: "burn" }
49
24
  ]
50
25
  }
51
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
  ];
@@ -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.11",
3
+ "version": "0.0.13",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",