pokemon-io-core 0.0.125 → 0.0.126
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/core/ids.d.ts +18 -6
- package/dist/core/ids.js +10 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +2 -1
- package/dist/core/randomEvents.d.ts +21 -5
- package/dist/core/randomEvents.js +4 -4
- package/dist/core/randomEventsList.d.ts +10 -4
- package/dist/core/randomEventsList.js +24 -163
- package/dist/core/status.d.ts +1 -0
- package/dist/core/validation.d.ts +21 -0
- package/dist/core/validation.js +183 -0
- package/dist/engine/combat/damage.js +2 -1
- package/dist/engine/events/applyRandomEvent.js +13 -6
- package/dist/engine/turn/resolveTurn.js +3 -2
- package/dist/skins/CombatSkin.d.ts +1 -0
- package/dist/skins/cliches/clicheSkin.d.ts +2 -1
- package/dist/skins/cliches/clicheSkin.js +3 -0
- package/dist/skins/cliches/constants.d.ts +135 -0
- package/dist/skins/cliches/constants.js +136 -0
- package/dist/skins/cliches/fighters.d.ts +1 -1
- package/dist/skins/cliches/fighters.js +165 -165
- package/dist/skins/cliches/items.js +45 -44
- package/dist/skins/cliches/moves.js +74 -69
- package/dist/skins/cliches/randomEvents.d.ts +7 -0
- package/dist/skins/cliches/randomEvents.js +1167 -0
- package/dist/skins/cliches/statuses.js +63 -36
- package/dist/skins/cliches/types.d.ts +12 -12
- package/dist/skins/cliches/types.js +12 -11
- package/dist/skins/pokemon/constants.d.ts +83 -0
- package/dist/skins/pokemon/constants.js +84 -0
- package/dist/skins/pokemon/fighters.js +55 -55
- package/dist/skins/pokemon/items.js +23 -22
- package/dist/skins/pokemon/moves.js +59 -58
- package/dist/skins/pokemon/pokemonSkin.d.ts +2 -1
- package/dist/skins/pokemon/pokemonSkin.js +3 -0
- package/dist/skins/pokemon/randomEvents.d.ts +6 -0
- package/dist/skins/pokemon/randomEvents.js +165 -0
- package/dist/skins/pokemon/statuses.js +5 -4
- package/dist/skins/pokemon/types.d.ts +6 -6
- package/dist/skins/pokemon/types.js +7 -6
- package/package.json +1 -1
package/dist/core/ids.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type
|
|
6
|
-
export type
|
|
1
|
+
declare const __brand: unique symbol;
|
|
2
|
+
type Brand<T, TBrand extends string> = T & {
|
|
3
|
+
[__brand]: TBrand;
|
|
4
|
+
};
|
|
5
|
+
export type TypeId = Brand<string, 'TypeId'>;
|
|
6
|
+
export type FighterId = Brand<string, 'FighterId'>;
|
|
7
|
+
export type MoveId = Brand<string, 'MoveId'>;
|
|
8
|
+
export type ItemId = Brand<string, 'ItemId'>;
|
|
9
|
+
export type AmuletId = Brand<string, 'AmuletId'>;
|
|
10
|
+
export type StatusId = Brand<string, 'StatusId'>;
|
|
11
|
+
export declare const typeId: (id: string) => TypeId;
|
|
12
|
+
export declare const fighterId: (id: string) => FighterId;
|
|
13
|
+
export declare const moveId: (id: string) => MoveId;
|
|
14
|
+
export declare const itemId: (id: string) => ItemId;
|
|
15
|
+
export declare const amuletId: (id: string) => AmuletId;
|
|
16
|
+
export declare const statusId: (id: string) => StatusId;
|
|
17
|
+
export declare const SYSTEM_FIGHTER_ID: FighterId;
|
|
18
|
+
export {};
|
package/dist/core/ids.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// Helper functions to create branded IDs
|
|
2
|
+
// Use these when defining data to get type safety
|
|
3
|
+
export const typeId = (id) => id;
|
|
4
|
+
export const fighterId = (id) => id;
|
|
5
|
+
export const moveId = (id) => id;
|
|
6
|
+
export const itemId = (id) => id;
|
|
7
|
+
export const amuletId = (id) => id;
|
|
8
|
+
export const statusId = (id) => id;
|
|
9
|
+
// Special system IDs for events not caused by fighters
|
|
10
|
+
export const SYSTEM_FIGHTER_ID = fighterId("__system__");
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "./actions.js";
|
|
2
2
|
export * from "./randomEvents.js";
|
|
3
|
-
export { CORE_RANDOM_EVENTS, POKEMON_RANDOM_EVENTS } from "./randomEventsList.js";
|
|
3
|
+
export { CORE_RANDOM_EVENTS, POKEMON_RANDOM_EVENTS, TRIBES_RANDOM_EVENTS, getRandomEventsForSkin } from "./randomEventsList.js";
|
|
4
4
|
export * from "./emotes.js";
|
|
5
5
|
export * from "./events.js";
|
|
6
6
|
export * from "./events.js";
|
|
@@ -14,3 +14,4 @@ export * from "./battleRuntime.js";
|
|
|
14
14
|
export * from "./battleState.js";
|
|
15
15
|
export * from "./amulets.js";
|
|
16
16
|
export * from "./types.js";
|
|
17
|
+
export * from "./validation.js";
|
package/dist/core/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "./actions.js";
|
|
2
2
|
export * from "./randomEvents.js";
|
|
3
|
-
export { CORE_RANDOM_EVENTS, POKEMON_RANDOM_EVENTS } from "./randomEventsList.js";
|
|
3
|
+
export { CORE_RANDOM_EVENTS, POKEMON_RANDOM_EVENTS, TRIBES_RANDOM_EVENTS, getRandomEventsForSkin } from "./randomEventsList.js";
|
|
4
4
|
export * from "./emotes.js";
|
|
5
5
|
export * from "./events.js";
|
|
6
6
|
export * from "./events.js";
|
|
@@ -14,3 +14,4 @@ export * from "./battleRuntime.js";
|
|
|
14
14
|
export * from "./battleState.js";
|
|
15
15
|
export * from "./amulets.js";
|
|
16
16
|
export * from "./types.js";
|
|
17
|
+
export * from "./validation.js";
|
|
@@ -1,10 +1,26 @@
|
|
|
1
|
+
import { StatusId } from "./ids";
|
|
1
2
|
export type EventRarity = "common" | "rare" | "epic" | "legendary";
|
|
2
3
|
export type EventEffectType = "damage_percent" | "heal_percent" | "stat_change" | "apply_status" | "reset_stats" | "swap_hp";
|
|
3
4
|
export type EventTarget = "both" | "self" | "opponent" | "random_one";
|
|
4
|
-
export
|
|
5
|
-
type: "has_item"
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export type EventCondition = {
|
|
6
|
+
type: "has_item";
|
|
7
|
+
} | {
|
|
8
|
+
type: "hp_below_percent";
|
|
9
|
+
value: number;
|
|
10
|
+
} | {
|
|
11
|
+
type: "hp_above_percent";
|
|
12
|
+
value: number;
|
|
13
|
+
} | {
|
|
14
|
+
type: "fighter_type";
|
|
15
|
+
value: string | string[];
|
|
16
|
+
} | {
|
|
17
|
+
type: "has_status";
|
|
18
|
+
} | {
|
|
19
|
+
type: "stat_stage_above";
|
|
20
|
+
value: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: "always";
|
|
23
|
+
};
|
|
8
24
|
export interface RandomEventEffect {
|
|
9
25
|
effectType: EventEffectType;
|
|
10
26
|
target: EventTarget;
|
|
@@ -12,7 +28,7 @@ export interface RandomEventEffect {
|
|
|
12
28
|
percentValue?: number;
|
|
13
29
|
statType?: "offense" | "defense" | "speed" | "crit" | "accuracy" | "evasion";
|
|
14
30
|
statChange?: number;
|
|
15
|
-
statusId?:
|
|
31
|
+
statusId?: StatusId;
|
|
16
32
|
statusDuration?: number;
|
|
17
33
|
randomChoices?: RandomEventEffect[];
|
|
18
34
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { RandomEventDefinition } from "./randomEvents.js";
|
|
2
2
|
/**
|
|
3
3
|
* CORE Random Events - Generic events that work with any skin
|
|
4
|
-
*
|
|
4
|
+
* These events only use damage/heal effects and stat changes
|
|
5
|
+
* They don't reference any skin-specific types, statuses, or items
|
|
5
6
|
*/
|
|
6
7
|
export declare const CORE_RANDOM_EVENTS: RandomEventDefinition[];
|
|
8
|
+
export { POKEMON_RANDOM_EVENTS } from "../skins/pokemon/randomEvents.js";
|
|
9
|
+
export { TRIBES_RANDOM_EVENTS } from "../skins/cliches/randomEvents.js";
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
11
|
+
* Get all random events for a specific skin
|
|
12
|
+
* Combines core events with skin-specific events
|
|
13
|
+
*
|
|
14
|
+
* @param skinId - The ID of the skin (e.g., "pokemon", "tribes")
|
|
15
|
+
* @returns Array of all available random events for that skin
|
|
10
16
|
*/
|
|
11
|
-
export declare
|
|
17
|
+
export declare function getRandomEventsForSkin(skinId: string): RandomEventDefinition[];
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// @pokemon-io/combat-core/src/core/randomEventsList.ts
|
|
2
|
+
import { POKEMON_RANDOM_EVENTS } from "../skins/pokemon/randomEvents.js";
|
|
3
|
+
import { TRIBES_RANDOM_EVENTS } from "../skins/cliches/randomEvents.js";
|
|
2
4
|
/**
|
|
3
5
|
* CORE Random Events - Generic events that work with any skin
|
|
4
|
-
*
|
|
6
|
+
* These events only use damage/heal effects and stat changes
|
|
7
|
+
* They don't reference any skin-specific types, statuses, or items
|
|
5
8
|
*/
|
|
6
9
|
export const CORE_RANDOM_EVENTS = [
|
|
7
10
|
// ==================== COMMON (4 events) ====================
|
|
@@ -162,167 +165,25 @@ export const CORE_RANDOM_EVENTS = [
|
|
|
162
165
|
],
|
|
163
166
|
},
|
|
164
167
|
];
|
|
168
|
+
// Export skin-specific events
|
|
169
|
+
export { POKEMON_RANDOM_EVENTS } from "../skins/pokemon/randomEvents.js";
|
|
170
|
+
export { TRIBES_RANDOM_EVENTS } from "../skins/cliches/randomEvents.js";
|
|
165
171
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
172
|
+
* Get all random events for a specific skin
|
|
173
|
+
* Combines core events with skin-specific events
|
|
174
|
+
*
|
|
175
|
+
* @param skinId - The ID of the skin (e.g., "pokemon", "tribes")
|
|
176
|
+
* @returns Array of all available random events for that skin
|
|
168
177
|
*/
|
|
169
|
-
export
|
|
170
|
-
|
|
171
|
-
{
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
percentValue: 15,
|
|
182
|
-
},
|
|
183
|
-
],
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
id: "team_rocket",
|
|
187
|
-
name: "🚀 ¡Equipo Rocket!",
|
|
188
|
-
description: "¡Preparaos para los problemas! El ataque de ambos baja.",
|
|
189
|
-
narration: ["¡Prepárense para los problemas!", "¡Y más vale que teman!"],
|
|
190
|
-
rarity: "common",
|
|
191
|
-
effects: [
|
|
192
|
-
{
|
|
193
|
-
effectType: "stat_change",
|
|
194
|
-
target: "both",
|
|
195
|
-
statType: "offense",
|
|
196
|
-
statChange: -1,
|
|
197
|
-
},
|
|
198
|
-
],
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
id: "rare_candy",
|
|
202
|
-
name: "🍬 Caramelo Raro",
|
|
203
|
-
description: "Un Pokémon aleatorio encuentra un caramelo y se fortalece.",
|
|
204
|
-
narration: ["¡Un objeto brillante aparece!", "¡Es un Caramelo Raro!"],
|
|
205
|
-
rarity: "common",
|
|
206
|
-
effects: [
|
|
207
|
-
{
|
|
208
|
-
effectType: "stat_change",
|
|
209
|
-
target: "random_one",
|
|
210
|
-
statType: "offense",
|
|
211
|
-
statChange: 1,
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
effectType: "stat_change",
|
|
215
|
-
target: "random_one",
|
|
216
|
-
statType: "defense",
|
|
217
|
-
statChange: 1,
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
},
|
|
221
|
-
// ==================== RARE (4 events) ====================
|
|
222
|
-
{
|
|
223
|
-
id: "sandstorm",
|
|
224
|
-
name: "🌪️ Tormenta de Arena",
|
|
225
|
-
description: "Una tormenta de arena daña a todos los Pokémon.",
|
|
226
|
-
narration: ["¡El viento empieza a soplar con fuerza!", "¡Una tormenta de arena azota el combate!"],
|
|
227
|
-
rarity: "rare",
|
|
228
|
-
effects: [
|
|
229
|
-
{
|
|
230
|
-
effectType: "damage_percent",
|
|
231
|
-
target: "both",
|
|
232
|
-
percentValue: 8,
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
id: "professor_oak",
|
|
238
|
-
name: "👴 Profesor Oak",
|
|
239
|
-
description: "¡El Profesor Oak anima a tu Pokémon!",
|
|
240
|
-
narration: ["¡Un mensaje del laboratorio!", "¡El Profesor Oak envía sus ánimos!"],
|
|
241
|
-
rarity: "rare",
|
|
242
|
-
effects: [
|
|
243
|
-
{
|
|
244
|
-
effectType: "stat_change",
|
|
245
|
-
target: "random_one",
|
|
246
|
-
statType: "offense",
|
|
247
|
-
statChange: 2,
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
effectType: "stat_change",
|
|
251
|
-
target: "random_one",
|
|
252
|
-
statType: "speed",
|
|
253
|
-
statChange: 1,
|
|
254
|
-
},
|
|
255
|
-
],
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
id: "dawn_stone",
|
|
259
|
-
name: "💎 Piedra Alba",
|
|
260
|
-
description: "Una piedra evolutiva aumenta la defensa de un Pokémon.",
|
|
261
|
-
narration: ["¡Una extraña piedra emite luz!", "¡El poder de la Piedra Alba se manifiesta!"],
|
|
262
|
-
rarity: "rare",
|
|
263
|
-
effects: [
|
|
264
|
-
{
|
|
265
|
-
effectType: "stat_change",
|
|
266
|
-
target: "random_one",
|
|
267
|
-
statType: "defense",
|
|
268
|
-
statChange: 3,
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
id: "potion_spray",
|
|
274
|
-
name: "💊 Lluvia de Pociones",
|
|
275
|
-
description: "Pociones caen del cielo curando a ambos Pokémon.",
|
|
276
|
-
narration: ["¡Empieza a llover... ¿medicina?!", "¡Una lluvia de pociones cubre el estadio!"],
|
|
277
|
-
rarity: "rare",
|
|
278
|
-
effects: [
|
|
279
|
-
{
|
|
280
|
-
effectType: "heal_percent",
|
|
281
|
-
target: "both",
|
|
282
|
-
percentValue: 12,
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
},
|
|
286
|
-
// ==================== EPIC (2 events) ====================
|
|
287
|
-
{
|
|
288
|
-
id: "legendary_appearance",
|
|
289
|
-
name: "✨ Aparición Legendaria",
|
|
290
|
-
description: "Un Pokémon legendario otorga poder a un luchador aleatorio.",
|
|
291
|
-
narration: ["¡UNA PRESENCIA ABRUMADORA SE SIENTE!", "¡Un Pokémon Legendario observa el combate!"],
|
|
292
|
-
rarity: "epic",
|
|
293
|
-
effects: [
|
|
294
|
-
{
|
|
295
|
-
effectType: "stat_change",
|
|
296
|
-
target: "random_one",
|
|
297
|
-
statType: "offense",
|
|
298
|
-
statChange: 3,
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
effectType: "stat_change",
|
|
302
|
-
target: "random_one",
|
|
303
|
-
statType: "defense",
|
|
304
|
-
statChange: 2,
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
effectType: "stat_change",
|
|
308
|
-
target: "random_one",
|
|
309
|
-
statType: "speed",
|
|
310
|
-
statChange: 2,
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
id: "max_revive",
|
|
316
|
-
name: "⚡ Revivir Máximo",
|
|
317
|
-
description: "El Pokémon con menos vida recupera un 30% de HP.",
|
|
318
|
-
narration: ["¡Un cristal amarillo brilla con intensidad!", "¡El poder del Revivir Máximo restaura la energía!"],
|
|
319
|
-
rarity: "epic",
|
|
320
|
-
effects: [
|
|
321
|
-
{
|
|
322
|
-
effectType: "heal_percent",
|
|
323
|
-
target: "random_one", // TODO: Cambiar a "lowest_hp" cuando se implemente
|
|
324
|
-
percentValue: 30,
|
|
325
|
-
},
|
|
326
|
-
],
|
|
327
|
-
},
|
|
328
|
-
];
|
|
178
|
+
export function getRandomEventsForSkin(skinId) {
|
|
179
|
+
const coreEvents = CORE_RANDOM_EVENTS;
|
|
180
|
+
switch (skinId) {
|
|
181
|
+
case "pokemon":
|
|
182
|
+
return [...coreEvents, ...POKEMON_RANDOM_EVENTS];
|
|
183
|
+
case "tribes":
|
|
184
|
+
return [...coreEvents, ...TRIBES_RANDOM_EVENTS];
|
|
185
|
+
default:
|
|
186
|
+
// For unknown skins, return only core events
|
|
187
|
+
return coreEvents;
|
|
188
|
+
}
|
|
189
|
+
}
|
package/dist/core/status.d.ts
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CombatSkin } from "../skins/CombatSkin.js";
|
|
2
|
+
export declare class SkinValidationError extends Error {
|
|
3
|
+
skinId: string;
|
|
4
|
+
entityType: string;
|
|
5
|
+
entityId: string;
|
|
6
|
+
issue: string;
|
|
7
|
+
constructor(skinId: string, entityType: string, entityId: string, issue: string);
|
|
8
|
+
}
|
|
9
|
+
export interface ValidationResult {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
errors: SkinValidationError[];
|
|
12
|
+
warnings: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare class SkinValidator {
|
|
15
|
+
validate(skin: CombatSkin): ValidationResult;
|
|
16
|
+
private validateFighters;
|
|
17
|
+
private validateMoves;
|
|
18
|
+
private validateStatuses;
|
|
19
|
+
private validateTypeEffectiveness;
|
|
20
|
+
private checkDuplicates;
|
|
21
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export class SkinValidationError extends Error {
|
|
2
|
+
skinId;
|
|
3
|
+
entityType;
|
|
4
|
+
entityId;
|
|
5
|
+
issue;
|
|
6
|
+
constructor(skinId, entityType, entityId, issue) {
|
|
7
|
+
super(`[${skinId}] ${entityType} "${entityId}": ${issue}`);
|
|
8
|
+
this.skinId = skinId;
|
|
9
|
+
this.entityType = entityType;
|
|
10
|
+
this.entityId = entityId;
|
|
11
|
+
this.issue = issue;
|
|
12
|
+
this.name = 'SkinValidationError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class SkinValidator {
|
|
16
|
+
validate(skin) {
|
|
17
|
+
const errors = [];
|
|
18
|
+
const warnings = [];
|
|
19
|
+
const types = skin.getTypes();
|
|
20
|
+
const moves = skin.getMoves();
|
|
21
|
+
const fighters = skin.getFighters ? skin.getFighters() : [];
|
|
22
|
+
const statuses = skin.getStatuses();
|
|
23
|
+
const items = skin.getItems();
|
|
24
|
+
// Create sets for fast lookups
|
|
25
|
+
const typeIds = new Set(types.map((t) => t.id));
|
|
26
|
+
const moveIds = new Set(moves.map((m) => m.id));
|
|
27
|
+
const fighterIds = new Set(fighters.map((f) => f.id));
|
|
28
|
+
const statusIds = new Set(statuses.map((s) => s.id));
|
|
29
|
+
const itemIds = new Set(items.map((i) => i.id));
|
|
30
|
+
// Validate fighters
|
|
31
|
+
this.validateFighters(skin.id, fighters, typeIds, moveIds, itemIds, errors);
|
|
32
|
+
// Validate moves
|
|
33
|
+
this.validateMoves(skin.id, moves, typeIds, statusIds, fighterIds, errors);
|
|
34
|
+
// Validate statuses
|
|
35
|
+
this.validateStatuses(skin.id, statuses, errors);
|
|
36
|
+
// Validate type effectiveness matrix
|
|
37
|
+
this.validateTypeEffectiveness(skin.id, types, skin.getTypeEffectiveness(), warnings);
|
|
38
|
+
// Check for duplicate IDs
|
|
39
|
+
this.checkDuplicates('Type', types.map((t) => t.id), skin.id, errors);
|
|
40
|
+
this.checkDuplicates('Fighter', fighters.map((f) => f.id), skin.id, errors);
|
|
41
|
+
this.checkDuplicates('Move', moves.map((m) => m.id), skin.id, errors);
|
|
42
|
+
this.checkDuplicates('Status', statuses.map((s) => s.id), skin.id, errors);
|
|
43
|
+
this.checkDuplicates('Item', items.map((i) => i.id), skin.id, errors);
|
|
44
|
+
return {
|
|
45
|
+
valid: errors.length === 0,
|
|
46
|
+
errors,
|
|
47
|
+
warnings
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
validateFighters(skinId, fighters, typeIds, moveIds, itemIds, errors) {
|
|
51
|
+
for (const fighter of fighters) {
|
|
52
|
+
// Validate types
|
|
53
|
+
if (!fighter.classIds || fighter.classIds.length === 0) {
|
|
54
|
+
errors.push(new SkinValidationError(skinId, 'Fighter', fighter.id, 'Must have at least one type'));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
for (const typeId of fighter.classIds) {
|
|
58
|
+
if (!typeIds.has(typeId)) {
|
|
59
|
+
errors.push(new SkinValidationError(skinId, 'Fighter', fighter.id, `Invalid type "${typeId}"`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Validate recommended moves
|
|
64
|
+
if (fighter.recommendedMoves) {
|
|
65
|
+
for (const moveId of fighter.recommendedMoves) {
|
|
66
|
+
if (!moveIds.has(moveId)) {
|
|
67
|
+
errors.push(new SkinValidationError(skinId, 'Fighter', fighter.id, `Invalid recommended move "${moveId}"`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Validate recommended items
|
|
72
|
+
if (fighter.recommendedItems) {
|
|
73
|
+
for (const itemId of fighter.recommendedItems) {
|
|
74
|
+
if (!itemIds.has(itemId)) {
|
|
75
|
+
errors.push(new SkinValidationError(skinId, 'Fighter', fighter.id, `Invalid recommended item "${itemId}"`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Validate base stats
|
|
80
|
+
if (!fighter.baseStats) {
|
|
81
|
+
errors.push(new SkinValidationError(skinId, 'Fighter', fighter.id, 'Missing base stats'));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const { offense, defense, speed, crit } = fighter.baseStats;
|
|
85
|
+
if (offense < 0 || defense < 0 || speed < 0 || crit < 0) {
|
|
86
|
+
errors.push(new SkinValidationError(skinId, 'Fighter', fighter.id, 'Stats cannot be negative'));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
validateMoves(skinId, moves, typeIds, statusIds, fighterIds, errors) {
|
|
92
|
+
for (const move of moves) {
|
|
93
|
+
// Validate type
|
|
94
|
+
if (!typeIds.has(move.typeId)) {
|
|
95
|
+
errors.push(new SkinValidationError(skinId, 'Move', move.id, `Invalid type "${move.typeId}"`));
|
|
96
|
+
}
|
|
97
|
+
// Validate effects
|
|
98
|
+
if (!move.effects || move.effects.length === 0) {
|
|
99
|
+
errors.push(new SkinValidationError(skinId, 'Move', move.id, 'Must have at least one effect'));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
for (let i = 0; i < move.effects.length; i++) {
|
|
103
|
+
const effect = move.effects[i];
|
|
104
|
+
// Validate apply_status effects
|
|
105
|
+
if (effect.kind === 'apply_status' && effect.statusId) {
|
|
106
|
+
if (!statusIds.has(effect.statusId)) {
|
|
107
|
+
errors.push(new SkinValidationError(skinId, 'Move', move.id, `Invalid status "${effect.statusId}" in effect ${i + 1}`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Validate clear_status effects
|
|
111
|
+
if (effect.kind === 'clear_status' && effect.statusIds) {
|
|
112
|
+
for (const statusId of effect.statusIds) {
|
|
113
|
+
if (!statusIds.has(statusId)) {
|
|
114
|
+
errors.push(new SkinValidationError(skinId, 'Move', move.id, `Invalid status "${statusId}" in clear_status effect`));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Validate onlyForFighterIds
|
|
121
|
+
if (move.onlyForFighterIds) {
|
|
122
|
+
for (const fighterId of move.onlyForFighterIds) {
|
|
123
|
+
if (!fighterIds.has(fighterId)) {
|
|
124
|
+
errors.push(new SkinValidationError(skinId, 'Move', move.id, `Invalid fighter restriction "${fighterId}"`));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Validate accuracy
|
|
129
|
+
if (move.accuracy !== undefined && (move.accuracy < 0 || move.accuracy > 100)) {
|
|
130
|
+
errors.push(new SkinValidationError(skinId, 'Move', move.id, `Accuracy must be between 0 and 100, got ${move.accuracy}`));
|
|
131
|
+
}
|
|
132
|
+
// Validate maxPP
|
|
133
|
+
if (move.maxPP <= 0) {
|
|
134
|
+
errors.push(new SkinValidationError(skinId, 'Move', move.id, `maxPP must be positive, got ${move.maxPP}`));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
validateStatuses(skinId, statuses, errors) {
|
|
139
|
+
for (const status of statuses) {
|
|
140
|
+
// Validate duration
|
|
141
|
+
if (status.minDurationTurns < 0) {
|
|
142
|
+
errors.push(new SkinValidationError(skinId, 'Status', status.id, `minDurationTurns cannot be negative`));
|
|
143
|
+
}
|
|
144
|
+
if (status.maxDurationTurns < status.minDurationTurns) {
|
|
145
|
+
errors.push(new SkinValidationError(skinId, 'Status', status.id, `maxDurationTurns (${status.maxDurationTurns}) must be >= minDurationTurns (${status.minDurationTurns})`));
|
|
146
|
+
}
|
|
147
|
+
// Validate kind
|
|
148
|
+
if (status.kind !== 'hard_cc' && status.kind !== 'soft') {
|
|
149
|
+
errors.push(new SkinValidationError(skinId, 'Status', status.id, `Invalid kind "${status.kind}", must be "hard_cc" or "soft"`));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
validateTypeEffectiveness(skinId, types, effectiveness, warnings) {
|
|
154
|
+
for (const attackerType of types) {
|
|
155
|
+
if (!effectiveness[attackerType.id]) {
|
|
156
|
+
warnings.push(`Type "${attackerType.id}" missing from effectiveness matrix`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
for (const defenderType of types) {
|
|
160
|
+
const value = effectiveness[attackerType.id][defenderType.id];
|
|
161
|
+
if (value === undefined) {
|
|
162
|
+
warnings.push(`Missing effectiveness: ${attackerType.id} vs ${defenderType.id}`);
|
|
163
|
+
}
|
|
164
|
+
else if (![0, 0.8, 0.9, 1, 1.1, 1.2].includes(value)) {
|
|
165
|
+
warnings.push(`Unusual effectiveness value ${value} for ${attackerType.id} vs ${defenderType.id}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
checkDuplicates(entityType, ids, skinId, errors) {
|
|
171
|
+
const seen = new Set();
|
|
172
|
+
const duplicates = new Set();
|
|
173
|
+
for (const id of ids) {
|
|
174
|
+
if (seen.has(id)) {
|
|
175
|
+
duplicates.add(id);
|
|
176
|
+
}
|
|
177
|
+
seen.add(id);
|
|
178
|
+
}
|
|
179
|
+
for (const duplicate of duplicates) {
|
|
180
|
+
errors.push(new SkinValidationError(skinId, entityType, duplicate, `Duplicate ID found`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TRIBE_STATUS_IDS } from "../../skins/cliches/constants.js";
|
|
1
2
|
import { dbg } from "../debug.js";
|
|
2
3
|
import { createBaseEvent } from "../events.js";
|
|
3
4
|
import { randomInRange } from "../rng.js";
|
|
@@ -63,7 +64,7 @@ export const applyDamageToFighter = (state, defender, amount, actorId, isCritica
|
|
|
63
64
|
events.push({
|
|
64
65
|
...createBaseEvent(state.turnNumber, "status_cleared", `${defender.fighterId} se despierta por el golpe`),
|
|
65
66
|
targetId: defender.fighterId,
|
|
66
|
-
statusIds: [
|
|
67
|
+
statusIds: [TRIBE_STATUS_IDS.SLEEP],
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
events.push({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SYSTEM_FIGHTER_ID } from "../../core/ids.js";
|
|
1
2
|
import { applyStatusToFighter } from "../status/apply.js";
|
|
2
3
|
import { applyDamageToFighter } from "../combat/damage.js";
|
|
3
4
|
import { applyHealToFighter } from "../combat/heal.js";
|
|
@@ -93,10 +94,17 @@ const checkCondition = (fighter, condition) => {
|
|
|
93
94
|
const maxHp2 = fighter.baseStats.offense + fighter.baseStats.defense;
|
|
94
95
|
const hpPercent2 = (fighter.currentHp / maxHp2) * 100;
|
|
95
96
|
return hpPercent2 > condition.value;
|
|
96
|
-
case "fighter_type":
|
|
97
|
-
|
|
97
|
+
case "fighter_type": {
|
|
98
|
+
const { value } = condition;
|
|
99
|
+
if (!value)
|
|
98
100
|
return false;
|
|
99
|
-
|
|
101
|
+
// Handle array of types (fighter matches if it has ANY of the types)
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
return value.some(typeId => fighter.classIds.includes(typeId));
|
|
104
|
+
}
|
|
105
|
+
// Handle single type
|
|
106
|
+
return fighter.classIds.includes(value);
|
|
107
|
+
}
|
|
100
108
|
case "has_status":
|
|
101
109
|
return fighter.statuses && fighter.statuses.length > 0;
|
|
102
110
|
case "stat_stage_above":
|
|
@@ -120,8 +128,7 @@ const applySingleEffectToFighter = (state, playerKey, fighter, effect) => {
|
|
|
120
128
|
if (effect.percentValue) {
|
|
121
129
|
const maxHp = fighter.baseStats.offense + fighter.baseStats.defense;
|
|
122
130
|
const damage = Math.floor(maxHp * (effect.percentValue / 100));
|
|
123
|
-
const res = applyDamageToFighter(state, updatedFighter, damage,
|
|
124
|
-
false);
|
|
131
|
+
const res = applyDamageToFighter(state, updatedFighter, damage, SYSTEM_FIGHTER_ID, false);
|
|
125
132
|
updatedFighter = res.updatedDefender;
|
|
126
133
|
events.push(...res.events);
|
|
127
134
|
}
|
|
@@ -130,7 +137,7 @@ const applySingleEffectToFighter = (state, playerKey, fighter, effect) => {
|
|
|
130
137
|
if (effect.percentValue) {
|
|
131
138
|
const maxHp = fighter.baseStats.offense + fighter.baseStats.defense;
|
|
132
139
|
const healAmount = Math.floor(maxHp * (effect.percentValue / 100));
|
|
133
|
-
const res = applyHealToFighter(state, updatedFighter, healAmount,
|
|
140
|
+
const res = applyHealToFighter(state, updatedFighter, healAmount, SYSTEM_FIGHTER_ID);
|
|
134
141
|
updatedFighter = res.updated;
|
|
135
142
|
events.push(...res.events);
|
|
136
143
|
}
|
|
@@ -13,6 +13,7 @@ import { hasHardCc } from "../status/hardCc.js";
|
|
|
13
13
|
import { shouldTriggerRandomEvent, selectRandomEvent } from "../events/randomEventTrigger.js";
|
|
14
14
|
import { applyRandomEvent } from "../events/applyRandomEvent.js";
|
|
15
15
|
import { CORE_RANDOM_EVENTS } from "../../core/index.js";
|
|
16
|
+
import { TRIBE_STATUS_IDS } from "../../skins/cliches/constants.js";
|
|
16
17
|
// ------------------------------------------------------
|
|
17
18
|
// Helpers KO -> forced switch (estilo Pokémon real)
|
|
18
19
|
// ------------------------------------------------------
|
|
@@ -153,7 +154,7 @@ export const resolveTurn = (state, actions) => {
|
|
|
153
154
|
events.push({
|
|
154
155
|
...createBaseEvent(currentState.turnNumber, "action_skipped_hard_cc", `${self.fighterId} está paralizado y no puede moverse`),
|
|
155
156
|
actorId: self.fighterId,
|
|
156
|
-
statusId:
|
|
157
|
+
statusId: TRIBE_STATUS_IDS.PARALYSIS,
|
|
157
158
|
});
|
|
158
159
|
continue;
|
|
159
160
|
}
|
|
@@ -190,7 +191,7 @@ export const resolveTurn = (state, actions) => {
|
|
|
190
191
|
freeze: `${self.fighterId} está congelado y no puede actuar`,
|
|
191
192
|
flinch: `${self.fighterId} se acobarda y no puede atacar`,
|
|
192
193
|
};
|
|
193
|
-
const statusId = ccStatus?.statusId ||
|
|
194
|
+
const statusId = ccStatus?.statusId || TRIBE_STATUS_IDS.SLEEP;
|
|
194
195
|
const message = statusMessages[statusId] ||
|
|
195
196
|
`${self.fighterId} no puede actuar por control total`;
|
|
196
197
|
events.push({
|
|
@@ -13,6 +13,7 @@ export interface CombatSkin {
|
|
|
13
13
|
readonly id: string;
|
|
14
14
|
getTypes(): TypeDefinition[];
|
|
15
15
|
getTypeEffectiveness(): TypeEffectivenessMatrix;
|
|
16
|
+
getFighters(): any[];
|
|
16
17
|
getMoves(): MoveDefinition[];
|
|
17
18
|
getItems(): ItemDefinition[];
|
|
18
19
|
getAmulets(): AmuletDefinition[];
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ItemDefinition, MoveDefinition } from "../../core/index.js";
|
|
1
|
+
import { FighterDefinition, ItemDefinition, MoveDefinition } from "../../core/index.js";
|
|
2
2
|
import { PlayerBattleConfig } from "../../engine/rules.js";
|
|
3
3
|
import type { CombatSkin, PlayerTeamLoadout } from "../CombatSkin.js";
|
|
4
4
|
export declare class TribeSkin implements CombatSkin {
|
|
5
5
|
readonly id = "tribe";
|
|
6
6
|
getTypes(): import("../../index.js").TypeDefinition[];
|
|
7
|
+
getFighters(): FighterDefinition[];
|
|
7
8
|
getTypeEffectiveness(): import("../../index.js").TypeEffectivenessMatrix;
|
|
8
9
|
getMoves(): MoveDefinition[];
|
|
9
10
|
getItems(): ItemDefinition[];
|