pokemon-io-core 0.0.69 → 0.0.71
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.
- package/dist/api/room.d.ts +4 -2
- package/dist/core/actions.d.ts +2 -1
- package/dist/core/engine.d.ts +32 -0
- package/dist/core/engine.js +100 -24
- package/dist/core/events.d.ts +8 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/skins/CombatSkin.d.ts +16 -1
- package/dist/skins/cliches/clicheSkin.js +16 -4
- package/dist/skins/pokemon/pokemonSkin.js +13 -1
- package/package.json +1 -1
package/dist/api/room.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export interface PlayerLoadoutView {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
fighterIds: string[];
|
|
3
|
+
movesByFighterId: Record<string, string[]>;
|
|
4
4
|
itemIds: string[];
|
|
5
5
|
amuletId: string | null;
|
|
6
|
+
fighterId: string | null;
|
|
7
|
+
moveIds: string[];
|
|
6
8
|
}
|
|
7
9
|
export interface PlayerView {
|
|
8
10
|
id: string;
|
package/dist/core/actions.d.ts
CHANGED
package/dist/core/engine.d.ts
CHANGED
|
@@ -16,7 +16,39 @@ 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
|
+
}
|
|
19
42
|
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
|
+
*/
|
|
20
52
|
fighter: FighterDefinition;
|
|
21
53
|
maxHp: number;
|
|
22
54
|
moves: MoveDefinition[];
|
package/dist/core/engine.js
CHANGED
|
@@ -30,29 +30,26 @@ const cloneStats = (stats) => ({
|
|
|
30
30
|
// ------------------------------------------------------
|
|
31
31
|
// Creación de estado inicial
|
|
32
32
|
// ------------------------------------------------------
|
|
33
|
-
const createBattleFighter = (
|
|
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
|
-
}
|
|
33
|
+
const createBattleFighter = (slot, playerItems) => {
|
|
40
34
|
return {
|
|
41
|
-
fighterId:
|
|
42
|
-
classId:
|
|
43
|
-
maxHp:
|
|
44
|
-
currentHp:
|
|
45
|
-
baseStats:
|
|
46
|
-
effectiveStats:
|
|
47
|
-
moves:
|
|
48
|
-
moveId:
|
|
49
|
-
currentPP:
|
|
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,
|
|
50
44
|
})),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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,
|
|
54
51
|
})),
|
|
55
|
-
amuletId:
|
|
52
|
+
amuletId: slot.amulet?.id ?? null,
|
|
56
53
|
statuses: [],
|
|
57
54
|
isAlive: true,
|
|
58
55
|
};
|
|
@@ -89,10 +86,36 @@ const recomputeEffectiveStatsForFighter = (state, fighter) => {
|
|
|
89
86
|
effectiveStats: eff,
|
|
90
87
|
};
|
|
91
88
|
};
|
|
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
|
+
};
|
|
92
104
|
const createPlayerBattleState = (cfg) => {
|
|
93
|
-
|
|
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));
|
|
94
117
|
return {
|
|
95
|
-
fighterTeam
|
|
118
|
+
fighterTeam,
|
|
96
119
|
activeIndex: 0,
|
|
97
120
|
};
|
|
98
121
|
};
|
|
@@ -762,6 +785,35 @@ const applyEndOfTurnStatuses = (state) => {
|
|
|
762
785
|
};
|
|
763
786
|
return { state: newState, events };
|
|
764
787
|
};
|
|
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
|
+
};
|
|
765
817
|
// ------------------------------------------------------
|
|
766
818
|
// Bucle principal de turno
|
|
767
819
|
// ------------------------------------------------------
|
|
@@ -797,6 +849,7 @@ export const resolveTurn = (state, actions) => {
|
|
|
797
849
|
...getMovePriorityAndSpeed(runtimeState, "player2", actions.player2),
|
|
798
850
|
},
|
|
799
851
|
];
|
|
852
|
+
// Orden clásico por prioridad + velocidad
|
|
800
853
|
entries.sort((a, b) => {
|
|
801
854
|
if (b.priority !== a.priority) {
|
|
802
855
|
return b.priority - a.priority;
|
|
@@ -813,7 +866,29 @@ export const resolveTurn = (state, actions) => {
|
|
|
813
866
|
priority: e.priority,
|
|
814
867
|
speed: e.speed,
|
|
815
868
|
})));
|
|
816
|
-
|
|
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) {
|
|
817
892
|
const { playerKey, action } = entry;
|
|
818
893
|
if (action.kind === "no_action")
|
|
819
894
|
continue;
|
|
@@ -838,7 +913,7 @@ export const resolveTurn = (state, actions) => {
|
|
|
838
913
|
events.push(...result.events);
|
|
839
914
|
}
|
|
840
915
|
else {
|
|
841
|
-
//
|
|
916
|
+
// Cualquier otro tipo de acción futura se ignora de momento
|
|
842
917
|
continue;
|
|
843
918
|
}
|
|
844
919
|
const winnerAfterAction = checkWinner(currentState);
|
|
@@ -857,6 +932,7 @@ export const resolveTurn = (state, actions) => {
|
|
|
857
932
|
};
|
|
858
933
|
}
|
|
859
934
|
}
|
|
935
|
+
// 🔁 3) Estados de final de turno
|
|
860
936
|
const statusResult = applyEndOfTurnStatuses(currentState);
|
|
861
937
|
currentState = statusResult.state;
|
|
862
938
|
events.push(...statusResult.events);
|
package/dist/core/events.d.ts
CHANGED
|
@@ -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" | "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" | "shield_applied" | "status_applied" | "status_refreshed" | "status_expired" | "fighter_fainted" | "fighter_switched" | "turn_end" | "battle_end";
|
|
3
3
|
export interface BaseBattleEvent {
|
|
4
4
|
id: string;
|
|
5
5
|
turnNumber: number;
|
|
@@ -60,8 +60,14 @@ export interface FighterFaintedEvent extends BaseBattleEvent {
|
|
|
60
60
|
kind: "fighter_fainted";
|
|
61
61
|
fighterId: FighterId;
|
|
62
62
|
}
|
|
63
|
+
export interface FighterSwitchedEvent extends BaseBattleEvent {
|
|
64
|
+
kind: "fighter_switched";
|
|
65
|
+
player: "player1" | "player2";
|
|
66
|
+
oldFighterId: FighterId;
|
|
67
|
+
newFighterId: FighterId;
|
|
68
|
+
}
|
|
63
69
|
export interface BattleEndEvent extends BaseBattleEvent {
|
|
64
70
|
kind: "battle_end";
|
|
65
71
|
winner: "player1" | "player2" | "draw";
|
|
66
72
|
}
|
|
67
|
-
export type BattleEvent = ActionDeclaredEvent | MoveMissEvent | MoveHitEvent | ItemUsedEvent | DamageEvent | HealEvent | StatusAppliedEvent | StatusExpiredEvent | FighterFaintedEvent | BattleEndEvent | BaseBattleEvent;
|
|
73
|
+
export type BattleEvent = ActionDeclaredEvent | MoveMissEvent | MoveHitEvent | ItemUsedEvent | DamageEvent | HealEvent | StatusAppliedEvent | StatusExpiredEvent | FighterFaintedEvent | FighterSwitchedEvent | BattleEndEvent | BaseBattleEvent;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,9 +2,24 @@ 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[];
|
|
5
6
|
itemIds: string[];
|
|
6
7
|
amuletId: string | null;
|
|
7
|
-
|
|
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;
|
|
8
23
|
}
|
|
9
24
|
export interface CombatSkin {
|
|
10
25
|
readonly id: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// pokemon-io-core/src/skins/
|
|
1
|
+
// pokemon-io-core/src/skins/tribes/clicheSkin.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
|
|
5
|
+
const findTribeFighterById = (id) => {
|
|
6
6
|
const lower = id.toLowerCase();
|
|
7
7
|
return TRIBE_FIGHTERS.find((f) => f.id.toLowerCase() === lower) ?? null;
|
|
8
8
|
};
|
|
@@ -72,15 +72,27 @@ 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 =
|
|
75
|
+
const fighter = findTribeFighterById(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;
|
|
81
82
|
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.
|
|
82
94
|
fighter,
|
|
83
|
-
maxHp
|
|
95
|
+
maxHp,
|
|
84
96
|
moves,
|
|
85
97
|
items,
|
|
86
98
|
amulet: null,
|
|
@@ -77,9 +77,21 @@ 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;
|
|
80
81
|
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.
|
|
81
93
|
fighter,
|
|
82
|
-
maxHp
|
|
94
|
+
maxHp,
|
|
83
95
|
moves,
|
|
84
96
|
items,
|
|
85
97
|
amulet: null,
|