pokemon-io-core 0.0.73 → 0.0.75

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.
@@ -9,6 +9,7 @@ export interface BattlePlayerView {
9
9
  nickname: string;
10
10
  hp: number;
11
11
  statuses: BattlePlayerStatusView[];
12
+ activeFighterId: string;
12
13
  }
13
14
  export type BattleStatus = "ongoing" | "finished";
14
15
  export interface BattleView {
@@ -23,4 +24,8 @@ export interface BattleView {
23
24
  [playerId: string]: number;
24
25
  };
25
26
  turnNumber: number;
27
+ pendingActions?: {
28
+ player1: boolean;
29
+ player2: boolean;
30
+ };
26
31
  }
@@ -1,6 +1,9 @@
1
- export interface PlayerLoadoutView {
2
- fighterId: string | null;
1
+ export interface PlayerLoadoutFighterView {
2
+ fighterId: string;
3
3
  moveIds: string[];
4
+ }
5
+ export interface PlayerLoadoutView {
6
+ fighters: PlayerLoadoutFighterView[];
4
7
  itemIds: string[];
5
8
  amuletId: string | null;
6
9
  }
@@ -20,8 +20,10 @@ export interface ClientToServerEvents {
20
20
  }) => void;
21
21
  "room:setLoadout": (payload: {
22
22
  roomId: string;
23
- fighterId: string;
24
- moveIds: string[];
23
+ fighters: {
24
+ fighterId: string;
25
+ moveIds: string[];
26
+ }[];
25
27
  itemIds: string[];
26
28
  amuletId?: string | null;
27
29
  }) => void;
@@ -22,7 +22,6 @@ export interface BattleFighter {
22
22
  baseStats: FighterStats;
23
23
  effectiveStats: FighterStats;
24
24
  moves: EquippedMove[];
25
- items: EquippedItem[];
26
25
  amuletId: AmuletId | null;
27
26
  statuses: ActiveStatus[];
28
27
  isAlive: boolean;
@@ -30,6 +29,7 @@ export interface BattleFighter {
30
29
  export interface PlayerBattleState {
31
30
  fighterTeam: BattleFighter[];
32
31
  activeIndex: number;
32
+ inventory: EquippedItem[];
33
33
  }
34
34
  export interface BattleState {
35
35
  turnNumber: number;
@@ -16,10 +16,14 @@ export interface BattleRuntime {
16
16
  statusesById: Record<StatusId, StatusDefinition>;
17
17
  typeEffectiveness: TypeEffectivenessMatrix;
18
18
  }
19
- export interface PlayerBattleConfig {
19
+ export interface PlayerFighterBattleConfig {
20
20
  fighter: FighterDefinition;
21
21
  maxHp: number;
22
22
  moves: MoveDefinition[];
23
+ amulet: AmuletDefinition | null;
24
+ }
25
+ export interface PlayerBattleConfig {
26
+ fighters: PlayerFighterBattleConfig[];
23
27
  items: ItemDefinition[];
24
28
  amulet: AmuletDefinition | null;
25
29
  }
@@ -34,9 +34,6 @@ const createBattleFighter = (cfg) => {
34
34
  if (cfg.moves.length !== 4) {
35
35
  throw new Error("Each fighter must have exactly 4 moves in MVP");
36
36
  }
37
- if (cfg.items.length > 4) {
38
- throw new Error("A fighter cannot have more than 4 items");
39
- }
40
37
  return {
41
38
  fighterId: cfg.fighter.id,
42
39
  classId: cfg.fighter.classId,
@@ -48,10 +45,6 @@ const createBattleFighter = (cfg) => {
48
45
  moveId: move.id,
49
46
  currentPP: move.maxPP,
50
47
  })),
51
- items: cfg.items.map((item) => ({
52
- itemId: item.id,
53
- usesRemaining: item.maxUses,
54
- })),
55
48
  amuletId: cfg.amulet ? cfg.amulet.id : null,
56
49
  statuses: [],
57
50
  isAlive: true,
@@ -90,10 +83,15 @@ const recomputeEffectiveStatsForFighter = (state, fighter) => {
90
83
  };
91
84
  };
92
85
  const createPlayerBattleState = (cfg) => {
93
- const fighter = createBattleFighter(cfg);
86
+ const team = cfg.fighters.map((fCfg) => createBattleFighter(fCfg));
87
+ const inventory = cfg.items.map((item) => ({
88
+ itemId: item.id,
89
+ usesRemaining: item.maxUses,
90
+ }));
94
91
  return {
95
- fighterTeam: [fighter],
92
+ fighterTeam: team,
96
93
  activeIndex: 0,
94
+ inventory,
97
95
  };
98
96
  };
99
97
  export const createInitialBattleState = (config) => {
@@ -132,6 +130,16 @@ const getOpponentAndSelf = (state, playerKey) => {
132
130
  opponent: getActiveFighter(oppPlayer),
133
131
  };
134
132
  };
133
+ const getPlayersAndActives = (state, playerKey) => {
134
+ const selfPlayer = playerKey === "player1" ? state.player1 : state.player2;
135
+ const oppPlayer = playerKey === "player1" ? state.player2 : state.player1;
136
+ return {
137
+ selfPlayer,
138
+ oppPlayer,
139
+ self: getActiveFighter(selfPlayer),
140
+ opponent: getActiveFighter(oppPlayer),
141
+ };
142
+ };
135
143
  const getTypeEffectiveness = (state, attackerTypeId, defenderTypeId) => {
136
144
  const matrix = state.runtime.typeEffectiveness;
137
145
  const row = matrix[attackerTypeId];
@@ -480,34 +488,6 @@ const applyEffectsOnTarget = (state, actor, target, moveTypeId, isCritical, effe
480
488
  }
481
489
  return { actor: currentActor, target: currentTarget, events };
482
490
  };
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
- };
511
491
  const getMovePriorityAndSpeed = (state, playerKey, action) => {
512
492
  const { self } = getOpponentAndSelf(state, playerKey);
513
493
  if (action.kind === "no_action") {
@@ -537,6 +517,53 @@ const getMovePriorityAndSpeed = (state, playerKey, action) => {
537
517
  speed: self.effectiveStats.speed,
538
518
  };
539
519
  };
520
+ const resolveSwitchFighter = (state, playerKey, action) => {
521
+ if (action.kind !== "switch_fighter")
522
+ return { state, events: [] };
523
+ const events = [];
524
+ const selfPlayer = playerKey === "player1" ? state.player1 : state.player2;
525
+ const fromIndex = selfPlayer.activeIndex;
526
+ const toIndex = action.newIndex;
527
+ // validaciones
528
+ if (toIndex < 0 || toIndex >= selfPlayer.fighterTeam.length) {
529
+ return { state, events };
530
+ }
531
+ if (toIndex === fromIndex) {
532
+ return { state, events };
533
+ }
534
+ const target = selfPlayer.fighterTeam[toIndex];
535
+ if (!target || !target.isAlive || target.currentHp <= 0) {
536
+ return { state, events };
537
+ }
538
+ const fromFighter = selfPlayer.fighterTeam[fromIndex];
539
+ events.push({
540
+ ...createBaseEvent(state.turnNumber, "action_declared", `${fromFighter.fighterId} cambia a ${target.fighterId}`),
541
+ actorId: fromFighter.fighterId,
542
+ });
543
+ // actualizar activeIndex
544
+ if (playerKey === "player1") {
545
+ return {
546
+ state: {
547
+ ...state,
548
+ player1: {
549
+ ...state.player1,
550
+ activeIndex: toIndex,
551
+ },
552
+ },
553
+ events,
554
+ };
555
+ }
556
+ return {
557
+ state: {
558
+ ...state,
559
+ player2: {
560
+ ...state.player2,
561
+ activeIndex: toIndex,
562
+ },
563
+ },
564
+ events,
565
+ };
566
+ };
540
567
  // ------------------------------------------------------
541
568
  // use_move
542
569
  // ------------------------------------------------------
@@ -634,9 +661,9 @@ const resolveItemUse = (state, playerKey, action) => {
634
661
  if (action.kind !== "use_item") {
635
662
  return { state, events: [] };
636
663
  }
637
- const { self, opponent } = getOpponentAndSelf(state, playerKey);
664
+ const { selfPlayer, oppPlayer, self, opponent } = getPlayersAndActives(state, playerKey);
638
665
  const events = [];
639
- const itemSlot = self.items[action.itemIndex];
666
+ const itemSlot = selfPlayer.inventory[action.itemIndex];
640
667
  if (!itemSlot || itemSlot.usesRemaining <= 0) {
641
668
  dbg("use_item: no charges", {
642
669
  playerKey,
@@ -652,10 +679,10 @@ const resolveItemUse = (state, playerKey, action) => {
652
679
  });
653
680
  return { state, events };
654
681
  }
655
- const updatedItems = self.items.map((it, idx) => idx === action.itemIndex
682
+ const updatedInventory = selfPlayer.inventory.map((it, idx) => idx === action.itemIndex
656
683
  ? { ...it, usesRemaining: Math.max(0, it.usesRemaining - 1) }
657
684
  : it);
658
- let updatedSelf = { ...self, items: updatedItems };
685
+ let updatedSelf = { ...self };
659
686
  let updatedOpponent = { ...opponent };
660
687
  events.push({
661
688
  ...createBaseEvent(state.turnNumber, "action_declared", `${self.fighterId} usó ${itemDef.name}`),
@@ -664,7 +691,26 @@ const resolveItemUse = (state, playerKey, action) => {
664
691
  const { actor: finalSelf, target: finalOpp, events: extraEvents, } = applyEffectsOnTarget(state, updatedSelf, updatedOpponent, null, // los items no usan fórmula de tipo por ahora
665
692
  false, itemDef.effects);
666
693
  events.push(...extraEvents);
667
- const newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
694
+ let newState = updateFightersInState(state, playerKey, finalSelf, finalOpp);
695
+ // persistimos el inventario gastado en el jugador que actuó
696
+ if (playerKey === "player1") {
697
+ newState = {
698
+ ...newState,
699
+ player1: {
700
+ ...newState.player1,
701
+ inventory: updatedInventory,
702
+ },
703
+ };
704
+ }
705
+ else {
706
+ newState = {
707
+ ...newState,
708
+ player2: {
709
+ ...newState.player2,
710
+ inventory: updatedInventory,
711
+ },
712
+ };
713
+ }
668
714
  return { state: newState, events };
669
715
  };
670
716
  // ------------------------------------------------------
@@ -705,10 +751,8 @@ const hasHardCc = (state, fighter) => fighter.statuses.some((st) => {
705
751
  return def?.kind === "hard_cc";
706
752
  });
707
753
  const checkWinner = (state) => {
708
- const f1 = getActiveFighter(state.player1);
709
- const f2 = getActiveFighter(state.player2);
710
- const p1Alive = f1.isAlive && f1.currentHp > 0;
711
- const p2Alive = f2.isAlive && f2.currentHp > 0;
754
+ const p1Alive = state.player1.fighterTeam.some((f) => f.isAlive && f.currentHp > 0);
755
+ const p2Alive = state.player2.fighterTeam.some((f) => f.isAlive && f.currentHp > 0);
712
756
  if (p1Alive && !p2Alive)
713
757
  return "player1";
714
758
  if (!p1Alive && p2Alive)
@@ -861,6 +905,11 @@ export const resolveTurn = (state, actions) => {
861
905
  });
862
906
  continue;
863
907
  }
908
+ if (action.kind === "switch_fighter") {
909
+ const result = resolveSwitchFighter(currentState, playerKey, action);
910
+ currentState = result.state;
911
+ events.push(...result.events);
912
+ }
864
913
  if (action.kind === "use_move") {
865
914
  const result = resolveDamageMove(currentState, playerKey, action);
866
915
  currentState = result.state;
@@ -871,11 +920,6 @@ export const resolveTurn = (state, actions) => {
871
920
  currentState = result.state;
872
921
  events.push(...result.events);
873
922
  }
874
- if (action.kind === "switch_fighter") {
875
- const result = resolveSwitchFighter(currentState, playerKey, action);
876
- currentState = result.state;
877
- events.push(...result.events);
878
- }
879
923
  else {
880
924
  // switch_fighter u otros no implementados aún
881
925
  continue;
@@ -1,10 +1,13 @@
1
1
  import type { TypeDefinition, TypeEffectivenessMatrix, MoveDefinition, ItemDefinition, AmuletDefinition, StatusDefinition } from "../core/index.js";
2
2
  import type { PlayerBattleConfig } from "../core/engine.js";
3
- export interface FighterLoadout {
3
+ export interface TeamFighterLoadout {
4
4
  fighterId: string | null;
5
+ moveIds: string[];
6
+ }
7
+ export interface PlayerTeamLoadout {
8
+ fighters: TeamFighterLoadout[];
5
9
  itemIds: string[];
6
10
  amuletId: string | null;
7
- moveIds: string[];
8
11
  }
9
12
  export interface CombatSkin {
10
13
  readonly id: string;
@@ -14,5 +17,5 @@ export interface CombatSkin {
14
17
  getItems(): ItemDefinition[];
15
18
  getAmulets(): AmuletDefinition[];
16
19
  getStatuses(): StatusDefinition[];
17
- buildPlayerConfig(loadout: FighterLoadout): PlayerBattleConfig;
20
+ buildPlayerConfig(loadout: PlayerTeamLoadout): PlayerBattleConfig;
18
21
  }
@@ -1,5 +1,5 @@
1
1
  import { ItemDefinition, MoveDefinition, PlayerBattleConfig } from "../../core/index.js";
2
- import type { CombatSkin, FighterLoadout } from "../CombatSkin.js";
2
+ import type { CombatSkin, PlayerTeamLoadout } from "../CombatSkin.js";
3
3
  export declare class TribeSkin implements CombatSkin {
4
4
  readonly id = "tribe";
5
5
  getTypes(): import("../../index.js").TypeDefinition[];
@@ -8,6 +8,6 @@ export declare class TribeSkin implements CombatSkin {
8
8
  getItems(): ItemDefinition[];
9
9
  getAmulets(): never[];
10
10
  getStatuses(): import("../../index.js").StatusDefinition[];
11
- buildPlayerConfig(loadout: FighterLoadout): PlayerBattleConfig;
11
+ buildPlayerConfig(loadout: PlayerTeamLoadout): PlayerBattleConfig;
12
12
  }
13
13
  export declare const createTribeSkin: () => CombatSkin;
@@ -13,8 +13,7 @@ const findItemById = (id) => {
13
13
  return POKEMON_ITEMS.find((i) => i.id === id) ?? null;
14
14
  };
15
15
  const FILLER_MOVE_ID = "tackle";
16
- const buildMovesFromLoadout = (fighter, loadout) => {
17
- const selectedMoveIds = loadout.moveIds ?? [];
16
+ const buildMovesFromLoadout = (fighter, selectedMoveIds) => {
18
17
  const selectedMoves = selectedMoveIds
19
18
  .map((id) => findMoveById(id))
20
19
  .filter((m) => m !== null);
@@ -68,22 +67,30 @@ export class TribeSkin {
68
67
  return TRIBE_STATUSES; // más adelante
69
68
  }
70
69
  buildPlayerConfig(loadout) {
71
- if (!loadout.fighterId) {
72
- throw new Error("fighterId is required in FighterLoadout");
70
+ if (!loadout.fighters || loadout.fighters.length === 0) {
71
+ throw new Error("fighters[] is required in PlayerTeamLoadout");
73
72
  }
74
- console.log("buildPlayerConfig", loadout.fighterId);
75
- const fighter = findPokemonById(loadout.fighterId);
76
- if (!fighter) {
77
- throw new Error(`Unknown fighterId for Pokemon skin: ${loadout.fighterId}`);
78
- }
79
- const moves = buildMovesFromLoadout(fighter, loadout);
73
+ const fighters = loadout.fighters.map((slot, idx) => {
74
+ if (!slot.fighterId) {
75
+ throw new Error(`fighterId is required in slot ${idx}`);
76
+ }
77
+ const fighter = findPokemonById(slot.fighterId);
78
+ if (!fighter) {
79
+ throw new Error(`Unknown fighterId for Pokemon skin: ${slot.fighterId}`);
80
+ }
81
+ const moves = buildMovesFromLoadout(fighter, slot.moveIds ?? []);
82
+ return {
83
+ fighter,
84
+ maxHp: fighter.baseStats.defense + fighter.baseStats.offense,
85
+ moves,
86
+ amulet: null
87
+ };
88
+ });
80
89
  const items = buildItemsFromLoadout(loadout);
81
90
  return {
82
- fighter,
83
- maxHp: fighter.baseStats.defense + fighter.baseStats.offense,
84
- moves,
91
+ fighters,
85
92
  items,
86
- amulet: null,
93
+ amulet: null
87
94
  };
88
95
  }
89
96
  }
@@ -1,5 +1,5 @@
1
1
  import { ItemDefinition, MoveDefinition, PlayerBattleConfig } from "../../core/index.js";
2
- import type { CombatSkin, FighterLoadout } from "../CombatSkin.js";
2
+ import type { CombatSkin, PlayerTeamLoadout } from "../CombatSkin.js";
3
3
  export declare class PokemonSkin implements CombatSkin {
4
4
  readonly id = "pokemon";
5
5
  getTypes(): import("../../index.js").TypeDefinition[];
@@ -8,6 +8,6 @@ export declare class PokemonSkin implements CombatSkin {
8
8
  getItems(): ItemDefinition[];
9
9
  getAmulets(): never[];
10
10
  getStatuses(): import("../../index.js").StatusDefinition[];
11
- buildPlayerConfig(loadout: FighterLoadout): PlayerBattleConfig;
11
+ buildPlayerConfig(loadout: PlayerTeamLoadout): PlayerBattleConfig;
12
12
  }
13
13
  export declare const createPokemonSkin: () => CombatSkin;
@@ -12,8 +12,7 @@ const findItemById = (id) => {
12
12
  return POKEMON_ITEMS.find((i) => i.id === id) ?? null;
13
13
  };
14
14
  const FILLER_MOVE_ID = "tackle";
15
- const buildMovesFromLoadout = (fighter, loadout) => {
16
- const selectedMoveIds = loadout.moveIds ?? [];
15
+ const buildMovesFromLoadout = (fighter, selectedMoveIds) => {
17
16
  const selectedMoves = selectedMoveIds
18
17
  .map((id) => findMoveById(id))
19
18
  .filter((m) => m !== null);
@@ -67,22 +66,30 @@ export class PokemonSkin {
67
66
  return POKEMON_STATUSES; // más adelante
68
67
  }
69
68
  buildPlayerConfig(loadout) {
70
- if (!loadout.fighterId) {
71
- throw new Error("fighterId is required in FighterLoadout");
69
+ if (!loadout.fighters || loadout.fighters.length === 0) {
70
+ throw new Error("fighters[] is required in PlayerTeamLoadout");
72
71
  }
73
- console.log("buildPlayerConfig", loadout.fighterId);
74
- const fighter = findPokemonById(loadout.fighterId);
75
- if (!fighter) {
76
- throw new Error(`Unknown fighterId for Pokemon skin: ${loadout.fighterId}`);
77
- }
78
- const moves = buildMovesFromLoadout(fighter, loadout);
72
+ const fighters = loadout.fighters.map((slot, idx) => {
73
+ if (!slot.fighterId) {
74
+ throw new Error(`fighterId is required in slot ${idx}`);
75
+ }
76
+ const fighter = findPokemonById(slot.fighterId);
77
+ if (!fighter) {
78
+ throw new Error(`Unknown fighterId for Pokemon skin: ${slot.fighterId}`);
79
+ }
80
+ const moves = buildMovesFromLoadout(fighter, slot.moveIds ?? []);
81
+ return {
82
+ fighter,
83
+ maxHp: fighter.baseStats.defense + fighter.baseStats.offense,
84
+ moves,
85
+ amulet: null
86
+ };
87
+ });
79
88
  const items = buildItemsFromLoadout(loadout);
80
89
  return {
81
- fighter,
82
- maxHp: fighter.baseStats.defense + fighter.baseStats.offense,
83
- moves,
90
+ fighters,
84
91
  items,
85
- amulet: null,
92
+ amulet: null
86
93
  };
87
94
  }
88
95
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pokemon-io-core",
3
- "version": "0.0.73",
3
+ "version": "0.0.75",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",