pokemon-io-core 0.0.95 → 0.0.97

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,9 +1,6 @@
1
1
  import { BattleView } from "./battle.js";
2
2
  import { RoomView } from "./room.js";
3
3
  export interface ClientToServerEvents {
4
- "player:hello": (payload: {
5
- playerKey: string;
6
- }) => void;
7
4
  "room:create": (payload: {
8
5
  nickname: string;
9
6
  roomName: string;
@@ -19,7 +16,7 @@ export interface ClientToServerEvents {
19
16
  }) => void;
20
17
  "room:reconnect": (payload: {
21
18
  roomId: string;
22
- playerKey: string;
19
+ oldPlayerId: string;
23
20
  }) => void;
24
21
  "room:setLoadout": (payload: {
25
22
  roomId: string;
@@ -21,11 +21,20 @@ export interface BattleFighter {
21
21
  currentHp: number;
22
22
  baseStats: FighterStats;
23
23
  effectiveStats: FighterStats;
24
+ statStages: StatStages;
24
25
  moves: EquippedMove[];
25
26
  amuletId: AmuletId | null;
26
27
  statuses: ActiveStatus[];
27
28
  isAlive: boolean;
28
29
  }
30
+ export interface StatStages {
31
+ offense: number;
32
+ defense: number;
33
+ speed: number;
34
+ crit: number;
35
+ accuracy: number;
36
+ evasion: number;
37
+ }
29
38
  export interface PlayerBattleState {
30
39
  fighterTeam: BattleFighter[];
31
40
  activeIndex: number;
@@ -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" | "fighter_switched" | "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" | "fighter_switched" | "shield_applied" | "status_applied" | "status_refreshed" | "status_expired" | "fighter_fainted" | "turn_end" | "stats_changed" | "battle_end";
3
3
  export interface BaseBattleEvent {
4
4
  id: string;
5
5
  turnNumber: number;
@@ -7,6 +7,18 @@ export interface BaseBattleEvent {
7
7
  message: string;
8
8
  timestamp: number;
9
9
  }
10
+ export interface StatsChangedEvent extends BaseBattleEvent {
11
+ kind: "stats_changed";
12
+ targetId: FighterId;
13
+ changes: {
14
+ offense?: number;
15
+ defense?: number;
16
+ speed?: number;
17
+ crit?: number;
18
+ accuracy?: number;
19
+ evasion?: number;
20
+ };
21
+ }
10
22
  export interface ActionDeclaredEvent extends BaseBattleEvent {
11
23
  kind: "action_declared";
12
24
  actorId: FighterId;
@@ -75,4 +87,4 @@ export interface BattleEndEvent extends BaseBattleEvent {
75
87
  kind: "battle_end";
76
88
  winner: "player1" | "player2" | "draw";
77
89
  }
78
- export type BattleEvent = ActionDeclaredEvent | MoveMissEvent | MoveHitEvent | ItemUsedEvent | DamageEvent | HealEvent | StatusAppliedEvent | StatusExpiredEvent | FighterFaintedEvent | FighterSwitchedEvent | BattleEndEvent | BaseBattleEvent;
90
+ export type BattleEvent = ActionDeclaredEvent | MoveMissEvent | MoveHitEvent | ItemUsedEvent | DamageEvent | HealEvent | StatusAppliedEvent | StatusExpiredEvent | FighterFaintedEvent | FighterSwitchedEvent | BattleEndEvent | StatsChangedEvent | BaseBattleEvent;
@@ -1,4 +1,5 @@
1
1
  import { computeCritChance } from "../combat/crit.js";
2
+ import { getAccuracyMultiplier } from "../combat/stages.js";
2
3
  import { getTypeEffectiveness } from "../combat/typeEffectiveness.js";
3
4
  import { dbg } from "../debug.js";
4
5
  import { applyEffectsOnTarget } from "../effects/applyEffects.js";
@@ -38,9 +39,13 @@ export const resolveDamageMove = (state, playerKey, action) => {
38
39
  ...createBaseEvent(state.turnNumber, "action_declared", `${self.fighterId} usó ${move.name}`),
39
40
  actorId: self.fighterId,
40
41
  });
41
- const accuracy = move.accuracy ?? 100;
42
+ // Cálculo de puntería con Stages
43
+ const accStage = self.statStages.accuracy - opponent.statStages.evasion;
44
+ const accMult = getAccuracyMultiplier(accStage);
45
+ const baseAcc = move.accuracy ?? 100;
46
+ const finalAccuracy = baseAcc * accMult;
42
47
  const hitRoll = randomInRange(0, 100);
43
- const hit = hitRoll < accuracy;
48
+ const hit = hitRoll < finalAccuracy;
44
49
  const updatedMoves = self.moves.map((m, idx) => idx === action.moveIndex
45
50
  ? { ...m, currentPP: Math.max(0, m.currentPP - 1) }
46
51
  : m);
@@ -52,7 +57,7 @@ export const resolveDamageMove = (state, playerKey, action) => {
52
57
  moveId: move.id,
53
58
  moveName: move.name,
54
59
  hitRoll,
55
- accuracy,
60
+ accuracy: finalAccuracy,
56
61
  });
57
62
  events.push({
58
63
  ...createBaseEvent(state.turnNumber, "move_miss", `${self.fighterId} falla ${move.name}`),
@@ -0,0 +1,5 @@
1
+ export declare const STAGE_MULTIPLIERS_STATS: Record<number, number>;
2
+ export declare const STAGE_MULTIPLIERS_ACCURACY: Record<number, number>;
3
+ export declare const getCritChanceFromStage: (stage: number) => number;
4
+ export declare const getStatMultiplier: (stage: number) => number;
5
+ export declare const getAccuracyMultiplier: (stage: number) => number;
@@ -0,0 +1,53 @@
1
+ export const STAGE_MULTIPLIERS_STATS = {
2
+ "-6": 2 / 8,
3
+ "-5": 2 / 7,
4
+ "-4": 2 / 6,
5
+ "-3": 2 / 5,
6
+ "-2": 2 / 4,
7
+ "-1": 2 / 3,
8
+ 0: 2 / 2,
9
+ 1: 3 / 2,
10
+ 2: 4 / 2,
11
+ 3: 5 / 2,
12
+ 4: 6 / 2,
13
+ 5: 7 / 2,
14
+ 6: 8 / 2,
15
+ };
16
+ // Accuracy/Evasion multipliers (3/3 base)
17
+ export const STAGE_MULTIPLIERS_ACCURACY = {
18
+ "-6": 3 / 9,
19
+ "-5": 3 / 8,
20
+ "-4": 3 / 7,
21
+ "-3": 3 / 6,
22
+ "-2": 3 / 5,
23
+ "-1": 3 / 4,
24
+ 0: 3 / 3,
25
+ 1: 4 / 3,
26
+ 2: 5 / 3,
27
+ 3: 6 / 3,
28
+ 4: 7 / 3,
29
+ 5: 8 / 3,
30
+ 6: 9 / 3,
31
+ };
32
+ // Critical Hit Ratio (Table)
33
+ // Stage 0: ~4.17% (1/24)
34
+ // Stage 1: 12.5% (1/8)
35
+ // Stage 2: 50% (1/2)
36
+ // Stage 3+: 100% (1/1)
37
+ export const getCritChanceFromStage = (stage) => {
38
+ if (stage <= 0)
39
+ return 1 / 24;
40
+ if (stage === 1)
41
+ return 1 / 8;
42
+ if (stage === 2)
43
+ return 1 / 2;
44
+ return 1;
45
+ };
46
+ export const getStatMultiplier = (stage) => {
47
+ const clamped = Math.max(-6, Math.min(6, stage));
48
+ return STAGE_MULTIPLIERS_STATS[clamped] ?? 1;
49
+ };
50
+ export const getAccuracyMultiplier = (stage) => {
51
+ const clamped = Math.max(-6, Math.min(6, stage));
52
+ return STAGE_MULTIPLIERS_ACCURACY[clamped] ?? 1;
53
+ };
@@ -4,6 +4,8 @@ import { dbg } from "../debug.js";
4
4
  import { applyStatusToFighter } from "../status/apply.js";
5
5
  import { clearStatusFromFighter } from "../status/clear.js";
6
6
  import { resolveEffectTarget } from "./target.js";
7
+ import { createBaseEvent } from "../events.js";
8
+ import { recomputeEffectiveStatsForFighter } from "../fighters/fighter.js";
7
9
  export const applyEffectsOnTarget = (state, actor, target, moveTypeId, isCritical, effects) => {
8
10
  dbg("applyEffectsOnTarget", {
9
11
  actorId: actor.fighterId,
@@ -94,7 +96,74 @@ export const applyEffectsOnTarget = (state, actor, target, moveTypeId, isCritica
94
96
  }
95
97
  break;
96
98
  }
97
- // TODO: conectar shield, modify_stats, etc.
99
+ case "modify_stats": {
100
+ const { primary, isSelf } = resolveEffectTarget(eff, currentActor, currentTarget);
101
+ const newStages = { ...primary.statStages };
102
+ const changes = {};
103
+ if (eff.offenseDelta) {
104
+ newStages.offense = Math.max(-6, Math.min(6, newStages.offense + eff.offenseDelta));
105
+ changes.offense = eff.offenseDelta;
106
+ }
107
+ if (eff.defenseDelta) {
108
+ newStages.defense = Math.max(-6, Math.min(6, newStages.defense + eff.defenseDelta));
109
+ changes.defense = eff.defenseDelta;
110
+ }
111
+ if (eff.speedDelta) {
112
+ newStages.speed = Math.max(-6, Math.min(6, newStages.speed + eff.speedDelta));
113
+ changes.speed = eff.speedDelta;
114
+ }
115
+ if (eff.critDelta) {
116
+ newStages.crit = Math.max(0, Math.min(6, newStages.crit + eff.critDelta));
117
+ changes.crit = eff.critDelta;
118
+ }
119
+ let updated = { ...primary, statStages: newStages };
120
+ updated = recomputeEffectiveStatsForFighter(state, updated);
121
+ events.push({
122
+ ...createBaseEvent(state.turnNumber, "stats_changed", `Estadísticas cambiadas`),
123
+ targetId: primary.fighterId,
124
+ changes
125
+ });
126
+ if (isSelf)
127
+ currentActor = updated;
128
+ else
129
+ currentTarget = updated;
130
+ break;
131
+ }
132
+ case "modify_crit_chance": {
133
+ const { primary, isSelf } = resolveEffectTarget(eff, currentActor, currentTarget);
134
+ const newStages = { ...primary.statStages };
135
+ newStages.crit = Math.max(0, Math.min(6, newStages.crit + eff.delta));
136
+ let updated = { ...primary, statStages: newStages };
137
+ updated = recomputeEffectiveStatsForFighter(state, updated);
138
+ events.push({
139
+ ...createBaseEvent(state.turnNumber, "stats_changed", `Ratio de crítico cambiado`),
140
+ targetId: primary.fighterId,
141
+ changes: { crit: eff.delta }
142
+ });
143
+ if (isSelf)
144
+ currentActor = updated;
145
+ else
146
+ currentTarget = updated;
147
+ break;
148
+ }
149
+ case "modify_hit_chance": {
150
+ // Interpreted as Accuracy Stage
151
+ const { primary, isSelf } = resolveEffectTarget(eff, currentActor, currentTarget);
152
+ const newStages = { ...primary.statStages };
153
+ newStages.accuracy = Math.max(-6, Math.min(6, newStages.accuracy + eff.delta));
154
+ let updated = { ...primary, statStages: newStages };
155
+ updated = recomputeEffectiveStatsForFighter(state, updated);
156
+ events.push({
157
+ ...createBaseEvent(state.turnNumber, "stats_changed", `Precisión cambiada`),
158
+ targetId: primary.fighterId,
159
+ changes: { accuracy: eff.delta }
160
+ });
161
+ if (isSelf)
162
+ currentActor = updated;
163
+ else
164
+ currentTarget = updated;
165
+ break;
166
+ }
98
167
  default:
99
168
  break;
100
169
  }
@@ -1,4 +1,5 @@
1
1
  import { cloneStats } from "../runtime.js";
2
+ import { getCritChanceFromStage, getStatMultiplier, } from "../combat/stages.js";
2
3
  export const createBattleFighter = (cfg) => {
3
4
  if (cfg.moves.length !== 4) {
4
5
  throw new Error("Each fighter must have exactly 4 moves in MVP");
@@ -10,6 +11,14 @@ export const createBattleFighter = (cfg) => {
10
11
  currentHp: cfg.maxHp,
11
12
  baseStats: cloneStats(cfg.fighter.baseStats),
12
13
  effectiveStats: cloneStats(cfg.fighter.baseStats),
14
+ statStages: {
15
+ offense: 0,
16
+ defense: 0,
17
+ speed: 0,
18
+ crit: 0,
19
+ accuracy: 0,
20
+ evasion: 0,
21
+ },
13
22
  moves: cfg.moves.map((move) => ({
14
23
  moveId: move.id,
15
24
  currentPP: move.maxPP,
@@ -22,7 +31,14 @@ export const createBattleFighter = (cfg) => {
22
31
  export const recomputeEffectiveStatsForFighter = (state, fighter) => {
23
32
  // Partimos de base
24
33
  let eff = { ...fighter.baseStats };
25
- // 1) Aplicar estados
34
+ // 1) Aplicar Etapas (Stages)
35
+ eff.offense = Math.floor(eff.offense * getStatMultiplier(fighter.statStages.offense));
36
+ eff.defense = Math.floor(eff.defense * getStatMultiplier(fighter.statStages.defense));
37
+ eff.speed = Math.floor(eff.speed * getStatMultiplier(fighter.statStages.speed));
38
+ // Crit se reemplaza por el valor de la etapa (o se suma si baseStats tuviera un % base)
39
+ // Asumimos que baseStats.crit es 0 o un bono base pequeño.
40
+ eff.crit = getCritChanceFromStage(fighter.statStages.crit) + eff.crit;
41
+ // 2) Aplicar estados (Bonificadores temporales/external)
26
42
  for (const st of fighter.statuses) {
27
43
  const def = state.runtime.statusesById[st.statusId];
28
44
  if (!def)
@@ -32,10 +48,11 @@ export const recomputeEffectiveStatsForFighter = (state, fighter) => {
32
48
  const totalStacks = stacks; // podrías multiplicar efectos por stacks
33
49
  switch (effDef.kind) {
34
50
  case "modify_stats":
51
+ // Estos serían bonos flat POST-etapas
35
52
  eff.offense += (effDef.offenseDelta ?? 0) * totalStacks;
36
53
  eff.defense += (effDef.defenseDelta ?? 0) * totalStacks;
37
54
  eff.speed += (effDef.speedDelta ?? 0) * totalStacks;
38
- eff.crit += (effDef.critDelta ?? 0) * totalStacks;
55
+ // eff.crit += (effDef.critDelta ?? 0) * totalStacks; // Crit ya va por stages
39
56
  break;
40
57
  case "modify_effective_speed":
41
58
  eff.speed = Math.floor(eff.speed * Math.pow(effDef.multiplier, totalStacks));
@@ -45,7 +62,7 @@ export const recomputeEffectiveStatsForFighter = (state, fighter) => {
45
62
  }
46
63
  }
47
64
  }
48
- // 2) Aquí podrías aplicar amuletos, buffs temporales o lo que quieras
65
+ // 3) Aquí podrías aplicar amuletos, buffs temporales o lo que quieras
49
66
  return {
50
67
  ...fighter,
51
68
  effectiveStats: eff,
@@ -26,8 +26,8 @@ export const POKEMON_MOVES = [
26
26
  "basePower": 40
27
27
  },
28
28
  {
29
- "kind": "damage",
30
- "flatAmount": 10,
29
+ "kind": "modify_stats",
30
+ "offenseDelta": -2,
31
31
  "target": "self"
32
32
  }
33
33
  ]
@@ -97,6 +97,11 @@ export const POKEMON_MOVES = [
97
97
  {
98
98
  "kind": "damage",
99
99
  "basePower": 15
100
+ },
101
+ {
102
+ "kind": "modify_crit_chance",
103
+ "delta": 1,
104
+ "target": "self"
100
105
  }
101
106
  ]
102
107
  },
@@ -129,7 +134,7 @@ export const POKEMON_MOVES = [
129
134
  },
130
135
  {
131
136
  "kind": "modify_stats",
132
- "offenseDelta": -15
137
+ "offenseDelta": -2
133
138
  }
134
139
  ]
135
140
  },
@@ -198,6 +203,11 @@ export const POKEMON_MOVES = [
198
203
  {
199
204
  "kind": "damage",
200
205
  "basePower": 15
206
+ },
207
+ {
208
+ "kind": "modify_stats",
209
+ "offenseDelta": 1,
210
+ "target": "self"
201
211
  }
202
212
  ]
203
213
  },
@@ -230,7 +240,7 @@ export const POKEMON_MOVES = [
230
240
  },
231
241
  {
232
242
  "kind": "modify_stats",
233
- "offenseDelta": -20
243
+ "offenseDelta": -2
234
244
  }
235
245
  ]
236
246
  },
@@ -299,6 +309,11 @@ export const POKEMON_MOVES = [
299
309
  {
300
310
  "kind": "damage",
301
311
  "basePower": 15
312
+ },
313
+ {
314
+ "kind": "modify_hit_chance",
315
+ "delta": -1,
316
+ "target": "enemy"
302
317
  }
303
318
  ]
304
319
  },
@@ -331,7 +346,7 @@ export const POKEMON_MOVES = [
331
346
  },
332
347
  {
333
348
  "kind": "modify_stats",
334
- "offenseDelta": -20
349
+ "offenseDelta": -2
335
350
  }
336
351
  ]
337
352
  },
@@ -400,6 +415,11 @@ export const POKEMON_MOVES = [
400
415
  {
401
416
  "kind": "damage",
402
417
  "basePower": 15
418
+ },
419
+ {
420
+ "kind": "modify_stats",
421
+ "defenseDelta": -1,
422
+ "target": "enemy"
403
423
  }
404
424
  ]
405
425
  },
@@ -432,7 +452,7 @@ export const POKEMON_MOVES = [
432
452
  },
433
453
  {
434
454
  "kind": "modify_stats",
435
- "offenseDelta": -20
455
+ "offenseDelta": -2
436
456
  }
437
457
  ]
438
458
  },
@@ -501,6 +521,11 @@ export const POKEMON_MOVES = [
501
521
  {
502
522
  "kind": "damage",
503
523
  "basePower": 15
524
+ },
525
+ {
526
+ "kind": "modify_crit_chance",
527
+ "delta": 1,
528
+ "target": "self"
504
529
  }
505
530
  ]
506
531
  },
@@ -533,7 +558,7 @@ export const POKEMON_MOVES = [
533
558
  },
534
559
  {
535
560
  "kind": "modify_stats",
536
- "offenseDelta": -15
561
+ "offenseDelta": -2
537
562
  }
538
563
  ]
539
564
  },
@@ -602,6 +627,11 @@ export const POKEMON_MOVES = [
602
627
  {
603
628
  "kind": "damage",
604
629
  "basePower": 15
630
+ },
631
+ {
632
+ "kind": "modify_stats",
633
+ "speedDelta": 1,
634
+ "target": "self"
605
635
  }
606
636
  ]
607
637
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pokemon-io-core",
3
- "version": "0.0.95",
3
+ "version": "0.0.97",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",