Merge branch 'pagefaultgames:main' into flingImplementation

This commit is contained in:
AyushBarik 2024-06-26 19:53:28 +05:30 committed by GitHub
commit 004a3a748c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
525 changed files with 22223 additions and 14548 deletions

View File

@ -31,11 +31,6 @@
font-family: 'emerald';
src: url('./fonts/pokemon-emerald-pro.ttf') format('truetype');
}
@font-face {
font-family: 'unifont';
src: url('./fonts/unifont-15.1.05.otf') format('opentype');
size-adjust: 70%;
}
@font-face {
font-family: 'pkmnems';

6
package-lock.json generated
View File

@ -8365,9 +8365,9 @@
"dev": true
},
"node_modules/ws": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"engines": {
"node": ">=10.0.0"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -29,8 +29,8 @@
"15-mega",
"150-mega",
"150-mega",
"150-mega",
"150-mega",
"150-mega-x",
"150-mega-y",
"18-mega",
"18-mega",
"181-mega",
@ -185,8 +185,8 @@
"531-mega",
"6-mega",
"6-mega",
"6-mega",
"6-mega",
"6-mega-x",
"6-mega-y",
"6058",
"6058",
"6059",
@ -1161,8 +1161,8 @@
"15b-mega",
"150b-mega",
"150b-mega",
"150b-mega",
"150b-mega",
"150b-mega-x",
"150b-mega-y",
"18b-mega",
"18b-mega",
"181b-mega",
@ -1317,8 +1317,8 @@
"531b-mega",
"6b-mega",
"6b-mega",
"6b-mega",
"6b-mega",
"6b-mega-x",
"6b-mega-y",
"6058b",
"6058b",
"6059b",
@ -2295,8 +2295,8 @@
"15sb-mega",
"150sb-mega",
"150sb-mega",
"150sb-mega",
"150sb-mega",
"150sb-mega-x",
"150sb-mega-y",
"18sb-mega",
"18sb-mega",
"181sb-mega",
@ -2451,8 +2451,8 @@
"531sb-mega",
"6sb-mega",
"6sb-mega",
"6sb-mega",
"6sb-mega",
"6sb-mega-x",
"6sb-mega-y",
"6058sb",
"6058sb",
"6059sb",
@ -3432,8 +3432,8 @@
"15s-mega",
"150s-mega",
"150s-mega",
"150s-mega",
"150s-mega",
"150s-mega-x",
"150s-mega-y",
"18s-mega",
"18s-mega",
"181s-mega",
@ -3588,8 +3588,8 @@
"531s-mega",
"6s-mega",
"6s-mega",
"6s-mega",
"6s-mega",
"6s-mega-x",
"6s-mega-y",
"6058s",
"6058s",
"6059s",

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 B

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,92 +1,36 @@
{
"1": {
"207cc1": "c67429",
"44b4f4": "eea747",
"1d5486": "923d13",
"826c4d": "dd493b",
"f8eaba": "fff3ec",
"cdac7b": "bd9b8e",
"0d0d0d": "101010",
"090909": "101010",
"3b3f47": "c32625",
"3c3f47": "c32625",
"1f2025": "101010",
"202226": "101010",
"5e5f62": "dd493b",
"1c5486": "923d13",
"1f7cc1": "c67429",
"826b4d": "dd493b",
"992137": "8691d5",
"e6414a": "c9d4ff",
"000000": "101010",
"f4f4f4": "f8f8f8",
"992035": "8691d5",
"2b2d33": "682a23",
"2a2c31": "682a23",
"282a2e": "682a23",
"e0e0e0": "f37754",
"030303": "101010",
"393f47": "c32625",
"1c1d22": "101010",
"195486": "923d13",
"1c7cc1": "c67429",
"82694d": "dd493b",
"991d31": "8691d5",
"ffffff": "f8f8f8",
"26282c": "682a23",
"060606": "101010",
"3a3f47": "c32625",
"1d1f24": "101010",
"1a5486": "923d13",
"1d7cc1": "c67429",
"826a4d": "dd493b",
"991f33": "8691d5",
"292b2f": "682a23",
"27292d": "682a23"
},
"2": {
"207cc1": "582c81",
"44b4f4": "7b43a1",
"1d5486": "411f70",
"826c4d": "f2a366",
"f8eaba": "ffedf4",
"cdac7b": "d7aec0",
"0d0d0d": "101010",
"090909": "101010",
"3b3f47": "bc6532",
"3c3f47": "bc6532",
"1f2025": "101010",
"202226": "101010",
"5e5f62": "f2a366",
"1c5486": "411f70",
"1f7cc1": "582c81",
"826b4d": "f2a366",
"992137": "a62869",
"e6414a": "e15693",
"000000": "101010",
"f4f4f4": "f8f8f8",
"992035": "a62869",
"2b2d33": "202b47",
"2a2c31": "202b47",
"282a2e": "202b47",
"e0e0e0": "ffdb85",
"030303": "101010",
"393f47": "bc6532",
"1c1d22": "101010",
"195486": "411f70",
"1c7cc1": "582c81",
"82694d": "f2a366",
"991d31": "a62869",
"ffffff": "f8f8f8",
"26282c": "202b47",
"060606": "101010",
"3a3f47": "bc6532",
"1d1f24": "101010",
"1a5486": "411f70",
"1d7cc1": "582c81",
"826a4d": "f2a366",
"991f33": "a62869",
"292b2f": "202b47",
"27292d": "202b47"
}
}

View File

@ -1,11 +1,11 @@
{
"textures": [
{
"image": "678msb.png",
"image": "9-mega_2.png",
"format": "RGBA8888",
"size": {
"w": 55,
"h": 55
"w": 88,
"h": 88
},
"scale": 1,
"frames": [
@ -14,20 +14,20 @@
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 46,
"h": 55
"w": 88,
"h": 87
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 46,
"h": 55
"w": 88,
"h": 87
},
"frame": {
"x": 0,
"y": 0,
"w": 46,
"h": 55
"w": 88,
"h": 87
}
}
]
@ -36,6 +36,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:979291307687f1a4af47bc4d29542ccf:f61247ececc23f282bb95c5bfe49e179:bbcc2663448733722c64bc1ebafbf9c6$"
"smartupdate": "$TexturePacker:SmartUpdate:9b1bebbbe735399ba2678aa35a42b156:2dc7c20135acd855e9e97cb010985396:00f61506d33ec61875296e0fb5a82ee9$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "9-mega_3.png",
"format": "RGBA8888",
"size": {
"w": 88,
"h": 88
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 88,
"h": 87
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 88,
"h": 87
},
"frame": {
"x": 0,
"y": 0,
"w": 88,
"h": 87
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:9b1bebbbe735399ba2678aa35a42b156:2dc7c20135acd855e9e97cb010985396:00f61506d33ec61875296e0fb5a82ee9$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "9-mega_2.png",
"format": "RGBA8888",
"size": {
"w": 77,
"h": 77
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 77,
"h": 77
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 77,
"h": 77
},
"frame": {
"x": 0,
"y": 0,
"w": 77,
"h": 77
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:5d9f38fc4da0e99e00a40be3452a927c:2c58504a78e2752d4c536f8a79dab703:00f61506d33ec61875296e0fb5a82ee9$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "9-mega_3.png",
"format": "RGBA8888",
"size": {
"w": 77,
"h": 77
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 77,
"h": 77
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 77,
"h": 77
},
"frame": {
"x": 0,
"y": 0,
"w": 77,
"h": 77
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:5d9f38fc4da0e99e00a40be3452a927c:2c58504a78e2752d4c536f8a79dab703:00f61506d33ec61875296e0fb5a82ee9$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 B

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "types_pt_BR.png",
"image": "types_pt-BR.png",
"format": "RGBA8888",
"size": {
"w": 32,

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "types_zh_CN.png",
"image": "types_zh-CN.png",
"format": "RGBA8888",
"size": {
"w": 32,

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "types_zh_TW.png",
"image": "types_zh-TW.png",
"format": "RGBA8888",
"size": {
"w": 32,

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

View File

@ -0,0 +1,78 @@
{
"textures": [
{
"image": "shiny_icons.png",
"format": "RGBA8888",
"size": {
"w": 15,
"h": 14
},
"scale": 1,
"frames": [
{
"filename": "0",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
}
},
{
"filename": "1",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 15,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 15,
"y": 0,
"w": 15,
"h": 14
}
},
{
"filename": "2",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 30,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 30,
"y": 0,
"w": 15,
"h": 14
}
}
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

View File

Before

Width:  |  Height:  |  Size: 800 B

After

Width:  |  Height:  |  Size: 800 B

View File

Before

Width:  |  Height:  |  Size: 799 B

After

Width:  |  Height:  |  Size: 799 B

View File

@ -0,0 +1,78 @@
{
"textures": [
{
"image": "shiny_icons.png",
"format": "RGBA8888",
"size": {
"w": 15,
"h": 14
},
"scale": 1,
"frames": [
{
"filename": "0",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
}
},
{
"filename": "1",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 15,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 15,
"y": 0,
"w": 15,
"h": 14
}
},
{
"filename": "2",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 30,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 30,
"y": 0,
"w": 15,
"h": 14
}
}
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

9
src/@types/i18next.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { enConfig } from "#app/locales/en/config.js";
// Module declared to make referencing keys in the localization files type-safe.
declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "menu", // needed here as well for typedoc
resources: typeof enConfig
}
}

View File

@ -3,6 +3,7 @@ import UI from "./ui/ui";
import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase } from "./phases";
import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
import { Constructor } from "#app/utils";
import * as Utils from "./utils";
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball";
@ -36,7 +37,7 @@ import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
import { addUiThemeOverrides } from "./ui/ui-theme";
import PokemonData from "./system/pokemon-data";
import { Nature } from "./data/nature";
import { SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges } from "./data/pokemon-forms";
import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges } from "./data/pokemon-forms";
import { FormChangePhase, QuietFormChangePhase } from "./form-change-phase";
import { getTypeRgb } from "./data/type";
import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler";
@ -47,7 +48,7 @@ import { biomeDepths, getBiomeName } from "./data/biomes";
import { SceneBase } from "./scene-base";
import CandyBar from "./ui/candy-bar";
import { Variant, variantData } from "./data/variant";
import { Localizable } from "./plugins/i18n";
import { Localizable } from "#app/interfaces/locales";
import * as Overrides from "./overrides";
import {InputsController} from "./inputs-controller";
import {UiInputs} from "./ui-inputs";
@ -156,6 +157,8 @@ export default class BattleScene extends SceneBase {
public fusionPaletteSwaps: boolean = true;
public enableTouchControls: boolean = false;
public enableVibration: boolean = false;
public showBgmBar: boolean = true;
/**
* Determines the selected battle style.
* - 0 = 'Switch'
@ -218,6 +221,7 @@ export default class BattleScene extends SceneBase {
public arenaFlyout: ArenaFlyout;
private fieldOverlay: Phaser.GameObjects.Rectangle;
private shopOverlay: Phaser.GameObjects.Rectangle;
public modifiers: PersistentModifier[];
private enemyModifiers: PersistentModifier[];
public uiContainer: Phaser.GameObjects.Container;
@ -331,7 +335,9 @@ export default class BattleScene extends SceneBase {
launchBattle() {
this.arenaBg = this.add.sprite(0, 0, "plains_bg");
this.arenaBg.setName("sprite-arena-bg");
this.arenaBgTransition = this.add.sprite(0, 0, "plains_bg");
this.arenaBgTransition.setName("sprite-arena-bg-transition");
[ this.arenaBgTransition, this.arenaBg ].forEach(a => {
a.setPipeline(this.fieldSpritePipeline);
@ -341,13 +347,13 @@ export default class BattleScene extends SceneBase {
});
const field = this.add.container(0, 0);
field.setName("field");
field.setScale(6);
field.setName("container-field");
this.field = field;
const fieldUI = this.add.container(0, this.game.canvas.height);
fieldUI.setName("container-field-ui");
fieldUI.setName("field-ui");
fieldUI.setDepth(1);
fieldUI.setScale(6);
@ -371,7 +377,7 @@ export default class BattleScene extends SceneBase {
this.add.existing(transition);
const uiContainer = this.add.container(0, 0);
uiContainer.setName("container-ui");
uiContainer.setName("ui");
uiContainer.setDepth(2);
uiContainer.setScale(6);
@ -385,16 +391,22 @@ export default class BattleScene extends SceneBase {
this.fieldOverlay.setAlpha(0);
this.fieldUI.add(this.fieldOverlay);
this.shopOverlay = this.add.rectangle(0, overlayHeight * -1 - 48, overlayWidth, overlayHeight, 0x070707);
this.shopOverlay.setName("rect-shop-overlay");
this.shopOverlay.setOrigin(0, 0);
this.shopOverlay.setAlpha(0);
this.fieldUI.add(this.shopOverlay);
this.modifiers = [];
this.enemyModifiers = [];
this.modifierBar = new ModifierBar(this);
this.modifierBar.setName("container-modifier-bar");
this.modifierBar.setName("modifier-bar");
this.add.existing(this.modifierBar);
uiContainer.add(this.modifierBar);
this.enemyModifierBar = new ModifierBar(this, true);
this.enemyModifierBar.setName("container-enemy-modifier-bar");
this.enemyModifierBar.setName("enemy-modifier-bar");
this.add.existing(this.enemyModifierBar);
uiContainer.add(this.enemyModifierBar);
@ -405,28 +417,28 @@ export default class BattleScene extends SceneBase {
this.fieldUI.add(this.charSprite);
this.pbTray = new PokeballTray(this, true);
this.pbTray.setName("container-pb-tray");
this.pbTray.setName("pb-tray");
this.pbTray.setup();
this.pbTrayEnemy = new PokeballTray(this, false);
this.pbTrayEnemy.setName("container-enemy-pb-tray");
this.pbTrayEnemy.setName("enemy-pb-tray");
this.pbTrayEnemy.setup();
this.fieldUI.add(this.pbTray);
this.fieldUI.add(this.pbTrayEnemy);
this.abilityBar = new AbilityBar(this);
this.abilityBar.setName("container-ability-bar");
this.abilityBar.setName("ability-bar");
this.abilityBar.setup();
this.fieldUI.add(this.abilityBar);
this.partyExpBar = new PartyExpBar(this);
this.partyExpBar.setName("container-party-exp-bar");
this.partyExpBar.setName("party-exp-bar");
this.partyExpBar.setup();
this.fieldUI.add(this.partyExpBar);
this.candyBar = new CandyBar(this);
this.candyBar.setName("container-candy-bar");
this.candyBar.setName("candy-bar");
this.candyBar.setup();
this.fieldUI.add(this.candyBar);
@ -478,13 +490,13 @@ export default class BattleScene extends SceneBase {
const loadPokemonAssets = [];
this.arenaPlayer = new ArenaBase(this, true);
this.arenaPlayer.setName("container-arena-player");
this.arenaPlayer.setName("arena-player");
this.arenaPlayerTransition = new ArenaBase(this, true);
this.arenaPlayerTransition.setName("container-arena-player-transition");
this.arenaPlayerTransition.setName("arena-player-transition");
this.arenaEnemy = new ArenaBase(this, false);
this.arenaEnemy.setName("container-arena-enemy");
this.arenaEnemy.setName("arena-enemy");
this.arenaNextEnemy = new ArenaBase(this, false);
this.arenaNextEnemy.setName("container-arena-next-enemy");
this.arenaNextEnemy.setName("arena-next-enemy");
this.arenaBgTransition.setVisible(false);
this.arenaPlayerTransition.setVisible(false);
@ -736,6 +748,14 @@ export default class BattleScene extends SceneBase {
: ret;
}
/**
* Returns the ModifierBar of this scene, which is declared private and therefore not accessible elsewhere
* @returns {ModifierBar}
*/
getModifierBar(): ModifierBar {
return this.modifierBar;
}
// store info toggles to be accessible by the ui
addInfoToggle(infoToggle: InfoToggle): void {
this.infoToggles.push(infoToggle);
@ -797,8 +817,10 @@ export default class BattleScene extends SceneBase {
addPokemonIcon(pokemon: Pokemon, x: number, y: number, originX: number = 0.5, originY: number = 0.5, ignoreOverride: boolean = false): Phaser.GameObjects.Container {
const container = this.add.container(x, y);
container.setName(`${pokemon.name}-icon`);
const icon = this.add.sprite(0, 0, pokemon.getIconAtlasKey(ignoreOverride));
icon.setName(`sprite-${pokemon.name}-icon`);
icon.setFrame(pokemon.getIconId(true));
// Temporary fix to show pokemon's default icon if variant icon doesn't exist
if (icon.frame.name !== pokemon.getIconId(true)) {
@ -815,6 +837,7 @@ export default class BattleScene extends SceneBase {
if (pokemon.isFusion()) {
const fusionIcon = this.add.sprite(0, 0, pokemon.getFusionIconAtlasKey(ignoreOverride));
fusionIcon.setName("sprite-fusion-icon");
fusionIcon.setOrigin(0.5, 0);
fusionIcon.setFrame(pokemon.getFusionIconId(true));
@ -1099,8 +1122,10 @@ export default class BattleScene extends SceneBase {
playerField.forEach((_, p) => this.unshiftPhase(new ReturnPhase(this, p)));
for (const pokemon of this.getParty()) {
if (pokemon.hasAbility(Abilities.ICE_FACE)) {
pokemon.formIndex = 0;
// Only trigger form change when Eiscue is in Noice form
// Hardcoded Eiscue for now in case it is fused with another pokemon
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
this.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
pokemon.resetBattleData();
@ -1396,6 +1421,30 @@ export default class BattleScene extends SceneBase {
});
}
showShopOverlay(duration: integer): Promise<void> {
return new Promise(resolve => {
this.tweens.add({
targets: this.shopOverlay,
alpha: 0.8,
ease: "Sine.easeOut",
duration: duration,
onComplete: () => resolve()
});
});
}
hideShopOverlay(duration: integer): Promise<void> {
return new Promise(resolve => {
this.tweens.add({
targets: this.shopOverlay,
alpha: 0,
duration: duration,
ease: "Cubic.easeIn",
onComplete: () => resolve()
});
});
}
showEnemyModifierBar(): void {
this.enemyModifierBar.setVisible(true);
}
@ -1407,6 +1456,7 @@ export default class BattleScene extends SceneBase {
updateBiomeWaveText(): void {
const isBoss = !(this.currentBattle.waveIndex % 10);
const biomeString: string = getBiomeName(this.arena.biomeType);
this.fieldUI.moveAbove(this.biomeWaveText, this.luckText);
this.biomeWaveText.setText( biomeString + " - " + this.currentBattle.waveIndex.toString());
this.biomeWaveText.setColor(!isBoss ? "#ffffff" : "#f89890");
this.biomeWaveText.setShadowColor(!isBoss ? "#636363" : "#984038");
@ -1591,6 +1641,7 @@ export default class BattleScene extends SceneBase {
: this.getBgmLoopPoint(bgmName);
let loaded = false;
const playNewBgm = () => {
this.ui.bgmBar.setBgmToBgmBar(bgmName);
if (bgmName === null && this.bgm && !this.bgm.pendingRemove) {
this.bgm.play({
volume: this.masterVolume * this.bgmVolume
@ -1846,8 +1897,26 @@ export default class BattleScene extends SceneBase {
return 13.940;
case "end_summit": //PMD RTDX Sky Tower Summit
return 30.025;
case "battle_rocket_grunt": //HGSS Team Rocket Battle
return 12.707;
case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle
return 12.062;
case "battle_galactic_grunt": //BDSP Team Galactic Battle
return 13.043;
case "battle_plasma_grunt": //BW Team Plasma Battle
return 12.974;
case "battle_flare_grunt": //XY Team Flare Battle
return 4.228;
case "battle_rocket_boss": //USUM Giovanni Battle
return 9.115;
case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle
return 14.847;
case "battle_galactic_boss": //BDSP Cyrus Battle
return 106.962;
case "battle_plasma_boss": //B2W2 Ghetsis Battle
return 25.624;
case "battle_flare_boss": //XY Lysandre Battle
return 8.085;
}
return 0;
@ -2340,8 +2409,14 @@ export default class BattleScene extends SceneBase {
return false;
}
getModifiers(modifierType: { new(...args: any[]): Modifier }, player: boolean = true): PersistentModifier[] {
return (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType);
/**
* Get all of the modifiers that match `modifierType`
* @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier}
* @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true`
* @returns the list of all modifiers that matched `modifierType`.
*/
getModifiers<T extends PersistentModifier>(modifierType: Constructor<T>, player: boolean = true): T[] {
return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType);
}
findModifiers(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier[] {
@ -2352,7 +2427,7 @@ export default class BattleScene extends SceneBase {
return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m));
}
applyShuffledModifiers(scene: BattleScene, modifierType: { new(...args: any[]): Modifier }, player: boolean = true, ...args: any[]): PersistentModifier[] {
applyShuffledModifiers(scene: BattleScene, modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier[] {
let modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args));
scene.executeWithSeedOffset(() => {
const shuffleModifiers = mods => {
@ -2367,7 +2442,7 @@ export default class BattleScene extends SceneBase {
return this.applyModifiersInternal(modifiers, player, args);
}
applyModifiers(modifierType: { new(...args: any[]): Modifier }, player: boolean = true, ...args: any[]): PersistentModifier[] {
applyModifiers(modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier[] {
const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args));
return this.applyModifiersInternal(modifiers, player, args);
}
@ -2384,7 +2459,7 @@ export default class BattleScene extends SceneBase {
return appliedModifiers;
}
applyModifier(modifierType: { new(...args: any[]): Modifier }, player: boolean = true, ...args: any[]): PersistentModifier {
applyModifier(modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier {
const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args));
for (const modifier of modifiers) {
if (modifier.apply(args)) {
@ -2396,7 +2471,7 @@ export default class BattleScene extends SceneBase {
return null;
}
triggerPokemonFormChange(pokemon: Pokemon, formChangeTriggerType: { new(...args: any[]): SpeciesFormChangeTrigger }, delayed: boolean = false, modal: boolean = false): boolean {
triggerPokemonFormChange(pokemon: Pokemon, formChangeTriggerType: Constructor<SpeciesFormChangeTrigger>, delayed: boolean = false, modal: boolean = false): boolean {
if (pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId)) {
const matchingFormChange = pokemonFormChanges[pokemon.species.speciesId].find(fc => fc.findTrigger(formChangeTriggerType) && fc.canChange(pokemon));
if (matchingFormChange) {
@ -2420,7 +2495,7 @@ export default class BattleScene extends SceneBase {
return false;
}
validateAchvs(achvType: { new(...args: any[]): Achv }, ...args: any[]): void {
validateAchvs(achvType: Constructor<Achv>, ...args: unknown[]): void {
const filteredAchvs = Object.values(achvs).filter(a => a instanceof achvType);
for (const achv of filteredAchvs) {
this.validateAchv(achv, args);

View File

@ -7,6 +7,7 @@ import { GameMode } from "./game-mode";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball";
import {trainerConfigs} from "#app/data/trainer-config";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattleSpec } from "#enums/battle-spec";
import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender";
@ -166,6 +167,10 @@ export default class Battle {
const moneyAmount = new Utils.IntegerHolder(scene.currentBattle.moneyScattered);
scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
if (scene.arena.getTag(ArenaTagType.HAPPY_HOUR)) {
moneyAmount.value *= 2;
}
scene.addMoney(moneyAmount.value);
scene.queueMessage(`You picked up ₽${moneyAmount.value.toLocaleString("en-US")}!`, null, true);
@ -426,8 +431,8 @@ export interface FixedBattleConfigs {
/**
* Youngster/Lass on 5
* Rival on 8, 55, 95, 145, 195
* Evil team grunts on 35, 62, 64, 65, 112, 114 (Not currently spawning)
* Evil leader on 115, 165 (Not currently spawning)
* Evil team grunts on 35, 62, 64, 66, 112, 114
* Evil leader on 115, 165
* E4 on 182, 184, 186, 188
* Champion on 190
*/
@ -438,28 +443,28 @@ export const classicFixedBattles: FixedBattleConfigs = {
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
[25]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_2, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
// [35]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
// .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
[35]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
[55]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_3, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
// [62]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
// .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
// [64]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
// .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
// [65]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
// .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
[62]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
[64]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
[66]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
[95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
// [112]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
// .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
// [114]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
// .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
// [115]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
// .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE ])),
[112]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
[114]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ])),
[115]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE ])),
[145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
// [165]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
// .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2 ])),
[165]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2 ])),
[182]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ], TrainerType.MARNIE_ELITE, TrainerType.RIKA, TrainerType.CRISPIN ])),
[184]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182)

447
src/data/ability.ts Executable file → Normal file
View File

@ -1,11 +1,12 @@
import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
import { Type } from "./type";
import { Constructor } from "#app/utils";
import * as Utils from "../utils";
import { BattleStat, getBattleStatName } from "./battle-stat";
import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
import { getPokemonMessage, getPokemonNameWithAffix } from "../messages";
import { Weather, WeatherType } from "./weather";
import { BattlerTag } from "./battler-tags";
import { BattlerTag, GroundedTag } from "./battler-tags";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr } from "./move";
@ -14,7 +15,8 @@ import { Stat, getStatName } from "./pokemon-stat";
import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier";
import { TerrainType } from "./terrain";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import i18next, { Localizable } from "#app/plugins/i18n.js";
import i18next from "i18next";
import { Localizable } from "#app/interfaces/locales.js";
import { Command } from "../ui/command-ui-handler";
import { BerryModifierType } from "#app/modifier/modifier-type";
import { getPokeballName } from "./pokeball";
@ -60,7 +62,7 @@ export class Ability implements Localizable {
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends AbAttr>(attrType: new(...args: any[]) => T ): T[] {
getAttrs<T extends AbAttr>(attrType: Constructor<T> ): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
}
@ -69,18 +71,18 @@ export class Ability implements Localizable {
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns true if the ability has attribute `attrType`
*/
hasAttr<T extends AbAttr>(attrType: new(...args: any[]) => T): boolean {
hasAttr<T extends AbAttr>(attrType: Constructor<T>): boolean {
return this.attrs.some((attr) => attr instanceof attrType);
}
attr<T extends new (...args: any[]) => AbAttr>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
const attr = new AttrType(...args);
this.attrs.push(attr);
return this;
}
conditionalAttr<T extends new (...args: any[]) => AbAttr>(condition: AbAttrCondition, AttrType: T, ...args: ConstructorParameters<T>): Ability {
conditionalAttr<T extends Constructor<AbAttr>>(condition: AbAttrCondition, AttrType: T, ...args: ConstructorParameters<T>): Ability {
const attr = new AttrType(...args);
attr.addCondition(condition);
this.attrs.push(attr);
@ -299,18 +301,19 @@ export class StabBoostAbAttr extends AbAttr {
export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
protected condition: PokemonDefendCondition;
private powerMultiplier: number;
private damageMultiplier: number;
constructor(condition: PokemonDefendCondition, powerMultiplier: number) {
constructor(condition: PokemonDefendCondition, damageMultiplier: number) {
super();
this.condition = condition;
this.powerMultiplier = powerMultiplier;
this.damageMultiplier = damageMultiplier;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
(args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * this.damageMultiplier);
return true;
}
@ -319,19 +322,19 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
}
export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(moveType: Type, powerMultiplier: number) {
super((user, target, move) => move.type === moveType, powerMultiplier);
constructor(moveType: Type, damageMultiplier: number) {
super((user, target, move) => move.type === moveType, damageMultiplier);
}
}
export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
export class PreDefendMoveDamageToOneAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(condition: PokemonDefendCondition) {
super(condition, 1);
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = 1;
(args[0] as Utils.NumberHolder).value = Math.floor(pokemon.getMaxHp() / 8);
return true;
}
@ -1044,6 +1047,56 @@ export class PreAttackAbAttr extends AbAttr {
}
}
/**
* Modifies moves additional effects with multipliers, ie. Sheer Force, Serene Grace.
* @extends AbAttr
* @see {@linkcode apply}
*/
export class MoveEffectChanceMultiplierAbAttr extends AbAttr {
private chanceMultiplier: number;
constructor(chanceMultiplier?: number) {
super(true);
this.chanceMultiplier = chanceMultiplier;
}
/**
* @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance. Has to be higher than or equal to 0.
* [1]: {@linkcode Moves } Move used by the ability user.
*/
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if ((args[0] as Utils.NumberHolder).value <= 0 || (args[1] as Move).id === Moves.ORDER_UP) {
return false;
}
(args[0] as Utils.NumberHolder).value *= this.chanceMultiplier;
(args[0] as Utils.NumberHolder).value = Math.min((args[0] as Utils.NumberHolder).value, 100);
return true;
}
}
/**
* Sets incoming moves additional effect chance to zero, ignoring all effects from moves. ie. Shield Dust.
* @extends PreDefendAbAttr
* @see {@linkcode applyPreDefend}
*/
export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr {
/**
* @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance.
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if ((args[0] as Utils.NumberHolder).value <= 0) {
return false;
}
(args[0] as Utils.NumberHolder).value = 0;
return true;
}
}
export class VariableMovePowerAbAttr extends PreAttackAbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder;
@ -1282,17 +1335,18 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
}
}
export class FieldVariableMovePowerAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder;
return false;
}
}
export class FieldMovePowerBoostAbAttr extends FieldVariableMovePowerAbAttr {
/**
* Boosts the power of a Pokémon's move under certain conditions.
* @extends AbAttr
*/
export class FieldMovePowerBoostAbAttr extends AbAttr {
private condition: PokemonAttackCondition;
private powerMultiplier: number;
/**
* @param condition - A function that determines whether the power boost condition is met.
* @param powerMultiplier - The multiplier to apply to the move's power when the condition is met.
*/
constructor(condition: PokemonAttackCondition, powerMultiplier: number) {
super(false);
this.condition = condition;
@ -1310,12 +1364,34 @@ export class FieldMovePowerBoostAbAttr extends FieldVariableMovePowerAbAttr {
}
}
/**
* Boosts the power of a specific type of move.
* @extends FieldMovePowerBoostAbAttr
*/
export class FieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostAbAttr {
/**
* @param boostedType - The type of move that will receive the power boost.
* @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided.
*/
constructor(boostedType: Type, powerMultiplier?: number) {
super((pokemon, defender, move) => move.type === boostedType, powerMultiplier || 1.5);
}
}
/**
* Boosts the power of moves in specified categories.
* @extends FieldMovePowerBoostAbAttr
*/
export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr {
/**
* @param boostedCategories - The categories of moves that will receive the power boost.
* @param powerMultiplier - The multiplier to apply to the move's power.
*/
constructor(boostedCategories: MoveCategory[], powerMultiplier: number) {
super((pokemon, defender, move) => boostedCategories.includes(move.category), powerMultiplier);
}
}
export class BattleStatMultiplierAbAttr extends AbAttr {
private battleStat: BattleStat;
private multiplier: number;
@ -1394,7 +1470,8 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
}
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) {
/**Status inflicted by abilities post attacking are also considered additional effects.*/
if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return attacker.trySetStatus(effect, true, pokemon);
}
@ -1424,10 +1501,9 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
}
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) {
/**Battler tags inflicted by abilities post attacking are also considered additional effects.*/
if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return attacker.addTag(effect);
}
@ -1627,11 +1703,44 @@ export class PostIntimidateStatChangeAbAttr extends AbAttr {
}
}
/**
* Base class for defining all {@linkcode Ability} Attributes post summon
* @see {@linkcode applyPostSummon()}
*/
export class PostSummonAbAttr extends AbAttr {
/**
* Applies ability post summon (after switching in)
* @param pokemon {@linkcode Pokemon} with this ability
* @param passive Whether this ability is a passive
* @param args Set of unique arguments needed by this attribute
* @returns true if application of the ability succeeds
*/
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
return false;
}
}
/**
* Removes specified arena tags when a Pokemon is summoned.
*/
export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr {
private arenaTags: ArenaTagType[];
/**
* @param arenaTags {@linkcode ArenaTagType[]} - the arena tags to be removed
*/
constructor(arenaTags: ArenaTagType[]) {
super(true);
this.arenaTags = arenaTags;
}
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
for (const arenaTag of this.arenaTags) {
pokemon.scene.arena.removeTag(arenaTag);
}
return true;
}
}
export class PostSummonMessageAbAttr extends PostSummonAbAttr {
private messageFunc: (pokemon: Pokemon) => string;
@ -1877,7 +1986,11 @@ export class PostSummonFormChangeAbAttr extends PostSummonAbAttr {
}
}
export class TraceAbAttr extends PostSummonAbAttr {
/** Attempts to copy a pokemon's ability */
export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
private target: Pokemon;
private targetAbilityName: string;
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
const targets = pokemon.getOpponents();
if (!targets.length) {
@ -1891,18 +2004,57 @@ export class TraceAbAttr extends PostSummonAbAttr {
target = targets[0];
}
// Wonder Guard is normally uncopiable so has the attribute, but trace specifically can copy it
if (target.getAbility().hasAttr(UncopiableAbilityAbAttr) && target.getAbility().id !== Abilities.WONDER_GUARD) {
if (
target.getAbility().hasAttr(UncopiableAbilityAbAttr) &&
// Wonder Guard is normally uncopiable so has the attribute, but Trace specifically can copy it
!(pokemon.hasAbility(Abilities.TRACE) && target.getAbility().id === Abilities.WONDER_GUARD)
) {
return false;
}
this.target = target;
this.targetAbilityName = allAbilities[target.getAbility().id].name;
pokemon.summonData.ability = target.getAbility().id;
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` traced ${target.name}'s\n${allAbilities[target.getAbility().id].name}!`));
setAbilityRevealed(target);
pokemon.updateInfo();
return true;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return i18next.t("abilityTriggers:trace", {
pokemonName: getPokemonNameWithAffix(pokemon),
targetName: getPokemonNameWithAffix(this.target),
abilityName: this.targetAbilityName,
});
}
}
/** Attempt to copy the stat changes on an ally pokemon */
export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr {
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
if (!pokemon.scene.currentBattle.double) {
return false;
}
const ally = pokemon.getAlly();
if (!ally || ally.summonData.battleStats.every((change) => change === 0)) {
return false;
}
pokemon.summonData.battleStats = ally.summonData.battleStats;
pokemon.updateInfo();
return true;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return i18next.t("abilityTriggers:costar", {
pokemonName: getPokemonNameWithAffix(pokemon),
allyName: getPokemonNameWithAffix(pokemon.getAlly()),
});
}
}
export class PostSummonTransformAbAttr extends PostSummonAbAttr {
@ -2253,7 +2405,7 @@ export class BlockNonDirectDamageAbAttr extends AbAttr {
/**
* This attribute will block any status damage that you put in the parameter.
*/
export class BlockStatusDamageAbAttr extends BlockNonDirectDamageAbAttr {
export class BlockStatusDamageAbAttr extends AbAttr {
private effects: StatusEffect[];
/**
@ -2273,7 +2425,7 @@ export class BlockStatusDamageAbAttr extends BlockNonDirectDamageAbAttr {
* @returns Returns true if status damage is blocked
*/
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.effects.includes(pokemon.status.effect)) {
if (this.effects.includes(pokemon.status?.effect)) {
cancelled.value = true;
return true;
}
@ -2356,8 +2508,40 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr {
}
}
/**
* Condition function to applied to abilities related to Sheer Force.
* Checks if last move used against target was affected by a Sheer Force user and:
* Disables: Color Change, Pickpocket, Wimp Out, Emergency Exit, Berserk, Anger Shell
* @returns {AbAttrCondition} If false disables the ability which the condition is applied to.
*/
function getSheerForceHitDisableAbCondition(): AbAttrCondition {
return (pokemon: Pokemon) => {
if (!pokemon.turnData) {
return true;
}
const lastReceivedAttack = pokemon.turnData.attacksReceived[0];
if (!lastReceivedAttack) {
return true;
}
const lastAttacker = pokemon.getOpponents().find(p => p.id === lastReceivedAttack.sourceId);
if (!lastAttacker) {
return true;
}
/**if the last move chance is greater than or equal to cero, and the last attacker's ability is sheer force*/
const SheerForceAffected = allMoves[lastReceivedAttack.move].chance >= 0 && lastAttacker.hasAbility(Abilities.SHEER_FORCE);
return !SheerForceAffected;
};
}
function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
return (pokemon: Pokemon) => {
if (!pokemon.scene?.arena) {
return false;
}
if (pokemon.scene.arena.weather?.isEffectSuppressed(pokemon.scene)) {
return false;
}
@ -2543,15 +2727,11 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
}
applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather, args: any[]): boolean {
if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${abilityName}!`));
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER);
return true;
}
return false;
const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${abilityName}!`));
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER);
return true;
}
}
@ -3557,13 +3737,21 @@ export class PostSummonStatChangeOnArenaAbAttr extends PostSummonStatChangeAbAtt
}
/**
* Applies immunity to physical moves.
* Takes no damage from the first hit of a physical move.
* This is used in Ice Face ability.
*/
export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr {
export class IceFaceBlockPhysicalAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
private multiplier: number;
constructor(condition: PokemonDefendCondition, multiplier: number) {
super(condition, multiplier);
this.multiplier = multiplier;
}
/**
* Applies the Ice Face pre-defense ability to the Pokémon.
* Removes BattlerTagType.ICE_FACE hit by physical attack and is in Ice Face form.
* Removes BattlerTagType.ICE_FACE when hit by physical attack and is in Ice Face form.
*
* @param {Pokemon} pokemon - The Pokémon with the Ice Face ability.
* @param {boolean} passive - Whether the ability is passive.
@ -3574,16 +3762,13 @@ export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr {
* @returns {boolean} - Whether the immunity was applied.
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const isImmune = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (isImmune) {
const simulated = args.length > 1 && args[1];
if (!simulated) {
pokemon.removeTag(BattlerTagType.ICE_FACE);
}
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = this.multiplier;
pokemon.removeTag(BattlerTagType.ICE_FACE);
return true;
}
return isImmune;
return false;
}
/**
@ -3598,7 +3783,55 @@ export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr {
}
}
function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any[]): TAttr },
/**
* If a Pokémon with this Ability selects a damaging move, it has a 30% chance of going first in its priority bracket. If the Ability activates, this is announced at the start of the turn (after move selection).
*
* @extends AbAttr
*/
export class BypassSpeedChanceAbAttr extends AbAttr {
public chance: integer;
/**
* @param {integer} chance probability of ability being active.
*/
constructor(chance: integer) {
super(true);
this.chance = chance;
}
/**
* bypass move order in their priority bracket when pokemon choose damaging move
* @param {Pokemon} pokemon {@linkcode Pokemon} the Pokemon applying this ability
* @param {boolean} passive N/A
* @param {Utils.BooleanHolder} cancelled N/A
* @param {any[]} args [0] {@linkcode Utils.BooleanHolder} set to true when the ability activated
* @returns {boolean} - whether the ability was activated.
*/
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const bypassSpeed = args[0] as Utils.BooleanHolder;
if (!bypassSpeed.value && pokemon.randSeedInt(100) < this.chance) {
const turnCommand =
pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
const isCommandFight = turnCommand?.command === Command.FIGHT;
const move = allMoves[turnCommand.move?.move];
const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
if (isCommandFight && isDamageMove) {
bypassSpeed.value = true;
return true;
}
}
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return i18next.t("abilityTriggers:quickDraw", {pokemonName: getPokemonNameWithAffix(pokemon)});
}
}
function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: Constructor<TAttr>,
pokemon: Pokemon, applyFunc: AbAttrApplyFunc<TAttr>, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise<void> {
return new Promise(resolve => {
if (!pokemon.canApplyAbility(passive)) {
@ -3613,7 +3846,7 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
const attrs = ability.getAttrs(attrType);
const clearSpliceQueueAndResolve = () => {
pokemon.scene.clearPhaseQueueSplice();
pokemon.scene?.clearPhaseQueueSplice();
if (!passive) {
return applyAbAttrsInternal(attrType, pokemon, applyFunc, args, isAsync, showAbilityInstant, quiet, true).then(() => resolve());
} else {
@ -3676,32 +3909,32 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
});
}
export function applyAbAttrs(attrType: { new(...args: any[]): AbAttr }, pokemon: Pokemon, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
export function applyAbAttrs(attrType: Constructor<AbAttr>, pokemon: Pokemon, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<AbAttr>(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, cancelled, args), args);
}
export function applyPostBattleInitAbAttrs(attrType: { new(...args: any[]): PostBattleInitAbAttr },
export function applyPostBattleInitAbAttrs(attrType: Constructor<PostBattleInitAbAttr>,
pokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostBattleInitAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostBattleInit(pokemon, passive, args), args);
}
export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr },
export function applyPreDefendAbAttrs(attrType: Constructor<PreDefendAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated);
}
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr },
export function applyPostDefendAbAttrs(attrType: Constructor<PostDefendAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args);
}
export function applyPostMoveUsedAbAttrs(attrType: { new(...args: any[]): PostMoveUsedAbAttr },
export function applyPostMoveUsedAbAttrs(attrType: Constructor<PostMoveUsedAbAttr>,
pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostMoveUsedAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, args), args);
}
export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[]): BattleStatMultiplierAbAttr },
export function applyBattleStatMultiplierAbAttrs(attrType: Constructor<BattleStatMultiplierAbAttr>,
pokemon: Pokemon, battleStat: BattleStat, statValue: Utils.NumberHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<BattleStatMultiplierAbAttr>(attrType, pokemon, (attr, passive) => attr.applyBattleStat(pokemon, passive, battleStat, statValue, args), args);
}
@ -3716,98 +3949,98 @@ export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[])
* @param hasApplied {@linkcode Utils.BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat
* @param args unused
*/
export function applyFieldBattleStatMultiplierAbAttrs(attrType: { new(...args: any[]): FieldMultiplyBattleStatAbAttr },
export function applyFieldBattleStatMultiplierAbAttrs(attrType: Constructor<FieldMultiplyBattleStatAbAttr>,
pokemon: Pokemon, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<FieldMultiplyBattleStatAbAttr>(attrType, pokemon, (attr, passive) => attr.applyFieldBattleStat(pokemon, passive, stat, statValue, checkedPokemon, hasApplied, args), args);
}
export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttackAbAttr },
export function applyPreAttackAbAttrs(attrType: Constructor<PreAttackAbAttr>,
pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args);
}
export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAttackAbAttr },
export function applyPostAttackAbAttrs(attrType: Constructor<PostAttackAbAttr>,
pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args);
}
export function applyPostKnockOutAbAttrs(attrType: { new(...args: any[]): PostKnockOutAbAttr },
export function applyPostKnockOutAbAttrs(attrType: Constructor<PostKnockOutAbAttr>,
pokemon: Pokemon, knockedOut: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostKnockOutAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostKnockOut(pokemon, passive, knockedOut, args), args);
}
export function applyPostVictoryAbAttrs(attrType: { new(...args: any[]): PostVictoryAbAttr },
export function applyPostVictoryAbAttrs(attrType: Constructor<PostVictoryAbAttr>,
pokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostVictoryAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostVictory(pokemon, passive, args), args);
}
export function applyPostSummonAbAttrs(attrType: { new(...args: any[]): PostSummonAbAttr },
export function applyPostSummonAbAttrs(attrType: Constructor<PostSummonAbAttr>,
pokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostSummonAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostSummon(pokemon, passive, args), args);
}
export function applyPreSwitchOutAbAttrs(attrType: { new(...args: any[]): PreSwitchOutAbAttr },
export function applyPreSwitchOutAbAttrs(attrType: Constructor<PreSwitchOutAbAttr>,
pokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreSwitchOutAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, args), args, false, true);
}
export function applyPreStatChangeAbAttrs(attrType: { new(...args: any[]): PreStatChangeAbAttr },
export function applyPreStatChangeAbAttrs(attrType: Constructor<PreStatChangeAbAttr>,
pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreStatChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, stat, cancelled, args), args);
}
export function applyPostStatChangeAbAttrs(attrType: { new(...args: any[]): PostStatChangeAbAttr },
export function applyPostStatChangeAbAttrs(attrType: Constructor<PostStatChangeAbAttr>,
pokemon: Pokemon, stats: BattleStat[], levels: integer, selfTarget: boolean, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostStatChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostStatChange(pokemon, stats, levels, selfTarget, args), args);
}
export function applyPreSetStatusAbAttrs(attrType: { new(...args: any[]): PreSetStatusAbAttr },
export function applyPreSetStatusAbAttrs(attrType: Constructor<PreSetStatusAbAttr>,
pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, effect, cancelled, args), args, false, false, !simulated);
}
export function applyPreApplyBattlerTagAbAttrs(attrType: { new(...args: any[]): PreApplyBattlerTagAbAttr },
export function applyPreApplyBattlerTagAbAttrs(attrType: Constructor<PreApplyBattlerTagAbAttr>,
pokemon: Pokemon, tag: BattlerTag, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreApplyBattlerTagAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreApplyBattlerTag(pokemon, passive, tag, cancelled, args), args);
}
export function applyPreWeatherEffectAbAttrs(attrType: { new(...args: any[]): PreWeatherEffectAbAttr },
export function applyPreWeatherEffectAbAttrs(attrType: Constructor<PreWeatherEffectAbAttr>,
pokemon: Pokemon, weather: Weather, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreWeatherDamageAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, weather, cancelled, args), args, false, true);
}
export function applyPostTurnAbAttrs(attrType: { new(...args: any[]): PostTurnAbAttr },
export function applyPostTurnAbAttrs(attrType: Constructor<PostTurnAbAttr>,
pokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostTurnAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostTurn(pokemon, passive, args), args);
}
export function applyPostWeatherChangeAbAttrs(attrType: { new(...args: any[]): PostWeatherChangeAbAttr },
export function applyPostWeatherChangeAbAttrs(attrType: Constructor<PostWeatherChangeAbAttr>,
pokemon: Pokemon, weather: WeatherType, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostWeatherChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostWeatherChange(pokemon, passive, weather, args), args);
}
export function applyPostWeatherLapseAbAttrs(attrType: { new(...args: any[]): PostWeatherLapseAbAttr },
export function applyPostWeatherLapseAbAttrs(attrType: Constructor<PostWeatherLapseAbAttr>,
pokemon: Pokemon, weather: Weather, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostWeatherLapseAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, weather, args), args);
}
export function applyPostTerrainChangeAbAttrs(attrType: { new(...args: any[]): PostTerrainChangeAbAttr },
export function applyPostTerrainChangeAbAttrs(attrType: Constructor<PostTerrainChangeAbAttr>,
pokemon: Pokemon, terrain: TerrainType, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostTerrainChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostTerrainChange(pokemon, passive, terrain, args), args);
}
export function applyCheckTrappedAbAttrs(attrType: { new(...args: any[]): CheckTrappedAbAttr },
export function applyCheckTrappedAbAttrs(attrType: Constructor<CheckTrappedAbAttr>,
pokemon: Pokemon, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<CheckTrappedAbAttr>(attrType, pokemon, (attr, passive) => attr.applyCheckTrapped(pokemon, passive, trapped, otherPokemon, args), args, true);
}
export function applyPostBattleAbAttrs(attrType: { new(...args: any[]): PostBattleAbAttr },
export function applyPostBattleAbAttrs(attrType: Constructor<PostBattleAbAttr>,
pokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostBattleAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostBattle(pokemon, passive, args), args);
}
export function applyPostFaintAbAttrs(attrType: { new(...args: any[]): PostFaintAbAttr },
export function applyPostFaintAbAttrs(attrType: Constructor<PostFaintAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostFaintAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, attacker, move, hitResult, args), args);
}
@ -3886,7 +4119,8 @@ export function initAbilities() {
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
.ignorable(),
new Ability(Abilities.COLOR_CHANGE, 3)
.attr(PostDefendTypeChangeAbAttr),
.attr(PostDefendTypeChangeAbAttr)
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.IMMUNITY, 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(),
@ -3894,8 +4128,8 @@ export function initAbilities() {
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1, (pokemon: Pokemon) => !pokemon.status || pokemon.status.effect !== StatusEffect.FREEZE)
.ignorable(),
new Ability(Abilities.SHIELD_DUST, 3)
.ignorable()
.unimplemented(),
.attr(IgnoreMoveEffectsAbAttr)
.partial(),
new Ability(Abilities.OWN_TEMPO, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
.attr(IntimidateImmunityAbAttr)
@ -3921,7 +4155,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr)
.ignorable(),
new Ability(Abilities.LEVITATE, 3)
.attr(TypeImmunityAbAttr, Type.GROUND, (pokemon: Pokemon) => !pokemon.getTag(BattlerTagType.IGNORE_FLYING) && !pokemon.scene.arena.getTag(ArenaTagType.GRAVITY) && !pokemon.getTag(BattlerTagType.GROUNDED))
.attr(TypeImmunityAbAttr, Type.GROUND, (pokemon: Pokemon) => !pokemon.getTag(GroundedTag) && !pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))
.ignorable(),
new Ability(Abilities.EFFECT_SPORE, 3)
.attr(EffectSporeAbAttr),
@ -3938,7 +4172,8 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPATK, 1)
.ignorable(),
new Ability(Abilities.SERENE_GRACE, 3)
.unimplemented(),
.attr(MoveEffectChanceMultiplierAbAttr, 2)
.partial(),
new Ability(Abilities.SWIFT_SWIM, 3)
.attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
@ -3950,7 +4185,7 @@ export function initAbilities() {
.attr(DoubleBattleChanceAbAttr)
.ignorable(),
new Ability(Abilities.TRACE, 3)
.attr(TraceAbAttr)
.attr(PostSummonCopyAbilityAbAttr)
.attr(UncopiableAbilityAbAttr),
new Ability(Abilities.HUGE_POWER, 3)
.attr(BattleStatMultiplierAbAttr, BattleStat.ATK, 2),
@ -4214,9 +4449,12 @@ export function initAbilities() {
new Ability(Abilities.BAD_DREAMS, 4)
.attr(PostTurnHurtIfSleepingAbAttr),
new Ability(Abilities.PICKPOCKET, 5)
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT)),
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT))
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.SHEER_FORCE, 5)
.unimplemented(),
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461/4096)
.attr(MoveEffectChanceMultiplierAbAttr, 0)
.partial(),
new Ability(Abilities.CONTRARY, 5)
.attr(StatChangeMultiplierAbAttr, -1)
.ignorable(),
@ -4350,8 +4588,8 @@ export function initAbilities() {
.attr(HealFromBerryUseAbAttr, 1/3)
.partial(), // Healing not blocked by Heal Block
new Ability(Abilities.PROTEAN, 6)
.attr(PokemonTypeChangeAbAttr)
.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.PROTEAN)),
.attr(PokemonTypeChangeAbAttr),
//.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.PROTEAN)), //Gen 9 Implementation
new Ability(Abilities.FUR_COAT, 6)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, 0.5)
.ignorable(),
@ -4403,7 +4641,8 @@ export function initAbilities() {
.attr(FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 4 / 3),
new Ability(Abilities.AURA_BREAK, 6)
.ignorable()
.unimplemented(),
.conditionalAttr(target => target.hasAbility(Abilities.DARK_AURA), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16)
.conditionalAttr(target => target.hasAbility(Abilities.FAIRY_AURA), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16),
new Ability(Abilities.PRIMORDIAL_SEA, 6)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
@ -4425,8 +4664,10 @@ export function initAbilities() {
new Ability(Abilities.STAMINA, 7)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattleStat.DEF, 1),
new Ability(Abilities.WIMP_OUT, 7)
.condition(getSheerForceHitDisableAbCondition())
.unimplemented(),
new Ability(Abilities.EMERGENCY_EXIT, 7)
.condition(getSheerForceHitDisableAbCondition())
.unimplemented(),
new Ability(Abilities.WATER_COMPACTION, 7)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2),
@ -4452,7 +4693,8 @@ export function initAbilities() {
new Ability(Abilities.STEELWORKER, 7)
.attr(MoveTypePowerBoostAbAttr, Type.STEEL),
new Ability(Abilities.BERSERK, 7)
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [BattleStat.SPATK], 1),
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [BattleStat.SPATK], 1)
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.SLUSH_RUSH, 7)
.attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
.condition(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW)),
@ -4476,7 +4718,7 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.DISGUISE, 7)
.attr(PreDefendMovePowerToOneAbAttr, (target, user, move) => target.formIndex === 0 && target.getAttackTypeEffectiveness(move.type, user) > 0)
.attr(PreDefendMoveDamageToOneAbAttr, (target, user, move) => target.formIndex === 0 && target.getAttackTypeEffectiveness(move.type, user) > 0)
.attr(PostSummonFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostDefendFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
@ -4526,7 +4768,7 @@ export function initAbilities() {
new Ability(Abilities.DANCER, 7)
.attr(PostDancingMoveAbAttr),
new Ability(Abilities.BATTERY, 7)
.unimplemented(),
.attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL], 1.3),
new Ability(Abilities.FLUFFY, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.type === Type.FIRE, 2)
@ -4590,8 +4832,8 @@ export function initAbilities() {
.attr(PostSummonStatChangeAbAttr, BattleStat.DEF, 1, true)
.condition(getOncePerBattleCondition(Abilities.DAUNTLESS_SHIELD)),
new Ability(Abilities.LIBERO, 8)
.attr(PokemonTypeChangeAbAttr)
.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.LIBERO)),
.attr(PokemonTypeChangeAbAttr),
//.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.LIBERO)), //Gen 9 Implementation
new Ability(Abilities.BALL_FETCH, 8)
.attr(FetchBallAbAttr)
.condition(getOncePerBattleCondition(Abilities.BALL_FETCH)),
@ -4635,14 +4877,14 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0)
// When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
.attr(IceFaceMoveImmunityAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE))
.attr(IceFaceBlockPhysicalAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0)
.ignorable(),
new Ability(Abilities.POWER_SPOT, 8)
.unimplemented(),
.attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL], 1.3),
new Ability(Abilities.MIMICRY, 8)
.unimplemented(),
new Ability(Abilities.SCREEN_CLEANER, 8)
.unimplemented(),
.attr(PostSummonRemoveArenaTagAbAttr, [ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT]),
new Ability(Abilities.STEELY_SPIRIT, 8)
.attr(MoveTypePowerBoostAbAttr, Type.STEEL)
.partial(),
@ -4673,7 +4915,7 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr)
.condition((pokemon) => !pokemon.isTerastallized()),
new Ability(Abilities.QUICK_DRAW, 8)
.unimplemented(),
.attr(BypassSpeedChanceAbAttr, 30),
new Ability(Abilities.UNSEEN_FIST, 8)
.attr(IgnoreProtectOnContactAbAttr),
new Ability(Abilities.CURIOUS_MEDICINE, 8)
@ -4711,7 +4953,8 @@ export function initAbilities() {
.ignorable(),
new Ability(Abilities.ANGER_SHELL, 9)
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 1)
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.DEF, BattleStat.SPDEF ], -1),
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.DEF, BattleStat.SPDEF ], -1)
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.PURIFYING_SALT, 9)
.attr(StatusEffectImmunityAbAttr)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.GHOST, 0.5)
@ -4798,7 +5041,7 @@ export function initAbilities() {
.attr(VariableMovePowerBoostAbAttr, (user, target, move) => 1 + 0.1 * Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 5))
.partial(),
new Ability(Abilities.COSTAR, 9)
.unimplemented(),
.attr(PostSummonCopyAllyStatsAbAttr),
new Ability(Abilities.TOXIC_DEBRIS, 9)
.attr(PostDefendApplyArenaTrapTagAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, ArenaTagType.TOXIC_SPIKES)
.bypassFaint(),

View File

@ -4,7 +4,7 @@ import * as Utils from "../utils";
import { MoveCategory, allMoves, MoveTarget } from "./move";
import { getPokemonMessage } from "../messages";
import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase} from "../phases";
import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
import { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle";
import { BlockNonDirectDamageAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
@ -63,6 +63,10 @@ export abstract class ArenaTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Mist_(move) Mist}.
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
*/
export class MistTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
@ -86,37 +90,57 @@ export class MistTag extends ArenaTag {
}
}
/**
* Reduces the damage of specific move categories in the arena.
* @extends ArenaTag
*/
export class WeakenMoveScreenTag extends ArenaTag {
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) {
protected weakenedCategories: MoveCategory[];
/**
* Creates a new instance of the WeakenMoveScreenTag class.
*
* @param tagType - The type of the arena tag.
* @param turnCount - The number of turns the tag is active.
* @param sourceMove - The move that created the tag.
* @param sourceId - The ID of the source of the tag.
* @param side - The side (player or enemy) the tag affects.
* @param weakenedCategories - The categories of moves that are weakened by this tag.
*/
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
super(tagType, turnCount, sourceMove, sourceId, side);
this.weakenedCategories = weakenedCategories;
}
/**
* Applies the weakening effect to the move.
*
* @param arena - The arena where the move is applied.
* @param args - The arguments for the move application.
* @param args[0] - The category of the move.
* @param args[1] - A boolean indicating whether it is a double battle.
* @param args[2] - An object of type `Utils.NumberHolder` that holds the damage multiplier
*
* @returns True if the move was weakened, otherwise false.
*/
apply(arena: Arena, args: any[]): boolean {
if ((args[1] as boolean)) {
(args[2] as Utils.NumberHolder).value = 2732/4096;
} else {
(args[2] as Utils.NumberHolder).value = 0.5;
}
return true;
}
}
class ReflectTag extends WeakenMoveScreenTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side);
}
apply(arena: Arena, args: any[]): boolean {
if ((args[0] as MoveCategory) === MoveCategory.PHYSICAL) {
if ((args[1] as boolean)) {
(args[2] as Utils.NumberHolder).value = 2732/4096;
} else {
(args[2] as Utils.NumberHolder).value = 0.5;
}
if (this.weakenedCategories.includes((args[0] as MoveCategory))) {
(args[2] as Utils.NumberHolder).value = (args[1] as boolean) ? 2732/4096 : 0.5;
return true;
}
return false;
}
}
/**
* Reduces the damage of physical moves.
* Used by {@linkcode Moves.REFLECT}
*/
class ReflectTag extends WeakenMoveScreenTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [MoveCategory.PHYSICAL]);
}
onAdd(arena: Arena, quiet: boolean = false): void {
if (!quiet) {
@ -125,21 +149,13 @@ class ReflectTag extends WeakenMoveScreenTag {
}
}
/**
* Reduces the damage of special moves.
* Used by {@linkcode Moves.LIGHT_SCREEN}
*/
class LightScreenTag extends WeakenMoveScreenTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side);
}
apply(arena: Arena, args: any[]): boolean {
if ((args[0] as MoveCategory) === MoveCategory.SPECIAL) {
if ((args[1] as boolean)) {
(args[2] as Utils.NumberHolder).value = 2732/4096;
} else {
(args[2] as Utils.NumberHolder).value = 0.5;
}
return true;
}
return false;
super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [MoveCategory.SPECIAL]);
}
onAdd(arena: Arena, quiet: boolean = false): void {
@ -149,9 +165,13 @@ class LightScreenTag extends WeakenMoveScreenTag {
}
}
/**
* Reduces the damage of physical and special moves.
* Used by {@linkcode Moves.AURORA_VEIL}
*/
class AuroraVeilTag extends WeakenMoveScreenTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side);
super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL]);
}
onAdd(arena: Arena, quiet: boolean = false): void {
@ -283,6 +303,10 @@ class CraftyShieldTag extends ConditionalProtectTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) Wish}.
* Heals the Pokémon in the user's position the turn after Wish is used.
*/
class WishTag extends ArenaTag {
private battlerIndex: BattlerIndex;
private triggerMessage: string;
@ -308,9 +332,21 @@ class WishTag extends ArenaTag {
}
}
/**
* Abstract class to implement weakened moves of a specific type.
*/
export class WeakenMoveTypeTag extends ArenaTag {
private weakenedType: Type;
/**
* Creates a new instance of the WeakenMoveTypeTag class.
*
* @param tagType - The type of the arena tag.
* @param turnCount - The number of turns the tag is active.
* @param type - The type being weakened from this tag.
* @param sourceMove - The move that created the tag.
* @param sourceId - The ID of the source of the tag.
*/
constructor(tagType: ArenaTagType, turnCount: integer, type: Type, sourceMove: Moves, sourceId: integer) {
super(tagType, turnCount, sourceMove, sourceId);
@ -327,6 +363,10 @@ export class WeakenMoveTypeTag extends ArenaTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Mud_Sport_(move) Mud Sport}.
* Weakens Electric type moves for a set amount of turns, usually 5.
*/
class MudSportTag extends WeakenMoveTypeTag {
constructor(turnCount: integer, sourceId: integer) {
super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId);
@ -341,6 +381,10 @@ class MudSportTag extends WeakenMoveTypeTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Water_Sport_(move) Water Sport}.
* Weakens Fire type moves for a set amount of turns, usually 5.
*/
class WaterSportTag extends WeakenMoveTypeTag {
constructor(turnCount: integer, sourceId: integer) {
super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId);
@ -355,10 +399,22 @@ class WaterSportTag extends WeakenMoveTypeTag {
}
}
/**
* Abstract class to implement arena traps.
*/
export class ArenaTrapTag extends ArenaTag {
public layers: integer;
public maxLayers: integer;
/**
* Creates a new instance of the ArenaTrapTag class.
*
* @param tagType - The type of the arena tag.
* @param sourceMove - The move that created the tag.
* @param sourceId - The ID of the source of the tag.
* @param side - The side (player or enemy) the tag affects.
* @param maxLayers - The maximum amount of layers this tag can have.
*/
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) {
super(tagType, 0, sourceMove, sourceId, side);
@ -392,6 +448,11 @@ export class ArenaTrapTag extends ArenaTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Spikes_(move) Spikes}.
* Applies up to 3 layers of Spikes, dealing 1/8th, 1/6th, or 1/4th of the the Pokémon's HP
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
*/
class SpikesTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
@ -428,6 +489,12 @@ class SpikesTag extends ArenaTrapTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Spikes_(move) Toxic Spikes}.
* Applies up to 2 layers of Toxic Spikes, poisoning or badly poisoning any Pokémon who is
* summoned into this trap if 1 or 2 layers of Toxic Spikes respectively are up. Poison-type
* Pokémon summoned into this trap remove it entirely.
*/
class ToxicSpikesTag extends ArenaTrapTag {
private neutralized: boolean;
@ -481,6 +548,11 @@ class ToxicSpikesTag extends ArenaTrapTag {
}
}
/**
* Arena Tag class for delayed attacks, such as {@linkcode Moves.FUTURE_SIGHT} or {@linkcode Moves.DOOM_DESIRE}.
* Delays the attack's effect by a set amount of turns, usually 3 (including the turn the move is used),
* and deals damage after the turn count is reached.
*/
class DelayedAttackTag extends ArenaTag {
public targetIndex: BattlerIndex;
@ -503,6 +575,11 @@ class DelayedAttackTag extends ArenaTag {
onRemove(arena: Arena): void { }
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Stealth_Rock_(move) Stealth Rock}.
* Applies up to 1 layer of Stealth Rocks, dealing percentage-based damage to any Pokémon
* who is summoned into the trap, based on the Rock type's type effectiveness.
*/
class StealthRockTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
@ -574,6 +651,11 @@ class StealthRockTag extends ArenaTrapTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Sticky_Web_(move) Sticky Web}.
* Applies up to 1 layer of Sticky Web, which lowers the Speed by one stage
* to any Pokémon who is summoned into this trap.
*/
class StickyWebTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1);
@ -606,6 +688,11 @@ class StickyWebTag extends ArenaTrapTag {
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Trick_Room_(move) Trick Room}.
* Reverses the Speed stats for all Pokémon on the field as long as this arena tag is up,
* also reversing the turn order for all Pokémon on the field as well.
*/
export class TrickRoomTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer) {
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
@ -626,6 +713,11 @@ export class TrickRoomTag extends ArenaTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Gravity_(move) Gravity}.
* Grounds all Pokémon on the field, including Flying-types and those with
* {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns.
*/
export class GravityTag extends ArenaTag {
constructor(turnCount: integer) {
super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY);
@ -633,6 +725,11 @@ export class GravityTag extends ArenaTag {
onAdd(arena: Arena): void {
arena.scene.queueMessage("Gravity intensified!");
arena.scene.getField(true).forEach((pokemon) => {
if (pokemon !== null) {
pokemon.removeTag(BattlerTagType.MAGNET_RISEN);
}
});
}
onRemove(arena: Arena): void {
@ -640,6 +737,11 @@ export class GravityTag extends ArenaTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Tailwind_(move) Tailwind}.
* Doubles the Speed of the Pokémon who created this arena tag, as well as all allied Pokémon.
* Applies this arena tag for 4 turns (including the turn the move was used).
*/
class TailwindTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side);
@ -674,6 +776,24 @@ class TailwindTag extends ArenaTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Happy_Hour_(move) Happy Hour}.
* Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}.
*/
class HappyHourTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side);
}
onAdd(arena: Arena): void {
arena.scene.queueMessage("Everyone is caught up in the happy atmosphere!");
}
onRemove(arena: Arena): void {
arena.scene.queueMessage("The atmosphere returned to normal.");
}
}
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag {
switch (tagType) {
case ArenaTagType.MIST:
@ -715,5 +835,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new AuroraVeilTag(turnCount, sourceId, side);
case ArenaTagType.TAILWIND:
return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side);
}
}

View File

@ -32,34 +32,38 @@ export function getBattleStatName(stat: BattleStat) {
}
}
export function getBattleStatLevelChangeDescription(levels: integer, up: boolean) {
if (up) {
switch (levels) {
case 1:
return i18next.t("battle:statRose");
case 2:
return i18next.t("battle:statSharplyRose");
case 3:
case 4:
case 5:
case 6:
return i18next.t("battle:statRoseDrastically");
default:
return i18next.t("battle:statWontGoAnyHigher");
export function getBattleStatLevelChangeDescription(pokemonNameWithAffix: string, stats: string, levels: integer, up: boolean) {
const stringKey = (() => {
if (up) {
switch (levels) {
case 1:
return "battle:statRose";
case 2:
return "battle:statSharplyRose";
case 3:
case 4:
case 5:
case 6:
return "battle:statRoseDrastically";
default:
return "battle:statWontGoAnyHigher";
}
} else {
switch (levels) {
case 1:
return "battle:statFell";
case 2:
return "battle:statHarshlyFell";
case 3:
case 4:
case 5:
case 6:
return "battle:statSeverelyFell";
default:
return "battle:statWontGoAnyLower";
}
}
} else {
switch (levels) {
case 1:
return i18next.t("battle:statFell");
case 2:
return i18next.t("battle:statHarshlyFell");
case 3:
case 4:
case 5:
case 6:
return i18next.t("battle:statSeverelyFell");
default:
return i18next.t("battle:statWontGoAnyLower");
}
}
})();
return i18next.t(stringKey, { pokemonNameWithAffix, stats });
}

View File

@ -17,6 +17,7 @@ import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import i18next from "#app/plugins/i18n.js";
export enum BattlerTagLapseType {
FAINT,
@ -105,7 +106,7 @@ export class RechargingTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
super.lapse(pokemon, lapseType);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " must\nrecharge!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsRechargingLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.getMoveQueue().shift();
@ -134,7 +135,10 @@ export class TrappedTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` was freed\nfrom ${this.getMoveName()}!`));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsTrappedOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName()
}));
}
getDescriptor(): string {
@ -146,7 +150,7 @@ export class TrappedTag extends BattlerTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, " can no\nlonger escape!");
return i18next.t("battle:battlerTagsTrappedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -169,7 +173,7 @@ export class FlinchedTag extends BattlerTag {
super.lapse(pokemon, lapseType);
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " flinched!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsFlinchedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return true;
}
@ -218,26 +222,26 @@ export class ConfusedTag extends BattlerTag {
super.onAdd(pokemon);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " became\nconfused!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " snapped\nout of confusion!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is\nalready confused!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM && super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is\nconfused!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
// 1/3 chance of hitting self with a 40 base power move
@ -245,7 +249,7 @@ export class ConfusedTag extends BattlerTag {
const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF);
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
pokemon.scene.queueMessage("It hurt itself in its\nconfusion!");
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedLapseHurtItself"));
pokemon.damageAndUpdate(damage);
pokemon.battleData.hitCount++;
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
@ -292,14 +296,17 @@ export class DestinyBondTag extends BattlerTag {
return false;
}
const targetMessage = getPokemonMessage(pokemon, "");
if (pokemon.isBossImmune()) {
pokemon.scene.queueMessage(`${targetMessage} is unaffected\nby the effects of Destiny Bond.`);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsDestinyBondLapseIsBoss", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return false;
}
pokemon.scene.queueMessage(`${getPokemonMessage(source, ` took\n${targetMessage} down with it!`)}`);
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsDestinyBondLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(source),
pokemonNameWithAffix2: getPokemonNameWithAffix(pokemon)
})
);
pokemon.damageAndUpdate(pokemon.hp, HitResult.ONE_HIT_KO, false, false, true);
return false;
}
@ -317,24 +324,34 @@ export class InfatuatedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` fell in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`));
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: pokemon.scene.getPokemonById(this.sourceId).name
})
);
}
onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is\nalready in love!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsInfatuatedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`));
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: pokemon.scene.getPokemonById(this.sourceId).name
})
);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT));
if (pokemon.randSeedInt(2)) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is\nimmobilized by love!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsInfatuatedLapseImmobilize", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
}
}
@ -345,7 +362,7 @@ export class InfatuatedTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " got over\nits infatuation."));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsInfatuatedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
isSourceLinked(): boolean {
@ -380,7 +397,7 @@ export class SeedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " was seeded!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSeededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex();
}
@ -400,7 +417,7 @@ export class SeedTag extends BattlerTag {
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(),
!reverseDrain ? damage : damage * -1,
!reverseDrain ? getPokemonMessage(pokemon, "'s health is\nsapped by Leech Seed!") : getPokemonMessage(source, "'s Leech Seed\nsucked up the liquid ooze!"),
!reverseDrain ? i18next.t("battle:battlerTagsSeededLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }) : i18next.t("battle:battlerTagsSeededLapseShed", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
false, true));
}
}
@ -422,20 +439,20 @@ export class NightmareTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " began\nhaving a Nightmare!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsNightmareOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is\nalready locked in a Nightmare!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsNightmareOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is locked\nin a Nightmare!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsNightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
const cancelled = new Utils.BooleanHolder(false);
@ -527,7 +544,7 @@ export class EncoreTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " got\nan Encore!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsEncoreOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
const movePhase = pokemon.scene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon);
if (movePhase) {
@ -543,7 +560,7 @@ export class EncoreTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, "'s Encore\nended!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsEncoreOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
@ -553,7 +570,12 @@ export class HelpingHandTag extends BattlerTag {
}
onAdd(pokemon: Pokemon): void {
pokemon.scene.queueMessage(getPokemonMessage(pokemon.scene.getPokemonById(this.sourceId), ` is ready to\nhelp ${pokemon.name}!`));
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsHelpingHandOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)),
pokemonName: pokemon.name
})
);
}
}
@ -581,15 +603,22 @@ export class IngrainTag extends TrappedTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), Math.floor(pokemon.getMaxHp() / 16),
getPokemonMessage(pokemon, " absorbed\nnutrients with its roots!"), true));
pokemon.scene.unshiftPhase(
new PokemonHealPhase(
pokemon.scene,
pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16),
i18next.t("battle:battlerTagsIngrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
true
)
);
}
return ret;
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, " planted its roots!");
return i18next.t("battle:battlerTagsIngrainOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
getDescriptor(): string {
@ -605,15 +634,23 @@ export class AquaRingTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " surrounded\nitself with a veil of water!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsAquaRingOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
pokemon.scene.unshiftPhase(
new PokemonHealPhase(
pokemon.scene,
pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16),
i18next.t("battle:battlerTagsAquaRingLapse", {
moveName: this.getMoveName(),
pokemonName: pokemon.name
}),
true));
}
return ret;
@ -659,7 +696,7 @@ export class DrowsyTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " grew drowsy!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsDrowsyOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -702,7 +739,12 @@ export abstract class DamagingTrapTag extends TrappedTag {
const ret = super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`));
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsDamagingTrapLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName()
})
);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
const cancelled = new Utils.BooleanHolder(false);
@ -723,7 +765,11 @@ export class BindTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, ` was squeezed by\n${pokemon.scene.getPokemonById(this.sourceId).name}'s ${this.getMoveName()}!`);
return i18next.t("battle:battlerTagsBindOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: pokemon.scene.getPokemonById(this.sourceId).name,
moveName: this.getMoveName()
});
}
}
@ -733,7 +779,10 @@ export class WrapTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, ` was Wrapped\nby ${pokemon.scene.getPokemonById(this.sourceId).name}!`);
return i18next.t("battle:battlerTagsWrapOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: pokemon.scene.getPokemonById(this.sourceId).name
});
}
}
@ -743,7 +792,7 @@ export abstract class VortexTrapTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, " was trapped\nin the vortex!");
return i18next.t("battle:battlerTagsVortexOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -765,7 +814,10 @@ export class ClampTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon.scene.getPokemonById(this.sourceId), ` Clamped\n${pokemon.name}!`);
return i18next.t("battle:battlerTagsClampOnTrap", {
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)),
pokemonName: pokemon.name,
});
}
}
@ -775,7 +827,10 @@ export class SandTombTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, ` became trapped\nby ${this.getMoveName()}!`);
return i18next.t("battle:battlerTagsSandTombOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName()
});
}
}
@ -785,7 +840,7 @@ export class MagmaStormTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, " became trapped\nby swirling magma!");
return i18next.t("battle:battlerTagsMagmaStormOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -795,7 +850,7 @@ export class SnapTrapTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, " got trapped\nby a snap trap!");
return i18next.t("battle:battlerTagsSnapTrapOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -805,7 +860,10 @@ export class ThunderCageTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon.scene.getPokemonById(this.sourceId), ` trapped\n${getPokemonNameWithAffix(pokemon)}!`);
return i18next.t("battle:battlerTagsThunderCageOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
});
}
}
@ -815,7 +873,10 @@ export class InfestationTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, ` has been afflicted \nwith an infestation by ${getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))}!`);
return i18next.t("battle:battlerTagsInfestationOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
});
}
}
@ -828,13 +889,13 @@ export class ProtectedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, "\nprotected itself!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsProtectedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
new CommonBattleAnim(CommonAnim.PROTECT, pokemon).play(pokemon.scene);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, "\nprotected itself!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsProtectedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return true;
}
@ -959,12 +1020,12 @@ export class EnduringTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " braced\nitself!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsEnduringOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " endured\nthe hit!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsEnduringLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return true;
}
@ -979,7 +1040,7 @@ export class SturdyTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " endured\nthe hit!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSturdyLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return true;
}
@ -1000,7 +1061,12 @@ export class PerishSongTag extends BattlerTag {
const ret = super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, `\'s perish count fell to ${this.turnCount}.`));
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsPerishSongLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
turnCount: this.turnCount
})
);
} else {
pokemon.damageAndUpdate(pokemon.hp, HitResult.ONE_HIT_KO, false, true, true);
}
@ -1009,6 +1075,33 @@ export class PerishSongTag extends BattlerTag {
}
}
/**
* Applies the "Center of Attention" volatile status effect, the effect applied by Follow Me, Rage Powder, and Spotlight.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Center_of_attention | Center of Attention}
*/
export class CenterOfAttentionTag extends BattlerTag {
public powder: boolean;
constructor(sourceMove: Moves) {
super(BattlerTagType.CENTER_OF_ATTENTION, BattlerTagLapseType.TURN_END, 1, sourceMove);
this.powder = (this.sourceMove === Moves.RAGE_POWDER);
}
/** "Center of Attention" can't be added if an ally is already the Center of Attention. */
canAdd(pokemon: Pokemon): boolean {
const activeTeam = pokemon.isPlayer() ? pokemon.scene.getPlayerField() : pokemon.scene.getEnemyField();
return !activeTeam.find(p => p.getTag(BattlerTagType.CENTER_OF_ATTENTION));
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " became the center\nof attention!"));
}
}
export class AbilityBattlerTag extends BattlerTag {
public ability: Abilities;
@ -1044,7 +1137,7 @@ export class TruantTag extends AbilityBattlerTag {
if (lastMove && lastMove.move !== Moves.NONE) {
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, passive));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is\nloafing around!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsTruantLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
return true;
@ -1059,7 +1152,7 @@ export class SlowStartTag extends AbilityBattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " can't\nget it going!"), null, false, null, true);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSlowStartOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null, true);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1073,7 +1166,7 @@ export class SlowStartTag extends AbilityBattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " finally\ngot its act together!"), null, false, null);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSlowStartOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null);
}
}
@ -1119,13 +1212,13 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
break;
}
pokemon.scene.queueMessage(getPokemonMessage(pokemon, `'s ${getStatName(highestStat)}\nwas heightened!`), null, false, null, true);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHighestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: getStatName(highestStat) }), null, false, null, true);
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(`The effects of ${getPokemonMessage(pokemon, `'s\n${allAbilities[this.ability].name} wore off!`)}`);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHighestStatBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: allAbilities[this.ability].name }));
}
}
@ -1165,7 +1258,7 @@ export class TerrainHighestStatBoostTag extends HighestStatBoostTag implements T
}
}
export class HideSpriteTag extends BattlerTag {
export class SemiInvulnerableTag extends BattlerTag {
constructor(tagType: BattlerTagType, turnCount: integer, sourceMove: Moves) {
super(tagType, BattlerTagLapseType.MOVE_EFFECT, turnCount, sourceMove);
}
@ -1187,8 +1280,11 @@ export class HideSpriteTag extends BattlerTag {
export class TypeImmuneTag extends BattlerTag {
public immuneType: Type;
constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number) {
super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove);
constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number = 1) {
super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove);
this.immuneType = immuneType;
}
/**
@ -1205,6 +1301,18 @@ export class MagnetRisenTag extends TypeImmuneTag {
constructor(tagType: BattlerTagType, sourceMove: Moves) {
super(tagType, sourceMove, Type.GROUND, 5);
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " levitated with electromagnetism!"));
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " stopped levitating!"));
}
}
export class TypeBoostTag extends BattlerTag {
@ -1244,7 +1352,7 @@ export class CritBoostTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is getting\npumped!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsCritBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1254,7 +1362,7 @@ export class CritBoostTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " relaxed."));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsCritBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
@ -1289,7 +1397,7 @@ export class SaltCuredTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is being salt cured!"));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSaltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex();
}
@ -1306,7 +1414,12 @@ export class SaltCuredTag extends BattlerTag {
const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER);
pokemon.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt by ${this.getMoveName()}!`));
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsSaltCuredLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName()
})
);
}
}
@ -1332,8 +1445,6 @@ export class CursedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " has been cursed!"));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex();
}
@ -1348,7 +1459,7 @@ export class CursedTag extends BattlerTag {
if (!cancelled.value) {
pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 4), 1));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt by the ${this.getMoveName()}!`));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsCursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
@ -1356,6 +1467,17 @@ export class CursedTag extends BattlerTag {
}
}
/**
* Battler tag for effects that ground the source, allowing Ground-type moves to hit them. Encompasses two tag types:
* @item IGNORE_FLYING: Persistent grounding effects (i.e. from Smack Down and Thousand Waves)
* @item ROOSTED: One-turn grounding effects (i.e. from Roost)
*/
export class GroundedTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, sourceMove: Moves) {
super(tagType, lapseType, 1, sourceMove);
}
}
/**
* Provides the Ice Face ability's effects.
*/
@ -1479,6 +1601,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
return new SturdyTag(sourceMove);
case BattlerTagType.PERISH_SONG:
return new PerishSongTag(turnCount);
case BattlerTagType.CENTER_OF_ATTENTION:
return new CenterOfAttentionTag(sourceMove);
case BattlerTagType.TRUANT:
return new TruantTag();
case BattlerTagType.SLOW_START:
@ -1491,7 +1615,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
case BattlerTagType.UNDERGROUND:
case BattlerTagType.UNDERWATER:
case BattlerTagType.HIDDEN:
return new HideSpriteTag(tagType, turnCount, sourceMove);
return new SemiInvulnerableTag(tagType, turnCount, sourceMove);
case BattlerTagType.FIRE_BOOST:
return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false);
case BattlerTagType.CRIT_BOOST:
@ -1505,9 +1629,9 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
case BattlerTagType.BYPASS_SLEEP:
return new BattlerTag(BattlerTagType.BYPASS_SLEEP, BattlerTagLapseType.TURN_END, turnCount, sourceMove);
case BattlerTagType.IGNORE_FLYING:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove);
case BattlerTagType.GROUNDED:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount - 1, sourceMove);
return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove);
case BattlerTagType.ROOSTED:
return new GroundedTag(tagType, BattlerTagLapseType.TURN_END, sourceMove);
case BattlerTagType.SALT_CURED:
return new SaltCuredTag(sourceId);
case BattlerTagType.CURSED:

View File

@ -5,7 +5,7 @@ import { BattleStat } from "./battle-stat";
import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability";
import i18next from "../plugins/i18n";
import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";

View File

@ -1,15 +1,18 @@
import * as Utils from "../utils";
import i18next from "#app/plugins/i18n.js";
import { GameData } from "#app/system/game-data.js";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species";
import i18next from "i18next";
import { DexAttrProps, GameData } from "#app/system/game-data.js";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species";
import Pokemon from "#app/field/pokemon.js";
import { BattleType, FixedBattleConfig } from "#app/battle.js";
import Trainer, { TrainerVariant } from "#app/field/trainer.js";
import { GameMode } from "#app/game-mode.js";
import { Type } from "./type";
import { pokemonEvolutions } from "./pokemon-evolutions";
import { pokemonFormChanges } from "./pokemon-forms";
import { Challenges } from "#enums/challenges";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import { TypeColor, TypeShadow } from "#app/enums/color.js";
/**
* An enum for all the challenge types. The parameter entries on these describe the
@ -158,7 +161,7 @@ export abstract class Challenge {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return i18next.t(`challenges:${this.geti18nKey()}.desc.${this.value}`);
return `${i18next.t("challenges:usePokemon")}${i18next.t(`challenges:${this.geti18nKey()}.desc.${this.value}`)}`;
}
/**
@ -277,8 +280,22 @@ export class SingleGenerationChallenge extends Challenge {
case ChallengeType.STARTER_CHOICE:
const species = args[0] as PokemonSpecies;
const isValidStarter = args[1] as Utils.BooleanHolder;
const amountOfPokemon = args[3] as number;
const starterGeneration = species.speciesId === Species.VICTINI ? 5 : species.generation;
if (starterGeneration !== this.value) {
const generations = [starterGeneration];
if (amountOfPokemon > 0) {
const speciesToCheck = [species.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
generations.push(getPokemonSpecies(e.speciesId).generation);
});
}
}
}
if (!generations.includes(this.value)) {
isValidStarter.value = false;
return true;
}
@ -331,6 +348,37 @@ export class SingleGenerationChallenge extends Challenge {
return this.value > 0 ? 1 : 0;
}
/**
* Returns the textual representation of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised name for the current value.
*/
getValue(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
if (this.value === 0) {
return i18next.t("settings:off");
}
return i18next.t(`starterSelectUiHandler:gen${this.value}`);
}
/**
* Returns the description of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised description for the current value.
*/
getDescription(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
if (this.value === 0) {
return i18next.t("challenges:singleGeneration.desc_default");
}
return i18next.t("challenges:singleGeneration.desc", { gen: i18next.t(`challenges:singleGeneration.gen_${this.value}`) });
}
static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge {
const newChallenge = new SingleGenerationChallenge();
newChallenge.value = source.value;
@ -372,7 +420,32 @@ export class SingleTypeChallenge extends Challenge {
case ChallengeType.STARTER_CHOICE:
const species = args[0] as PokemonSpecies;
const isValidStarter = args[1] as Utils.BooleanHolder;
if (!species.isOfType(this.value - 1)) {
const dexAttr = args[2] as DexAttrProps;
const amountOfPokemon = args[3] as number;
const speciesForm = getPokemonSpeciesForm(species.speciesId, dexAttr.formIndex);
const types = [speciesForm.type1, speciesForm.type2];
if (amountOfPokemon > 0) {
const speciesToCheck = [species.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2);
});
}
if (pokemonFormChanges.hasOwnProperty(checking)) {
pokemonFormChanges[checking].forEach(f1 => {
getPokemonSpecies(checking).forms.forEach(f2 => {
if (f1.formKey === f2.formKey) {
types.push(f2.type1, f2.type2);
}
});
});
}
}
}
if (!types.includes(this.value - 1)) {
isValidStarter.value = false;
return true;
}
@ -397,6 +470,34 @@ export class SingleTypeChallenge extends Challenge {
return this.value > 0 ? 1 : 0;
}
/**
* Returns the textual representation of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised name for the current value.
*/
getValue(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return Type[this.value - 1].toLowerCase();
}
/**
* Returns the description of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised description for the current value.
*/
getDescription(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
const type = i18next.t(`pokemonInfo:Type.${Type[this.value - 1]}`);
const typeColor = `[color=${TypeColor[Type[this.value-1]]}][shadow=${TypeShadow[Type[this.value-1]]}]${type}[/shadow][/color]`;
const defaultDesc = i18next.t("challenges:singleType.desc_default");
const typeDesc = i18next.t("challenges:singleType.desc", {type: typeColor});
return this.value === 0 ? defaultDesc : typeDesc;
}
static loadChallenge(source: SingleTypeChallenge | any): SingleTypeChallenge {
const newChallenge = new SingleTypeChallenge();
newChallenge.value = source.value;
@ -535,7 +636,7 @@ export class LowerStarterPointsChallenge extends Challenge {
/**
* Apply all challenges of a given challenge type.
* @param {BattleScene} scene The current scene
* @param {GameMode} gameMode The current game mode
* @param {ChallengeType} challengeType What challenge type to apply
* @param {any[]} args Any args for that challenge type
* @returns {boolean} True if any challenge was successfully applied.

View File

@ -1,97 +1,526 @@
import BattleScene from "../battle-scene";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species";
import i18next from "../plugins/i18n";
import { VariantTier } from "../enums/variant-tiers";
import * as Utils from "../utils";
import * as Overrides from "../overrides";
import { pokemonPrevolutions } from "./pokemon-evolutions";
import { PlayerPokemon } from "#app/field/pokemon";
import i18next from "i18next";
import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species";
import { EggSourceType } from "#app/enums/egg-source-types.js";
export const EGG_SEED = 1073741824;
export enum GachaType {
MOVE,
LEGENDARY,
SHINY
// Rates for specific random properties in 1/x
const DEFAULT_SHINY_RATE = 128;
const GACHA_SHINY_UP_SHINY_RATE = 64;
const SAME_SPECIES_EGG_SHINY_RATE = 32;
const SAME_SPECIES_EGG_HA_RATE = 16;
const MANAPHY_EGG_MANAPHY_RATE = 8;
// 1/x for legendary eggs, 1/x*2 for epic eggs, 1/x*4 for rare eggs, and 1/x*8 for common eggs
const DEFAULT_RARE_EGGMOVE_RATE = 6;
const SAME_SPECIES_EGG_RARE_EGGMOVE_RATE = 3;
const GACHA_MOVE_UP_RARE_EGGMOVE_RATE = 3;
/** Egg options to override egg properties */
export interface IEggOptions {
/** Id. Used to check if egg type will be manaphy (id % 204 === 0) */
id?: number;
/** Timestamp when this egg got created */
timestamp?: number;
/** Defines if the egg got pulled from a gacha or not. If true, egg pity and pull statistics will be applyed.
* Egg will be automaticly added to the game data.
* NEEDS scene eggOption to work.
*/
pulled?: boolean;
/** Defines where the egg comes from. Applies specific modifiers.
* Will also define the text displayed in the egg list.
*/
sourceType?: EggSourceType;
/** Needs to be defined if eggOption pulled is defined or if no species or isShiny is degined since this will be needed to generate them. */
scene?: BattleScene;
/** Sets the tier of the egg. Only species of this tier can be hatched from this egg.
* Tier will be overriden if species eggOption is set.
*/
tier?: EggTier;
/** Sets how many waves it will take till this egg hatches. */
hatchWaves?: number;
/** Sets the exact species that will hatch from this egg.
* Needs scene eggOption if not provided.
*/
species?: Species;
/** Defines if the hatched pokemon will be a shiny. */
isShiny?: boolean;
/** Defines the variant of the pokemon that will hatch from this egg. If no variantTier is given the normal variant rates will apply. */
variantTier?: VariantTier;
/** Defines which egg move will be unlocked. 3 = rare egg move. */
eggMoveIndex?: number;
/** Defines if the egg will hatch with the hidden ability of this species.
* If no hidden ability exist, a random one will get choosen.
*/
overrideHiddenAbility?: boolean
}
export class Egg {
public id: integer;
public tier: EggTier;
public gachaType: GachaType;
public hatchWaves: integer;
public timestamp: integer;
constructor(id: integer, gachaType: GachaType, hatchWaves: integer, timestamp: integer) {
this.id = id;
this.tier = Math.floor(id / EGG_SEED);
this.gachaType = gachaType;
this.hatchWaves = hatchWaves;
this.timestamp = timestamp;
////
// #region Privat properties
////
private _id: number;
private _tier: EggTier;
private _sourceType: EggSourceType | undefined;
private _hatchWaves: number;
private _timestamp: number;
private _species: Species;
private _isShiny: boolean;
private _variantTier: VariantTier;
private _eggMoveIndex: number;
private _overrideHiddenAbility: boolean;
////
// #endregion
////
////
// #region Public facing properties
////
get id(): number {
return this._id;
}
isManaphyEgg(): boolean {
return this.tier === EggTier.COMMON && !(this.id % 204);
get tier(): EggTier {
return this._tier;
}
getKey(): string {
get sourceType(): EggSourceType | undefined {
return this._sourceType;
}
get hatchWaves(): number {
return this._hatchWaves;
}
set hatchWaves(value: number) {
this._hatchWaves = value;
}
get timestamp(): number {
return this._timestamp;
}
get species(): Species {
return this._species;
}
get isShiny(): boolean {
return this._isShiny;
}
get variantTier(): VariantTier {
return this._variantTier;
}
get eggMoveIndex(): number {
return this._eggMoveIndex;
}
get overrideHiddenAbility(): boolean {
return this._overrideHiddenAbility;
}
////
// #endregion
////
constructor(eggOptions?: IEggOptions) {
//if (eggOptions.tier && eggOptions.species) throw Error("Error egg can't have species and tier as option. only choose one of them.")
this._sourceType = eggOptions.sourceType ?? undefined;
// Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced
this._tier = eggOptions.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
// If egg was pulled, check if egg pity needs to override the egg tier
if (eggOptions.pulled) {
// Needs this._tier and this._sourceType to work
this.checkForPityTierOverrides(eggOptions.scene);
}
this._id = eggOptions.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier);
this._sourceType = eggOptions.sourceType ?? undefined;
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
this._timestamp = eggOptions.timestamp ?? new Date().getTime();
// First roll shiny and variant so we can filter if species with an variant exist
this._isShiny = eggOptions.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
this._variantTier = eggOptions.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
this._species = eggOptions.species ?? this.rollSpecies(eggOptions.scene);
this._overrideHiddenAbility = eggOptions.overrideHiddenAbility ?? false;
// Override egg tier and hatchwaves if species was given
if (eggOptions.species) {
this._tier = this.getEggTierFromSpeciesStarterValue();
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
// If species has no variant, set variantTier to common. This needs to
// be done because species with no variants get filtered at rollSpecies but since the
// species is set the check never happens
if (!getPokemonSpecies(this.species).hasVariants()) {
this._variantTier = VariantTier.COMMON;
}
}
// Needs this._tier so it needs to be generated afer the tier override if bought from same species
this._eggMoveIndex = eggOptions.eggMoveIndex ?? this.rollEggMoveIndex();
if (eggOptions.pulled) {
this.increasePullStatistic(eggOptions.scene);
this.addEggToGameData(eggOptions.scene);
}
}
////
// #region Public methodes
////
public isManaphyEgg(): boolean {
return (this._species === Species.PHIONE || this._species === Species.MANAPHY) ||
this._tier === EggTier.COMMON && !(this._id % 204) && !this._species;
}
public getKey(): string {
if (this.isManaphyEgg()) {
return "manaphy";
}
return this.tier.toString();
return this._tier.toString();
}
// Generates a PlayerPokemon from an egg
public generatePlayerPokemon(scene: BattleScene): PlayerPokemon {
// Legacy egg wants to hatch. Generate missing properties
if (!this._species) {
this._isShiny = this.rollShiny();
this._species = this.rollSpecies(scene);
}
const pokemonSpecies = getPokemonSpecies(this._species);
// Sets the hidden ability if a hidden ability exists and the override is set
// or if the same species egg hits the chance
let abilityIndex = undefined;
if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility
|| (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE)))) {
abilityIndex = pokemonSpecies.ability2 ? 2 : 1;
}
// This function has way to many optional parameters
const ret: PlayerPokemon = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
ret.shiny = this._isShiny;
ret.variant = this._variantTier;
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295));
for (let s = 0; s < ret.ivs.length; s++) {
ret.ivs[s] = Math.max(ret.ivs[s], secondaryIvs[s]);
}
return ret;
}
// Doesn't need to be called if the egg got pulled by a gacha machiene
public addEggToGameData(scene: BattleScene): void {
scene.gameData.eggs.push(this);
}
public getEggDescriptor(): string {
if (this.isManaphyEgg()) {
return "Manaphy";
}
switch (this.tier) {
case EggTier.GREAT:
return i18next.t("egg:greatTier");
case EggTier.ULTRA:
return i18next.t("egg:ultraTier");
case EggTier.MASTER:
return i18next.t("egg:masterTier");
default:
return i18next.t("egg:defaultTier");
}
}
public getEggHatchWavesMessage(): string {
if (this.hatchWaves <= 5) {
return i18next.t("egg:hatchWavesMessageSoon");
}
if (this.hatchWaves <= 15) {
return i18next.t("egg:hatchWavesMessageClose");
}
if (this.hatchWaves <= 50) {
return i18next.t("egg:hatchWavesMessageNotClose");
}
return i18next.t("egg:hatchWavesMessageLongTime");
}
public getEggTypeDescriptor(scene: BattleScene): string {
switch (this.sourceType) {
case EggSourceType.SAME_SPECIES_EGG:
return i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName()});
case EggSourceType.GACHA_LEGENDARY:
return `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`;
case EggSourceType.GACHA_SHINY:
return i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE:
return i18next.t("egg:gachaTypeMove");
}
}
////
// #endregion
////
////
// #region Private methodes
////
private rollEggMoveIndex() {
let baseChance = DEFAULT_RARE_EGGMOVE_RATE;
switch (this._sourceType) {
case EggSourceType.SAME_SPECIES_EGG:
baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE;
break;
case EggSourceType.GACHA_MOVE:
baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE;
break;
default:
break;
}
return Utils.randSeedInt(baseChance * Math.pow(2, 3 - this.tier)) ? Utils.randSeedInt(3) : 3;
}
private getEggTierDefaultHatchWaves(eggTier?: EggTier): number {
if (this._species === Species.PHIONE || this._species === Species.MANAPHY) {
return 50;
}
switch (eggTier ?? this._tier) {
case EggTier.COMMON:
return 10;
case EggTier.GREAT:
return 25;
case EggTier.ULTRA:
return 50;
}
return 100;
}
private rollEggTier(): EggTier {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? 1 : 0;
const tierValue = Utils.randInt(256);
return tierValue >= 52 + tierValueOffset ? EggTier.COMMON : tierValue >= 8 + tierValueOffset ? EggTier.GREAT : tierValue >= 1 + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER;
}
private rollSpecies(scene: BattleScene): Species {
if (!scene) {
return undefined;
}
/**
* Manaphy eggs have a 1/8 chance of being Manaphy and 7/8 chance of being Phione
* Legendary eggs pulled from the legendary gacha have a 50% of being converted into
* the species that was the legendary focus at the time
*/
if (this.isManaphyEgg()) {
const rand = Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE);
return rand ? Species.PHIONE : Species.MANAPHY;
} else if (this.tier === EggTier.MASTER
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) {
if (!Utils.randSeedInt(2)) {
return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp);
}
}
let minStarterValue: integer;
let maxStarterValue: integer;
switch (this.tier) {
case EggTier.GREAT:
minStarterValue = 4;
maxStarterValue = 5;
break;
case EggTier.ULTRA:
minStarterValue = 6;
maxStarterValue = 7;
break;
case EggTier.MASTER:
minStarterValue = 8;
maxStarterValue = 9;
break;
default:
minStarterValue = 1;
maxStarterValue = 3;
break;
}
const ignoredSpecies = [Species.PHIONE, Species.MANAPHY, Species.ETERNATUS];
let speciesPool = Object.keys(speciesStarters)
.filter(s => speciesStarters[s] >= minStarterValue && speciesStarters[s] <= maxStarterValue)
.map(s => parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
// If this is the 10th egg without unlocking something new, attempt to force it.
if (scene.gameData.unlockPity[this.tier] >= 9) {
const lockedPool = speciesPool.filter(s => !scene.gameData.dexData[s].caughtAttr && !scene.gameData.eggs.some(e => e.species === s));
if (lockedPool.length) { // Skip this if everything is unlocked
speciesPool = lockedPool;
}
}
// If egg variant is set to RARE or EPIC, filter species pool to only include ones with variants.
if (this.variantTier && (this.variantTier === VariantTier.RARE || this.variantTier === VariantTier.EPIC)) {
speciesPool = speciesPool.filter(s => getPokemonSpecies(s).hasVariants());
}
/**
* Pokemon that are cheaper in their tier get a weight boost. Regionals get a weight penalty
* 1 cost mons get 2x
* 2 cost mons get 1.5x
* 4, 6, 8 cost mons get 1.75x
* 3, 5, 7, 9 cost mons get 1x
* Alolan, Galarian, and Paldean mons get 0.5x
* Hisui mons get 0.125x
*
* The total weight is also being calculated EACH time there is an egg hatch instead of being generated once
* and being the same each time
*/
let totalWeight = 0;
const speciesWeights = [];
for (const speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId);
if (species.isRegional()) {
weight = Math.floor(weight / (species.isRareRegional() ? 8 : 2));
}
speciesWeights.push(totalWeight + weight);
totalWeight += weight;
}
let species: Species;
const rand = Utils.randSeedInt(totalWeight);
for (let s = 0; s < speciesWeights.length; s++) {
if (rand < speciesWeights[s]) {
species = speciesPool[s];
break;
}
}
if (!!scene.gameData.dexData[species].caughtAttr || scene.gameData.eggs.some(e => e.species === species)) {
scene.gameData.unlockPity[this.tier] = Math.min(scene.gameData.unlockPity[this.tier] + 1, 10);
} else {
scene.gameData.unlockPity[this.tier] = 0;
}
return species;
}
/**
* Rolls whether the egg is shiny or not.
* @returns True if the egg is shiny
**/
private rollShiny(): boolean {
let shinyChance = DEFAULT_SHINY_RATE;
switch (this._sourceType) {
case EggSourceType.GACHA_SHINY:
shinyChance = GACHA_SHINY_UP_SHINY_RATE;
break;
case EggSourceType.SAME_SPECIES_EGG:
shinyChance = SAME_SPECIES_EGG_SHINY_RATE;
break;
default:
break;
}
return !Utils.randSeedInt(shinyChance);
}
// Uses the same logic as pokemon.generateVariant(). I would like to only have this logic in one
// place but I don't want to touch the pokemon class.
private rollVariant(): VariantTier {
if (!this.isShiny) {
return VariantTier.COMMON;
}
const rand = Utils.randSeedInt(10);
if (rand >= 4) {
return VariantTier.COMMON; // 6/10
} else if (rand >= 1) {
return VariantTier.RARE; // 3/10
} else {
return VariantTier.EPIC; // 1/10
}
}
private checkForPityTierOverrides(scene: BattleScene): void {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? 1 : 0;
scene.gameData.eggPity[EggTier.GREAT] += 1;
scene.gameData.eggPity[EggTier.ULTRA] += 1;
scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset;
// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered.
if (scene.gameData.eggPity[EggTier.MASTER] >= 412 && this._tier === EggTier.COMMON) {
this._tier = EggTier.MASTER;
} else if (scene.gameData.eggPity[EggTier.ULTRA] >= 59 && this._tier === EggTier.COMMON) {
this._tier = EggTier.ULTRA;
} else if (scene.gameData.eggPity[EggTier.GREAT] >= 9 && this._tier === EggTier.COMMON) {
this._tier = EggTier.GREAT;
}
scene.gameData.eggPity[this._tier] = 0;
}
private increasePullStatistic(scene: BattleScene): void {
scene.gameData.gameStats.eggsPulled++;
if (this.isManaphyEgg()) {
scene.gameData.gameStats.manaphyEggsPulled++;
this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.ULTRA);
return;
}
switch (this.tier) {
case EggTier.GREAT:
scene.gameData.gameStats.rareEggsPulled++;
break;
case EggTier.ULTRA:
scene.gameData.gameStats.epicEggsPulled++;
break;
case EggTier.MASTER:
scene.gameData.gameStats.legendaryEggsPulled++;
break;
}
}
private getEggTierFromSpeciesStarterValue(): EggTier {
const speciesStartValue = speciesStarters[this.species];
if (speciesStartValue >= 1 && speciesStartValue <= 3) {
return EggTier.COMMON;
}
if (speciesStartValue >= 4 && speciesStartValue <= 5) {
return EggTier.GREAT;
}
if (speciesStartValue >= 6 && speciesStartValue <= 7) {
return EggTier.ULTRA;
}
if (speciesStartValue >= 8) {
return EggTier.MASTER;
}
}
////
// #endregion
////
}
export function getEggTierDefaultHatchWaves(tier: EggTier): integer {
switch (tier) {
case EggTier.COMMON:
return 10;
case EggTier.GREAT:
return 25;
case EggTier.ULTRA:
return 50;
}
return 100;
}
export function getEggDescriptor(egg: Egg): string {
if (egg.isManaphyEgg()) {
return "Manaphy";
}
switch (egg.tier) {
case EggTier.GREAT:
return i18next.t("egg:greatTier");
case EggTier.ULTRA:
return i18next.t("egg:ultraTier");
case EggTier.MASTER:
return i18next.t("egg:masterTier");
default:
return i18next.t("egg:defaultTier");
}
}
export function getEggHatchWavesMessage(hatchWaves: integer): string {
if (hatchWaves <= 5) {
return i18next.t("egg:hatchWavesMessageSoon");
}
if (hatchWaves <= 15) {
return i18next.t("egg:hatchWavesMessageClose");
}
if (hatchWaves <= 50) {
return i18next.t("egg:hatchWavesMessageNotClose");
}
return i18next.t("egg:hatchWavesMessageLongTime");
}
export function getEggGachaTypeDescriptor(scene: BattleScene, egg: Egg): string {
switch (egg.gachaType) {
case GachaType.LEGENDARY:
return `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, egg.timestamp)).getName()})`;
case GachaType.MOVE:
return i18next.t("egg:gachaTypeMove");
case GachaType.SHINY:
return i18next.t("egg:gachaTypeShiny");
}
}
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: integer): Species {
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species {
const legendarySpecies = Object.entries(speciesStarters)
.filter(s => s[1] >= 8 && s[1] <= 9)
.map(s => parseInt(s[0]))

View File

@ -1,15 +1,16 @@
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { BattleEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases";
import { BattleStat, getBattleStatName } from "./battle-stat";
import { EncoreTag } from "./battler-tags";
import { getPokemonMessage } from "../messages";
import { EncoreTag, SemiInvulnerableTag } from "./battler-tags";
import { getPokemonMessage, getPokemonNameWithAffix } from "../messages";
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects} from "./status-effect";
import { Type } from "./type";
import { Constructor } from "#app/utils";
import * as Utils from "../utils";
import { WeatherType } from "./weather";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr } from "./ability";
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr } from "./ability";
import { allAbilities } from "./ability";
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier";
import { BattlerIndex } from "../battle";
@ -18,7 +19,8 @@ import { TerrainType } from "./terrain";
import { SpeciesFormChangeActiveTrigger } from "./pokemon-forms";
import { ModifierPoolType } from "#app/modifier/modifier-type";
import { Command } from "../ui/command-ui-handler";
import i18next, { Localizable } from "../plugins/i18n";
import i18next from "i18next";
import { Localizable } from "#app/interfaces/locales";
import { getBerryEffectFunc } from "./berry";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
@ -156,7 +158,7 @@ export default class Move implements Localizable {
* @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends MoveAttr>(attrType: new(...args: any[]) => T): T[] {
getAttrs<T extends MoveAttr>(attrType: Constructor<T>): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
}
@ -165,7 +167,7 @@ export default class Move implements Localizable {
* @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns true if the move has attribute `attrType`
*/
hasAttr<T extends MoveAttr>(attrType: new(...args: any[]) => T): boolean {
hasAttr<T extends MoveAttr>(attrType: Constructor<T>): boolean {
return this.attrs.some((attr) => attr instanceof attrType);
}
@ -185,7 +187,7 @@ export default class Move implements Localizable {
* @param args the args needed to instantiate a the given class
* @returns the called object {@linkcode Move}
*/
attr<T extends new (...args: any[]) => MoveAttr>(AttrType: T, ...args: ConstructorParameters<T>): this {
attr<T extends Constructor<MoveAttr>>(AttrType: T, ...args: ConstructorParameters<T>): this {
const attr = new AttrType(...args);
this.attrs.push(attr);
let attrCondition = attr.getCondition();
@ -278,18 +280,29 @@ export default class Move implements Localizable {
}
/**
* Checks if the move is immune to certain types
* currently only look at case of Grass types and powder moves
* @param type {@linkcode Type} enum
* Checks if the move is immune to certain types.
* Currently looks at cases of Grass types with powder moves and Dark types with moves affected by Prankster.
* @param {Pokemon} user the source of this move
* @param {Pokemon} target the target of this move
* @param {Type} type the type of the move's target
* @returns boolean
*/
isTypeImmune(type: Type): boolean {
isTypeImmune(user: Pokemon, target: Pokemon, type: Type): boolean {
if (this.moveTarget === MoveTarget.USER) {
return false;
}
switch (type) {
case Type.GRASS:
if (this.hasFlag(MoveFlags.POWDER_MOVE)) {
return true;
}
break;
case Type.DARK:
if (user.hasAbility(Abilities.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) {
return true;
}
break;
}
return false;
}
@ -794,7 +807,7 @@ export enum MoveEffectTrigger {
*/
export class MoveEffectAttr extends MoveAttr {
/** Defines when this effect should trigger in the move's effect order
* @see {@linkcode MoveEffectPhase.start}
* @see {@linkcode phases.MoveEffectPhase.start}
*/
public trigger: MoveEffectTrigger;
/** Should this effect only apply on the first hit? */
@ -825,6 +838,22 @@ export class MoveEffectAttr extends MoveAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
return this.canApply(user, target, move, args);
}
/**
* Gets the used move's additional effect chance.
* If user's ability has MoveEffectChanceMultiplierAbAttr or IgnoreMoveEffectsAbAttr modifies the base chance.
* @param user {@linkcode Pokemon} using this move
* @param target {@linkcode Pokemon} target of this move
* @param move {@linkcode Move} being used
* @param selfEffect {@linkcode Boolean} if move targets user.
* @returns Move chance value.
*/
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean): integer {
const moveChance = new Utils.NumberHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, moveChance, move, target, selfEffect);
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr,target,user,null,null, moveChance);
return moveChance.value;
}
}
export class PreMoveMessageAttr extends MoveAttr {
@ -1669,7 +1698,8 @@ export class StatusEffectAttr extends MoveEffectAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const statusCheck = move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance;
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
if (statusCheck) {
const pokemon = this.selfTarget ? user : target;
if (pokemon.status) {
@ -1679,7 +1709,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
return false;
}
}
if ((!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0))
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect);
return true;
@ -1689,7 +1719,8 @@ export class StatusEffectAttr extends MoveEffectAttr {
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0;
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
}
}
@ -1708,7 +1739,8 @@ export class MultiStatusEffectAttr extends StatusEffectAttr {
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0;
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
}
}
@ -2311,7 +2343,8 @@ export class StatChangeAttr extends MoveEffectAttr {
return false;
}
if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) {
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
const levels = this.getLevels(user);
user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage));
return true;
@ -3778,10 +3811,9 @@ export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTy
if (!target.getTag(BattlerTagType.IGNORE_FLYING)) {
const multiplier = args[0] as Utils.NumberHolder;
//When a flying type is hit, the first hit is always 1x multiplier. Levitating pokemon are instantly affected by typing
if (target.isOfType(Type.FLYING)) {
if (target.isOfType(Type.FLYING) || target.hasAbility(Abilities.LEVITATE)) {
multiplier.value = 1;
}
target.addTag(BattlerTagType.IGNORE_FLYING, 20, move.id, user.id); //TODO: Grounded effect should not have turn limit
return true;
}
@ -4036,18 +4068,14 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
return false;
}
const chance = this.getTagChance(user, target, move);
if (chance < 0 || chance === 100 || user.randSeedInt(100) < chance) {
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id);
}
return false;
}
getTagChance(user: Pokemon, target: Pokemon, move: Move): integer {
return move.chance;
}
getCondition(): MoveConditionFunc {
return this.failOnOverlap
? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType)
@ -4084,6 +4112,8 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
return -3;
case BattlerTagType.ENCORE:
return -2;
case BattlerTagType.MINIMIZED:
return 0;
case BattlerTagType.INGRAIN:
case BattlerTagType.IGNORE_ACCURACY:
case BattlerTagType.AQUA_RING:
@ -4097,11 +4127,11 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
let chance = this.getTagChance(user, target, move);
if (chance < 0) {
chance = 100;
let moveChance = this.getMoveChance(user,target,move,this.selfTarget);
if (moveChance < 0) {
moveChance = 100;
}
return Math.floor(this.getTagTargetBenefitScore(user, target, move) * (chance / 100));
return Math.floor(this.getTagTargetBenefitScore(user, target, move) * (moveChance / 100));
}
}
@ -4110,12 +4140,18 @@ export class CurseAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean {
if (user.getTypes(true).includes(Type.GHOST)) {
if (target.getTag(BattlerTagType.CURSED)) {
user.scene.queueMessage("But it failed!");
user.scene.queueMessage(i18next.t("battle:attackFailed"));
return false;
}
const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2));
user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true);
user.scene.queueMessage(getPokemonMessage(user, ` cut its own HP\nand laid a curse on the ${target.name}!`));
user.scene.queueMessage(
i18next.t("battle:battlerTagsCursedOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(user),
pokemonName: target.name
})
);
target.addTag(BattlerTagType.CURSED, 0, move.id, user.id);
return true;
} else {
@ -4366,15 +4402,42 @@ export class AddArenaTrapTagAttr extends AddArenaTagAttr {
getCondition(): MoveConditionFunc {
return (user, target, move) => {
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (move.category !== MoveCategory.STATUS || !user.scene.arena.getTagOnSide(this.tagType, side)) {
const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
if (!tag) {
return true;
}
const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
return tag.layers < tag.maxLayers;
};
}
}
/**
* Attribute used for Stone Axe and Ceaseless Edge.
* Applies the given ArenaTrapTag when move is used.
* @extends AddArenaTagAttr
* @see {@linkcode apply}
*/
export class AddArenaTrapTagHitAttr extends AddArenaTagAttr {
/**
* @param user {@linkcode Pokemon} using this move
* @param target {@linkcode Pokemon} target of this move
* @param move {@linkcode Move} being used
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance)) {
user.scene.arena.addTag(this.tagType, 0, move.id, user.id, side);
if (!tag) {
return true;
}
return tag.layers < tag.maxLayers;
}
return false;
}
}
export class RemoveArenaTrapAttr extends MoveEffectAttr {
private targetBothSides: boolean;
@ -4534,7 +4597,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
}
resolve(true);
} else {
user.scene.queueMessage("But it failed!");
user.scene.queueMessage(i18next.t("battle:attackFailed"));
resolve(false);
}
});
@ -4700,7 +4763,7 @@ export class RemoveTypeAttr extends MoveEffectAttr {
export class CopyTypeAttr extends MoveEffectAttr {
constructor() {
super(true);
super(false);
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -5066,14 +5129,36 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
}
}
/**
* Attribute used for moves that reduce PP of the target's last used move.
* Used for Spite.
*/
export class ReducePpMoveAttr extends MoveEffectAttr {
protected reduction: number;
constructor(reduction: number) {
super();
this.reduction = reduction;
}
/**
* Reduces the PP of the target's last-used move by an amount based on this attribute instance's {@linkcode reduction}.
*
* @param user {@linkcode Pokemon} that used the attack
* @param target {@linkcode Pokemon} targeted by the attack
* @param move {@linkcode Move} being used
* @param args N/A
* @returns {boolean} true
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// Null checks can be skipped due to condition function
const lastMove = target.getLastXMoves().find(() => true);
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move);
const lastPpUsed = movesetMove.ppUsed;
movesetMove.ppUsed = Math.min(movesetMove.ppUsed + 4, movesetMove.getMovePp());
user.scene.queueMessage(`It reduced the PP of ${getPokemonMessage(target, `'s\n${movesetMove.getName()} by ${movesetMove.ppUsed - lastPpUsed}!`)}`);
movesetMove.ppUsed = Math.min(movesetMove.ppUsed + this.reduction, movesetMove.getMovePp());
const message = i18next.t("battle:ppReduced", {targetName: target.name, moveName: movesetMove.getName(), reduction: movesetMove.ppUsed - lastPpUsed});
user.scene.queueMessage(message);
return true;
}
@ -5108,6 +5193,42 @@ export class ReducePpMoveAttr extends MoveEffectAttr {
}
}
/**
* Attribute used for moves that damage target, and then reduce PP of the target's last used move.
* Used for Eerie Spell.
*/
export class AttackReducePpMoveAttr extends ReducePpMoveAttr {
constructor(reduction: number) {
super(reduction);
}
/**
* Checks if the target has used a move prior to the attack. PP-reduction is applied through the super class if so.
*
* @param user {@linkcode Pokemon} that used the attack
* @param target {@linkcode Pokemon} targeted by the attack
* @param move {@linkcode Move} being used
* @param args N/A
* @returns {boolean} true
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const lastMove = target.getLastXMoves().find(() => true);
if (lastMove) {
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move);
if (Boolean(movesetMove?.getPpRatio())) {
super.apply(user, target, move, args);
}
}
return true;
}
// Override condition function to always perform damage. Instead, perform pp-reduction condition check in apply function above
getCondition(): MoveConditionFunc {
return (user, target, move) => true;
}
}
// TODO: Review this
const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
const targetMoves = target.getMoveHistory().filter(m => !m.virtual);
@ -5319,7 +5440,16 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr {
}
}
/**
* Attribute used for moves that suppress abilities like {@linkcode Moves.GASTRO_ACID}.
* A suppressed ability cannot be activated.
*
* @extends MoveEffectAttr
* @see {@linkcode apply}
* @see {@linkcode getCondition}
*/
export class SuppressAbilitiesAttr extends MoveEffectAttr {
/** Sets ability suppression for the target pokemon and displays a message. */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(user, target, move, args)) {
return false;
@ -5332,8 +5462,9 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr {
return true;
}
/** Causes the effect to fail when the target's ability is unsupressable or already suppressed. */
getCondition(): MoveConditionFunc {
return (user, target, move) => !target.getAbility().hasAttr(UnsuppressableAbilityAbAttr);
return (user, target, move) => !target.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !target.summonData.abilitySuppressed;
}
}
@ -5348,7 +5479,7 @@ export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr {
* abillity cannot be suppressed. This is a secondary effect and has no bearing on the success or failure of the move.
*
* @returns True if the move occurred, otherwise false. Note that true will be returned even if the target has not
* yet moved or if the target's abiilty is un-suppressable.
* yet moved or if the suppression failed to apply.
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(user, target, move, args)) {
@ -5537,7 +5668,7 @@ function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, targe
});
}
export function applyMoveAttrs(attrType: { new(...args: any[]): MoveAttr }, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> {
export function applyMoveAttrs(attrType: Constructor<MoveAttr>, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> {
return applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
}
@ -5703,7 +5834,7 @@ export function initMoves() {
.attr(ChargeAttr, ChargeAnim.FLY_CHARGING, "flew\nup high!", BattlerTagType.FLYING)
.condition(failOnGravityCondition)
.ignoresVirtual(),
new AttackMove(Moves.BIND, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, 100, 0, 1)
new AttackMove(Moves.BIND, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1)
.attr(TrapAttr, BattlerTagType.BIND),
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1),
new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1),
@ -5736,7 +5867,7 @@ export function initMoves() {
.attr(MinimizeAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, 100, 0, 1)
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1)
.attr(TrapAttr, BattlerTagType.WRAP),
new AttackMove(Moves.TAKE_DOWN, Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, 0, 1)
.attr(RecoilAttr)
@ -5761,7 +5892,7 @@ export function initMoves() {
new AttackMove(Moves.PIN_MISSILE, Type.BUG, MoveCategory.PHYSICAL, 25, 95, 20, -1, 0, 1)
.attr(MultiHitAttr)
.makesContact(false),
new StatusMove(Moves.LEER, Type.NORMAL, 100, 30, 100, 0, 1)
new StatusMove(Moves.LEER, Type.NORMAL, 100, 30, -1, 0, 1)
.attr(StatChangeAttr, BattleStat.DEF, -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.BITE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 1)
@ -5870,7 +6001,7 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.DRAGON_RAGE, Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 1)
.attr(FixedDamageAttr, 40),
new AttackMove(Moves.FIRE_SPIN, Type.FIRE, MoveCategory.SPECIAL, 35, 85, 15, 100, 0, 1)
new AttackMove(Moves.FIRE_SPIN, Type.FIRE, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 1)
.attr(TrapAttr, BattlerTagType.FIRE_SPIN),
new AttackMove(Moves.THUNDER_SHOCK, Type.ELECTRIC, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
@ -5986,11 +6117,11 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.BURN),
new AttackMove(Moves.WATERFALL, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 1)
.attr(FlinchAttr),
new AttackMove(Moves.CLAMP, Type.WATER, MoveCategory.PHYSICAL, 35, 85, 15, 100, 0, 1)
new AttackMove(Moves.CLAMP, Type.WATER, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 1)
.attr(TrapAttr, BattlerTagType.CLAMP),
new AttackMove(Moves.SWIFT, Type.NORMAL, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, 100, 0, 1)
new AttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1)
.attr(ChargeAttr, ChargeAnim.SKULL_BASH_CHARGING, "lowered\nits head!", null, true)
.attr(StatChangeAttr, BattleStat.DEF, 1, true)
.ignoresVirtual(),
@ -6139,7 +6270,7 @@ export function initMoves() {
new AttackMove(Moves.REVERSAL, Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2)
.attr(LowHpPowerAttr),
new StatusMove(Moves.SPITE, Type.GHOST, 100, 10, -1, 0, 2)
.attr(ReducePpMoveAttr),
.attr(ReducePpMoveAttr, 4),
new AttackMove(Moves.POWDER_SNOW, Type.ICE, MoveCategory.SPECIAL, 40, 100, 25, 10, 0, 2)
.attr(StatusEffectAttr, StatusEffect.FREEZE)
.target(MoveTarget.ALL_NEAR_ENEMIES),
@ -6339,7 +6470,7 @@ export function initMoves() {
.attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, "foresaw\nan attack!"),
new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2)
.attr(StatChangeAttr, BattleStat.DEF, -1),
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, 100, 0, 2)
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2)
.attr(TrapAttr, BattlerTagType.WHIRLPOOL)
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2)
@ -6391,7 +6522,7 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1)
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS),
new SelfStatusMove(Moves.FOLLOW_ME, Type.NORMAL, -1, 20, -1, 2, 3)
.unimplemented(),
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true),
new StatusMove(Moves.NATURE_POWER, Type.NORMAL, -1, 20, -1, 0, 3)
.attr(NaturePowerAttr)
.ignoresVirtual(),
@ -6415,7 +6546,7 @@ export function initMoves() {
.ignoresVirtual(),
new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true),
new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, 100, 0, 3)
new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1, true),
new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3)
.unimplemented(),
@ -6517,7 +6648,7 @@ export function initMoves() {
.slicingMove()
.windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.OVERHEAT, Type.FIRE, MoveCategory.SPECIAL, 130, 90, 5, 100, 0, 3)
new AttackMove(Moves.OVERHEAT, Type.FIRE, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 3)
.attr(StatChangeAttr, BattleStat.SPATK, -2, true)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE),
new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3)
@ -6550,7 +6681,7 @@ export function initMoves() {
new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3)
.attr(HitsTagAttr, BattlerTagType.FLYING)
.punchingMove(),
new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, 100, 0, 3)
new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3)
.attr(TrapAttr, BattlerTagType.SAND_TOMB)
.makesContact(false),
new AttackMove(Moves.SHEER_COLD, Type.ICE, MoveCategory.SPECIAL, 200, 20, 5, -1, 0, 3)
@ -6620,11 +6751,11 @@ export function initMoves() {
.pulseMove(),
new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3)
.attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, "chose\nDoom Desire as its destiny!"),
new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, 100, 0, 3)
new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3)
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new SelfStatusMove(Moves.ROOST, Type.FLYING, -1, 5, -1, 0, 4)
.attr(HealAttr, 0.5)
.attr(AddBattlerTagAttr, BattlerTagType.GROUNDED, true, false, 1)
.attr(AddBattlerTagAttr, BattlerTagType.ROOSTED, true, false)
.triageMove(),
new StatusMove(Moves.GRAVITY, Type.PSYCHIC, -1, 5, -1, 0, 4)
.attr(AddArenaTagAttr, ArenaTagType.GRAVITY, 5)
@ -6634,7 +6765,7 @@ export function initMoves() {
new AttackMove(Moves.WAKE_UP_SLAP, Type.FIGHTING, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 4)
.attr(MovePowerMultiplierAttr, (user, target, move) => targetSleptOrComatoseCondition(user, target, move) ? 2 : 1)
.attr(HealStatusEffectAttr, false, StatusEffect.SLEEP),
new AttackMove(Moves.HAMMER_ARM, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 90, 10, 100, 0, 4)
new AttackMove(Moves.HAMMER_ARM, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 4)
.attr(StatChangeAttr, BattleStat.SPD, -1, true)
.punchingMove(),
new AttackMove(Moves.GYRO_BALL, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
@ -6668,7 +6799,7 @@ export function initMoves() {
.target(MoveTarget.ATTACKER),
new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
.attr(ForceSwitchOutAttr, true, false),
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, 100, 0, 4)
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.BALL ? 2 : 1),
@ -6712,9 +6843,9 @@ export function initMoves() {
new SelfStatusMove(Moves.COPYCAT, Type.NORMAL, -1, 20, -1, 0, 4)
.attr(CopyMoveAttr)
.ignoresVirtual(),
new StatusMove(Moves.POWER_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 4)
new StatusMove(Moves.POWER_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4)
.unimplemented(),
new StatusMove(Moves.GUARD_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 4)
new StatusMove(Moves.GUARD_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4)
.unimplemented(),
new AttackMove(Moves.PUNISHMENT, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
.makesContact(true)
@ -6734,10 +6865,7 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true),
new SelfStatusMove(Moves.MAGNET_RISE, Type.ELECTRIC, -1, 10, -1, 0, 4)
.attr(AddBattlerTagAttr, BattlerTagType.MAGNET_RISEN, true, true)
.condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) &&
!user.getTag(BattlerTagType.IGNORE_FLYING) && !user.getTag(BattlerTagType.INGRAIN) &&
!user.getTag(BattlerTagType.MAGNET_RISEN))
.unimplemented(),
.condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [BattlerTagType.MAGNET_RISEN, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN].every((tag) => !user.getTag(tag))),
new AttackMove(Moves.FLARE_BLITZ, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 4)
.attr(RecoilAttr, false, 0.33)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
@ -6845,7 +6973,7 @@ export function initMoves() {
.attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5)
.ignoresProtect()
.target(MoveTarget.BOTH_SIDES),
new AttackMove(Moves.DRACO_METEOR, Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, 100, 0, 4)
new AttackMove(Moves.DRACO_METEOR, Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4)
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new AttackMove(Moves.DISCHARGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
@ -6853,7 +6981,7 @@ export function initMoves() {
new AttackMove(Moves.LAVA_PLUME, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
.attr(StatusEffectAttr, StatusEffect.BURN)
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.LEAF_STORM, Type.GRASS, MoveCategory.SPECIAL, 130, 90, 5, 100, 0, 4)
new AttackMove(Moves.LEAF_STORM, Type.GRASS, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4)
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new AttackMove(Moves.POWER_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 4),
new AttackMove(Moves.ROCK_WRECKER, Type.ROCK, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4)
@ -6923,7 +7051,7 @@ export function initMoves() {
.unimplemented(),
new AttackMove(Moves.CRUSH_GRIP, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
.attr(OpponentHighHpPowerAttr),
new AttackMove(Moves.MAGMA_STORM, Type.FIRE, MoveCategory.SPECIAL, 100, 75, 5, 100, 0, 4)
new AttackMove(Moves.MAGMA_STORM, Type.FIRE, MoveCategory.SPECIAL, 100, 75, 5, -1, 0, 4)
.attr(TrapAttr, BattlerTagType.MAGMA_STORM),
new StatusMove(Moves.DARK_VOID, Type.DARK, 50, 10, -1, 0, 4)
.attr(StatusEffectAttr, StatusEffect.SLEEP)
@ -6959,7 +7087,7 @@ export function initMoves() {
.partial(),
new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5)
.powderMove()
.unimplemented(),
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true),
new StatusMove(Moves.TELEKINESIS, Type.PSYCHIC, -1, 15, -1, 0, 5)
.condition(failOnGravityCondition)
.unimplemented(),
@ -6968,9 +7096,9 @@ export function initMoves() {
.target(MoveTarget.BOTH_SIDES)
.unimplemented(),
new AttackMove(Moves.SMACK_DOWN, Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 5)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING])
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
.makesContact(false),
new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5)
@ -7183,7 +7311,7 @@ export function initMoves() {
new AttackMove(Moves.ICICLE_CRASH, Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, 30, 0, 5)
.attr(FlinchAttr)
.makesContact(false),
new AttackMove(Moves.V_CREATE, Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, 100, 0, 5)
new AttackMove(Moves.V_CREATE, Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, 0, 5)
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF, BattleStat.SPD ], -1, true),
new AttackMove(Moves.FUSION_FLARE, Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 5)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
@ -7202,7 +7330,7 @@ export function initMoves() {
.condition(new FirstMoveCondition()),
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
.condition((user, target, move) => user.battleData.berriesEaten.length > 0),
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, 100, 0, 6)
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6)
.target(MoveTarget.ALL)
.condition((user,target,move) => {
// If any fielded pokémon is grass-type and grounded.
@ -7221,7 +7349,7 @@ export function initMoves() {
new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
.attr(AddTypeAttr, Type.GHOST)
.partial(),
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, 100, 0, 6)
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1)
.soundBased(),
new StatusMove(Moves.ION_DELUGE, Type.ELECTRIC, -1, 25, -1, 1, 6)
@ -7244,7 +7372,7 @@ export function initMoves() {
new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, 100, 0, 6)
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
.attr(ForceSwitchOutAttr, true, false)
.soundBased(),
@ -7257,9 +7385,9 @@ export function initMoves() {
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
.target(MoveTarget.USER_SIDE)
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true),
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, 100, 0, 6)
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
.target(MoveTarget.ALL)
.unimplemented(),
.attr(StatChangeAttr, BattleStat.DEF, 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
new StatusMove(Moves.GRASSY_TERRAIN, Type.GRASS, -1, 10, -1, 0, 6)
.attr(TerrainChangeAttr, TerrainType.GRASSY)
.target(MoveTarget.BOTH_SIDES),
@ -7282,9 +7410,9 @@ export function initMoves() {
.unimplemented(),
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD),
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, 100, 0, 6)
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
.attr(StatChangeAttr, BattleStat.ATK, -1),
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, 100, 0, 6)
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
.attr(StatChangeAttr, BattleStat.SPATK, -1)
.soundBased(),
new AttackMove(Moves.DIAMOND_STORM, Type.ROCK, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 6)
@ -7310,7 +7438,7 @@ export function initMoves() {
.target(MoveTarget.NEAR_ALLY),
new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6)
.attr(StatChangeAttr, BattleStat.SPATK, -2),
new StatusMove(Moves.VENOM_DRENCH, Type.POISON, 100, 20, 100, 0, 6)
new StatusMove(Moves.VENOM_DRENCH, Type.POISON, 100, 20, -1, 0, 6)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], -1, false, (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
@ -7325,8 +7453,8 @@ export function initMoves() {
.target(MoveTarget.USER_AND_ALLIES)
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => p.hasAbility(a, false)))),
new StatusMove(Moves.HAPPY_HOUR, Type.NORMAL, -1, 30, -1, 0, 6) // No animation
.target(MoveTarget.USER_SIDE)
.unimplemented(),
.attr(AddArenaTagAttr, ArenaTagType.HAPPY_HOUR, null, true)
.target(MoveTarget.USER_SIDE),
new StatusMove(Moves.ELECTRIC_TERRAIN, Type.ELECTRIC, -1, 10, -1, 0, 6)
.attr(TerrainChangeAttr, TerrainType.ELECTRIC)
.target(MoveTarget.BOTH_SIDES),
@ -7341,7 +7469,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
new AttackMove(Moves.HOLD_BACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 6)
.attr(SurviveDamageAttr),
new AttackMove(Moves.INFESTATION, Type.BUG, MoveCategory.SPECIAL, 20, 100, 20, 100, 0, 6)
new AttackMove(Moves.INFESTATION, Type.BUG, MoveCategory.SPECIAL, 20, 100, 20, -1, 0, 6)
.makesContact()
.attr(TrapAttr, BattlerTagType.INFESTATION),
new AttackMove(Moves.POWER_UP_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 20, 100, 0, 6)
@ -7350,11 +7478,12 @@ export function initMoves() {
new AttackMove(Moves.OBLIVION_WING, Type.FLYING, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
.attr(HitHealAttr, 0.75)
.triageMove(),
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, 100, 0, 6)
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false)
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING])
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
.makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.THOUSAND_WAVES, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
@ -7373,9 +7502,9 @@ export function initMoves() {
new AttackMove(Moves.PRECIPICE_BLADES, Type.GROUND, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 6)
.makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.DRAGON_ASCENT, Type.FLYING, MoveCategory.PHYSICAL, 120, 100, 5, 100, 0, 6)
new AttackMove(Moves.DRAGON_ASCENT, Type.FLYING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 6)
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
new AttackMove(Moves.HYPERSPACE_FURY, Type.DARK, MoveCategory.PHYSICAL, 100, -1, 5, 100, 0, 6)
new AttackMove(Moves.HYPERSPACE_FURY, Type.DARK, MoveCategory.PHYSICAL, 100, -1, 5, -1, 0, 6)
.attr(StatChangeAttr, BattleStat.DEF, -1, true)
.makesContact(false)
.ignoresProtect(),
@ -7499,23 +7628,23 @@ export function initMoves() {
.condition(new FirstMoveCondition()),
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER),
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 7)
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1)
.makesContact(false),
new AttackMove(Moves.DARKEST_LARIAT, Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
.attr(IgnoreOpponentStatChangesAttr),
new AttackMove(Moves.SPARKLING_ARIA, Type.WATER, MoveCategory.SPECIAL, 90, 100, 10, -1, 0, 7)
new AttackMove(Moves.SPARKLING_ARIA, Type.WATER, MoveCategory.SPECIAL, 90, 100, 10, 100, 0, 7)
.attr(HealStatusEffectAttr, false, StatusEffect.BURN)
.soundBased()
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.ICE_HAMMER, Type.ICE, MoveCategory.PHYSICAL, 100, 90, 10, 100, 0, 7)
new AttackMove(Moves.ICE_HAMMER, Type.ICE, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 7)
.attr(StatChangeAttr, BattleStat.SPD, -1, true)
.punchingMove(),
new StatusMove(Moves.FLORAL_HEALING, Type.FAIRY, -1, 10, -1, 0, 7)
.attr(BoostHealAttr, 0.5, 2/3, true, false, (user, target, move) => user.scene.arena.terrain?.terrainType === TerrainType.GRASSY)
.triageMove(),
new AttackMove(Moves.HIGH_HORSEPOWER, Type.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7),
new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, 100, 0, 7)
new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, -1, 0, 7)
.attr(HitHealAttr, null, Stat.ATK)
.attr(StatChangeAttr, BattleStat.ATK, -1)
.condition((user, target, move) => target.summonData.battleStats[BattleStat.ATK] > -6)
@ -7527,8 +7656,8 @@ export function initMoves() {
new AttackMove(Moves.LEAFAGE, Type.GRASS, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 7)
.makesContact(false),
new StatusMove(Moves.SPOTLIGHT, Type.NORMAL, -1, 15, -1, 3, 7)
.unimplemented(),
new StatusMove(Moves.TOXIC_THREAD, Type.POISON, 100, 20, 100, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, false),
new StatusMove(Moves.TOXIC_THREAD, Type.POISON, 100, 20, -1, 0, 7)
.attr(StatusEffectAttr, StatusEffect.POISON)
.attr(StatChangeAttr, BattleStat.SPD, -1),
new SelfStatusMove(Moves.LASER_FOCUS, Type.NORMAL, -1, 30, -1, 0, 7)
@ -7543,7 +7672,7 @@ export function initMoves() {
.attr(StatusCategoryOnAllyAttr)
.attr(HealOnAllyAttr, 0.5, true, false)
.ballBombMove(),
new AttackMove(Moves.ANCHOR_SHOT, Type.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, -1, 0, 7)
new AttackMove(Moves.ANCHOR_SHOT, Type.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, 100, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1),
new StatusMove(Moves.PSYCHIC_TERRAIN, Type.PSYCHIC, -1, 10, -1, 0, 7)
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
@ -7587,7 +7716,7 @@ export function initMoves() {
.ballBombMove()
.makesContact(false)
.partial(),
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, 100, 0, 7)
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, -1, 0, 7)
.attr(StatChangeAttr, BattleStat.DEF, -1, true)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
@ -7621,17 +7750,17 @@ export function initMoves() {
new AttackMove(Moves.PULVERIZING_PANCAKE, Type.NORMAL, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7)
.partial()
.ignoresVirtual(),
new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, 100, 0, 7)
new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, -1, 0, 7)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true)
.ignoresVirtual(),
new AttackMove(Moves.GENESIS_SUPERNOVA, Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, -1, 0, 7)
new AttackMove(Moves.GENESIS_SUPERNOVA, Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7)
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
.ignoresVirtual(),
/* End Unused */
new AttackMove(Moves.SHELL_TRAP, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, -3, 7)
.target(MoveTarget.ALL_NEAR_ENEMIES)
.partial(),
new AttackMove(Moves.FLEUR_CANNON, Type.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, 100, 0, 7)
new AttackMove(Moves.FLEUR_CANNON, Type.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 7)
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new AttackMove(Moves.PSYCHIC_FANGS, Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
.bitingMove()
@ -7654,7 +7783,7 @@ export function initMoves() {
new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7)
.ignoresAbilities()
.partial(),
new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, 100, 0, 7)
new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, -1, 0, 7)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1),
new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7)
.attr(FlinchAttr),
@ -7753,7 +7882,7 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, false, 1)
.bitingMove(),
new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, 100, 0, 8) // TODO: Stuff Cheeks should not be selectable when the user does not have a berry, see wiki
new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) // TODO: Stuff Cheeks should not be selectable when the user does not have a berry, see wiki
.attr(EatBerryAttr)
.attr(StatChangeAttr, BattleStat.DEF, 2, true)
.condition((user) => {
@ -7761,10 +7890,10 @@ export function initMoves() {
return userBerries.length > 0;
})
.partial(),
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, 100, 0, 8)
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, true, 1),
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, 100, 0, 8)
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, -1, 0, 8)
.attr(StatChangeAttr, BattleStat.SPD, -1)
.partial(),
new StatusMove(Moves.MAGIC_POWDER, Type.PSYCHIC, 100, 20, -1, 0, 8)
@ -7860,18 +7989,18 @@ export function initMoves() {
.unimplemented()
.ignoresVirtual(),
/* End Unused */
new SelfStatusMove(Moves.CLANGOROUS_SOUL, Type.DRAGON, 100, 5, 100, 0, 8)
new SelfStatusMove(Moves.CLANGOROUS_SOUL, Type.DRAGON, 100, 5, -1, 0, 8)
.attr(CutHpStatBoostAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, 3)
.soundBased()
.danceMove(),
new AttackMove(Moves.BODY_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8)
.attr(DefAtkAttr),
new StatusMove(Moves.DECORATE, Type.FAIRY, -1, 15, 100, 0, 8)
new StatusMove(Moves.DECORATE, Type.FAIRY, -1, 15, -1, 0, 8)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 2),
new AttackMove(Moves.DRUM_BEATING, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8)
.attr(StatChangeAttr, BattleStat.SPD, -1)
.makesContact(false),
new AttackMove(Moves.SNAP_TRAP, Type.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, 100, 0, 8)
new AttackMove(Moves.SNAP_TRAP, Type.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, -1, 0, 8)
.attr(TrapAttr, BattlerTagType.SNAP_TRAP),
new AttackMove(Moves.PYRO_BALL, Type.FIRE, MoveCategory.PHYSICAL, 120, 90, 5, 10, 0, 8)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
@ -7923,7 +8052,7 @@ export function initMoves() {
new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8)
.attr(ClearTerrainAttr)
.condition((user, target, move) => !!user.scene.arena.terrain),
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, 100, 0, 8)
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8)
//.attr(StatChangeAttr, BattleStat.SPD, 1, true) // TODO: Have boosts only apply at end of move, not after every hit
//.attr(StatChangeAttr, BattleStat.DEF, -1, true)
.attr(MultiHitAttr)
@ -7964,7 +8093,7 @@ export function initMoves() {
new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8)
.target(MoveTarget.ALL_NEAR_OTHERS)
.unimplemented(),
new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, 100, 0, 8)
new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, -1, 0, 8)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], 1)
.target(MoveTarget.NEAR_ALLY),
new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8)
@ -7990,7 +8119,7 @@ export function initMoves() {
.attr(MultiHitAttr, MultiHitType._3)
.attr(CritOnlyAttr)
.punchingMove(),
new AttackMove(Moves.THUNDER_CAGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 90, 15, 100, 0, 8)
new AttackMove(Moves.THUNDER_CAGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 90, 15, -1, 0, 8)
.attr(TrapAttr, BattlerTagType.THUNDER_CAGE),
new AttackMove(Moves.DRAGON_ENERGY, Type.DRAGON, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 8)
.attr(HpPowerAttr)
@ -8008,16 +8137,16 @@ export function initMoves() {
new AttackMove(Moves.ASTRAL_BARRAGE, Type.GHOST, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 8)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.EERIE_SPELL, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 5, 100, 0, 8)
.soundBased()
.partial(),
.attr(AttackReducePpMoveAttr, 3)
.soundBased(),
new AttackMove(Moves.DIRE_CLAW, Type.POISON, MoveCategory.PHYSICAL, 80, 100, 15, 50, 0, 8)
.attr(MultiStatusEffectAttr, [StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP]),
new AttackMove(Moves.PSYSHIELD_BASH, Type.PSYCHIC, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8)
.attr(StatChangeAttr, BattleStat.DEF, 1, true),
new SelfStatusMove(Moves.POWER_SHIFT, Type.NORMAL, -1, 10, 100, 0, 8)
new SelfStatusMove(Moves.POWER_SHIFT, Type.NORMAL, -1, 10, -1, 0, 8)
.unimplemented(),
new AttackMove(Moves.STONE_AXE, Type.ROCK, MoveCategory.PHYSICAL, 65, 90, 15, 100, 0, 8)
.attr(AddArenaTrapTagAttr, ArenaTagType.STEALTH_ROCK)
.attr(AddArenaTrapTagHitAttr, ArenaTagType.STEALTH_ROCK)
.slicingMove(),
new AttackMove(Moves.SPRINGTIDE_STORM, Type.FAIRY, MoveCategory.SPECIAL, 100, 80, 5, 30, 0, 8)
.attr(StatChangeAttr, BattleStat.ATK, -1)
@ -8035,11 +8164,12 @@ export function initMoves() {
new AttackMove(Moves.CHLOROBLAST, Type.GRASS, MoveCategory.SPECIAL, 150, 95, 5, -1, 0, 8)
.attr(RecoilAttr, true, 0.5),
new AttackMove(Moves.MOUNTAIN_GALE, Type.ICE, MoveCategory.PHYSICAL, 100, 85, 10, 30, 0, 8)
.makesContact(false)
.attr(FlinchAttr),
new SelfStatusMove(Moves.VICTORY_DANCE, Type.FIGHTING, -1, 10, 100, 0, 8)
new SelfStatusMove(Moves.VICTORY_DANCE, Type.FIGHTING, -1, 10, -1, 0, 8)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPD ], 1, true)
.danceMove(),
new AttackMove(Moves.HEADLONG_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 120, 100, 5, 100, 0, 8)
new AttackMove(Moves.HEADLONG_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 8)
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true)
.punchingMove(),
new AttackMove(Moves.BARB_BARRAGE, Type.POISON, MoveCategory.PHYSICAL, 60, 100, 10, 50, 0, 8)
@ -8061,7 +8191,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.BURN)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status ? 2 : 1),
new AttackMove(Moves.CEASELESS_EDGE, Type.DARK, MoveCategory.PHYSICAL, 65, 90, 15, 100, 0, 8)
.attr(AddArenaTrapTagAttr, ArenaTagType.SPIKES)
.attr(AddArenaTrapTagHitAttr, ArenaTagType.SPIKES)
.slicingMove(),
new AttackMove(Moves.BLEAKWIND_STORM, Type.FLYING, MoveCategory.SPECIAL, 100, 80, 10, 30, 0, 8)
.attr(ThunderAccuracyAttr)
@ -8202,15 +8332,15 @@ export function initMoves() {
.makesContact(false),
new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
.attr(StatChangeAttr, BattleStat.SPDEF, -2),
new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 9)
new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
.makesContact(false)
.partial(),
new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
.punchingMove(),
new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, 100, 0, 9)
new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, -1, 0, 9)
.attr(StatChangeAttr, BattleStat.ATK, 2)
.attr(StatChangeAttr, BattleStat.DEF, -2),
new AttackMove(Moves.SPIN_OUT, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, 100, 0, 9)
new AttackMove(Moves.SPIN_OUT, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
.attr(StatChangeAttr, BattleStat.SPD, -2, true),
new AttackMove(Moves.POPULATION_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 9)
.attr(MultiHitAttr, MultiHitType._10)
@ -8224,7 +8354,7 @@ export function initMoves() {
.triageMove()
.attr(RevivalBlessingAttr)
.target(MoveTarget.USER),
new AttackMove(Moves.SALT_CURE, Type.ROCK, MoveCategory.PHYSICAL, 40, 100, 15, -1, 0, 9)
new AttackMove(Moves.SALT_CURE, Type.ROCK, MoveCategory.PHYSICAL, 40, 100, 15, 100, 0, 9)
.attr(AddBattlerTagAttr, BattlerTagType.SALT_CURED)
.makesContact(false),
new AttackMove(Moves.TRIPLE_DIVE, Type.WATER, MoveCategory.PHYSICAL, 30, 95, 10, -1, 0, 9)
@ -8287,7 +8417,7 @@ export function initMoves() {
.attr(WeatherChangeAttr, WeatherType.SNOW)
.attr(ForceSwitchOutAttr, true, false)
.target(MoveTarget.BOTH_SIDES),
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, 100, 0, 9)
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPD ], 1, true, null, true, true)
.attr(RemoveArenaTrapAttr)
.target(MoveTarget.BOTH_SIDES),
@ -8298,7 +8428,7 @@ export function initMoves() {
.attr(StatChangeAttr, BattleStat.SPD, -1),
new AttackMove(Moves.TRAILBLAZE, Type.GRASS, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9)
.attr(StatChangeAttr, BattleStat.SPD, 1, true),
new AttackMove(Moves.CHILLING_WATER, Type.WATER, MoveCategory.SPECIAL, 50, 100, 20, -1, 0, 9)
new AttackMove(Moves.CHILLING_WATER, Type.WATER, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 9)
.attr(StatChangeAttr, BattleStat.ATK, -1),
new AttackMove(Moves.HYPER_DRILL, Type.NORMAL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
.ignoresProtect(),
@ -8360,8 +8490,7 @@ export function initMoves() {
.attr(HealStatusEffectAttr, false, StatusEffect.FREEZE)
.attr(StatusEffectAttr, StatusEffect.BURN)
.target(MoveTarget.ALL_NEAR_ENEMIES)
.triageMove()
.partial(),
.triageMove(),
new AttackMove(Moves.SYRUP_BOMB, Type.GRASS, MoveCategory.SPECIAL, 60, 85, 10, -1, 0, 9)
.attr(StatChangeAttr, BattleStat.SPD, -1) //Temporary
.ballBombMove()
@ -8379,7 +8508,7 @@ export function initMoves() {
new AttackMove(Moves.FICKLE_BEAM, Type.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9)
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
.attr(DoublePowerChanceAttr),
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, 100, 4, 9)
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK),
new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9)
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].category !== MoveCategory.STATUS),
@ -8391,7 +8520,7 @@ export function initMoves() {
.slicingMove(),
new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
.attr(OpponentHighHpPowerAttr),
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, 100, 0, 9)
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, -1, 0, 9)
.attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, true)
.target(MoveTarget.NEAR_ALLY)
.partial(),

View File

@ -1,5 +1,5 @@
import BattleScene from "../battle-scene";
import i18next from "../plugins/i18n";
import i18next from "i18next";
export enum PokeballType {
POKEBALL,

View File

@ -3,6 +3,7 @@ import Pokemon from "../field/pokemon";
import { SpeciesFormKey } from "./pokemon-species";
import { StatusEffect } from "./status-effect";
import { MoveCategory, allMoves } from "./move";
import { Constructor } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
@ -178,7 +179,7 @@ export class SpeciesFormChange {
return true;
}
findTrigger(triggerType: { new(...args: any[]): SpeciesFormChangeTrigger }): SpeciesFormChangeTrigger {
findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger {
if (!this.trigger.hasTriggerType(triggerType)) {
return null;
}
@ -208,7 +209,7 @@ export abstract class SpeciesFormChangeTrigger {
return true;
}
hasTriggerType(triggerType: { new(...args: any[]): SpeciesFormChangeTrigger }): boolean {
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return this instanceof triggerType;
}
}
@ -236,7 +237,7 @@ export class SpeciesFormChangeCompoundTrigger {
return true;
}
hasTriggerType(triggerType: { new(...args: any[]): SpeciesFormChangeTrigger }): boolean {
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return !!this.triggers.find(t => t.hasTriggerType(triggerType));
}
}

View File

@ -13,7 +13,8 @@ import { speciesEggMoves } from "./egg-moves";
import { GameMode } from "../game-mode";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import { VariantSet } from "./variant";
import i18next, { Localizable } from "../plugins/i18n";
import i18next from "i18next";
import { Localizable } from "#app/interfaces/locales";
import { Stat } from "./pokemon-stat";
import { Abilities } from "#enums/abilities";
import { PartyMemberStrength } from "#enums/party-member-strength";
@ -818,6 +819,10 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
return super.isObtainable();
}
hasVariants() {
return variantData.hasOwnProperty(this.speciesId);
}
getFormSpriteKey(formIndex?: integer) {
if (this.forms.length && formIndex >= this.forms.length) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${this.getName()} with only ${this.forms.length || 0} forms`);

View File

@ -1,4 +1,4 @@
import i18next from "../plugins/i18n";
import i18next from "i18next";
export enum Stat {
HP = 0,

View File

@ -1,4 +1,4 @@
import i18next from "../plugins/i18n";
import i18next from "i18next";
export function getBattleCountSplashMessage(): string {
return `{COUNT} ${i18next.t("splashMessages:battlesWon")}`;

File diff suppressed because it is too large Load Diff

View File

@ -531,13 +531,18 @@ export class TrainerConfig {
* Initializes the trainer configuration for an evil team leader. Temporarily hardcoding evil leader teams though.
* @param {Species | Species[]} signatureSpecies - The signature species for the evil team leader.
* @param {Type[]} specialtyTypes - The specialty types for the evil team Leader.
* @param boolean whether or not this is the rematch fight
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* **/
initForEvilTeamLeader(title: string, signatureSpecies: (Species | Species[])[], ...specialtyTypes: Type[]): TrainerConfig {
initForEvilTeamLeader(title: string, signatureSpecies: (Species | Species[])[], rematch: boolean = false, ...specialtyTypes: Type[]): TrainerConfig {
if (!getIsInitialized()) {
initI18n();
}
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
if (rematch) {
this.setPartyTemplates(trainerPartyTemplates.ELITE_FOUR);
} else {
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
}
signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
@ -551,11 +556,11 @@ export class TrainerConfig {
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
this.name = i18next.t(`trainerNames:${nameForCall}`);
this.setTitle(title);
this.setMoneyMultiplier(2.5);
this.setMoneyMultiplier(2.25);
this.setBoss();
this.setStaticParty();
this.setBattleBgm("battle_unova_gym"); // TODO: change
this.setVictoryBgm("victory_gym"); // TODO: change
this.setBattleBgm("battle_plasma_boss");
this.setVictoryBgm("victory_team_plasma");
return this;
}
@ -814,7 +819,7 @@ interface TrainerConfigs {
}
/**
* The function to get variable strength grutns
* The function to get variable strength grunts
* @param scene the singleton scene being passed in
* @returns the correct TrainerPartyTemplate
*/
@ -822,22 +827,14 @@ function getEvilGruntPartyTemplate(scene: BattleScene): TrainerPartyTemplate {
const waveIndex = scene.currentBattle?.waveIndex;
if (waveIndex < 40) {
return trainerPartyTemplates.TWO_AVG;
} else if (waveIndex < 80) {
switch (waveIndex) {
case 62:
return trainerPartyTemplates.THREE_AVG;
case 64:
return trainerPartyTemplates.TWO_AVG_ONE_STRONG;
case 65:
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
}
} else if (waveIndex < 63) {
return trainerPartyTemplates.THREE_AVG;
} else if (waveIndex < 65) {
return trainerPartyTemplates.TWO_AVG_ONE_STRONG;
} else if (waveIndex < 112) {
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
} else {
switch (waveIndex) {
case 112:
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
case 114:
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
}
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
}
}
@ -1183,53 +1180,47 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesPools(
[Species.CATERPIE, Species.WEEDLE, Species.RATTATA, Species.SENTRET, Species.POOCHYENA, Species.ZIGZAGOON, Species.WURMPLE, Species.BIDOOF, Species.PATRAT, Species.LILLIPUP]
),
[TrainerType.ROCKET_GRUNT]: new TrainerConfig(++t).setHasGenders("Rocket Grunt Female").setHasDouble("Rocket Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
[TrainerType.ROCKET_GRUNT]: new TrainerConfig(++t).setHasGenders("Rocket Grunt Female").setHasDouble("Rocket Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.WEEDLE, Species.RATTATA, Species.EKANS, Species.SANDSHREW, Species.ZUBAT, Species.GEODUDE, Species.KOFFING],
[TrainerPoolTier.UNCOMMON]: [Species.GRIMER, Species.CUBONE, Species.ODDISH, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB],
[TrainerPoolTier.RARE]: [Species.GYARADOS, Species.TAUROS, Species.SCYTHER],
[TrainerPoolTier.SUPER_RARE]: [Species.PORYGON, Species.ALOLA_RATTATA, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE],
[TrainerPoolTier.ULTRA_RARE]: [Species.DRATINI, Species.LARVITAR]
[TrainerPoolTier.COMMON]: [ Species.WEEDLE, Species.RATTATA, Species.EKANS, Species.SANDSHREW, Species.ZUBAT, Species.GEODUDE, Species.KOFFING, Species.GRIMER, Species.ODDISH],
[TrainerPoolTier.UNCOMMON]: [ Species.GYARADOS, Species.TAUROS, Species.SCYTHER, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB],
[TrainerPoolTier.RARE]: [Species.PORYGON, Species.ALOLA_RATTATA, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE],
[TrainerPoolTier.SUPER_RARE]: [Species.DRATINI, Species.LARVITAR]
}),
[TrainerType.MAGMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Magma Grunt Female").setHasDouble("Magma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
[TrainerType.MAGMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Magma Grunt Female").setHasDouble("Magma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY],
[TrainerPoolTier.UNCOMMON]: [Species.PHANPY, Species.SWINUB, Species.GLIGAR, Species.TORKOAL, Species.BALTOY, Species.BARBOACH],
[TrainerPoolTier.RARE]: [Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA],
[TrainerPoolTier.SUPER_RARE]: [Species.TRAPINCH, Species.HEATMOR],
[TrainerPoolTier.ULTRA_RARE]: [Species.TURTONATOR, Species.CHARCADET]
[TrainerPoolTier.COMMON]: [Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY, Species.TORKOAL, Species.BALTOY, Species.BARBOACH],
[TrainerPoolTier.UNCOMMON]: [Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.SWINUB, Species.GLIGAR],
[TrainerPoolTier.RARE]: [Species.TRAPINCH, Species.HEATMOR],
[TrainerPoolTier.SUPER_RARE]: [Species.TURTONATOR, Species.CHARCADET]
}),
[TrainerType.AQUA_GRUNT]: new TrainerConfig(++t).setHasGenders("Aqua Grunt Female").setHasDouble("Aqua Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
[TrainerType.AQUA_GRUNT]: new TrainerConfig(++t).setHasGenders("Aqua Grunt Female").setHasDouble("Aqua Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.CARVANHA, Species.WAILMER, Species.ZIGZAGOON, Species.LOTAD, Species.CORPHISH],
[TrainerPoolTier.UNCOMMON]: [Species.SPHEAL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL ],
[TrainerPoolTier.RARE]: [Species.CLAMPERL, Species.REMORAID, Species.ARROKUDA],
[TrainerPoolTier.SUPER_RARE]: [Species.MANTINE, Species.BASCULEGION],
[TrainerPoolTier.ULTRA_RARE]: [Species.DONDOZO]
[TrainerPoolTier.COMMON]: [ Species.CARVANHA, Species.WAILMER, Species.ZIGZAGOON, Species.LOTAD, Species.CORPHISH, Species.SPHEAL ],
[TrainerPoolTier.UNCOMMON]: [Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.QWILFISH ],
[TrainerPoolTier.RARE]: [Species.MANTINE, Species.BASCULEGION, Species.REMORAID, Species.ARROKUDA],
[TrainerPoolTier.SUPER_RARE]: [Species.DONDOZO]
}),
[TrainerType.GALACTIC_GRUNT]: new TrainerConfig(++t).setHasGenders("Galactic Grunt Female").setHasDouble("Galactic Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
[TrainerType.GALACTIC_GRUNT]: new TrainerConfig(++t).setHasGenders("Galactic Grunt Female").setHasDouble("Galactic Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.GLAMEOW, Species.STUNKY, Species.CROAGUNK, Species.SHINX, Species.WURMPLE],
[TrainerPoolTier.UNCOMMON]: [Species.BRONZOR, Species.DRIFLOON, Species.BURMY],
[TrainerPoolTier.RARE]: [Species.CARNIVINE],
[TrainerPoolTier.SUPER_RARE]: [Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.HISUI_SNEASEL],
[TrainerPoolTier.ULTRA_RARE]: [Species.HISUI_ZORUA, Species.HISUI_SLIGGOO]
[TrainerPoolTier.COMMON]: [ Species.GLAMEOW, Species.STUNKY, Species.CROAGUNK, Species.SHINX, Species.WURMPLE, Species.BRONZOR, Species.DRIFLOON, Species.BURMY],
[TrainerPoolTier.UNCOMMON]: [ Species.CARNIVINE, Species.GROWLITHE, Species.QWILFISH, Species.SNEASEL ],
[TrainerPoolTier.RARE]: [Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.HISUI_SNEASEL],
[TrainerPoolTier.SUPER_RARE]: [Species.HISUI_ZORUA, Species.HISUI_SLIGGOO]
}),
[TrainerType.PLASMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Plasma Grunt Female").setHasDouble("Plasma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
[TrainerType.PLASMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Plasma Grunt Female").setHasDouble("Plasma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.PATRAT, Species.LILLIPUP, Species.PURRLOIN, Species.SCRAFTY, Species.WOOBAT, Species.VANILLITE],
[TrainerPoolTier.UNCOMMON]: [ Species.FRILLISH, Species.VENIPEDE, Species.SANDILE, Species.TRUBBISH, Species.GOLETT],
[TrainerPoolTier.RARE]: [Species.TIMBURR, Species.DARUMAKA, Species.AMOONGUSS, Species.DRILBUR, Species.KLINK, Species.VULLABY],
[TrainerPoolTier.SUPER_RARE]: [Species.PAWNIARD, Species.VULLABY, Species.DRUDDIGON, Species.BOUFFALANT, Species.ZORUA],
[TrainerPoolTier.ULTRA_RARE]: [Species.AXEW, Species.DEINO, Species.DURANT]
[TrainerPoolTier.COMMON]: [ Species.PATRAT, Species.LILLIPUP, Species.PURRLOIN, Species.SCRAFTY, Species.WOOBAT, Species.VANILLITE, Species.SANDILE, Species.TRUBBISH],
[TrainerPoolTier.UNCOMMON]: [ Species.FRILLISH, Species.VENIPEDE, Species.GOLETT, Species.TIMBURR, Species.DARUMAKA, Species.AMOONGUSS],
[TrainerPoolTier.RARE]: [Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK],
[TrainerPoolTier.SUPER_RARE]: [Species.DRUDDIGON, Species.BOUFFALANT, Species.AXEW, Species.DEINO, Species.DURANT]
}),
[TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
[TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI],
[TrainerPoolTier.UNCOMMON]: [Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.GULPIN],
[TrainerPoolTier.RARE]: [Species.LITWICK, Species.SNEASEL],
[TrainerPoolTier.SUPER_RARE]: [Species.NOIVERN],
[TrainerPoolTier.ULTRA_RARE]: []
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK],
[TrainerPoolTier.UNCOMMON]: [Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.GULPIN, Species.PURRLOIN, Species.POOCHYENA, Species.SCATTERBUG],
[TrainerPoolTier.RARE]: [Species.LITWICK, Species.SNEASEL, Species.PANCHAM, Species.PAWNIARD],
[TrainerPoolTier.SUPER_RARE]: [Species.NOIVERN, Species.DRUDDIGON]
}),
[TrainerType.BROCK]: new TrainerConfig((t = TrainerType.BROCK)).initForGymLeader(signatureSpecies["BROCK"],true, Type.ROCK).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.MISTY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["MISTY"],false, Type.WATER).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"),
@ -1509,7 +1500,7 @@ export const trainerConfigs: TrainerConfigs = {
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier];
}),
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss",[])
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss",[]).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN , Species.ALOLA_PERSIAN]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.NIDOKING , Species.NIDOQUEEN ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.RHYPERIOR ]))
@ -1521,14 +1512,14 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1;
})),
[TrainerType.ROCKET_BOSS_GIOVANNI_2]: new TrainerConfig(++t).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", [])
[TrainerType.ROCKET_BOSS_GIOVANNI_2]: new TrainerConfig(++t).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", [], true).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.TYRANITAR , Species.IRON_THORNS], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.EXCADRILL ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HIPPOWDON ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HIPPOWDON ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.EXCADRILL ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.KANGASKHAN ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
@ -1541,7 +1532,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.MAXIE]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss",[])
[TrainerType.MAXIE]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss",[]).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.MIGHTYENA ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.CROBAT, Species.GLISCOR ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.WEEZING, Species.GALAR_WEEZING ]))
@ -1553,33 +1544,33 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1;
})),
[TrainerType.MAXIE_2]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss",[])
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SOLROCK, Species.HOUNDOOM ], TrainerSlot.TRAINER, true, p => {
[TrainerType.MAXIE_2]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss",[], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SOLROCK, Species.TYPHLOSION ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.GREAT_TUSK ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.TORKOAL, Species.NINETALES ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // DROUGHT
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SHIFTRY, Species.SCOVILLAIN ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 0; // Chlorophyll
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.TORKOAL, Species.NINETALES ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // DROUGHT
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GROUDON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.CAMERUPT ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GREAT_TUSK ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.CAMERUPT ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GROUDON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.ARCHIE]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss",[])
[TrainerType.ARCHIE]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss",[]).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LINOONE ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.CROBAT, Species.PELIPPER ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.MUK, Species.ALOLA_MUK ]))
@ -1591,36 +1582,36 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1;
})),
[TrainerType.ARCHIE_2]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss",[])
[TrainerType.ARCHIE_2]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss",[], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.KINGDRA, Species.LUDICOLO ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BEARTIC, Species.ARMALDO ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Swift Swim
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HUNTAIL, Species.GOREBYSS ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 0; // Swift Swim
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.POLITOED, Species.PELIPPER ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.POLITOED, Species.PELIPPER ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Drizzle
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.KYOGRE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.BEARTIC, Species.ARMALDO ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
p.abilityIndex = 2; // Swift Swim
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.SHARPEDO ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.HUNTAIL, Species.GOREBYSS ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 0; // Swift Swim
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.SHARPEDO ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYOGRE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.CYRUS]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss",[])
[TrainerType.CYRUS]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss",[]).setMixedBattleBgm("battle_galactic_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GYARADOS, Species.BASCULEGION ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HONCHKROW, Species.HISUI_BRAVIARY ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CROBAT, Species.OVERQWIL ]))
@ -1635,25 +1626,29 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
})),
[TrainerType.CYRUS_2]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss",[])
[TrainerType.CYRUS_2]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss",[], true).setMixedBattleBgm("battle_galactic_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AZELF, Species.UXIE, Species.MESPRIT ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ELECTRODE, Species.HISUI_ELECTRODE ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SALAMENCE, Species.ROARING_MOON ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.HISUI_ZOROARK ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.DARKRAI ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.HOUNDOOM ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.WEAVILE ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.WEAVILE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DARKRAI ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.GHETSIS]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss",[])
[TrainerType.GHETSIS]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss",[]).setMixedBattleBgm("battle_plasma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.COFAGRIGUS, Species.RUNERIGUS ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BOUFFALANT ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SEISMITOAD, Species.CARRACOSTA ]))
@ -1664,7 +1659,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
})),
[TrainerType.GHETSIS_2]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss",[])
[TrainerType.GHETSIS_2]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss",[], true).setMixedBattleBgm("battle_plasma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SLITHER_WING, Species.IRON_MOTH ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
@ -1673,17 +1668,17 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.DURANT ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.DARMANITAN, Species.GALAR_DARMANITAN ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.KINGAMBIT ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.KYUREM ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.HYDREIGON, Species.IRON_JUGULIS ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.HYDREIGON, Species.IRON_JUGULIS ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYUREM ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.LYSANDRE]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss",[])
[TrainerType.LYSANDRE]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss",[]).setMixedBattleBgm("battle_flare_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.MIENSHAO ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HONCHKROW, Species.TALONFLAME ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.PYROAR ]))
@ -1695,7 +1690,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1;
})),
[TrainerType.LYSANDRE_2]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss",[])
[TrainerType.LYSANDRE_2]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss",[], true).setMixedBattleBgm("battle_flare_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SCREAM_TAIL, Species.FLUTTER_MANE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
@ -1704,15 +1699,15 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.GHOLDENGO, Species.AEGISLASH ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.PYROAR ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GOODRA, Species.HISUI_GOODRA ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.YVELTAL ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GYARADOS ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GYARADOS ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.YVELTAL ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
};

View File

@ -1,3 +1,5 @@
import { VariantTier } from "#app/enums/variant-tier.js";
export type Variant = 0 | 1 | 2;
export type VariantSet = [Variant, Variant, Variant];
@ -16,3 +18,14 @@ export function getVariantTint(variant: Variant): integer {
return 0xe81048;
}
}
export function getVariantIcon(variant: Variant): integer {
switch (variant) {
case 0:
return VariantTier.STANDARD;
case 1:
return VariantTier.RARE;
case 2:
return VariantTier.EPIC;
}
}

View File

@ -1,19 +1,16 @@
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import i18next from "i18next";
import { Phase } from "./phase";
import BattleScene, { AnySound } from "./battle-scene";
import * as Utils from "./utils";
import { Mode } from "./ui/ui";
import { EGG_SEED, Egg, GachaType, getLegendaryGachaSpeciesForTimestamp } from "./data/egg";
import { EGG_SEED, Egg } from "./data/egg";
import EggHatchSceneHandler from "./ui/egg-hatch-scene-handler";
import { PlayerPokemon } from "./field/pokemon";
import { getPokemonSpecies, speciesStarters } from "./data/pokemon-species";
import { achvs } from "./system/achv";
import { pokemonPrevolutions } from "./data/pokemon-evolutions";
import PokemonInfoContainer from "./ui/pokemon-info-container";
import EggCounterContainer from "./ui/egg-counter-container";
import { EggCountChangedEvent } from "./events/egg";
import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species";
/**
* Class that represents egg hatching
@ -345,7 +342,7 @@ export class EggHatchPhase extends Phase {
this.scene.playSoundWithoutBgm("evolution_fanfare");
this.scene.ui.showText(`${this.pokemon.name} hatched from the egg!`, null, () => {
this.scene.ui.showText(i18next.t("egg:hatchFromTheEgg", { pokemonName: this.pokemon.name }), null, () => {
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => {
@ -442,135 +439,10 @@ export class EggHatchPhase extends Phase {
*/
generatePokemon(): PlayerPokemon {
let ret: PlayerPokemon;
let speciesOverride: Species; // SpeciesOverride should probably be a passed in parameter for future species-eggs
this.scene.executeWithSeedOffset(() => {
/**
* Manaphy eggs have a 1/8 chance of being Manaphy and 7/8 chance of being Phione
* Legendary eggs pulled from the legendary gacha have a 50% of being converted into
* the species that was the legendary focus at the time
*/
if (this.egg.isManaphyEgg()) {
const rand = Utils.randSeedInt(8);
speciesOverride = rand ? Species.PHIONE : Species.MANAPHY;
} else if (this.egg.tier === EggTier.MASTER
&& this.egg.gachaType === GachaType.LEGENDARY) {
if (!Utils.randSeedInt(2)) {
speciesOverride = getLegendaryGachaSpeciesForTimestamp(this.scene, this.egg.timestamp);
}
}
if (speciesOverride) {
const pokemonSpecies = getPokemonSpecies(speciesOverride);
ret = this.scene.addPlayerPokemon(pokemonSpecies, 1, undefined, undefined, undefined, false);
} else {
let minStarterValue: integer;
let maxStarterValue: integer;
switch (this.egg.tier) {
case EggTier.GREAT:
minStarterValue = 4;
maxStarterValue = 5;
break;
case EggTier.ULTRA:
minStarterValue = 6;
maxStarterValue = 7;
break;
case EggTier.MASTER:
minStarterValue = 8;
maxStarterValue = 9;
break;
default:
minStarterValue = 1;
maxStarterValue = 3;
break;
}
const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ];
let speciesPool = Object.keys(speciesStarters)
.filter(s => speciesStarters[s] >= minStarterValue && speciesStarters[s] <= maxStarterValue)
.map(s => parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
// If this is the 10th egg without unlocking something new, attempt to force it.
if (this.scene.gameData.unlockPity[this.egg.tier] >= 9) {
const lockedPool = speciesPool.filter(s => !this.scene.gameData.dexData[s].caughtAttr);
if (lockedPool.length) { // Skip this if everything is unlocked
speciesPool = lockedPool;
}
}
/**
* Pokemon that are cheaper in their tier get a weight boost. Regionals get a weight penalty
* 1 cost mons get 2x
* 2 cost mons get 1.5x
* 4, 6, 8 cost mons get 1.75x
* 3, 5, 7, 9 cost mons get 1x
* Alolan, Galarian, and Paldean mons get 0.5x
* Hisui mons get 0.125x
*
* The total weight is also being calculated EACH time there is an egg hatch instead of being generated once
* and being the same each time
*/
let totalWeight = 0;
const speciesWeights = [];
for (const speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId);
if (species.isRegional()) {
weight = Math.floor(weight / (species.isRareRegional() ? 8 : 2));
}
speciesWeights.push(totalWeight + weight);
totalWeight += weight;
}
let species: Species;
const rand = Utils.randSeedInt(totalWeight);
for (let s = 0; s < speciesWeights.length; s++) {
if (rand < speciesWeights[s]) {
species = speciesPool[s];
break;
}
}
if (!!this.scene.gameData.dexData[species].caughtAttr) {
this.scene.gameData.unlockPity[this.egg.tier] = Math.min(this.scene.gameData.unlockPity[this.egg.tier] + 1, 10);
} else {
this.scene.gameData.unlockPity[this.egg.tier] = 0;
}
const pokemonSpecies = getPokemonSpecies(species);
ret = this.scene.addPlayerPokemon(pokemonSpecies, 1, undefined, undefined, undefined, false);
}
/**
* Non Shiny gacha Pokemon have a 1/128 chance of being shiny
* Shiny gacha Pokemon have a 1/64 chance of being shiny
* IVs are rolled twice and the higher of each stat's IV is taken
* The egg move gacha doubles the rate of rare egg moves but the base rates are
* Common: 1/48
* Rare: 1/24
* Epic: 1/12
* Legendary: 1/6
*/
ret.trySetShiny(this.egg.gachaType === GachaType.SHINY ? 1024 : 512);
ret.variant = ret.shiny ? ret.generateVariant() : 0;
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295));
for (let s = 0; s < ret.ivs.length; s++) {
ret.ivs[s] = Math.max(ret.ivs[s], secondaryIvs[s]);
}
const baseChance = this.egg.gachaType === GachaType.MOVE ? 3 : 6;
this.eggMoveIndex = Utils.randSeedInt(baseChance * Math.pow(2, 3 - this.egg.tier))
? Utils.randSeedInt(3)
: 3;
ret = this.egg.generatePlayerPokemon(this.scene);
this.eggMoveIndex = this.egg.eggMoveIndex;
}, this.egg.id, EGG_SEED.toString());

View File

@ -20,5 +20,6 @@ export enum ArenaTagType {
WIDE_GUARD = "WIDE_GUARD",
MAT_BLOCK = "MAT_BLOCK",
CRAFTY_SHIELD = "CRAFTY_SHIELD",
TAILWIND = "TAILWIND"
TAILWIND = "TAILWIND",
HAPPY_HOUR = "HAPPY_HOUR"
}

View File

@ -54,9 +54,10 @@ export enum BattlerTagType {
SALT_CURED = "SALT_CURED",
CURSED = "CURSED",
CHARGED = "CHARGED",
GROUNDED = "GROUNDED",
ROOSTED = "ROOSTED",
MAGNET_RISEN = "MAGNET_RISEN",
MINIMIZED = "MINIMIZED",
DESTINY_BOND = "DESTINY_BOND",
CENTER_OF_ATTENTION = "CENTER_OF_ATTENTION",
ICE_FACE = "ICE_FACE"
}

83
src/enums/color.ts Normal file
View File

@ -0,0 +1,83 @@
export enum Color {
WHITE = "#ffffff",
OFF_WHITE = "#f8f8f8",
LIGHT_GREY = "#a0a0a0",
GREY = "#484848",
DARK_GREY = "#404040",
PINK = "#f89890",
RED = "#e13d3d",
RED2 = "#e70808",
REDORANGE = "#d64b00",
ORANGE = "#f8b050",
LIGHT_YELLOW = "#e8e8a8",
YELLOW = "#ccbe00",
DARK_YELLOW = "#a68e17",
GREEN = "#78c850",
BLUE = "#40c8f8",
COMMON = "#ffffff",
GREAT = "#3890f8",
ULTRA = "#f8d038",
ROGUE = "#d52929",
MASTER = "#e020c0",
LUXURY = "#e64a18"
}
export enum TypeColor {
NORMAL = "#ADA594",
FIGHTING = "#A55239",
FLYING = "#9CADF7",
POISON = "#9141CB",
GROUND = "#AE7A3B",
ROCK = "#BDA55A",
BUG = "#ADBD21",
GHOST = "#6363B5",
STEEL = "#81A6BE",
FIRE = "#F75231",
WATER = "#399CFF",
GRASS = "#7BCE52",
ELECTRIC = "#FFC631",
PSYCHIC = "#EF4179",
ICE = "#5ACEE7",
DRAGON = "#7B63E7",
DARK = "#735A4A",
FAIRY = "#EF70EF",
}
export enum TypeShadow {
NORMAL = "#574F4A",
FIGHTING = "#4E637C",
FLYING = "#4E637C",
POISON = "#352166",
GROUND = "#572D1E",
ROCK = "#5F442D",
BUG = "#5F5010",
GHOST = "#323D5B",
STEEL = "#415C5F",
FIRE = "#7C1818",
WATER = "#1C4E80",
GRASS = "#4F6729",
ELECTRIC = "#804618",
PSYCHIC = "#782155",
ICE = "#2D5C74",
DRAGON = "#313874",
DARK = "#392725",
FAIRY = "#663878",
}
export enum ShadowColor {
GREY = "#636363",
PURPLE = "#6b5a73",
LIGHT_GREY = "#d0d0c8",
BROWN = "#69402a",
PINK = "#fca2a2",
BRIGHT_RED = "#f83018",
RED = "#984038",
MAROON = "#632929",
GREEN = "#306850",
BLUE = "#006090",
LIGHT_YELLOW = "#ded6b5",
YELLOW = "#ebd773",
DARK_YELLOW = "#a0a060",
ORANGE = "#c07800",
LIGHT_ORANGE = "#ffbd73",
}

View File

@ -0,0 +1,7 @@
export enum EggSourceType {
GACHA_MOVE,
GACHA_LEGENDARY,
GACHA_SHINY,
SAME_SPECIES_EGG,
EVENT
}

5
src/enums/gacha-types.ts Normal file
View File

@ -0,0 +1,5 @@
export enum GachaType {
MOVE,
LEGENDARY,
SHINY
}

View File

@ -0,0 +1,5 @@
export enum VariantTier {
STANDARD,
RARE,
EPIC
}

View File

@ -0,0 +1,5 @@
export enum VariantTier {
COMMON,
RARE,
EPIC
}

Some files were not shown because too many files have changed in this diff Show More