pokemon-io-core 0.0.71 → 0.0.72

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.
@@ -1,10 +1,8 @@
1
1
  export interface PlayerLoadoutView {
2
- fighterIds: string[];
3
- movesByFighterId: Record<string, string[]>;
4
- itemIds: string[];
5
- amuletId: string | null;
6
2
  fighterId: string | null;
7
3
  moveIds: string[];
4
+ itemIds: string[];
5
+ amuletId: string | null;
8
6
  }
9
7
  export interface PlayerView {
10
8
  id: string;
@@ -42,6 +42,10 @@ export interface ClientToServerEvents {
42
42
  roomId: string;
43
43
  itemIndex: number;
44
44
  }) => void;
45
+ "battle:switchFighter": (payload: {
46
+ roomId: string;
47
+ newIndex: number;
48
+ }) => void;
45
49
  "battle:rematchSameLoadout": (payload: {
46
50
  roomId: string;
47
51
  }) => void;
@@ -1 +1,2 @@
1
+ // @pokemon-io/combat-core/src/api/socketTypests
1
2
  export {};
@@ -16,39 +16,7 @@ export interface BattleRuntime {
16
16
  statusesById: Record<StatusId, StatusDefinition>;
17
17
  typeEffectiveness: TypeEffectivenessMatrix;
18
18
  }
19
- /**
20
- * Slot de equipo: UN luchador con sus stats base y movimientos.
21
- * OJO: aquí NO van los items; los items son por jugador.
22
- */
23
- export interface PlayerFighterConfig {
24
- fighter: FighterDefinition;
25
- maxHp: number;
26
- moves: MoveDefinition[];
27
- amulet: AmuletDefinition | null;
28
- }
29
- /**
30
- * Configuración de un jugador para el combate.
31
- *
32
- * - Forma nueva (multi-team): usar `team`.
33
- * - Forma legacy (1 vs 1): usar fighter/maxHp/moves/items/amulet como hasta ahora.
34
- * Si `team` no viene o está vacío, el engine construye un equipo de 1 con esos datos.
35
- */
36
- export interface FighterSlotConfig {
37
- fighter: FighterDefinition;
38
- maxHp: number;
39
- moves: MoveDefinition[];
40
- amulet: AmuletDefinition | null;
41
- }
42
19
  export interface PlayerBattleConfig {
43
- /**
44
- * Nueva forma: equipo completo. Si existe y tiene elementos,
45
- * el engine usará esto como fuente de verdad.
46
- */
47
- team?: FighterSlotConfig[];
48
- /**
49
- * Forma legacy (1 vs 1). Se mantiene para compatibilidad
50
- * con skins y backends actuales.
51
- */
52
20
  fighter: FighterDefinition;
53
21
  maxHp: number;
54
22
  moves: MoveDefinition[];
@@ -30,26 +30,29 @@ const cloneStats = (stats) => ({
30
30
  // ------------------------------------------------------
31
31
  // Creación de estado inicial
32
32
  // ------------------------------------------------------
33
- const createBattleFighter = (slot, playerItems) => {
33
+ const createBattleFighter = (cfg) => {
34
+ if (cfg.moves.length !== 4) {
35
+ throw new Error("Each fighter must have exactly 4 moves in MVP");
36
+ }
37
+ if (cfg.items.length > 4) {
38
+ throw new Error("A fighter cannot have more than 4 items");
39
+ }
34
40
  return {
35
- fighterId: slot.fighter.id,
36
- classId: slot.fighter.classId,
37
- maxHp: slot.maxHp,
38
- currentHp: slot.maxHp,
39
- baseStats: slot.fighter.baseStats,
40
- effectiveStats: slot.fighter.baseStats,
41
- moves: slot.moves.map((m) => ({
42
- moveId: m.id,
43
- currentPP: m.maxPP,
41
+ fighterId: cfg.fighter.id,
42
+ classId: cfg.fighter.classId,
43
+ maxHp: cfg.maxHp,
44
+ currentHp: cfg.maxHp,
45
+ baseStats: cloneStats(cfg.fighter.baseStats),
46
+ effectiveStats: cloneStats(cfg.fighter.baseStats),
47
+ moves: cfg.moves.map((move) => ({
48
+ moveId: move.id,
49
+ currentPP: move.maxPP,
44
50
  })),
45
- // ⚠️ De momento mantenemos el comportamiento actual:
46
- // cada luchador tiene su copia de items.
47
- // Más adelante, si quieres, los movemos a nivel jugador.
48
- items: playerItems.map((it) => ({
49
- itemId: it.id,
50
- usesRemaining: it.maxUses ?? 1,
51
+ items: cfg.items.map((item) => ({
52
+ itemId: item.id,
53
+ usesRemaining: item.maxUses,
51
54
  })),
52
- amuletId: slot.amulet?.id ?? null,
55
+ amuletId: cfg.amulet ? cfg.amulet.id : null,
53
56
  statuses: [],
54
57
  isAlive: true,
55
58
  };
@@ -86,36 +89,10 @@ const recomputeEffectiveStatsForFighter = (state, fighter) => {
86
89
  effectiveStats: eff,
87
90
  };
88
91
  };
89
- const normalizeTeamFromConfig = (cfg) => {
90
- // Si ya viene en forma de equipo, úsalo tal cual
91
- if (cfg.team && cfg.team.length > 0) {
92
- return cfg.team;
93
- }
94
- // Forma legacy: un solo fighter → lo envolvemos en un array
95
- return [
96
- {
97
- fighter: cfg.fighter,
98
- maxHp: cfg.maxHp,
99
- moves: cfg.moves,
100
- amulet: cfg.amulet,
101
- },
102
- ];
103
- };
104
92
  const createPlayerBattleState = (cfg) => {
105
- // Si viene team, lo usamos; si no, construimos equipo de 1 (modo legacy)
106
- const teamSlots = cfg.team && cfg.team.length > 0
107
- ? cfg.team
108
- : [
109
- {
110
- fighter: cfg.fighter,
111
- maxHp: cfg.maxHp,
112
- moves: cfg.moves,
113
- amulet: cfg.amulet,
114
- },
115
- ];
116
- const fighterTeam = teamSlots.map((slot) => createBattleFighter(slot, cfg.items));
93
+ const fighter = createBattleFighter(cfg);
117
94
  return {
118
- fighterTeam,
95
+ fighterTeam: [fighter],
119
96
  activeIndex: 0,
120
97
  };
121
98
  };
@@ -503,6 +480,34 @@ const applyEffectsOnTarget = (state, actor, target, moveTypeId, isCritical, effe
503
480
  }
504
481
  return { actor: currentActor, target: currentTarget, events };
505
482
  };
483
+ const resolveSwitchFighter = (state, playerKey, action) => {
484
+ if (action.kind !== "switch_fighter") {
485
+ return { state, events: [] };
486
+ }
487
+ const events = [];
488
+ const player = playerKey === "player1" ? state.player1 : state.player2;
489
+ const from = getActiveFighter(player);
490
+ const to = player.fighterTeam[action.newIndex];
491
+ if (!to)
492
+ return { state, events };
493
+ if (!to.isAlive || to.currentHp <= 0)
494
+ return { state, events };
495
+ if (action.newIndex === player.activeIndex)
496
+ return { state, events };
497
+ const updatedPlayer = {
498
+ ...player,
499
+ activeIndex: action.newIndex,
500
+ };
501
+ const newState = playerKey === "player1"
502
+ ? { ...state, player1: updatedPlayer }
503
+ : { ...state, player2: updatedPlayer };
504
+ events.push({
505
+ ...createBaseEvent(state.turnNumber, "fighter_switched", `${from.fighterId} cambia a ${to.fighterId}`),
506
+ fromFighterId: from.fighterId,
507
+ toFighterId: to.fighterId,
508
+ });
509
+ return { state: newState, events };
510
+ };
506
511
  const getMovePriorityAndSpeed = (state, playerKey, action) => {
507
512
  const { self } = getOpponentAndSelf(state, playerKey);
508
513
  if (action.kind === "no_action") {
@@ -520,6 +525,12 @@ const getMovePriorityAndSpeed = (state, playerKey, action) => {
520
525
  speed: self.effectiveStats.speed,
521
526
  };
522
527
  }
528
+ if (action.kind === "switch_fighter") {
529
+ return {
530
+ priority: 10, // prioridad especial (ajústalo si quieres)
531
+ speed: self.effectiveStats.speed,
532
+ };
533
+ }
523
534
  // Items y switch: prioridad base 0 por ahora
524
535
  return {
525
536
  priority: 0,
@@ -785,35 +796,6 @@ const applyEndOfTurnStatuses = (state) => {
785
796
  };
786
797
  return { state: newState, events };
787
798
  };
788
- const applySwitchAction = (state, playerKey, newIndex, events) => {
789
- const playerState = state[playerKey];
790
- const team = playerState.fighterTeam;
791
- // Validaciones básicas
792
- if (newIndex < 0 || newIndex >= team.length)
793
- return state;
794
- if (!team[newIndex]?.isAlive)
795
- return state;
796
- if (newIndex === playerState.activeIndex)
797
- return state;
798
- const oldIndex = playerState.activeIndex;
799
- const oldFighter = team[oldIndex];
800
- const newFighter = team[newIndex];
801
- const newPlayerState = {
802
- ...playerState,
803
- activeIndex: newIndex,
804
- };
805
- const newState = {
806
- ...state,
807
- [playerKey]: newPlayerState,
808
- };
809
- events.push({
810
- ...createBaseEvent(state.turnNumber, "fighter_switched", `${oldFighter.fighterId} se retira. ¡Adelante ${newFighter.fighterId}!`),
811
- player: playerKey,
812
- oldFighterId: oldFighter.fighterId,
813
- newFighterId: newFighter.fighterId,
814
- });
815
- return newState;
816
- };
817
799
  // ------------------------------------------------------
818
800
  // Bucle principal de turno
819
801
  // ------------------------------------------------------
@@ -849,7 +831,6 @@ export const resolveTurn = (state, actions) => {
849
831
  ...getMovePriorityAndSpeed(runtimeState, "player2", actions.player2),
850
832
  },
851
833
  ];
852
- // Orden clásico por prioridad + velocidad
853
834
  entries.sort((a, b) => {
854
835
  if (b.priority !== a.priority) {
855
836
  return b.priority - a.priority;
@@ -866,29 +847,7 @@ export const resolveTurn = (state, actions) => {
866
847
  priority: e.priority,
867
848
  speed: e.speed,
868
849
  })));
869
- // 🔁 1) FASE DE CAMBIOS (switch_fighter) – prioridad absoluta
870
- const switchEntries = entries.filter((e) => e.action.kind === "switch_fighter");
871
- for (const entry of switchEntries) {
872
- const { playerKey, action } = entry;
873
- if (action.kind !== "switch_fighter")
874
- continue;
875
- const { self } = getOpponentAndSelf(currentState, playerKey);
876
- // Si el activo ya está muerto, no tiene sentido cambiar (o el combate ya está decidido).
877
- if (!self.isAlive || self.currentHp <= 0)
878
- continue;
879
- if (hasHardCc(currentState, self)) {
880
- // No puede cambiar tampoco si está bajo hard CC
881
- events.push({
882
- ...createBaseEvent(currentState.turnNumber, "action_skipped_hard_cc", `${self.fighterId} no puede actuar por control total`),
883
- actorId: self.fighterId,
884
- });
885
- continue;
886
- }
887
- currentState = applySwitchAction(currentState, playerKey, action.newIndex, events);
888
- }
889
- // 🔁 2) FASE DE ACCIONES (movimientos / objetos)
890
- const actionEntries = entries.filter((e) => e.action.kind !== "switch_fighter");
891
- for (const entry of actionEntries) {
850
+ for (const entry of entries) {
892
851
  const { playerKey, action } = entry;
893
852
  if (action.kind === "no_action")
894
853
  continue;
@@ -907,13 +866,18 @@ export const resolveTurn = (state, actions) => {
907
866
  currentState = result.state;
908
867
  events.push(...result.events);
909
868
  }
910
- else if (action.kind === "use_item") {
869
+ if (action.kind === "use_item") {
911
870
  const result = resolveItemUse(currentState, playerKey, action);
912
871
  currentState = result.state;
913
872
  events.push(...result.events);
914
873
  }
874
+ if (action.kind === "switch_fighter") {
875
+ const result = resolveSwitchFighter(currentState, playerKey, action);
876
+ currentState = result.state;
877
+ events.push(...result.events);
878
+ }
915
879
  else {
916
- // Cualquier otro tipo de acción futura se ignora de momento
880
+ // switch_fighter u otros no implementados aún
917
881
  continue;
918
882
  }
919
883
  const winnerAfterAction = checkWinner(currentState);
@@ -932,7 +896,6 @@ export const resolveTurn = (state, actions) => {
932
896
  };
933
897
  }
934
898
  }
935
- // 🔁 3) Estados de final de turno
936
899
  const statusResult = applyEndOfTurnStatuses(currentState);
937
900
  currentState = statusResult.state;
938
901
  events.push(...statusResult.events);
@@ -1,5 +1,5 @@
1
1
  import type { FighterId, MoveId, ItemId, StatusId } from "./ids.js";
2
- export type BattleEventKind = "turn_start" | "action_declared" | "action_skipped_hard_cc" | "move_miss" | "move_hit" | "item_used" | "status_cleared" | "damage" | "heal" | "shield_applied" | "status_applied" | "status_refreshed" | "status_expired" | "fighter_fainted" | "fighter_switched" | "turn_end" | "battle_end";
2
+ export type BattleEventKind = "turn_start" | "action_declared" | "action_skipped_hard_cc" | "move_miss" | "move_hit" | "item_used" | "status_cleared" | "damage" | "heal" | "fighter_switched" | "shield_applied" | "status_applied" | "status_refreshed" | "status_expired" | "fighter_fainted" | "turn_end" | "battle_end";
3
3
  export interface BaseBattleEvent {
4
4
  id: string;
5
5
  turnNumber: number;
@@ -51,6 +51,11 @@ export interface StatusAppliedEvent extends BaseBattleEvent {
51
51
  stacks: 1 | 2;
52
52
  durationTurns: number;
53
53
  }
54
+ export interface FighterSwitchedEvent extends BaseBattleEvent {
55
+ kind: "fighter_switched";
56
+ fromFighterId: FighterId;
57
+ toFighterId: FighterId;
58
+ }
54
59
  export interface StatusExpiredEvent extends BaseBattleEvent {
55
60
  kind: "status_expired";
56
61
  targetId: FighterId;
@@ -60,12 +65,6 @@ export interface FighterFaintedEvent extends BaseBattleEvent {
60
65
  kind: "fighter_fainted";
61
66
  fighterId: FighterId;
62
67
  }
63
- export interface FighterSwitchedEvent extends BaseBattleEvent {
64
- kind: "fighter_switched";
65
- player: "player1" | "player2";
66
- oldFighterId: FighterId;
67
- newFighterId: FighterId;
68
- }
69
68
  export interface BattleEndEvent extends BaseBattleEvent {
70
69
  kind: "battle_end";
71
70
  winner: "player1" | "player2" | "draw";
@@ -2,24 +2,9 @@ import type { TypeDefinition, TypeEffectivenessMatrix, MoveDefinition, ItemDefin
2
2
  import type { PlayerBattleConfig } from "../core/engine.js";
3
3
  export interface FighterLoadout {
4
4
  fighterId: string | null;
5
- moveIds: string[];
6
5
  itemIds: string[];
7
6
  amuletId: string | null;
8
- /**
9
- * Lista de luchadores del equipo (máx. 6).
10
- * Si solo hay uno, puede contener un único id.
11
- */
12
- fighterIds?: string[];
13
- /**
14
- * Movimientos por luchador. Si falta para alguno, se usan
15
- * `moveIds` como fallback.
16
- */
17
- movesByFighterId?: Record<string, string[]>;
18
- /**
19
- * (Opcional) Futuro: cuál es el activo actual.
20
- * Si no se define, se asume fighterIds[0].
21
- */
22
- activeFighterId?: string | null;
7
+ moveIds: string[];
23
8
  }
24
9
  export interface CombatSkin {
25
10
  readonly id: string;
@@ -1,8 +1,8 @@
1
- // pokemon-io-core/src/skins/tribes/clicheSkin.ts
1
+ // pokemon-io-core/src/skins/pokemon/pokemonSkin.ts
2
2
  import { POKEMON_ITEMS } from "../pokemon/index.js";
3
3
  import { TRIBE_FIGHTERS, TRIBE_MOVES, TRIBE_STATUSES, TRIBE_TYPES, TRIBE_TYPE_MATRIX, } from "./index.js";
4
4
  // --- helpers ---
5
- const findTribeFighterById = (id) => {
5
+ const findPokemonById = (id) => {
6
6
  const lower = id.toLowerCase();
7
7
  return TRIBE_FIGHTERS.find((f) => f.id.toLowerCase() === lower) ?? null;
8
8
  };
@@ -72,27 +72,15 @@ export class TribeSkin {
72
72
  throw new Error("fighterId is required in FighterLoadout");
73
73
  }
74
74
  console.log("buildPlayerConfig", loadout.fighterId);
75
- const fighter = findTribeFighterById(loadout.fighterId);
75
+ const fighter = findPokemonById(loadout.fighterId);
76
76
  if (!fighter) {
77
77
  throw new Error(`Unknown fighterId for Pokemon skin: ${loadout.fighterId}`);
78
78
  }
79
79
  const moves = buildMovesFromLoadout(fighter, loadout);
80
80
  const items = buildItemsFromLoadout(loadout);
81
- const maxHp = fighter.baseStats.defense + fighter.baseStats.offense;
82
81
  return {
83
- // ✅ NUEVO: modelo multi-equipo
84
- team: [
85
- {
86
- fighter,
87
- maxHp,
88
- moves,
89
- amulet: null,
90
- },
91
- ],
92
- // ✅ LEGACY: seguimos rellenando los campos antiguos
93
- // por si algún sitio sigue leyéndolos.
94
82
  fighter,
95
- maxHp,
83
+ maxHp: fighter.baseStats.defense + fighter.baseStats.offense,
96
84
  moves,
97
85
  items,
98
86
  amulet: null,
@@ -77,21 +77,9 @@ export class PokemonSkin {
77
77
  }
78
78
  const moves = buildMovesFromLoadout(fighter, loadout);
79
79
  const items = buildItemsFromLoadout(loadout);
80
- const maxHp = fighter.baseStats.defense + fighter.baseStats.offense;
81
80
  return {
82
- // ✅ NUEVO: modelo multi-equipo
83
- team: [
84
- {
85
- fighter,
86
- maxHp,
87
- moves,
88
- amulet: null,
89
- },
90
- ],
91
- // ✅ LEGACY: seguimos rellenando los campos antiguos
92
- // por si algún sitio sigue leyéndolos.
93
81
  fighter,
94
- maxHp,
82
+ maxHp: fighter.baseStats.defense + fighter.baseStats.offense,
95
83
  moves,
96
84
  items,
97
85
  amulet: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pokemon-io-core",
3
- "version": "0.0.71",
3
+ "version": "0.0.72",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",