Merge branch 'beta' into remove-null-from-moveset
@ -123,6 +123,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
|
|||||||
- Involuntary-Twitch
|
- Involuntary-Twitch
|
||||||
- selstar
|
- selstar
|
||||||
- koda_want_to_sleep
|
- koda_want_to_sleep
|
||||||
|
- thedreadedden
|
||||||
|
|
||||||
### 🎨 Move Animations
|
### 🎨 Move Animations
|
||||||
- Pokémon Reborn
|
- Pokémon Reborn
|
||||||
|
87
package-lock.json
generated
@ -16,6 +16,7 @@
|
|||||||
"i18next-http-backend": "^2.6.1",
|
"i18next-http-backend": "^2.6.1",
|
||||||
"i18next-korean-postposition-processor": "^1.0.0",
|
"i18next-korean-postposition-processor": "^1.0.0",
|
||||||
"json-stable-stringify": "^1.1.0",
|
"json-stable-stringify": "^1.1.0",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
"phaser": "^3.70.0",
|
"phaser": "^3.70.0",
|
||||||
"phaser3-rex-plugins": "^1.1.84"
|
"phaser3-rex-plugins": "^1.1.84"
|
||||||
},
|
},
|
||||||
@ -2723,6 +2724,11 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/core-util-is": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||||
|
},
|
||||||
"node_modules/cross-fetch": {
|
"node_modules/cross-fetch": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||||
@ -4045,6 +4051,11 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immediate": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@ -4072,6 +4083,11 @@
|
|||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
"node_modules/ini": {
|
"node_modules/ini": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
|
||||||
@ -4481,6 +4497,17 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jszip": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||||
|
"dependencies": {
|
||||||
|
"lie": "~3.3.0",
|
||||||
|
"pako": "~1.0.2",
|
||||||
|
"readable-stream": "~2.3.6",
|
||||||
|
"setimmediate": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@ -4648,6 +4675,14 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lie": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/linkify-it": {
|
"node_modules/linkify-it": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
@ -5237,6 +5272,11 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0"
|
"license": "BlueOak-1.0.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||||
|
},
|
||||||
"node_modules/papaparse": {
|
"node_modules/papaparse": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
|
||||||
@ -5485,6 +5525,11 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/process-nextick-args": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||||
|
},
|
||||||
"node_modules/prompts": {
|
"node_modules/prompts": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||||
@ -5551,6 +5596,25 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
|
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||||
|
"dependencies": {
|
||||||
|
"core-util-is": "~1.0.0",
|
||||||
|
"inherits": "~2.0.3",
|
||||||
|
"isarray": "~1.0.0",
|
||||||
|
"process-nextick-args": "~2.0.0",
|
||||||
|
"safe-buffer": "~5.1.1",
|
||||||
|
"string_decoder": "~1.1.1",
|
||||||
|
"util-deprecate": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/readable-stream/node_modules/isarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||||
|
},
|
||||||
"node_modules/rechoir": {
|
"node_modules/rechoir": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
|
||||||
@ -5741,6 +5805,11 @@
|
|||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
|
},
|
||||||
"node_modules/safe-regex": {
|
"node_modules/safe-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz",
|
||||||
@ -5800,6 +5869,11 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/setimmediate": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@ -5917,6 +5991,14 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
@ -6473,6 +6555,11 @@
|
|||||||
"requires-port": "^1.0.0"
|
"requires-port": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.8",
|
"version": "5.4.8",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
"i18next-http-backend": "^2.6.1",
|
"i18next-http-backend": "^2.6.1",
|
||||||
"i18next-korean-postposition-processor": "^1.0.0",
|
"i18next-korean-postposition-processor": "^1.0.0",
|
||||||
"json-stable-stringify": "^1.1.0",
|
"json-stable-stringify": "^1.1.0",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
"phaser": "^3.70.0",
|
"phaser": "^3.70.0",
|
||||||
"phaser3-rex-plugins": "^1.1.84"
|
"phaser3-rex-plugins": "^1.1.84"
|
||||||
},
|
},
|
||||||
|
2496
public/battle-anims/common-powder.json
Normal file
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.2 KiB |
@ -4,7 +4,7 @@ import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
|||||||
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species";
|
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species";
|
||||||
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||||
import * as Utils from "#app/utils";
|
import * as Utils from "#app/utils";
|
||||||
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
|
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
|
||||||
import { Phase } from "#app/phase";
|
import { Phase } from "#app/phase";
|
||||||
@ -47,7 +47,7 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container";
|
|||||||
import { biomeDepths, getBiomeName } from "#app/data/balance/biomes";
|
import { biomeDepths, getBiomeName } from "#app/data/balance/biomes";
|
||||||
import { SceneBase } from "#app/scene-base";
|
import { SceneBase } from "#app/scene-base";
|
||||||
import CandyBar from "#app/ui/candy-bar";
|
import CandyBar from "#app/ui/candy-bar";
|
||||||
import { Variant, variantData } from "#app/data/variant";
|
import { Variant, variantColorCache, variantData, VariantSet } from "#app/data/variant";
|
||||||
import { Localizable } from "#app/interfaces/locales";
|
import { Localizable } from "#app/interfaces/locales";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { InputsController } from "#app/inputs-controller";
|
import { InputsController } from "#app/inputs-controller";
|
||||||
@ -345,6 +345,33 @@ export default class BattleScene extends SceneBase {
|
|||||||
this.load.atlas(key, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`);
|
this.load.atlas(key, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache}
|
||||||
|
*/
|
||||||
|
loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant) {
|
||||||
|
const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey);
|
||||||
|
if (useExpSprite) {
|
||||||
|
fileRoot = `exp/${fileRoot}`;
|
||||||
|
}
|
||||||
|
let variantConfig = variantData;
|
||||||
|
fileRoot.split("/").map(p => variantConfig ? variantConfig = variantConfig[p] : null);
|
||||||
|
const variantSet = variantConfig as VariantSet;
|
||||||
|
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
|
||||||
|
const populateVariantColors = (key: string): Promise<void> => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (variantColorCache.hasOwnProperty(key)) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`).then(res => res.json()).then(c => {
|
||||||
|
variantColorCache[key] = c;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
populateVariantColors(spriteKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async preload() {
|
async preload() {
|
||||||
if (DEBUG_RNG) {
|
if (DEBUG_RNG) {
|
||||||
const scene = this;
|
const scene = this;
|
||||||
@ -891,7 +918,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
return pokemon;
|
return pokemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
|
addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, shinyLock: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
|
||||||
if (Overrides.OPP_LEVEL_OVERRIDE > 0) {
|
if (Overrides.OPP_LEVEL_OVERRIDE > 0) {
|
||||||
level = Overrides.OPP_LEVEL_OVERRIDE;
|
level = Overrides.OPP_LEVEL_OVERRIDE;
|
||||||
}
|
}
|
||||||
@ -901,13 +928,11 @@ export default class BattleScene extends SceneBase {
|
|||||||
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
|
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, dataSource);
|
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, shinyLock, dataSource);
|
||||||
if (Overrides.OPP_FUSION_OVERRIDE) {
|
if (Overrides.OPP_FUSION_OVERRIDE) {
|
||||||
pokemon.generateFusionSpecies();
|
pokemon.generateFusionSpecies();
|
||||||
}
|
}
|
||||||
|
|
||||||
overrideModifiers(this, false);
|
|
||||||
overrideHeldItems(this, pokemon, false);
|
|
||||||
if (boss && !dataSource) {
|
if (boss && !dataSource) {
|
||||||
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296));
|
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296));
|
||||||
|
|
||||||
@ -2445,6 +2470,24 @@ export default class BattleScene extends SceneBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to add the input phase to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()}
|
||||||
|
* @param phase {@linkcode Phase} the phase to be added
|
||||||
|
* @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue}
|
||||||
|
* @returns `true` if a `targetPhase` was found to append to
|
||||||
|
*/
|
||||||
|
appendToPhase(phase: Phase, targetPhase: Constructor<Phase>): boolean {
|
||||||
|
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase);
|
||||||
|
|
||||||
|
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
|
||||||
|
this.phaseQueue.splice(targetIndex + 1, 0, phase);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.unshiftPhase(phase);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
|
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
|
||||||
* @param message string for MessagePhase
|
* @param message string for MessagePhase
|
||||||
@ -3009,7 +3052,8 @@ export default class BattleScene extends SceneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validateAchv(achv: Achv, args?: unknown[]): boolean {
|
validateAchv(achv: Achv, args?: unknown[]): boolean {
|
||||||
if (!this.gameData.achvUnlocks.hasOwnProperty(achv.id) && achv.validate(this, args)) {
|
if ((!this.gameData.achvUnlocks.hasOwnProperty(achv.id) || Overrides.ACHIEVEMENTS_REUNLOCK_OVERRIDE)
|
||||||
|
&& achv.validate(this, args)) {
|
||||||
this.gameData.achvUnlocks[achv.id] = new Date().getTime();
|
this.gameData.achvUnlocks[achv.id] = new Date().getTime();
|
||||||
this.ui.achvBar.showAchv(achv);
|
this.ui.achvBar.showAchv(achv);
|
||||||
if (vouchers.hasOwnProperty(achv.id)) {
|
if (vouchers.hasOwnProperty(achv.id)) {
|
||||||
|
@ -3720,16 +3720,16 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1)
|
* Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1)
|
||||||
* @param {Pokemon} pokemon Pokemon that has this ability
|
* @param pokemon Pokemon that has this ability
|
||||||
* @param {boolean} passive N/A
|
* @param passive N/A
|
||||||
* @param {boolean} simulated true if applying in a simulated call.
|
* @param simulated `true` if applying in a simulated call.
|
||||||
* @param {any[]} args N/A
|
* @param args N/A
|
||||||
* @returns {boolean} true if any opponents are sleeping
|
* @returns `true` if any opponents are sleeping
|
||||||
*/
|
*/
|
||||||
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
let hadEffect: boolean = false;
|
let hadEffect: boolean = false;
|
||||||
for (const opp of pokemon.getOpponents()) {
|
for (const opp of pokemon.getOpponents()) {
|
||||||
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER);
|
opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER);
|
||||||
pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }));
|
pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }));
|
||||||
@ -4112,9 +4112,13 @@ export class PostBattleAbAttr extends AbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
||||||
|
/**
|
||||||
|
* @param args - `[0]`: boolean for if the battle ended in a victory
|
||||||
|
* @returns `true` if successful
|
||||||
|
*/
|
||||||
applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||||
const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot;
|
const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot;
|
||||||
if (!simulated && postBattleLoot.length) {
|
if (!simulated && postBattleLoot.length && args[0]) {
|
||||||
const randItem = Utils.randSeedItem(postBattleLoot);
|
const randItem = Utils.randSeedItem(postBattleLoot);
|
||||||
//@ts-ignore - TODO see below
|
//@ts-ignore - TODO see below
|
||||||
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true, undefined, false)) { // TODO: fix. This is a promise!?
|
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true, undefined, false)) { // TODO: fix. This is a promise!?
|
||||||
@ -4575,14 +4579,15 @@ export class MoneyAbAttr extends PostBattleAbAttr {
|
|||||||
/**
|
/**
|
||||||
* @param pokemon {@linkcode Pokemon} that is the user of this ability.
|
* @param pokemon {@linkcode Pokemon} that is the user of this ability.
|
||||||
* @param passive N/A
|
* @param passive N/A
|
||||||
* @param args N/A
|
* @param args - `[0]`: boolean for if the battle ended in a victory
|
||||||
* @returns true
|
* @returns `true` if successful
|
||||||
*/
|
*/
|
||||||
applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||||
if (!simulated) {
|
if (!simulated && args[0]) {
|
||||||
pokemon.scene.currentBattle.moneyScattered += pokemon.scene.getWaveMoneyAmount(0.2);
|
pokemon.scene.currentBattle.moneyScattered += pokemon.scene.getWaveMoneyAmount(0.2);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4590,13 +4595,12 @@ export class MoneyAbAttr extends PostBattleAbAttr {
|
|||||||
* Applies a stat change after a Pokémon is summoned,
|
* Applies a stat change after a Pokémon is summoned,
|
||||||
* conditioned on the presence of a specific arena tag.
|
* conditioned on the presence of a specific arena tag.
|
||||||
*
|
*
|
||||||
* @extends {PostSummonStatStageChangeAbAttr}
|
* @extends PostSummonStatStageChangeAbAttr
|
||||||
*/
|
*/
|
||||||
export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageChangeAbAttr {
|
export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageChangeAbAttr {
|
||||||
/**
|
/**
|
||||||
* The type of arena tag that conditions the stat change.
|
* The type of arena tag that conditions the stat change.
|
||||||
* @private
|
* @private
|
||||||
* @type {ArenaTagType}
|
|
||||||
*/
|
*/
|
||||||
private tagType: ArenaTagType;
|
private tagType: ArenaTagType;
|
||||||
|
|
||||||
@ -4972,7 +4976,7 @@ class ForceSwitchOutHelper {
|
|||||||
pokemon.scene.clearEnemyHeldItemModifiers();
|
pokemon.scene.clearEnemyHeldItemModifiers();
|
||||||
|
|
||||||
if (switchOutTarget.hp) {
|
if (switchOutTarget.hp) {
|
||||||
pokemon.scene.pushPhase(new BattleEndPhase(pokemon.scene));
|
pokemon.scene.pushPhase(new BattleEndPhase(pokemon.scene, false));
|
||||||
pokemon.scene.pushPhase(new NewBattlePhase(pokemon.scene));
|
pokemon.scene.pushPhase(new NewBattlePhase(pokemon.scene));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5709,9 +5713,7 @@ export function initAbilities() {
|
|||||||
.condition(getSheerForceHitDisableAbCondition()),
|
.condition(getSheerForceHitDisableAbCondition()),
|
||||||
new Ability(Abilities.SHEER_FORCE, 5)
|
new Ability(Abilities.SHEER_FORCE, 5)
|
||||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096)
|
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096)
|
||||||
.attr(MoveEffectChanceMultiplierAbAttr, 0)
|
.attr(MoveEffectChanceMultiplierAbAttr, 0), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
|
||||||
.edgeCase() // Should disable shell bell and Meloetta's relic song transformation
|
|
||||||
.edgeCase(), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
|
|
||||||
new Ability(Abilities.CONTRARY, 5)
|
new Ability(Abilities.CONTRARY, 5)
|
||||||
.attr(StatStageChangeMultiplierAbAttr, -1)
|
.attr(StatStageChangeMultiplierAbAttr, -1)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
@ -5779,9 +5781,10 @@ export function initAbilities() {
|
|||||||
.attr(WonderSkinAbAttr)
|
.attr(WonderSkinAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.ANALYTIC, 5)
|
new Ability(Abilities.ANALYTIC, 5)
|
||||||
.attr(MovePowerBoostAbAttr, (user, target, move) =>
|
.attr(MovePowerBoostAbAttr, (user, target, move) => {
|
||||||
!!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn)
|
const movePhase = user?.scene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user.id);
|
||||||
|| user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command !== Command.FIGHT, 1.3),
|
return Utils.isNullOrUndefined(movePhase);
|
||||||
|
}, 1.3),
|
||||||
new Ability(Abilities.ILLUSION, 5)
|
new Ability(Abilities.ILLUSION, 5)
|
||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
.attr(UnswappableAbilityAbAttr)
|
.attr(UnswappableAbilityAbAttr)
|
||||||
@ -5929,10 +5932,10 @@ export function initAbilities() {
|
|||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
||||||
new Ability(Abilities.WIMP_OUT, 7)
|
new Ability(Abilities.WIMP_OUT, 7)
|
||||||
.attr(PostDamageForceSwitchAbAttr)
|
.attr(PostDamageForceSwitchAbAttr)
|
||||||
.edgeCase(), // Should not trigger when hurting itself in confusion
|
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
|
||||||
new Ability(Abilities.EMERGENCY_EXIT, 7)
|
new Ability(Abilities.EMERGENCY_EXIT, 7)
|
||||||
.attr(PostDamageForceSwitchAbAttr)
|
.attr(PostDamageForceSwitchAbAttr)
|
||||||
.edgeCase(), // Should not trigger when hurting itself in confusion
|
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
|
||||||
new Ability(Abilities.WATER_COMPACTION, 7)
|
new Ability(Abilities.WATER_COMPACTION, 7)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
||||||
new Ability(Abilities.MERCILESS, 7)
|
new Ability(Abilities.MERCILESS, 7)
|
||||||
@ -5948,7 +5951,7 @@ export function initAbilities() {
|
|||||||
.bypassFaint()
|
.bypassFaint()
|
||||||
.partial(), // Meteor form should protect against status effects and yawn
|
.partial(), // Meteor form should protect against status effects and yawn
|
||||||
new Ability(Abilities.STAKEOUT, 7)
|
new Ability(Abilities.STAKEOUT, 7)
|
||||||
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command === Command.POKEMON, 2),
|
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.turnData.switchedInThisTurn, 2),
|
||||||
new Ability(Abilities.WATER_BUBBLE, 7)
|
new Ability(Abilities.WATER_BUBBLE, 7)
|
||||||
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
|
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
|
||||||
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2)
|
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2)
|
||||||
@ -6340,8 +6343,7 @@ export function initAbilities() {
|
|||||||
.attr(IgnoreOpponentStatStagesAbAttr, [ Stat.EVA ])
|
.attr(IgnoreOpponentStatStagesAbAttr, [ Stat.EVA ])
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SUPERSWEET_SYRUP, 9)
|
new Ability(Abilities.SUPERSWEET_SYRUP, 9)
|
||||||
.attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1)
|
.attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1),
|
||||||
.condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)),
|
|
||||||
new Ability(Abilities.HOSPITALITY, 9)
|
new Ability(Abilities.HOSPITALITY, 9)
|
||||||
.attr(PostSummonAllyHealAbAttr, 4, true),
|
.attr(PostSummonAllyHealAbAttr, 4, true),
|
||||||
new Ability(Abilities.TOXIC_CHAIN, 9)
|
new Ability(Abilities.TOXIC_CHAIN, 9)
|
||||||
|
@ -1144,7 +1144,7 @@ class FireGrassPledgeTag extends ArenaTag {
|
|||||||
? arena.scene.getPlayerField()
|
? arena.scene.getPlayerField()
|
||||||
: arena.scene.getEnemyField();
|
: arena.scene.getEnemyField();
|
||||||
|
|
||||||
field.filter(pokemon => !pokemon.isOfType(Type.FIRE)).forEach(pokemon => {
|
field.filter(pokemon => !pokemon.isOfType(Type.FIRE) && !pokemon.switchOutStatus).forEach(pokemon => {
|
||||||
// "{pokemonNameWithAffix} was hurt by the sea of fire!"
|
// "{pokemonNameWithAffix} was hurt by the sea of fire!"
|
||||||
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||||
// TODO: Replace this with a proper animation
|
// TODO: Replace this with a proper animation
|
||||||
|
@ -16,9 +16,9 @@ interface PokemonSpeciesFormLevelMoves {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Moves that can only be learned with a memory-mushroom */
|
/** Moves that can only be learned with a memory-mushroom */
|
||||||
const RELEARN_MOVE = -1;
|
export const RELEARN_MOVE = -1;
|
||||||
/** Moves that can only be learned with an evolve */
|
/** Moves that can only be learned with an evolve */
|
||||||
const EVOLVE_MOVE = 0;
|
export const EVOLVE_MOVE = 0;
|
||||||
|
|
||||||
export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
|
export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
|
||||||
[Species.BULBASAUR]: [
|
[Species.BULBASAUR]: [
|
||||||
|
46
src/data/balance/special-species-groups.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Species } from "#enums/species";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all {@link https://bulbapedia.bulbagarden.net/wiki/Paradox_Pok%C3%A9mon | Paradox Pokemon}, NOT including the legendaries Miraidon and Koraidon.
|
||||||
|
*/
|
||||||
|
export const NON_LEGEND_PARADOX_POKEMON = [
|
||||||
|
Species.GREAT_TUSK,
|
||||||
|
Species.SCREAM_TAIL,
|
||||||
|
Species.BRUTE_BONNET,
|
||||||
|
Species.FLUTTER_MANE,
|
||||||
|
Species.SLITHER_WING,
|
||||||
|
Species.SANDY_SHOCKS,
|
||||||
|
Species.ROARING_MOON,
|
||||||
|
Species.WALKING_WAKE,
|
||||||
|
Species.GOUGING_FIRE,
|
||||||
|
Species.RAGING_BOLT,
|
||||||
|
Species.IRON_TREADS,
|
||||||
|
Species.IRON_BUNDLE,
|
||||||
|
Species.IRON_HANDS,
|
||||||
|
Species.IRON_JUGULIS,
|
||||||
|
Species.IRON_MOTH,
|
||||||
|
Species.IRON_THORNS,
|
||||||
|
Species.IRON_VALIANT,
|
||||||
|
Species.IRON_LEAVES,
|
||||||
|
Species.IRON_BOULDER,
|
||||||
|
Species.IRON_CROWN,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all {@link https://bulbapedia.bulbagarden.net/wiki/Ultra_Beast | Ultra Beasts}, NOT including legendaries such as Necrozma or the Cosmog line.
|
||||||
|
*
|
||||||
|
* Note that all of these Ultra Beasts are still considered Sub-Legendary.
|
||||||
|
*/
|
||||||
|
export const NON_LEGEND_ULTRA_BEASTS = [
|
||||||
|
Species.NIHILEGO,
|
||||||
|
Species.BUZZWOLE,
|
||||||
|
Species.PHEROMOSA,
|
||||||
|
Species.XURKITREE,
|
||||||
|
Species.CELESTEELA,
|
||||||
|
Species.KARTANA,
|
||||||
|
Species.GUZZLORD,
|
||||||
|
Species.POIPOLE,
|
||||||
|
Species.NAGANADEL,
|
||||||
|
Species.STAKATAKA,
|
||||||
|
Species.BLACEPHALON,
|
||||||
|
];
|
@ -3,10 +3,10 @@ import { Species } from "#enums/species";
|
|||||||
export const POKERUS_STARTER_COUNT = 5;
|
export const POKERUS_STARTER_COUNT = 5;
|
||||||
|
|
||||||
// #region Friendship constants
|
// #region Friendship constants
|
||||||
export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2;
|
export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 3;
|
||||||
export const FRIENDSHIP_GAIN_FROM_BATTLE = 2;
|
export const FRIENDSHIP_GAIN_FROM_BATTLE = 3;
|
||||||
export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5;
|
export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 6;
|
||||||
export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
|
export const FRIENDSHIP_LOSS_FROM_FAINT = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to get the cumulative friendship threshold at which a candy is earned
|
* Function to get the cumulative friendship threshold at which a candy is earned
|
||||||
@ -16,19 +16,19 @@ export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
|
|||||||
export function getStarterValueFriendshipCap(starterCost: number): number {
|
export function getStarterValueFriendshipCap(starterCost: number): number {
|
||||||
switch (starterCost) {
|
switch (starterCost) {
|
||||||
case 1:
|
case 1:
|
||||||
return 20;
|
return 25;
|
||||||
case 2:
|
case 2:
|
||||||
return 40;
|
return 50;
|
||||||
case 3:
|
case 3:
|
||||||
return 60;
|
return 75;
|
||||||
case 4:
|
case 4:
|
||||||
return 100;
|
return 100;
|
||||||
case 5:
|
case 5:
|
||||||
return 140;
|
return 150;
|
||||||
case 6:
|
case 6:
|
||||||
return 200;
|
return 200;
|
||||||
case 7:
|
case 7:
|
||||||
return 280;
|
return 300;
|
||||||
case 8:
|
case 8:
|
||||||
case 9:
|
case 9:
|
||||||
return 450;
|
return 450;
|
||||||
|
@ -90,6 +90,7 @@ export enum CommonAnim {
|
|||||||
RAGING_BULL_FIRE,
|
RAGING_BULL_FIRE,
|
||||||
RAGING_BULL_WATER,
|
RAGING_BULL_WATER,
|
||||||
SALT_CURE,
|
SALT_CURE,
|
||||||
|
POWDER,
|
||||||
SUNNY = 2100,
|
SUNNY = 2100,
|
||||||
RAIN,
|
RAIN,
|
||||||
SANDSTORM,
|
SANDSTORM,
|
||||||
|
@ -856,6 +856,57 @@ export class SeedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BattlerTag representing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Powder_(move) | Powder}.
|
||||||
|
* When the afflicted Pokemon uses a Fire-type move, the move is cancelled, and the
|
||||||
|
* Pokemon takes damage equal to 1/4 of it's maximum HP (rounded down).
|
||||||
|
*/
|
||||||
|
export class PowderTag extends BattlerTag {
|
||||||
|
constructor() {
|
||||||
|
super(BattlerTagType.POWDER, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
super.onAdd(pokemon);
|
||||||
|
|
||||||
|
// "{Pokemon} is covered in powder!"
|
||||||
|
pokemon.scene.queueMessage(i18next.t("battlerTags:powderOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies Powder's effects before the tag owner uses a Fire-type move.
|
||||||
|
* Also causes the tag to expire at the end of turn.
|
||||||
|
* @param pokemon {@linkcode Pokemon} the owner of this tag
|
||||||
|
* @param lapseType {@linkcode BattlerTagLapseType} the type of lapse functionality to carry out
|
||||||
|
* @returns `true` if the tag should not expire after this lapse; `false` otherwise.
|
||||||
|
*/
|
||||||
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
|
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
||||||
|
const movePhase = pokemon.scene.getCurrentPhase();
|
||||||
|
if (movePhase instanceof MovePhase) {
|
||||||
|
const move = movePhase.move.getMove();
|
||||||
|
if (pokemon.getMoveType(move) === Type.FIRE) {
|
||||||
|
movePhase.cancel();
|
||||||
|
|
||||||
|
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.POWDER));
|
||||||
|
|
||||||
|
const cancelDamage = new BooleanHolder(false);
|
||||||
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage);
|
||||||
|
if (!cancelDamage.value) {
|
||||||
|
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), HitResult.OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "When the flame touched the powder\non the Pokémon, it exploded!"
|
||||||
|
pokemon.scene.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return super.lapse(pokemon, lapseType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class NightmareTag extends BattlerTag {
|
export class NightmareTag extends BattlerTag {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, Moves.NIGHTMARE);
|
super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, Moves.NIGHTMARE);
|
||||||
@ -1085,10 +1136,6 @@ export class OctolockTag extends TrappedTag {
|
|||||||
super(BattlerTagType.OCTOLOCK, BattlerTagLapseType.TURN_END, 1, Moves.OCTOLOCK, sourceId);
|
super(BattlerTagType.OCTOLOCK, BattlerTagLapseType.TURN_END, 1, Moves.OCTOLOCK, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
|
||||||
return !pokemon.getTag(BattlerTagType.OCTOLOCK);
|
|
||||||
}
|
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
@ -2959,6 +3006,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||||||
return new InfatuatedTag(sourceMove, sourceId);
|
return new InfatuatedTag(sourceMove, sourceId);
|
||||||
case BattlerTagType.SEEDED:
|
case BattlerTagType.SEEDED:
|
||||||
return new SeedTag(sourceId);
|
return new SeedTag(sourceId);
|
||||||
|
case BattlerTagType.POWDER:
|
||||||
|
return new PowderTag();
|
||||||
case BattlerTagType.NIGHTMARE:
|
case BattlerTagType.NIGHTMARE:
|
||||||
return new NightmareTag();
|
return new NightmareTag();
|
||||||
case BattlerTagType.FRENZY:
|
case BattlerTagType.FRENZY:
|
||||||
|
285
src/data/move.ts
@ -1385,14 +1385,38 @@ export class UserHpDamageAttr extends FixedDamageAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TargetHalfHpDamageAttr extends FixedDamageAttr {
|
export class TargetHalfHpDamageAttr extends FixedDamageAttr {
|
||||||
|
// the initial amount of hp the target had before the first hit
|
||||||
|
// used for multi lens
|
||||||
|
private initialHp: number;
|
||||||
constructor() {
|
constructor() {
|
||||||
super(0);
|
super(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
(args[0] as Utils.IntegerHolder).value = Utils.toDmgValue(target.hp / 2);
|
// first, determine if the hit is coming from multi lens or not
|
||||||
|
const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0;
|
||||||
|
if (lensCount <= 0) {
|
||||||
|
// no multi lenses; we can just halve the target's hp and call it a day
|
||||||
|
(args[0] as Utils.NumberHolder).value = Utils.toDmgValue(target.hp / 2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
// figure out what hit # we're on
|
||||||
|
switch (user.turnData.hitCount - user.turnData.hitsLeft) {
|
||||||
|
case 0:
|
||||||
|
// first hit of move; update initialHp tracker
|
||||||
|
this.initialHp = target.hp;
|
||||||
|
default:
|
||||||
|
// multi lens added hit; use initialHp tracker to ensure correct damage
|
||||||
|
(args[0] as Utils.NumberHolder).value = Utils.toDmgValue(this.initialHp / 2);
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case lensCount + 1:
|
||||||
|
// parental bond added hit; calc damage as normal
|
||||||
|
(args[0] as Utils.NumberHolder).value = Utils.toDmgValue(target.hp / 2);
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||||
@ -1843,7 +1867,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
|
|||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value || !targetAlly) {
|
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5976,50 +6000,113 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
|
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
|
||||||
* If it did, the user of U-turn or Volt Switch will not be switched out.
|
* If it did, the user of U-turn or Volt Switch will not be switched out.
|
||||||
*/
|
*/
|
||||||
if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) &&
|
if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr)
|
||||||
(move.id === Moves.U_TURN || move.id === Moves.VOLT_SWITCH || move.id === Moves.FLIP_TURN)
|
&& [ Moves.U_TURN, Moves.VOLT_SWITCH, Moves.FLIP_TURN ].includes(move.id)
|
||||||
) {
|
) {
|
||||||
if (this.hpDroppedBelowHalf(target)) {
|
if (this.hpDroppedBelowHalf(target)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Switch out logic for the player's Pokemon
|
|
||||||
if (switchOutTarget.scene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
|
// Find indices of off-field Pokemon that are eligible to be switched into
|
||||||
|
const eligibleNewIndices: number[] = [];
|
||||||
|
switchOutTarget.scene.getPlayerParty().forEach((pokemon, index) => {
|
||||||
|
if (pokemon.isAllowedInBattle() && !pokemon.isOnField()) {
|
||||||
|
eligibleNewIndices.push(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (eligibleNewIndices.length < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (switchOutTarget.hp > 0) {
|
if (switchOutTarget.hp > 0) {
|
||||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
if (this.switchType === SwitchType.FORCE_SWITCH) {
|
||||||
user.scene.prependToPhase(new SwitchPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase);
|
switchOutTarget.leaveField(true);
|
||||||
return true;
|
const slotIndex = eligibleNewIndices[user.randSeedInt(eligibleNewIndices.length)];
|
||||||
|
user.scene.prependToPhase(
|
||||||
|
new SwitchSummonPhase(
|
||||||
|
user.scene,
|
||||||
|
this.switchType,
|
||||||
|
switchOutTarget.getFieldIndex(),
|
||||||
|
slotIndex,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
MoveEndPhase
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||||
|
user.scene.prependToPhase(
|
||||||
|
new SwitchPhase(
|
||||||
|
user.scene,
|
||||||
|
this.switchType,
|
||||||
|
switchOutTarget.getFieldIndex(),
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
MoveEndPhase
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
|
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) { // Switch out logic for enemy trainers
|
||||||
// Switch out logic for trainer battles
|
// Find indices of off-field Pokemon that are eligible to be switched into
|
||||||
if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
|
const eligibleNewIndices: number[] = [];
|
||||||
|
switchOutTarget.scene.getEnemyParty().forEach((pokemon, index) => {
|
||||||
|
if (pokemon.isAllowedInBattle() && !pokemon.isOnField()) {
|
||||||
|
eligibleNewIndices.push(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (eligibleNewIndices.length < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (switchOutTarget.hp > 0) {
|
if (switchOutTarget.hp > 0) {
|
||||||
// for opponent switching out
|
if (this.switchType === SwitchType.FORCE_SWITCH) {
|
||||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
switchOutTarget.leaveField(true);
|
||||||
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(),
|
const slotIndex = eligibleNewIndices[user.randSeedInt(eligibleNewIndices.length)];
|
||||||
(user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
|
user.scene.prependToPhase(
|
||||||
false, false), MoveEndPhase);
|
new SwitchSummonPhase(
|
||||||
|
user.scene,
|
||||||
|
this.switchType,
|
||||||
|
switchOutTarget.getFieldIndex(),
|
||||||
|
slotIndex,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
MoveEndPhase
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||||
|
user.scene.prependToPhase(
|
||||||
|
new SwitchSummonPhase(
|
||||||
|
user.scene,
|
||||||
|
this.switchType,
|
||||||
|
switchOutTarget.getFieldIndex(),
|
||||||
|
(user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
MoveEndPhase
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else { // Switch out logic for wild pokemon
|
||||||
/**
|
/**
|
||||||
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
|
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
|
||||||
* If it did, the user of U-turn or Volt Switch will not be switched out.
|
* If it did, the user of U-turn or Volt Switch will not be switched out.
|
||||||
*/
|
*/
|
||||||
if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) &&
|
if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr)
|
||||||
(move.id === Moves.U_TURN || move.id === Moves.VOLT_SWITCH) || move.id === Moves.FLIP_TURN) {
|
&& [ Moves.U_TURN, Moves.VOLT_SWITCH, Moves.FLIP_TURN ].includes(move.id)
|
||||||
|
) {
|
||||||
if (this.hpDroppedBelowHalf(target)) {
|
if (this.hpDroppedBelowHalf(target)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch out logic for everything else (eg: WILD battles)
|
|
||||||
if (user.scene.currentBattle.waveIndex % 10 === 0) {
|
if (user.scene.currentBattle.waveIndex % 10 === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -6044,7 +6131,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
user.scene.clearEnemyHeldItemModifiers();
|
user.scene.clearEnemyHeldItemModifiers();
|
||||||
|
|
||||||
if (switchOutTarget.hp) {
|
if (switchOutTarget.hp) {
|
||||||
user.scene.pushPhase(new BattleEndPhase(user.scene));
|
user.scene.pushPhase(new BattleEndPhase(user.scene, false));
|
||||||
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6650,6 +6737,126 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute used for moves that causes the target to repeat their last used move.
|
||||||
|
*
|
||||||
|
* Used for [Instruct](https://bulbapedia.bulbagarden.net/wiki/Instruct_(move)).
|
||||||
|
*/
|
||||||
|
export class RepeatMoveAttr extends MoveEffectAttr {
|
||||||
|
constructor() {
|
||||||
|
super(false, { trigger: MoveEffectTrigger.POST_APPLY }); // needed to ensure correct protect interaction
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the target to re-use their last used move again
|
||||||
|
*
|
||||||
|
* @param user {@linkcode Pokemon} that used the attack
|
||||||
|
* @param target {@linkcode Pokemon} targeted by the attack
|
||||||
|
* @param move N/A
|
||||||
|
* @param args N/A
|
||||||
|
* @returns `true` if the move succeeds
|
||||||
|
*/
|
||||||
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
|
// get the last move used (excluding status based failures) as well as the corresponding moveset slot
|
||||||
|
const lastMove = target.getLastXMoves(-1).find(m => m.move !== Moves.NONE)!;
|
||||||
|
const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove.move)!;
|
||||||
|
const moveTargets = lastMove.targets ?? [];
|
||||||
|
|
||||||
|
user.scene.queueMessage(i18next.t("moveTriggers:instructingMove", {
|
||||||
|
userPokemonName: getPokemonNameWithAffix(user),
|
||||||
|
targetPokemonName: getPokemonNameWithAffix(target)
|
||||||
|
}));
|
||||||
|
target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false });
|
||||||
|
target.turnData.extraTurns++;
|
||||||
|
target.scene.appendToPhase(new MovePhase(target.scene, target, moveTargets, movesetMove), MoveEndPhase);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCondition(): MoveConditionFunc {
|
||||||
|
return (user, target, move) => {
|
||||||
|
// TODO: Confirm behavior of instructing move known by target but called by another move
|
||||||
|
const lastMove = target.getLastXMoves(-1).find(m => m.move !== Moves.NONE);
|
||||||
|
const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove?.move);
|
||||||
|
const moveTargets = lastMove?.targets ?? [];
|
||||||
|
// TODO: Add a way of adding moves to list procedurally rather than a pre-defined blacklist
|
||||||
|
const unrepeatablemoves = [
|
||||||
|
// Locking/Continually Executed moves
|
||||||
|
Moves.OUTRAGE,
|
||||||
|
Moves.RAGING_FURY,
|
||||||
|
Moves.ROLLOUT,
|
||||||
|
Moves.PETAL_DANCE,
|
||||||
|
Moves.THRASH,
|
||||||
|
Moves.ICE_BALL,
|
||||||
|
// Multi-turn Moves
|
||||||
|
Moves.BIDE,
|
||||||
|
Moves.SHELL_TRAP,
|
||||||
|
Moves.BEAK_BLAST,
|
||||||
|
Moves.FOCUS_PUNCH,
|
||||||
|
// "First Turn Only" moves
|
||||||
|
Moves.FAKE_OUT,
|
||||||
|
Moves.FIRST_IMPRESSION,
|
||||||
|
Moves.MAT_BLOCK,
|
||||||
|
// Moves with a recharge turn
|
||||||
|
Moves.HYPER_BEAM,
|
||||||
|
Moves.ETERNABEAM,
|
||||||
|
Moves.FRENZY_PLANT,
|
||||||
|
Moves.BLAST_BURN,
|
||||||
|
Moves.HYDRO_CANNON,
|
||||||
|
Moves.GIGA_IMPACT,
|
||||||
|
Moves.PRISMATIC_LASER,
|
||||||
|
Moves.ROAR_OF_TIME,
|
||||||
|
Moves.ROCK_WRECKER,
|
||||||
|
Moves.METEOR_ASSAULT,
|
||||||
|
// Charging & 2-turn moves
|
||||||
|
Moves.DIG,
|
||||||
|
Moves.FLY,
|
||||||
|
Moves.BOUNCE,
|
||||||
|
Moves.SHADOW_FORCE,
|
||||||
|
Moves.PHANTOM_FORCE,
|
||||||
|
Moves.DIVE,
|
||||||
|
Moves.ELECTRO_SHOT,
|
||||||
|
Moves.ICE_BURN,
|
||||||
|
Moves.GEOMANCY,
|
||||||
|
Moves.FREEZE_SHOCK,
|
||||||
|
Moves.SKY_DROP,
|
||||||
|
Moves.SKY_ATTACK,
|
||||||
|
Moves.SKULL_BASH,
|
||||||
|
Moves.SOLAR_BEAM,
|
||||||
|
Moves.SOLAR_BLADE,
|
||||||
|
Moves.METEOR_BEAM,
|
||||||
|
// Other moves
|
||||||
|
Moves.INSTRUCT,
|
||||||
|
Moves.KINGS_SHIELD,
|
||||||
|
Moves.SKETCH,
|
||||||
|
Moves.TRANSFORM,
|
||||||
|
Moves.MIMIC,
|
||||||
|
Moves.STRUGGLE,
|
||||||
|
// TODO: Add Max/G-Move blockage if or when they are implemented
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!movesetMove // called move not in target's moveset (dancer, forgetting the move, etc.)
|
||||||
|
|| movesetMove.ppUsed === movesetMove.getMovePp() // move out of pp
|
||||||
|
|| allMoves[lastMove?.move ?? Moves.NONE].isChargingMove() // called move is a charging/recharging move
|
||||||
|
|| !moveTargets.length // called move has no targets
|
||||||
|
|| unrepeatablemoves.includes(lastMove?.move ?? Moves.NONE)) { // called move is explicitly in the banlist
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||||
|
// TODO: Make the AI acutally use instruct
|
||||||
|
/* Ideally, the AI would score instruct based on the scorings of the on-field pokemons'
|
||||||
|
* last used moves at the time of using Instruct (by the time the instructor gets to act)
|
||||||
|
* with respect to the user's side.
|
||||||
|
* In 99.9% of cases, this would be the pokemon's ally (unless the target had last
|
||||||
|
* used a move like Decorate on the user or its ally)
|
||||||
|
*/
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute used for moves that reduce PP of the target's last used move.
|
* Attribute used for moves that reduce PP of the target's last used move.
|
||||||
* Used for Spite.
|
* Used for Spite.
|
||||||
@ -7463,6 +7670,8 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po
|
|||||||
return party.some(pokemon => pokemon.isActive() && !pokemon.isOnField());
|
return party.some(pokemon => pokemon.isActive() && !pokemon.isOnField());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(Type.GHOST);
|
||||||
|
|
||||||
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||||
|
|
||||||
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
|
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
|
||||||
@ -7786,11 +7995,10 @@ export function initMoves() {
|
|||||||
.windMove(),
|
.windMove(),
|
||||||
new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1),
|
new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1),
|
||||||
new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1)
|
new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1)
|
||||||
.attr(ForceSwitchOutAttr)
|
.attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.hidesTarget()
|
.hidesTarget()
|
||||||
.windMove()
|
.windMove(),
|
||||||
.partial(), // Should force random switches
|
|
||||||
new ChargingAttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1)
|
new ChargingAttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1)
|
||||||
.chargeText(i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" }))
|
.chargeText(i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" }))
|
||||||
.chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING)
|
.chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING)
|
||||||
@ -7866,10 +8074,9 @@ export function initMoves() {
|
|||||||
.soundBased()
|
.soundBased()
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new StatusMove(Moves.ROAR, Type.NORMAL, -1, 20, -1, -6, 1)
|
new StatusMove(Moves.ROAR, Type.NORMAL, -1, 20, -1, -6, 1)
|
||||||
.attr(ForceSwitchOutAttr)
|
.attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH)
|
||||||
.soundBased()
|
.soundBased()
|
||||||
.hidesTarget()
|
.hidesTarget(),
|
||||||
.partial(), // Should force random switching
|
|
||||||
new StatusMove(Moves.SING, Type.NORMAL, 55, 15, -1, 0, 1)
|
new StatusMove(Moves.SING, Type.NORMAL, 55, 15, -1, 0, 1)
|
||||||
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
||||||
.soundBased(),
|
.soundBased(),
|
||||||
@ -8211,6 +8418,7 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.THIEF, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2)
|
new AttackMove(Moves.THIEF, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2)
|
||||||
.attr(StealHeldItemChanceAttr, 0.3),
|
.attr(StealHeldItemChanceAttr, 0.3),
|
||||||
new StatusMove(Moves.SPIDER_WEB, Type.BUG, -1, 10, -1, 0, 2)
|
new StatusMove(Moves.SPIDER_WEB, Type.BUG, -1, 10, -1, 0, 2)
|
||||||
|
.condition(failIfGhostTypeCondition)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
||||||
new StatusMove(Moves.MIND_READER, Type.NORMAL, -1, 5, -1, 0, 2)
|
new StatusMove(Moves.MIND_READER, Type.NORMAL, -1, 5, -1, 0, 2)
|
||||||
.attr(IgnoreAccuracyAttr),
|
.attr(IgnoreAccuracyAttr),
|
||||||
@ -8347,6 +8555,7 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.STEEL_WING, Type.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, 10, 0, 2)
|
new AttackMove(Moves.STEEL_WING, Type.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, 10, 0, 2)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, true),
|
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, true),
|
||||||
new StatusMove(Moves.MEAN_LOOK, Type.NORMAL, -1, 5, -1, 0, 2)
|
new StatusMove(Moves.MEAN_LOOK, Type.NORMAL, -1, 5, -1, 0, 2)
|
||||||
|
.condition(failIfGhostTypeCondition)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
||||||
new StatusMove(Moves.ATTRACT, Type.NORMAL, 100, 15, -1, 0, 2)
|
new StatusMove(Moves.ATTRACT, Type.NORMAL, 100, 15, -1, 0, 2)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.INFATUATED)
|
.attr(AddBattlerTagAttr, BattlerTagType.INFATUATED)
|
||||||
@ -8736,6 +8945,7 @@ export function initMoves() {
|
|||||||
new SelfStatusMove(Moves.IRON_DEFENSE, Type.STEEL, -1, 15, -1, 0, 3)
|
new SelfStatusMove(Moves.IRON_DEFENSE, Type.STEEL, -1, 15, -1, 0, 3)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
|
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
|
||||||
new StatusMove(Moves.BLOCK, Type.NORMAL, -1, 5, -1, 0, 3)
|
new StatusMove(Moves.BLOCK, Type.NORMAL, -1, 5, -1, 0, 3)
|
||||||
|
.condition(failIfGhostTypeCondition)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
||||||
new StatusMove(Moves.HOWL, Type.NORMAL, -1, 40, -1, 0, 3)
|
new StatusMove(Moves.HOWL, Type.NORMAL, -1, 40, -1, 0, 3)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK ], 1)
|
.attr(StatStageChangeAttr, [ Stat.ATK ], 1)
|
||||||
@ -9247,8 +9457,8 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.ATK ], 1, true)
|
.attr(StatStageChangeAttr, [ Stat.ATK ], 1, true)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true),
|
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true),
|
||||||
new AttackMove(Moves.CIRCLE_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
|
new AttackMove(Moves.CIRCLE_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
|
||||||
.attr(ForceSwitchOutAttr)
|
.attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH)
|
||||||
.partial(), // Should force random switches
|
.hidesTarget(),
|
||||||
new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||||
.attr(RemoveHeldItemAttr, true),
|
.attr(RemoveHeldItemAttr, true),
|
||||||
@ -9316,9 +9526,8 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.FROST_BREATH, Type.ICE, MoveCategory.SPECIAL, 60, 90, 10, 100, 0, 5)
|
new AttackMove(Moves.FROST_BREATH, Type.ICE, MoveCategory.SPECIAL, 60, 90, 10, 100, 0, 5)
|
||||||
.attr(CritOnlyAttr),
|
.attr(CritOnlyAttr),
|
||||||
new AttackMove(Moves.DRAGON_TAIL, Type.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
|
new AttackMove(Moves.DRAGON_TAIL, Type.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
|
||||||
.attr(ForceSwitchOutAttr)
|
.attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH)
|
||||||
.hidesTarget()
|
.hidesTarget(),
|
||||||
.partial(), // Should force random switches
|
|
||||||
new SelfStatusMove(Moves.WORK_UP, Type.NORMAL, -1, 30, -1, 0, 5)
|
new SelfStatusMove(Moves.WORK_UP, Type.NORMAL, -1, 30, -1, 0, 5)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, true),
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, true),
|
||||||
new AttackMove(Moves.ELECTROWEB, Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5)
|
new AttackMove(Moves.ELECTROWEB, Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5)
|
||||||
@ -9548,9 +9757,10 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], -1, false, { condition: (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC })
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], -1, false, { condition: (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC })
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
|
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
|
||||||
|
.attr(AddBattlerTagAttr, BattlerTagType.POWDER, false, true)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.powderMove()
|
.powderMove()
|
||||||
.unimplemented(),
|
.edgeCase(), // does not cancel Fire-type moves generated by Dancer
|
||||||
new ChargingSelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6)
|
new ChargingSelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6)
|
||||||
.chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" }))
|
.chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" }))
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
|
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
|
||||||
@ -9832,7 +10042,8 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
||||||
new StatusMove(Moves.INSTRUCT, Type.PSYCHIC, -1, 15, -1, 0, 7)
|
new StatusMove(Moves.INSTRUCT, Type.PSYCHIC, -1, 15, -1, 0, 7)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.unimplemented(),
|
.attr(RepeatMoveAttr)
|
||||||
|
.edgeCase(), // incorrect interactions with Gigaton Hammer, Blood Moon & Torment
|
||||||
new AttackMove(Moves.BEAK_BLAST, Type.FLYING, MoveCategory.PHYSICAL, 100, 100, 15, -1, -3, 7)
|
new AttackMove(Moves.BEAK_BLAST, Type.FLYING, MoveCategory.PHYSICAL, 100, 100, 15, -1, -3, 7)
|
||||||
.attr(BeakBlastHeaderAttr)
|
.attr(BeakBlastHeaderAttr)
|
||||||
.ballBombMove()
|
.ballBombMove()
|
||||||
@ -10041,6 +10252,7 @@ export function initMoves() {
|
|||||||
.attr(EatBerryAttr)
|
.attr(EatBerryAttr)
|
||||||
.target(MoveTarget.ALL),
|
.target(MoveTarget.ALL),
|
||||||
new StatusMove(Moves.OCTOLOCK, Type.FIGHTING, 100, 15, -1, 0, 8)
|
new StatusMove(Moves.OCTOLOCK, Type.FIGHTING, 100, 15, -1, 0, 8)
|
||||||
|
.condition(failIfGhostTypeCondition)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.OCTOLOCK, false, true, 1),
|
.attr(AddBattlerTagAttr, BattlerTagType.OCTOLOCK, false, true, 1),
|
||||||
new AttackMove(Moves.BOLT_BEAK, Type.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8)
|
new AttackMove(Moves.BOLT_BEAK, Type.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8)
|
||||||
.attr(FirstAttackDoublePowerAttr),
|
.attr(FirstAttackDoublePowerAttr),
|
||||||
@ -10589,6 +10801,7 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.TWIN_BEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 40, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.TWIN_BEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 40, 100, 10, -1, 0, 9)
|
||||||
.attr(MultiHitAttr, MultiHitType._2),
|
.attr(MultiHitAttr, MultiHitType._2),
|
||||||
new AttackMove(Moves.RAGE_FIST, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.RAGE_FIST, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9)
|
||||||
|
.partial() // Counter resets every wave instead of on arena reset
|
||||||
.attr(HitCountPowerAttr)
|
.attr(HitCountPowerAttr)
|
||||||
.punchingMove(),
|
.punchingMove(),
|
||||||
new AttackMove(Moves.ARMOR_CANNON, Type.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
new AttackMove(Moves.ARMOR_CANNON, Type.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
||||||
|
@ -216,6 +216,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||||||
species: getPokemonSpecies(Species.GREEDENT),
|
species: getPokemonSpecies(Species.GREEDENT),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 3,
|
bossSegments: 3,
|
||||||
|
shiny: false, // Shiny lock because of consistency issues between the different options
|
||||||
moveSet: [ Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH ],
|
moveSet: [ Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH ],
|
||||||
modifierConfigs: bossModifierConfigs,
|
modifierConfigs: bossModifierConfigs,
|
||||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||||
@ -353,9 +354,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||||||
})
|
})
|
||||||
.withOptionPhase(async (scene: BattleScene) => {
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
// Let it have the food
|
// Let it have the food
|
||||||
// Greedent joins the team, level equal to 2 below highest party member
|
// Greedent joins the team, level equal to 2 below highest party member (shiny locked)
|
||||||
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
|
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
|
||||||
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false);
|
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true);
|
||||||
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
|
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
|
||||||
greedent.passive = true;
|
greedent.passive = true;
|
||||||
|
|
||||||
|
@ -98,7 +98,9 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
|||||||
tint: 0.25,
|
tint: 0.25,
|
||||||
x: -5,
|
x: -5,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
isPokemon: true
|
isPokemon: true,
|
||||||
|
isShiny: bossPokemon.shiny,
|
||||||
|
variant: bossPokemon.variant
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -276,6 +276,8 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
|||||||
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
|
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
|
||||||
|
|
||||||
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||||
|
// For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier
|
||||||
|
// And Golden Eggs as Rogue tier
|
||||||
let numUltra = 0;
|
let numUltra = 0;
|
||||||
let numRogue = 0;
|
let numRogue = 0;
|
||||||
items.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
items.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||||
@ -285,7 +287,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
|||||||
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||||
numRogue += m.stackCount;
|
numRogue += m.stackCount;
|
||||||
scene.removeModifier(m);
|
scene.removeModifier(m);
|
||||||
} else if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) {
|
} else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) {
|
||||||
numUltra += m.stackCount;
|
numUltra += m.stackCount;
|
||||||
scene.removeModifier(m);
|
scene.removeModifier(m);
|
||||||
}
|
}
|
||||||
@ -456,7 +458,6 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem
|
|||||||
[ modifierTypes.LEFTOVERS, 4 ],
|
[ modifierTypes.LEFTOVERS, 4 ],
|
||||||
[ modifierTypes.SHELL_BELL, 4 ],
|
[ modifierTypes.SHELL_BELL, 4 ],
|
||||||
[ modifierTypes.SOUL_DEW, 10 ],
|
[ modifierTypes.SOUL_DEW, 10 ],
|
||||||
[ modifierTypes.SOOTHE_BELL, 3 ],
|
|
||||||
[ modifierTypes.SCOPE_LENS, 1 ],
|
[ modifierTypes.SCOPE_LENS, 1 ],
|
||||||
[ modifierTypes.BATON, 1 ],
|
[ modifierTypes.BATON, 1 ],
|
||||||
[ modifierTypes.FOCUS_BAND, 5 ],
|
[ modifierTypes.FOCUS_BAND, 5 ],
|
||||||
|
@ -92,9 +92,13 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
|||||||
.withCatchAllowed(true)
|
.withCatchAllowed(true)
|
||||||
.withFleeAllowed(false)
|
.withFleeAllowed(false)
|
||||||
.withOnVisualsStart((scene: BattleScene) => {
|
.withOnVisualsStart((scene: BattleScene) => {
|
||||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon()!);
|
const oricorio = scene.getEnemyPokemon()!;
|
||||||
danceAnim.play(scene);
|
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, scene.getPlayerPokemon()!);
|
||||||
|
danceAnim.play(scene, false, () => {
|
||||||
|
if (oricorio.shiny) {
|
||||||
|
oricorio.sparkle();
|
||||||
|
}
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
@ -136,7 +140,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const oricorioData = new PokemonData(enemyPokemon);
|
const oricorioData = new PokemonData(enemyPokemon);
|
||||||
const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, oricorioData);
|
const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
|
||||||
|
|
||||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||||
scene.getEnemyParty().forEach(enemyPokemon => {
|
scene.getEnemyParty().forEach(enemyPokemon => {
|
||||||
|
@ -8,7 +8,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
|
|||||||
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
|
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
|
||||||
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||||
@ -174,7 +174,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
|||||||
const roll = randSeedInt(100);
|
const roll = randSeedInt(100);
|
||||||
const starterTier: number | [number, number] =
|
const starterTier: number | [number, number] =
|
||||||
roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [ 9, 10 ];
|
roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [ 9, 10 ];
|
||||||
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterTier(starterTier, excludedBosses, bossTypes));
|
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterCost(starterTier, excludedBosses, bossTypes));
|
||||||
const pokemonConfig: EnemyPokemonConfig = {
|
const pokemonConfig: EnemyPokemonConfig = {
|
||||||
species: bossSpecies,
|
species: bossSpecies,
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
|
@ -114,7 +114,9 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
|||||||
tint: 0.25,
|
tint: 0.25,
|
||||||
x: -5,
|
x: -5,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
isPokemon: true
|
isPokemon: true,
|
||||||
|
isShiny: bossPokemon.shiny,
|
||||||
|
variant: bossPokemon.variant
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -194,10 +194,10 @@ async function summonPlayerPokemon(scene: BattleScene) {
|
|||||||
playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon);
|
playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also loads Wobbuffet data
|
// Also loads Wobbuffet data (cannot be shiny)
|
||||||
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
|
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
|
||||||
scene.currentBattle.enemyParty = [];
|
scene.currentBattle.enemyParty = [];
|
||||||
const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false);
|
const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true);
|
||||||
wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ];
|
wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ];
|
||||||
wobbuffet.setNature(Nature.MILD);
|
wobbuffet.setNature(Nature.MILD);
|
||||||
wobbuffet.setAlpha(0);
|
wobbuffet.setAlpha(0);
|
||||||
|
@ -12,8 +12,7 @@ import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon
|
|||||||
import { getTypeRgb } from "#app/data/type";
|
import { getTypeRgb } from "#app/data/type";
|
||||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import * as Utils from "#app/utils";
|
import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||||
import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
|
||||||
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
||||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
@ -27,6 +26,7 @@ import { trainerNamePools } from "#app/data/trainer-names";
|
|||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import type { PokeballType } from "#enums/pokeball";
|
import type { PokeballType } from "#enums/pokeball";
|
||||||
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/globalTradeSystem";
|
const namespace = "mysteryEncounters/globalTradeSystem";
|
||||||
@ -230,7 +230,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
|||||||
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
|
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
|
||||||
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
||||||
if (!tradePokemon.shiny) {
|
if (!tradePokemon.shiny) {
|
||||||
const shinyThreshold = new Utils.IntegerHolder(WONDER_TRADE_SHINY_CHANCE);
|
const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE);
|
||||||
if (scene.eventManager.isEventActive()) {
|
if (scene.eventManager.isEventActive()) {
|
||||||
shinyThreshold.value *= scene.eventManager.getShinyMultiplier();
|
shinyThreshold.value *= scene.eventManager.getShinyMultiplier();
|
||||||
}
|
}
|
||||||
@ -247,7 +247,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
|||||||
const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
|
const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
|
||||||
if (tradePokemon.species.abilityHidden) {
|
if (tradePokemon.species.abilityHidden) {
|
||||||
if (tradePokemon.abilityIndex < hiddenIndex) {
|
if (tradePokemon.abilityIndex < hiddenIndex) {
|
||||||
const hiddenAbilityChance = new IntegerHolder(64);
|
const hiddenAbilityChance = new NumberHolder(64);
|
||||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||||
|
|
||||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||||
@ -582,7 +582,13 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
|
|||||||
receivedPokemonTintSprite.setTintFill(getPokeballTintColor(receivedPokemon.pokeball));
|
receivedPokemonTintSprite.setTintFill(getPokeballTintColor(receivedPokemon.pokeball));
|
||||||
|
|
||||||
[ tradedPokemonSprite, tradedPokemonTintSprite ].map(sprite => {
|
[ tradedPokemonSprite, tradedPokemonTintSprite ].map(sprite => {
|
||||||
sprite.play(tradedPokemon.getSpriteKey(true));
|
const spriteKey = tradedPokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) });
|
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) });
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
sprite.setPipelineData("ignoreTimeTint", true);
|
||||||
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
|
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
|
||||||
@ -597,7 +603,13 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
|
|||||||
});
|
});
|
||||||
|
|
||||||
[ receivedPokemonSprite, receivedPokemonTintSprite ].map(sprite => {
|
[ receivedPokemonSprite, receivedPokemonTintSprite ].map(sprite => {
|
||||||
sprite.play(receivedPokemon.getSpriteKey(true));
|
const spriteKey = receivedPokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) });
|
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) });
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
sprite.setPipelineData("ignoreTimeTint", true);
|
||||||
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
|
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
|
||||||
@ -797,6 +809,14 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
|
|||||||
receivedPokeballSprite.x = tradeBaseBg.displayWidth / 2;
|
receivedPokeballSprite.x = tradeBaseBg.displayWidth / 2;
|
||||||
receivedPokeballSprite.y = tradeBaseBg.displayHeight / 2 - 100;
|
receivedPokeballSprite.y = tradeBaseBg.displayHeight / 2 - 100;
|
||||||
|
|
||||||
|
// Received pokemon sparkles
|
||||||
|
let pokemonShinySparkle: Phaser.GameObjects.Sprite;
|
||||||
|
if (receivedPokemon.shiny) {
|
||||||
|
pokemonShinySparkle = scene.add.sprite(receivedPokemonSprite.x, receivedPokemonSprite.y, "shiny");
|
||||||
|
pokemonShinySparkle.setVisible(false);
|
||||||
|
tradeContainer.add(pokemonShinySparkle);
|
||||||
|
}
|
||||||
|
|
||||||
const BASE_ANIM_DURATION = 1000;
|
const BASE_ANIM_DURATION = 1000;
|
||||||
|
|
||||||
// Pokeball falls to the screen
|
// Pokeball falls to the screen
|
||||||
@ -835,6 +855,11 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
|
|||||||
scale: 1,
|
scale: 1,
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
|
if (receivedPokemon.shiny) {
|
||||||
|
scene.time.delayedCall(500, () => {
|
||||||
|
doShinySparkleAnim(scene, pokemonShinySparkle, receivedPokemon.variant);
|
||||||
|
});
|
||||||
|
}
|
||||||
receivedPokeballSprite.destroy();
|
receivedPokeballSprite.destroy();
|
||||||
scene.time.delayedCall(2000, () => resolve());
|
scene.time.delayedCall(2000, () => resolve());
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
|
||||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||||
|
.withScenePartySizeRequirement(2, 6, true)
|
||||||
.withAutoHideIntroVisuals(false)
|
.withAutoHideIntroVisuals(false)
|
||||||
.withCatchAllowed(true)
|
.withCatchAllowed(true)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
|
@ -9,9 +9,9 @@ import { EnemyPokemon } from "#app/field/pokemon";
|
|||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { IntegerHolder, randSeedInt } from "#app/utils";
|
import { IntegerHolder, randSeedInt } from "#app/utils";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterCost, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
@ -19,6 +19,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
|||||||
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
|
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
|
||||||
import { SummonPhase } from "#app/phases/summon-phase";
|
import { SummonPhase } from "#app/phases/summon-phase";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
|
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/safariZone";
|
const namespace = "mysteryEncounters/safariZone";
|
||||||
@ -261,7 +262,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
|||||||
let enemySpecies;
|
let enemySpecies;
|
||||||
let pokemon;
|
let pokemon;
|
||||||
scene.executeWithSeedOffset(() => {
|
scene.executeWithSeedOffset(() => {
|
||||||
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false));
|
enemySpecies = getSafariSpeciesSpawn();
|
||||||
const level = scene.currentBattle.getLevelForWave();
|
const level = scene.currentBattle.getLevelForWave();
|
||||||
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
|
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
|
||||||
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
||||||
@ -526,3 +527,10 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number) {
|
|||||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
|
||||||
|
*/
|
||||||
|
export function getSafariSpeciesSpawn(): PokemonSpecies {
|
||||||
|
return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false));
|
||||||
|
}
|
||||||
|
@ -60,6 +60,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
|
|||||||
const pokemonConfig: EnemyPokemonConfig = {
|
const pokemonConfig: EnemyPokemonConfig = {
|
||||||
species: bossSpecies,
|
species: bossSpecies,
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
|
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||||
status: [ StatusEffect.SLEEP, 5 ], // Extra turns on timer for Snorlax's start of fight moves
|
status: [ StatusEffect.SLEEP, 5 ], // Extra turns on timer for Snorlax's start of fight moves
|
||||||
moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ],
|
moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ],
|
||||||
modifierConfigs: [
|
modifierConfigs: [
|
||||||
|
@ -21,7 +21,6 @@ import { EggSourceType } from "#enums/egg-source-types";
|
|||||||
import { EggTier } from "#enums/egg-type";
|
import { EggTier } from "#enums/egg-type";
|
||||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { achvs } from "#app/system/achv";
|
|
||||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { Type } from "#enums/type";
|
import { Type } from "#enums/type";
|
||||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||||
@ -520,12 +519,6 @@ function removePokemonFromPartyAndStoreHeldItems(scene: BattleScene, encounter:
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAchievement(scene: BattleScene) {
|
|
||||||
if (scene.arena.biomeType === Biome.SPACE) {
|
|
||||||
scene.validateAchv(achvs.BREEDERS_IN_SPACE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function restorePartyAndHeldItems(scene: BattleScene) {
|
function restorePartyAndHeldItems(scene: BattleScene) {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
// Restore original party
|
// Restore original party
|
||||||
@ -617,8 +610,6 @@ function onGameOver(scene: BattleScene) {
|
|||||||
function doPostEncounterCleanup(scene: BattleScene) {
|
function doPostEncounterCleanup(scene: BattleScene) {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
if (!encounter.misc.encounterFailed) {
|
if (!encounter.misc.encounterFailed) {
|
||||||
// Give achievement if in Space biome
|
|
||||||
checkAchievement(scene);
|
|
||||||
// Give 20 friendship to the chosen pokemon
|
// Give 20 friendship to the chosen pokemon
|
||||||
encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED);
|
encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED);
|
||||||
restorePartyAndHeldItems(scene);
|
restorePartyAndHeldItems(scene);
|
||||||
|
@ -4,8 +4,8 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { catchPokemon, getRandomSpeciesByStarterCost, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { speciesStarterCosts } from "#app/data/balance/starters";
|
import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
@ -17,6 +17,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
|||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounters/thePokemonSalesman";
|
const namespace = "mysteryEncounters/thePokemonSalesman";
|
||||||
@ -60,24 +61,22 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
|||||||
.withOnInit((scene: BattleScene) => {
|
.withOnInit((scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
|
|
||||||
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false));
|
let species = getSalesmanSpeciesOffer();
|
||||||
let tries = 0;
|
let tries = 0;
|
||||||
|
|
||||||
// Reroll any species that don't have HAs
|
// Reroll any species that don't have HAs
|
||||||
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
||||||
species = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false));
|
species = getSalesmanSpeciesOffer();
|
||||||
tries++;
|
tries++;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pokemon: PlayerPokemon;
|
let pokemon: PlayerPokemon;
|
||||||
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
|
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
|
||||||
// If no HA mon found or you roll 1%, give shiny Magikarp
|
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
|
||||||
species = getPokemonSpecies(Species.MAGIKARP);
|
species = getPokemonSpecies(Species.MAGIKARP);
|
||||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex, undefined, true);
|
||||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true, 0);
|
|
||||||
} else {
|
} else {
|
||||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex);
|
||||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
|
|
||||||
}
|
}
|
||||||
pokemon.generateAndPopulateMoveset();
|
pokemon.generateAndPopulateMoveset();
|
||||||
|
|
||||||
@ -87,7 +86,9 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
|||||||
fileRoot: fileRoot,
|
fileRoot: fileRoot,
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
isPokemon: true
|
isPokemon: true,
|
||||||
|
isShiny: pokemon.shiny,
|
||||||
|
variant: pokemon.variant
|
||||||
});
|
});
|
||||||
|
|
||||||
const starterTier = speciesStarterCosts[species.speciesId];
|
const starterTier = speciesStarterCosts[species.speciesId];
|
||||||
@ -164,3 +165,10 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
|
||||||
|
*/
|
||||||
|
export function getSalesmanSpeciesOffer(): PokemonSpecies {
|
||||||
|
return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false));
|
||||||
|
}
|
||||||
|
@ -79,6 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
|
|||||||
species: getPokemonSpecies(Species.SHUCKLE),
|
species: getPokemonSpecies(Species.SHUCKLE),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 5,
|
bossSegments: 5,
|
||||||
|
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||||
nature: Nature.BOLD,
|
nature: Nature.BOLD,
|
||||||
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
||||||
|
@ -61,11 +61,12 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
|||||||
.withOnInit((scene: BattleScene) => {
|
.withOnInit((scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
|
|
||||||
// Calculate boss mon
|
// Calculate boss mon (shiny locked)
|
||||||
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
|
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
|
||||||
const pokemonConfig: EnemyPokemonConfig = {
|
const pokemonConfig: EnemyPokemonConfig = {
|
||||||
species: bossSpecies,
|
species: bossSpecies,
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
|
shiny: false, // Shiny lock because of custom intro sprite
|
||||||
formIndex: 1, // Gmax
|
formIndex: 1, // Gmax
|
||||||
bossSegmentModifier: 1, // +1 Segment from normal
|
bossSegmentModifier: 1, // +1 Segment from normal
|
||||||
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ]
|
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ]
|
||||||
|
@ -100,7 +100,9 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: -5,
|
x: -5,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
isPokemon: true
|
isPokemon: true,
|
||||||
|
isShiny: pokemon.shiny,
|
||||||
|
variant: pokemon.variant
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -113,13 +115,15 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
const pokemonSprite = encounter.introVisuals!.getSprites();
|
const pokemonSprite = encounter.introVisuals!.getSprites();
|
||||||
|
|
||||||
scene.tweens.add({ // Bounce at the end
|
// Bounce at the end, then shiny sparkle if the Pokemon is shiny
|
||||||
|
scene.tweens.add({
|
||||||
targets: pokemonSprite,
|
targets: pokemonSprite,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
ease: "Cubic.easeOut",
|
ease: "Cubic.easeOut",
|
||||||
yoyo: true,
|
yoyo: true,
|
||||||
y: "-=20",
|
y: "-=20",
|
||||||
loop: 1,
|
loop: 1,
|
||||||
|
onComplete: () => encounter.introVisuals?.playShinySparkles()
|
||||||
});
|
});
|
||||||
|
|
||||||
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
|
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
|
||||||
|
@ -184,7 +184,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
dataSource = config.dataSource;
|
dataSource = config.dataSource;
|
||||||
enemySpecies = config.species;
|
enemySpecies = config.species;
|
||||||
isBoss = config.isBoss;
|
isBoss = config.isBoss;
|
||||||
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, dataSource);
|
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, dataSource);
|
||||||
} else {
|
} else {
|
||||||
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
|
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
enemySpecies = scene.randomSpecies(battle.waveIndex, level, true);
|
enemySpecies = scene.randomSpecies(battle.waveIndex, level, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, dataSource);
|
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, false, dataSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -731,7 +731,7 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
|
|||||||
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
||||||
scene.pushPhase(new EggLapsePhase(scene));
|
scene.pushPhase(new EggLapsePhase(scene));
|
||||||
} else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
|
} else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
|
||||||
scene.pushPhase(new BattleEndPhase(scene));
|
scene.pushPhase(new BattleEndPhase(scene, true));
|
||||||
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
|
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
|
||||||
scene.pushPhase(new TrainerVictoryPhase(scene));
|
scene.pushPhase(new TrainerVictoryPhase(scene));
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ export function getHighestStatTotalPlayerPokemon(scene: BattleScene, isAllowed:
|
|||||||
* @param allowMythical
|
* @param allowMythical
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[], allowSubLegendary: boolean = true, allowLegendary: boolean = true, allowMythical: boolean = true): Species {
|
export function getRandomSpeciesByStarterCost(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[], allowSubLegendary: boolean = true, allowLegendary: boolean = true, allowMythical: boolean = true): Species {
|
||||||
let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
|
let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
|
||||||
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
|
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
|
||||||
|
|
||||||
@ -290,7 +290,10 @@ export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon,
|
|||||||
if (damage <= 0) {
|
if (damage <= 0) {
|
||||||
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
|
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
|
||||||
}
|
}
|
||||||
|
// If a Pokemon would faint from the damage applied, its HP is instead set to 1.
|
||||||
|
if (pokemon.isAllowedInBattle() && pokemon.hp - damage <= 0) {
|
||||||
|
damage = pokemon.hp - 1;
|
||||||
|
}
|
||||||
applyHpChangeToPokemon(scene, pokemon, -damage);
|
applyHpChangeToPokemon(scene, pokemon, -damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,13 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke
|
|||||||
pokemonEvoTintSprite.setTintFill(0xFFFFFF);
|
pokemonEvoTintSprite.setTintFill(0xFFFFFF);
|
||||||
|
|
||||||
[ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
[ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
||||||
sprite.play(previousPokemon.getSpriteKey(true));
|
const spriteKey = previousPokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) });
|
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) });
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
sprite.setPipelineData("ignoreTimeTint", true);
|
||||||
sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
|
sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
|
||||||
@ -69,7 +75,13 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke
|
|||||||
});
|
});
|
||||||
|
|
||||||
[ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
[ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
||||||
sprite.play(transformPokemon.getSpriteKey(true));
|
const spriteKey = transformPokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
sprite.setPipelineData("ignoreTimeTint", true);
|
||||||
sprite.setPipelineData("spriteKey", transformPokemon.getSpriteKey());
|
sprite.setPipelineData("spriteKey", transformPokemon.getSpriteKey());
|
||||||
sprite.setPipelineData("shiny", transformPokemon.shiny);
|
sprite.setPipelineData("shiny", transformPokemon.shiny);
|
||||||
|
@ -351,6 +351,10 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove
|
|||||||
if (pokemon.scene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
|
if (pokemon.scene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
|
||||||
|
if (pokemon.hasAbility(Abilities.SHEER_FORCE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return super.canChange(pokemon);
|
return super.canChange(pokemon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPr
|
|||||||
import { Type } from "#enums/type";
|
import { Type } from "#enums/type";
|
||||||
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
|
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { Variant, VariantSet, variantColorCache, variantData } from "#app/data/variant";
|
import { Variant, VariantSet, variantData } from "#app/data/variant";
|
||||||
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
|
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
|
|
||||||
@ -511,29 +511,8 @@ export abstract class PokemonSpeciesForm {
|
|||||||
} else {
|
} else {
|
||||||
scene.anims.get(spriteKey).frameRate = 10;
|
scene.anims.get(spriteKey).frameRate = 10;
|
||||||
}
|
}
|
||||||
let spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
|
const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
|
||||||
const useExpSprite = scene.experimentalSprites && scene.hasExpSprite(spriteKey);
|
scene.loadPokemonVariantAssets(spriteKey, spritePath, variant);
|
||||||
if (useExpSprite) {
|
|
||||||
spritePath = `exp/${spritePath}`;
|
|
||||||
}
|
|
||||||
let config = variantData;
|
|
||||||
spritePath.split("/").map(p => config ? config = config[p] : null);
|
|
||||||
const variantSet = config as VariantSet;
|
|
||||||
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
|
|
||||||
const populateVariantColors = (key: string): Promise<void> => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (variantColorCache.hasOwnProperty(key)) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
scene.cachedFetch(`./images/pokemon/variant/${spritePath}.json`).then(res => res.json()).then(c => {
|
|
||||||
variantColorCache[key] = c;
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
populateVariantColors(spriteKey).then(() => resolve());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
if (startLoad) {
|
if (startLoad) {
|
||||||
|
@ -1173,16 +1173,28 @@ export function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: Tr
|
|||||||
if (!ignoreEvolution) {
|
if (!ignoreEvolution) {
|
||||||
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex);
|
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex);
|
||||||
}
|
}
|
||||||
return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess);
|
return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, false, undefined, postProcess);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilter, trainerSlot: TrainerSlot = TrainerSlot.TRAINER, allowLegendaries?: boolean, postProcess?: (EnemyPokemon: EnemyPokemon) => void): PartyMemberFunc {
|
function getSpeciesFilterRandomPartyMemberFunc(
|
||||||
const originalSpeciesFilter = speciesFilter;
|
originalSpeciesFilter: PokemonSpeciesFilter,
|
||||||
speciesFilter = (species: PokemonSpecies) => (allowLegendaries || (!species.legendary && !species.subLegendary && !species.mythical)) && !species.isTrainerForbidden() && originalSpeciesFilter(species);
|
trainerSlot: TrainerSlot = TrainerSlot.TRAINER,
|
||||||
return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => {
|
allowLegendaries?: boolean,
|
||||||
const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex)), level, trainerSlot, undefined, undefined, postProcess);
|
postProcess?: (EnemyPokemon: EnemyPokemon) => void
|
||||||
return ret;
|
): PartyMemberFunc {
|
||||||
|
|
||||||
|
const speciesFilter = (species: PokemonSpecies): boolean => {
|
||||||
|
const notLegendary = !species.legendary && !species.subLegendary && !species.mythical;
|
||||||
|
return (allowLegendaries || notLegendary) && !species.isTrainerForbidden() && originalSpeciesFilter(species);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (scene: BattleScene, level: number, strength: PartyMemberStrength) => {
|
||||||
|
const waveIndex = scene.currentBattle.waveIndex;
|
||||||
|
const species = getPokemonSpecies(scene.randomSpecies(waveIndex, level, false, speciesFilter)
|
||||||
|
.getTrainerSpeciesForLevel(level, true, strength, waveIndex));
|
||||||
|
|
||||||
|
return scene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,4 +93,5 @@ export enum BattlerTagType {
|
|||||||
GRUDGE = "GRUDGE",
|
GRUDGE = "GRUDGE",
|
||||||
PSYCHO_SHIFT = "PSYCHO_SHIFT",
|
PSYCHO_SHIFT = "PSYCHO_SHIFT",
|
||||||
ENDURE_TOKEN = "ENDURE_TOKEN",
|
ENDURE_TOKEN = "ENDURE_TOKEN",
|
||||||
|
POWDER = "POWDER",
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,7 @@ export enum SwitchType {
|
|||||||
/** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */
|
/** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */
|
||||||
BATON_PASS,
|
BATON_PASS,
|
||||||
/** Transfers the returning Pokemon's Substitute to the switched in Pokemon */
|
/** Transfers the returning Pokemon's Substitute to the switched in Pokemon */
|
||||||
SHED_TAIL
|
SHED_TAIL,
|
||||||
|
/** Force switchout to a random party member */
|
||||||
|
FORCE_SWITCH,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import BattleScene from "../battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
import * as Utils from "../utils";
|
import { Variant } from "#app/data/variant";
|
||||||
|
import { getFrameMs, randGauss } from "#app/utils";
|
||||||
|
|
||||||
export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void {
|
export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void {
|
||||||
switch (pokeballType) {
|
switch (pokeballType) {
|
||||||
@ -127,7 +128,7 @@ function doFanOutParticle(scene: BattleScene, trigIndex: integer, x: integer, y:
|
|||||||
|
|
||||||
const particleTimer = scene.tweens.addCounter({
|
const particleTimer = scene.tweens.addCounter({
|
||||||
repeat: -1,
|
repeat: -1,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
updateParticle();
|
updateParticle();
|
||||||
}
|
}
|
||||||
@ -159,7 +160,7 @@ export function addPokeballCaptureStars(scene: BattleScene, pokeball: Phaser.Gam
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const dist = Utils.randGauss(25);
|
const dist = randGauss(25);
|
||||||
scene.tweens.add({
|
scene.tweens.add({
|
||||||
targets: particle,
|
targets: particle,
|
||||||
x: pokeball.x + dist,
|
x: pokeball.x + dist,
|
||||||
@ -185,3 +186,31 @@ export function sin(index: integer, amplitude: integer): number {
|
|||||||
export function cos(index: integer, amplitude: integer): number {
|
export function cos(index: integer, amplitude: integer): number {
|
||||||
return amplitude * Math.cos(index * (Math.PI / 128));
|
return amplitude * Math.cos(index * (Math.PI / 128));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play the shiny sparkle animation and sound effect for the given sprite
|
||||||
|
* First ensures that the animation has been properly initialized
|
||||||
|
* @param sparkleSprite the Sprite to play the animation on
|
||||||
|
* @param variant which shiny {@linkcode variant} to play the animation for
|
||||||
|
*/
|
||||||
|
export function doShinySparkleAnim(scene: BattleScene, sparkleSprite: Phaser.GameObjects.Sprite, variant: Variant) {
|
||||||
|
const keySuffix = variant ? `_${variant + 1}` : "";
|
||||||
|
const spriteKey = `shiny${keySuffix}`;
|
||||||
|
const animationKey = `sparkle${keySuffix}`;
|
||||||
|
|
||||||
|
// Make sure the animation exists, and create it if not
|
||||||
|
if (!scene.anims.exists(animationKey)) {
|
||||||
|
const frameNames = scene.anims.generateFrameNames(spriteKey, { suffix: ".png", end: 34 });
|
||||||
|
scene.anims.create({
|
||||||
|
key: `sparkle${keySuffix}`,
|
||||||
|
frames: frameNames,
|
||||||
|
frameRate: 32,
|
||||||
|
showOnStart: true,
|
||||||
|
hideOnComplete: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play the animation
|
||||||
|
sparkleSprite.play(animationKey);
|
||||||
|
scene.playSound("se/sparkle");
|
||||||
|
}
|
||||||
|
@ -707,7 +707,7 @@ export class Arena {
|
|||||||
case Biome.METROPOLIS:
|
case Biome.METROPOLIS:
|
||||||
return 141.470;
|
return 141.470;
|
||||||
case Biome.FOREST:
|
case Biome.FOREST:
|
||||||
return 4.294;
|
return 0.341;
|
||||||
case Biome.SEA:
|
case Biome.SEA:
|
||||||
return 0.024;
|
return 0.024;
|
||||||
case Biome.SWAMP:
|
case Biome.SWAMP:
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { GameObjects } from "phaser";
|
import { GameObjects } from "phaser";
|
||||||
import BattleScene from "../battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import MysteryEncounter from "../data/mystery-encounters/mystery-encounter";
|
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig;
|
import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig;
|
||||||
|
import { Variant } from "#app/data/variant";
|
||||||
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
|
|
||||||
type KnownFileRoot =
|
type KnownFileRoot =
|
||||||
| "arenas"
|
| "arenas"
|
||||||
@ -59,6 +61,10 @@ export class MysteryEncounterSpriteConfig {
|
|||||||
scale?: number;
|
scale?: number;
|
||||||
/** If you are using a Pokemon sprite, set to `true`. This will ensure variant, form, gender, shiny sprites are loaded properly */
|
/** If you are using a Pokemon sprite, set to `true`. This will ensure variant, form, gender, shiny sprites are loaded properly */
|
||||||
isPokemon?: boolean;
|
isPokemon?: boolean;
|
||||||
|
/** If using a Pokemon shiny sprite, needs to be set to ensure the correct variant assets get loaded and displayed */
|
||||||
|
isShiny?: boolean;
|
||||||
|
/** If using a Pokemon shiny sprite, needs to be set to ensure the correct variant assets get loaded and displayed */
|
||||||
|
variant?: Variant;
|
||||||
/** If you are using an item sprite, set to `true` */
|
/** If you are using an item sprite, set to `true` */
|
||||||
isItem?: boolean;
|
isItem?: boolean;
|
||||||
/** The sprites alpha. `0` - `1` The lower the number, the more transparent */
|
/** The sprites alpha. `0` - `1` The lower the number, the more transparent */
|
||||||
@ -74,6 +80,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
public encounter: MysteryEncounter;
|
public encounter: MysteryEncounter;
|
||||||
public spriteConfigs: MysteryEncounterSpriteConfig[];
|
public spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||||
public enterFromRight: boolean;
|
public enterFromRight: boolean;
|
||||||
|
private shinySparkleSprites: { sprite: Phaser.GameObjects.Sprite, variant: Variant }[];
|
||||||
|
|
||||||
constructor(scene: BattleScene, encounter: MysteryEncounter) {
|
constructor(scene: BattleScene, encounter: MysteryEncounter) {
|
||||||
super(scene, -72, 76);
|
super(scene, -72, 76);
|
||||||
@ -86,7 +93,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!isNullOrUndefined(result.species)) {
|
if (!isNullOrUndefined(result.species)) {
|
||||||
const keys = getSpriteKeysFromSpecies(result.species);
|
const keys = getSpriteKeysFromSpecies(result.species, undefined, undefined, result.isShiny, result.variant);
|
||||||
result.spriteKey = keys.spriteKey;
|
result.spriteKey = keys.spriteKey;
|
||||||
result.fileRoot = keys.fileRoot;
|
result.fileRoot = keys.fileRoot;
|
||||||
result.isPokemon = true;
|
result.isPokemon = true;
|
||||||
@ -120,18 +127,36 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
// Sprites with custom X or Y defined will not count for normal spacing requirements
|
// Sprites with custom X or Y defined will not count for normal spacing requirements
|
||||||
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
||||||
|
|
||||||
|
this.shinySparkleSprites = [];
|
||||||
|
const shinySparkleSprites = scene.add.container(0, 0);
|
||||||
this.spriteConfigs?.forEach((config) => {
|
this.spriteConfigs?.forEach((config) => {
|
||||||
const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha } = config;
|
const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha, isPokemon, isShiny, variant } = config;
|
||||||
|
|
||||||
let sprite: GameObjects.Sprite;
|
let sprite: GameObjects.Sprite;
|
||||||
let tintSprite: GameObjects.Sprite;
|
let tintSprite: GameObjects.Sprite;
|
||||||
|
let pokemonShinySparkle: Phaser.GameObjects.Sprite | undefined;
|
||||||
|
|
||||||
if (!isItem) {
|
if (isItem) {
|
||||||
sprite = getSprite(spriteKey, hasShadow, yShadow);
|
|
||||||
tintSprite = getSprite(spriteKey);
|
|
||||||
} else {
|
|
||||||
sprite = getItemSprite(spriteKey, hasShadow, yShadow);
|
sprite = getItemSprite(spriteKey, hasShadow, yShadow);
|
||||||
tintSprite = getItemSprite(spriteKey);
|
tintSprite = getItemSprite(spriteKey);
|
||||||
|
} else {
|
||||||
|
sprite = getSprite(spriteKey, hasShadow, yShadow);
|
||||||
|
tintSprite = getSprite(spriteKey);
|
||||||
|
if (isPokemon && isShiny) {
|
||||||
|
// Set Pipeline for shiny variant
|
||||||
|
sprite.setPipelineData("spriteKey", spriteKey);
|
||||||
|
tintSprite.setPipelineData("spriteKey", spriteKey);
|
||||||
|
sprite.setPipelineData("shiny", true);
|
||||||
|
sprite.setPipelineData("variant", variant);
|
||||||
|
tintSprite.setPipelineData("shiny", true);
|
||||||
|
tintSprite.setPipelineData("variant", variant);
|
||||||
|
// Create Sprite for shiny Sparkle
|
||||||
|
pokemonShinySparkle = scene.add.sprite(sprite.x, sprite.y, "shiny");
|
||||||
|
pokemonShinySparkle.setOrigin(0.5, 1);
|
||||||
|
pokemonShinySparkle.setVisible(false);
|
||||||
|
this.shinySparkleSprites.push({ sprite: pokemonShinySparkle, variant: variant ?? 0 });
|
||||||
|
shinySparkleSprites.add(pokemonShinySparkle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sprite.setVisible(!config.hidden);
|
sprite.setVisible(!config.hidden);
|
||||||
@ -165,6 +190,11 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isNullOrUndefined(pokemonShinySparkle)) {
|
||||||
|
// Offset the sparkle to match the Pokemon's position
|
||||||
|
pokemonShinySparkle.setPosition(sprite.x, sprite.y);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isNullOrUndefined(alpha)) {
|
if (!isNullOrUndefined(alpha)) {
|
||||||
sprite.setAlpha(alpha);
|
sprite.setAlpha(alpha);
|
||||||
tintSprite.setAlpha(alpha);
|
tintSprite.setAlpha(alpha);
|
||||||
@ -173,6 +203,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
this.add(sprite);
|
this.add(sprite);
|
||||||
this.add(tintSprite);
|
this.add(tintSprite);
|
||||||
});
|
});
|
||||||
|
this.add(shinySparkleSprites);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,6 +218,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
this.spriteConfigs.forEach((config) => {
|
this.spriteConfigs.forEach((config) => {
|
||||||
if (config.isPokemon) {
|
if (config.isPokemon) {
|
||||||
this.scene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
|
this.scene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
|
||||||
|
if (config.isShiny) {
|
||||||
|
this.scene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant);
|
||||||
|
}
|
||||||
} else if (config.isItem) {
|
} else if (config.isItem) {
|
||||||
this.scene.loadAtlas("items", "");
|
this.scene.loadAtlas("items", "");
|
||||||
} else {
|
} else {
|
||||||
@ -240,11 +274,21 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
this.getSprites().map((sprite, i) => {
|
this.getSprites().map((sprite, i) => {
|
||||||
if (!this.spriteConfigs[i].isItem) {
|
if (!this.spriteConfigs[i].isItem) {
|
||||||
sprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
sprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
||||||
|
if (sprite.texture.frameTotal > 1) {
|
||||||
|
// Show the first animation frame for a smooth transition when the animation starts.
|
||||||
|
const firstFrame = sprite.texture.frames["0001.png"];
|
||||||
|
sprite.setFrame(firstFrame ?? 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.getTintSprites().map((tintSprite, i) => {
|
this.getTintSprites().map((tintSprite, i) => {
|
||||||
if (!this.spriteConfigs[i].isItem) {
|
if (!this.spriteConfigs[i].isItem) {
|
||||||
tintSprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
tintSprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
||||||
|
if (tintSprite.texture.frameTotal > 1) {
|
||||||
|
// Show the first frame for a smooth transition when the animation starts.
|
||||||
|
const firstFrame = tintSprite.texture.frames["0001.png"];
|
||||||
|
tintSprite.setFrame(firstFrame ?? 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -288,6 +332,17 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play shiny sparkle animations if there are shiny Pokemon
|
||||||
|
*/
|
||||||
|
playShinySparkles() {
|
||||||
|
for (const sparkleConfig of this.shinySparkleSprites) {
|
||||||
|
this.scene.time.delayedCall(500, () => {
|
||||||
|
doShinySparkleAnim(this.scene, sparkleConfig.sprite, sparkleConfig.variant);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For sprites with animation and that do not have animation disabled, will begin frame animation
|
* For sprites with animation and that do not have animation disabled, will begin frame animation
|
||||||
*/
|
*/
|
||||||
|
@ -23,13 +23,13 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/
|
|||||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
|
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
|
||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||||
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, PostDamageForceSwitchAbAttr, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/ability";
|
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/ability";
|
||||||
import PokemonData from "#app/system/pokemon-data";
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||||
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||||
import { LevelMoves } from "#app/data/balance/pokemon-level-moves";
|
import { EVOLVE_MOVE, LevelMoves, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves";
|
||||||
import { DamageAchv, achvs } from "#app/system/achv";
|
import { DamageAchv, achvs } from "#app/system/achv";
|
||||||
import { DexAttr, StarterDataEntry, StarterMoveset } from "#app/system/game-data";
|
import { DexAttr, StarterDataEntry, StarterMoveset } from "#app/system/game-data";
|
||||||
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
|
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
|
||||||
@ -51,7 +51,7 @@ import { Biome } from "#enums/biome";
|
|||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
import { FaintPhase } from "#app/phases/faint-phase";
|
import { FaintPhase } from "#app/phases/faint-phase";
|
||||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
@ -69,6 +69,16 @@ import { SpeciesFormKey } from "#enums/species-form-key";
|
|||||||
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates";
|
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
|
|
||||||
|
export enum LearnMoveSituation {
|
||||||
|
MISC,
|
||||||
|
LEVEL_UP,
|
||||||
|
RELEARN,
|
||||||
|
EVOLUTION,
|
||||||
|
EVOLUTION_FUSED, // If fusionSpecies has Evolved
|
||||||
|
EVOLUTION_FUSED_BASE // If fusion's base species has Evolved
|
||||||
|
}
|
||||||
|
|
||||||
export enum FieldPosition {
|
export enum FieldPosition {
|
||||||
CENTER,
|
CENTER,
|
||||||
@ -339,6 +349,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (!this.scene) {
|
if (!this.scene) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (this.switchOutStatus) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return this.scene.field.getIndex(this) > -1;
|
return this.scene.field.getIndex(this) > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,21 +697,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initShinySparkle(): void {
|
initShinySparkle(): void {
|
||||||
const keySuffix = this.variant ? `_${this.variant + 1}` : "";
|
const shinySparkle = this.scene.addFieldSprite(0, 0, "shiny");
|
||||||
const key = `shiny${keySuffix}`;
|
|
||||||
const shinySparkle = this.scene.addFieldSprite(0, 0, key);
|
|
||||||
shinySparkle.setVisible(false);
|
shinySparkle.setVisible(false);
|
||||||
shinySparkle.setOrigin(0.5, 1);
|
shinySparkle.setOrigin(0.5, 1);
|
||||||
const frameNames = this.scene.anims.generateFrameNames(key, { suffix: ".png", end: 34 });
|
|
||||||
if (!(this.scene.anims.exists(`sparkle${keySuffix}`))) {
|
|
||||||
this.scene.anims.create({
|
|
||||||
key: `sparkle${keySuffix}`,
|
|
||||||
frames: frameNames,
|
|
||||||
frameRate: 32,
|
|
||||||
showOnStart: true,
|
|
||||||
hideOnComplete: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.add(shinySparkle);
|
this.add(shinySparkle);
|
||||||
|
|
||||||
this.shinySparkle = shinySparkle;
|
this.shinySparkle = shinySparkle;
|
||||||
@ -1597,7 +1598,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trappedByAbility = new Utils.BooleanHolder(false);
|
const trappedByAbility = new Utils.BooleanHolder(false);
|
||||||
const opposingField = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
|
/**
|
||||||
|
* Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective
|
||||||
|
* Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger
|
||||||
|
*/
|
||||||
|
const opposingFieldUnfiltered = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
|
||||||
|
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
|
||||||
|
|
||||||
opposingField.forEach((opponent) =>
|
opposingField.forEach((opponent) =>
|
||||||
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated)
|
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated)
|
||||||
@ -1834,40 +1840,44 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param {boolean} includeRelearnerMoves Whether to include moves that would require a relearner. Note the move relearner inherently allows evolution moves
|
* @param {boolean} includeRelearnerMoves Whether to include moves that would require a relearner. Note the move relearner inherently allows evolution moves
|
||||||
* @returns {LevelMoves} A list of moves and the levels they can be learned at
|
* @returns {LevelMoves} A list of moves and the levels they can be learned at
|
||||||
*/
|
*/
|
||||||
getLevelMoves(startingLevel?: integer, includeEvolutionMoves: boolean = false, simulateEvolutionChain: boolean = false, includeRelearnerMoves: boolean = false): LevelMoves {
|
getLevelMoves(startingLevel?: integer, includeEvolutionMoves: boolean = false, simulateEvolutionChain: boolean = false, includeRelearnerMoves: boolean = false, learnSituation: LearnMoveSituation = LearnMoveSituation.MISC): LevelMoves {
|
||||||
const ret: LevelMoves = [];
|
const ret: LevelMoves = [];
|
||||||
let levelMoves: LevelMoves = [];
|
let levelMoves: LevelMoves = [];
|
||||||
if (!startingLevel) {
|
if (!startingLevel) {
|
||||||
startingLevel = this.level;
|
startingLevel = this.level;
|
||||||
}
|
}
|
||||||
if (simulateEvolutionChain) {
|
if (learnSituation === LearnMoveSituation.EVOLUTION_FUSED && this.fusionSpecies) { // For fusion evolutions, get ONLY the moves of the component mon that evolved
|
||||||
const evolutionChain = this.species.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer());
|
levelMoves = this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0);
|
||||||
for (let e = 0; e < evolutionChain.length; e++) {
|
|
||||||
// TODO: Might need to pass specific form index in simulated evolution chain
|
|
||||||
const speciesLevelMoves = getPokemonSpeciesForm(evolutionChain[e][0], this.formIndex).getLevelMoves();
|
|
||||||
if (includeRelearnerMoves) {
|
|
||||||
levelMoves.push(...speciesLevelMoves);
|
|
||||||
} else {
|
|
||||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || ((!e || lm[0] > 1) && (e === evolutionChain.length - 1 || lm[0] <= evolutionChain[e + 1][1]))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
levelMoves = this.getSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === 0) || (includeRelearnerMoves && lm[0] === -1) || lm[0] > 0);
|
|
||||||
}
|
|
||||||
if (this.fusionSpecies) {
|
|
||||||
if (simulateEvolutionChain) {
|
if (simulateEvolutionChain) {
|
||||||
const fusionEvolutionChain = this.fusionSpecies.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer());
|
const evolutionChain = this.species.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer());
|
||||||
for (let e = 0; e < fusionEvolutionChain.length; e++) {
|
for (let e = 0; e < evolutionChain.length; e++) {
|
||||||
// TODO: Might need to pass specific form index in simulated evolution chain
|
// TODO: Might need to pass specific form index in simulated evolution chain
|
||||||
const speciesLevelMoves = getPokemonSpeciesForm(fusionEvolutionChain[e][0], this.fusionFormIndex).getLevelMoves();
|
const speciesLevelMoves = getPokemonSpeciesForm(evolutionChain[e][0], this.formIndex).getLevelMoves();
|
||||||
if (includeRelearnerMoves) {
|
if (includeRelearnerMoves) {
|
||||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || lm[0] !== 0));
|
levelMoves.push(...speciesLevelMoves);
|
||||||
} else {
|
} else {
|
||||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || ((!e || lm[0] > 1) && (e === fusionEvolutionChain.length - 1 || lm[0] <= fusionEvolutionChain[e + 1][1]))));
|
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || ((!e || lm[0] > 1) && (e === evolutionChain.length - 1 || lm[0] <= evolutionChain[e + 1][1]))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
levelMoves.push(...this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === 0) || (includeRelearnerMoves && lm[0] === -1) || lm[0] > 0));
|
levelMoves = this.getSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0);
|
||||||
|
}
|
||||||
|
if (this.fusionSpecies && learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE) { // For fusion evolutions, get ONLY the moves of the component mon that evolved
|
||||||
|
if (simulateEvolutionChain) {
|
||||||
|
const fusionEvolutionChain = this.fusionSpecies.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer());
|
||||||
|
for (let e = 0; e < fusionEvolutionChain.length; e++) {
|
||||||
|
// TODO: Might need to pass specific form index in simulated evolution chain
|
||||||
|
const speciesLevelMoves = getPokemonSpeciesForm(fusionEvolutionChain[e][0], this.fusionFormIndex).getLevelMoves();
|
||||||
|
if (includeRelearnerMoves) {
|
||||||
|
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || lm[0] !== EVOLVE_MOVE));
|
||||||
|
} else {
|
||||||
|
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || ((!e || lm[0] > 1) && (e === fusionEvolutionChain.length - 1 || lm[0] <= fusionEvolutionChain[e + 1][1]))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
levelMoves.push(...this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
levelMoves.sort((lma: [integer, integer], lmb: [integer, integer]) => lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0);
|
levelMoves.sort((lma: [integer, integer], lmb: [integer, integer]) => lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0);
|
||||||
@ -1985,6 +1995,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
/**
|
/**
|
||||||
* Function that tries to set a Pokemon shiny based on seed.
|
* Function that tries to set a Pokemon shiny based on seed.
|
||||||
* For manual use only, usually to roll a Pokemon's shiny chance a second time.
|
* For manual use only, usually to roll a Pokemon's shiny chance a second time.
|
||||||
|
* If it rolls shiny, also sets a random variant and give the Pokemon the associated luck.
|
||||||
*
|
*
|
||||||
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
|
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
|
||||||
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
|
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
|
||||||
@ -2010,6 +2021,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
||||||
|
|
||||||
if (this.shiny) {
|
if (this.shiny) {
|
||||||
|
this.variant = this.generateShinyVariant();
|
||||||
|
this.luck = this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0);
|
||||||
this.initShinySparkle();
|
this.initShinySparkle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2635,8 +2648,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the attack deals fixed damaged, return a result with that much damage
|
// If the attack deals fixed damage, return a result with that much damage
|
||||||
const fixedDamage = new Utils.IntegerHolder(0);
|
const fixedDamage = new Utils.NumberHolder(0);
|
||||||
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage);
|
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage);
|
||||||
if (fixedDamage.value) {
|
if (fixedDamage.value) {
|
||||||
const multiLensMultiplier = new Utils.NumberHolder(1);
|
const multiLensMultiplier = new Utils.NumberHolder(1);
|
||||||
@ -2913,14 +2926,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.turnData.damageTaken += damage;
|
this.turnData.damageTaken += damage;
|
||||||
this.battleData.hitCount++;
|
this.battleData.hitCount++;
|
||||||
|
|
||||||
// Multi-Lens and Parental Bond check for Wimp Out/Emergency Exit
|
|
||||||
if (this.hasAbilityWithAttr(PostDamageForceSwitchAbAttr)) {
|
|
||||||
const multiHitModifier = source.getHeldItems().find(m => m instanceof PokemonMultiHitModifier);
|
|
||||||
if (multiHitModifier || source.hasAbilityWithAttr(AddSecondStrikeAbAttr)) {
|
|
||||||
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const attackResult = { move: move.id, result: result as DamageResult, damage: damage, critical: isCritical, sourceId: source.id, sourceBattlerIndex: source.getBattlerIndex() };
|
const attackResult = { move: move.id, result: result as DamageResult, damage: damage, critical: isCritical, sourceId: source.id, sourceBattlerIndex: source.getBattlerIndex() };
|
||||||
this.turnData.attacksReceived.unshift(attackResult);
|
this.turnData.attacksReceived.unshift(attackResult);
|
||||||
if (source.isPlayer() && !this.isPlayer()) {
|
if (source.isPlayer() && !this.isPlayer()) {
|
||||||
@ -3021,13 +3026,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param ignoreFaintPhase boolean to ignore adding a FaintPhase, passsed to damage()
|
* @param ignoreFaintPhase boolean to ignore adding a FaintPhase, passsed to damage()
|
||||||
* @returns integer of damage done
|
* @returns integer of damage done
|
||||||
*/
|
*/
|
||||||
damageAndUpdate(damage: integer, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): integer {
|
damageAndUpdate(damage: number, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): number {
|
||||||
const damagePhase = new DamagePhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical);
|
const damagePhase = new DamageAnimPhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical);
|
||||||
this.scene.unshiftPhase(damagePhase);
|
this.scene.unshiftPhase(damagePhase);
|
||||||
|
if (this.switchOutStatus && source) {
|
||||||
|
damage = 0;
|
||||||
|
}
|
||||||
damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase);
|
damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase);
|
||||||
// Damage amount may have changed, but needed to be queued before calling damage function
|
// Damage amount may have changed, but needed to be queued before calling damage function
|
||||||
damagePhase.updateAmount(damage);
|
damagePhase.updateAmount(damage);
|
||||||
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source);
|
/**
|
||||||
|
* Run PostDamageAbAttr from any source of damage that is not from a multi-hit
|
||||||
|
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
|
||||||
|
*/
|
||||||
|
if (!source || source.turnData.hitCount <= 1) {
|
||||||
|
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source);
|
||||||
|
}
|
||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3504,12 +3518,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE);
|
return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE);
|
||||||
}
|
}
|
||||||
|
|
||||||
canSetStatus(effect: StatusEffect | undefined, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon | null = null): boolean {
|
/**
|
||||||
|
* Checks if a status effect can be applied to the Pokemon.
|
||||||
|
*
|
||||||
|
* @param effect The {@linkcode StatusEffect} whose applicability is being checked
|
||||||
|
* @param quiet Whether in-battle messages should trigger or not
|
||||||
|
* @param overrideStatus Whether the Pokemon's current status can be overriden
|
||||||
|
* @param sourcePokemon The Pokemon that is setting the status effect
|
||||||
|
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
|
||||||
|
*/
|
||||||
|
canSetStatus(effect: StatusEffect | undefined, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon | null = null, ignoreField: boolean = false): boolean {
|
||||||
if (effect !== StatusEffect.FAINT) {
|
if (effect !== StatusEffect.FAINT) {
|
||||||
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.MISTY) {
|
if (this.isGrounded() && (!ignoreField && this.scene.arena.terrain?.terrainType === TerrainType.MISTY)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3559,7 +3582,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case StatusEffect.FREEZE:
|
case StatusEffect.FREEZE:
|
||||||
if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) {
|
if (this.isOfType(Type.ICE) || (!ignoreField && (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType)))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -3744,8 +3767,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
setFrameRate(frameRate: integer) {
|
setFrameRate(frameRate: integer) {
|
||||||
this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate;
|
this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate;
|
||||||
this.getSprite().play(this.getBattleSpriteKey());
|
try {
|
||||||
this.getTintSprite()?.play(this.getBattleSpriteKey());
|
this.getSprite().play(this.getBattleSpriteKey());
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.getTintSprite()?.play(this.getBattleSpriteKey());
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tint(color: number, alpha?: number, duration?: integer, ease?: string) {
|
tint(color: number, alpha?: number, duration?: integer, ease?: string) {
|
||||||
@ -3810,8 +3841,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
sparkle(): void {
|
sparkle(): void {
|
||||||
if (this.shinySparkle) {
|
if (this.shinySparkle) {
|
||||||
this.shinySparkle.play(`sparkle${this.variant ? `_${this.variant + 1}` : ""}`);
|
doShinySparkleAnim(this.scene, this.shinySparkle, this.variant);
|
||||||
this.scene.playSound("se/sparkle");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4288,28 +4318,29 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addFriendship(friendship: integer): void {
|
addFriendship(friendship: number): void {
|
||||||
const starterSpeciesId = this.species.getRootSpeciesId();
|
if (friendship > 0) {
|
||||||
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
|
const starterSpeciesId = this.species.getRootSpeciesId();
|
||||||
const starterData = [
|
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
|
||||||
this.scene.gameData.starterData[starterSpeciesId],
|
const starterData = [
|
||||||
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
|
this.scene.gameData.starterData[starterSpeciesId],
|
||||||
].filter(d => !!d);
|
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
|
||||||
const amount = new Utils.IntegerHolder(friendship);
|
].filter(d => !!d);
|
||||||
let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER;
|
const amount = new Utils.NumberHolder(friendship);
|
||||||
if (this.scene.eventManager.isEventActive()) {
|
|
||||||
candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier();
|
|
||||||
}
|
|
||||||
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1)));
|
|
||||||
if (amount.value > 0) {
|
|
||||||
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
||||||
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount);
|
let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER;
|
||||||
|
if (this.scene.eventManager.isEventActive()) {
|
||||||
|
candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier();
|
||||||
|
}
|
||||||
|
const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * (this.scene.gameMode.isClassic ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1)));
|
||||||
|
|
||||||
|
// Add friendship to this PlayerPokemon
|
||||||
this.friendship = Math.min(this.friendship + amount.value, 255);
|
this.friendship = Math.min(this.friendship + amount.value, 255);
|
||||||
if (this.friendship === 255) {
|
if (this.friendship === 255) {
|
||||||
this.scene.validateAchv(achvs.MAX_FRIENDSHIP);
|
this.scene.validateAchv(achvs.MAX_FRIENDSHIP);
|
||||||
}
|
}
|
||||||
starterData.forEach((sd: StarterDataEntry, i: integer) => {
|
// Add to candy progress for this mon's starter species and its fused species (if it has one)
|
||||||
|
starterData.forEach((sd: StarterDataEntry, i: number) => {
|
||||||
const speciesId = !i ? starterSpeciesId : fusionStarterSpeciesId as Species;
|
const speciesId = !i ? starterSpeciesId : fusionStarterSpeciesId as Species;
|
||||||
sd.friendship = (sd.friendship || 0) + starterAmount.value;
|
sd.friendship = (sd.friendship || 0) + starterAmount.value;
|
||||||
if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) {
|
if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) {
|
||||||
@ -4318,10 +4349,8 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.friendship = Math.max(this.friendship + amount.value, 0);
|
// Lose friendship upon fainting
|
||||||
for (const sd of starterData) {
|
this.friendship = Math.max(this.friendship + friendship, 0);
|
||||||
sd.friendship = Math.max((sd.friendship || 0) + starterAmount.value, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -4651,12 +4680,13 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
public aiType: AiType;
|
public aiType: AiType;
|
||||||
public bossSegments: integer;
|
public bossSegments: integer;
|
||||||
public bossSegmentIndex: integer;
|
public bossSegmentIndex: integer;
|
||||||
/** To indicate of the instance was populated with a dataSource -> e.g. loaded & populated from session data */
|
/** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */
|
||||||
public readonly isPopulatedFromDataSource: boolean;
|
public readonly isPopulatedFromDataSource: boolean;
|
||||||
|
|
||||||
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource?: PokemonData) {
|
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, shinyLock: boolean = false, dataSource?: PokemonData) {
|
||||||
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex,
|
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, dataSource?.gender,
|
||||||
dataSource?.gender, dataSource ? dataSource.shiny : false, dataSource ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, dataSource);
|
(!shinyLock && dataSource) ? dataSource.shiny : false, (!shinyLock && dataSource) ? dataSource.variant : undefined,
|
||||||
|
undefined, dataSource ? dataSource.nature : undefined, dataSource);
|
||||||
|
|
||||||
this.trainerSlot = trainerSlot;
|
this.trainerSlot = trainerSlot;
|
||||||
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
|
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
|
||||||
@ -4685,12 +4715,15 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
this.generateAndPopulateMoveset();
|
this.generateAndPopulateMoveset();
|
||||||
|
|
||||||
this.trySetShiny();
|
if (shinyLock || Overrides.OPP_SHINY_OVERRIDE === false) {
|
||||||
if (Overrides.OPP_SHINY_OVERRIDE) {
|
this.shiny = false;
|
||||||
|
} else {
|
||||||
|
this.trySetShiny();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.shiny && Overrides.OPP_SHINY_OVERRIDE) {
|
||||||
this.shiny = true;
|
this.shiny = true;
|
||||||
this.initShinySparkle();
|
this.initShinySparkle();
|
||||||
} else if (Overrides.OPP_SHINY_OVERRIDE === false) {
|
|
||||||
this.shiny = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.shiny) {
|
if (this.shiny) {
|
||||||
@ -5275,6 +5308,7 @@ export class PokemonBattleSummonData {
|
|||||||
export class PokemonTurnData {
|
export class PokemonTurnData {
|
||||||
public flinched: boolean = false;
|
public flinched: boolean = false;
|
||||||
public acted: boolean = false;
|
public acted: boolean = false;
|
||||||
|
/** How many times the move should hit the target(s) */
|
||||||
public hitCount: number = 0;
|
public hitCount: number = 0;
|
||||||
/**
|
/**
|
||||||
* - `-1` = Calculate how many hits are left
|
* - `-1` = Calculate how many hits are left
|
||||||
@ -5293,6 +5327,11 @@ export class PokemonTurnData {
|
|||||||
public switchedInThisTurn: boolean = false;
|
public switchedInThisTurn: boolean = false;
|
||||||
public failedRunAway: boolean = false;
|
public failedRunAway: boolean = false;
|
||||||
public joinedRound: boolean = false;
|
public joinedRound: boolean = false;
|
||||||
|
/**
|
||||||
|
* Used to make sure multi-hits occur properly when the user is
|
||||||
|
* forced to act again in the same turn
|
||||||
|
*/
|
||||||
|
public extraTurns: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AiType {
|
export enum AiType {
|
||||||
|
@ -1702,7 +1702,8 @@ const modifierPool: ModifierPool = {
|
|||||||
new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => {
|
new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => {
|
||||||
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8);
|
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8);
|
||||||
}, 8),
|
}, 8),
|
||||||
new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 1 : 0, 1),
|
new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 2 : 0, 2),
|
||||||
|
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2),
|
||||||
new WeightedModifierType(modifierTypes.TM_GREAT, 3),
|
new WeightedModifierType(modifierTypes.TM_GREAT, 3),
|
||||||
new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => {
|
new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => {
|
||||||
if (!party.find(p => p.getLearnableLevelMoves().length)) {
|
if (!party.find(p => p.getLearnableLevelMoves().length)) {
|
||||||
@ -1730,8 +1731,14 @@ const modifierPool: ModifierPool = {
|
|||||||
new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => {
|
new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => {
|
||||||
const { gameMode, gameData } = party[0].scene;
|
const { gameMode, gameData } = party[0].scene;
|
||||||
if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) {
|
if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) {
|
||||||
return party.some(p => ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)))
|
return party.some(p => {
|
||||||
&& !p.getHeldItems().some(i => i instanceof EvolutionStatBoosterModifier) && !p.isMax()) ? 10 : 0;
|
// Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd
|
||||||
|
if (!p.isMax() && ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)))) {
|
||||||
|
// Check if Pokemon is already holding an Eviolite
|
||||||
|
return !p.getHeldItems().some(i => i.type.id === "EVIOLITE");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}) ? 10 : 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}),
|
}),
|
||||||
@ -1744,19 +1751,59 @@ const modifierPool: ModifierPool = {
|
|||||||
|| (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId)))) ? 12 : 0;
|
|| (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId)))) ? 12 : 0;
|
||||||
}, 12),
|
}, 12),
|
||||||
new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => {
|
new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => {
|
||||||
const checkedAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD ];
|
return party.some(p => {
|
||||||
const checkedMoves = [ Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT ];
|
const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId);
|
||||||
// If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear
|
|
||||||
return party.some(p => !p.getHeldItems().some(i => i instanceof TurnStatusEffectModifier)
|
const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true);
|
||||||
&& (checkedAbilities.some(a => p.hasAbility(a, false, true))
|
const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB");
|
||||||
|| p.getMoveset(true).some(m => m && checkedMoves.includes(m.moveId)))) ? 10 : 0;
|
|
||||||
|
// Moves that take advantage of obtaining the actual status effect
|
||||||
|
const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ]
|
||||||
|
.some(m => moveset.includes(m));
|
||||||
|
// Moves that take advantage of being able to give the target a status orb
|
||||||
|
// TODO: Take moves from comment they are implemented
|
||||||
|
const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ]
|
||||||
|
.some(m => moveset.includes(m));
|
||||||
|
// Abilities that take advantage of obtaining the actual status effect
|
||||||
|
const hasRelevantAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD ]
|
||||||
|
.some(a => p.hasAbility(a, false, true));
|
||||||
|
|
||||||
|
if (!isHoldingOrb) {
|
||||||
|
if (canSetStatus) {
|
||||||
|
return hasRelevantAbilities || hasStatusMoves;
|
||||||
|
} else {
|
||||||
|
return hasItemMoves;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}) ? 10 : 0;
|
||||||
}, 10),
|
}, 10),
|
||||||
new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => {
|
new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => {
|
||||||
const checkedAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD ];
|
return party.some(p => {
|
||||||
const checkedMoves = [ Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT ];
|
const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId);
|
||||||
// If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear
|
const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true);
|
||||||
return party.some(p => !p.getHeldItems().some(i => i instanceof TurnStatusEffectModifier)
|
const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB");
|
||||||
&& (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && checkedMoves.includes(m.moveId)))) ? 10 : 0;
|
|
||||||
|
// Moves that take advantage of obtaining the actual status effect
|
||||||
|
const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ]
|
||||||
|
.some(m => moveset.includes(m));
|
||||||
|
// Moves that take advantage of being able to give the target a status orb
|
||||||
|
// TODO: Take moves from comment they are implemented
|
||||||
|
const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ]
|
||||||
|
.some(m => moveset.includes(m));
|
||||||
|
// Abilities that take advantage of obtaining the actual status effect
|
||||||
|
const hasRelevantAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD ]
|
||||||
|
.some(a => p.hasAbility(a, false, true));
|
||||||
|
|
||||||
|
if (!isHoldingOrb) {
|
||||||
|
if (canSetStatus) {
|
||||||
|
return hasRelevantAbilities || hasStatusMoves;
|
||||||
|
} else {
|
||||||
|
return hasItemMoves;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}) ? 10 : 0;
|
||||||
}, 10),
|
}, 10),
|
||||||
new WeightedModifierType(modifierTypes.WHITE_HERB, (party: Pokemon[]) => {
|
new WeightedModifierType(modifierTypes.WHITE_HERB, (party: Pokemon[]) => {
|
||||||
const checkedAbilities = [ Abilities.WEAK_ARMOR, Abilities.CONTRARY, Abilities.MOODY, Abilities.ANGER_SHELL, Abilities.COMPETITIVE, Abilities.DEFIANT ];
|
const checkedAbilities = [ Abilities.WEAK_ARMOR, Abilities.CONTRARY, Abilities.MOODY, Abilities.ANGER_SHELL, Abilities.COMPETITIVE, Abilities.DEFIANT ];
|
||||||
@ -1794,7 +1841,6 @@ const modifierPool: ModifierPool = {
|
|||||||
new WeightedModifierType(modifierTypes.SOUL_DEW, 7),
|
new WeightedModifierType(modifierTypes.SOUL_DEW, 7),
|
||||||
//new WeightedModifierType(modifierTypes.OVAL_CHARM, 6),
|
//new WeightedModifierType(modifierTypes.OVAL_CHARM, 6),
|
||||||
new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => !party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100 ? 4 : 0, 4),
|
new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => !party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100 ? 4 : 0, 4),
|
||||||
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 4),
|
|
||||||
new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)),
|
new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)),
|
||||||
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
|
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
|
||||||
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
|
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
|
||||||
|
@ -18,7 +18,6 @@ import type { VoucherType } from "#app/system/voucher";
|
|||||||
import { Command } from "#app/ui/command-ui-handler";
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
import { addTextObject, TextStyle } from "#app/ui/text";
|
import { addTextObject, TextStyle } from "#app/ui/text";
|
||||||
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils";
|
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils";
|
||||||
import { Abilities } from "#enums/abilities";
|
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -726,22 +725,6 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Applies to items with chance of activating secondary effects ie Kings Rock
|
|
||||||
getSecondaryChanceMultiplier(pokemon: Pokemon): number {
|
|
||||||
// Temporary quickfix to stop game from freezing when the opponet uses u-turn while holding on to king's rock
|
|
||||||
if (!pokemon.getLastXMoves()[0]) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const sheerForceAffected = allMoves[pokemon.getLastXMoves()[0].move].chance >= 0 && pokemon.hasAbility(Abilities.SHEER_FORCE);
|
|
||||||
|
|
||||||
if (sheerForceAffected) {
|
|
||||||
return 0;
|
|
||||||
} else if (pokemon.hasAbility(Abilities.SERENE_GRACE)) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number {
|
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number {
|
||||||
const pokemon = this.getPokemon(scene);
|
const pokemon = this.getPokemon(scene);
|
||||||
if (!pokemon) {
|
if (!pokemon) {
|
||||||
@ -1614,9 +1597,16 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for Pokemon held items like King's Rock
|
||||||
|
* Because King's Rock can be stacked in PokeRogue, unlike mainline, it does not receive a boost from Abilities.SERENE_GRACE
|
||||||
|
*/
|
||||||
export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
||||||
|
private chance: number;
|
||||||
constructor(type: ModifierType, pokemonId: number, stackCount?: number) {
|
constructor(type: ModifierType, pokemonId: number, stackCount?: number) {
|
||||||
super(type, pokemonId, stackCount);
|
super(type, pokemonId, stackCount);
|
||||||
|
|
||||||
|
this.chance = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
matchType(modifier: Modifier) {
|
matchType(modifier: Modifier) {
|
||||||
@ -1644,7 +1634,8 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
|||||||
* @returns `true` if {@linkcode FlinchChanceModifier} has been applied
|
* @returns `true` if {@linkcode FlinchChanceModifier} has been applied
|
||||||
*/
|
*/
|
||||||
override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean {
|
override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean {
|
||||||
if (!flinched.value && pokemon.randSeedInt(10) < (this.getStackCount() * this.getSecondaryChanceMultiplier(pokemon))) {
|
// The check for pokemon.battleSummonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch
|
||||||
|
if (pokemon.battleSummonData && !flinched.value && pokemon.randSeedInt(100) < (this.getStackCount() * this.chance)) {
|
||||||
flinched.value = true;
|
flinched.value = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1652,7 +1643,7 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxHeldItemCount(pokemon: Pokemon): number {
|
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,8 @@ class DefaultOverrides {
|
|||||||
readonly ITEM_UNLOCK_OVERRIDE: Unlockables[] = [];
|
readonly ITEM_UNLOCK_OVERRIDE: Unlockables[] = [];
|
||||||
/** Set to `true` to show all tutorials */
|
/** Set to `true` to show all tutorials */
|
||||||
readonly BYPASS_TUTORIAL_SKIP_OVERRIDE: boolean = false;
|
readonly BYPASS_TUTORIAL_SKIP_OVERRIDE: boolean = false;
|
||||||
|
/** Set to `true` to be able to re-earn already unlocked achievements */
|
||||||
|
readonly ACHIEVEMENTS_REUNLOCK_OVERRIDE: boolean = false;
|
||||||
/** Set to `true` to force Paralysis and Freeze to always activate, or `false` to force them to not activate */
|
/** Set to `true` to force Paralysis and Freeze to always activate, or `false` to force them to not activate */
|
||||||
readonly STATUS_ACTIVATION_OVERRIDE: boolean | null = null;
|
readonly STATUS_ACTIVATION_OVERRIDE: boolean | null = null;
|
||||||
|
|
||||||
@ -175,7 +177,11 @@ class DefaultOverrides {
|
|||||||
// MYSTERY ENCOUNTER OVERRIDES
|
// MYSTERY ENCOUNTER OVERRIDES
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
|
||||||
/** 1 to 256, set to null to ignore */
|
/**
|
||||||
|
* `1` (almost never) to `256` (always), set to `null` to disable the override
|
||||||
|
*
|
||||||
|
* Note: Make sure `STARTING_WAVE_OVERRIDE > 10`, otherwise MEs won't trigger
|
||||||
|
*/
|
||||||
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = null;
|
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = null;
|
||||||
readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier | null = null;
|
readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier | null = null;
|
||||||
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = null;
|
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = null;
|
||||||
|
@ -52,7 +52,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
enemyPokemon.trySetStatus(StatusEffect.FAINT);
|
enemyPokemon.trySetStatus(StatusEffect.FAINT);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scene.pushPhase(new BattleEndPhase(this.scene));
|
this.scene.pushPhase(new BattleEndPhase(this.scene, false));
|
||||||
this.scene.pushPhase(new NewBattlePhase(this.scene));
|
this.scene.pushPhase(new NewBattlePhase(this.scene));
|
||||||
} else {
|
} else {
|
||||||
playerPokemon.turnData.failedRunAway = true;
|
playerPokemon.turnData.failedRunAway = true;
|
||||||
|
@ -8,7 +8,7 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
/** If true, will increment battles won */
|
/** If true, will increment battles won */
|
||||||
isVictory: boolean;
|
isVictory: boolean;
|
||||||
|
|
||||||
constructor(scene: BattleScene, isVictory: boolean = true) {
|
constructor(scene: BattleScene, isVictory: boolean) {
|
||||||
super(scene);
|
super(scene);
|
||||||
|
|
||||||
this.isVictory = isVictory;
|
this.isVictory = isVictory;
|
||||||
@ -17,16 +17,17 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
this.scene.gameData.gameStats.battles++;
|
||||||
|
if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) {
|
||||||
|
this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isVictory) {
|
if (this.isVictory) {
|
||||||
this.scene.currentBattle.addBattleScore(this.scene);
|
this.scene.currentBattle.addBattleScore(this.scene);
|
||||||
|
|
||||||
this.scene.gameData.gameStats.battles++;
|
|
||||||
if (this.scene.currentBattle.trainer) {
|
if (this.scene.currentBattle.trainer) {
|
||||||
this.scene.gameData.gameStats.trainersDefeated++;
|
this.scene.gameData.gameStats.trainersDefeated++;
|
||||||
}
|
}
|
||||||
if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) {
|
|
||||||
this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endless graceful end
|
// Endless graceful end
|
||||||
@ -42,7 +43,7 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of this.scene.getPokemonAllowedInBattle()) {
|
for (const pokemon of this.scene.getPokemonAllowedInBattle()) {
|
||||||
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon);
|
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon, false, this.isVictory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.scene.currentBattle.moneyScattered) {
|
if (this.scene.currentBattle.moneyScattered) {
|
||||||
|
@ -5,7 +5,6 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
import { PostSummonPhase } from "./post-summon-phase";
|
|
||||||
import { SummonMissingPhase } from "./summon-missing-phase";
|
import { SummonMissingPhase } from "./summon-missing-phase";
|
||||||
import { SwitchPhase } from "./switch-phase";
|
import { SwitchPhase } from "./switch-phase";
|
||||||
import { SwitchType } from "#enums/switch-type";
|
import { SwitchType } from "#enums/switch-type";
|
||||||
@ -54,7 +53,6 @@ export class CheckSwitchPhase extends BattlePhase {
|
|||||||
this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => {
|
this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => {
|
||||||
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
this.scene.ui.setMode(Mode.MESSAGE);
|
||||||
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
|
||||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true));
|
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true));
|
||||||
this.end();
|
this.end();
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import BattleScene from "#app/battle-scene";
|
import type BattleScene from "#app/battle-scene";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { type BattlerIndex } from "#app/battle";
|
||||||
import { BattleSpec } from "#app/enums/battle-spec";
|
import { BattleSpec } from "#enums/battle-spec";
|
||||||
import { DamageResult, HitResult } from "#app/field/pokemon";
|
import { type DamageResult, HitResult } from "#app/field/pokemon";
|
||||||
import * as Utils from "#app/utils";
|
import { fixedInt } from "#app/utils";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "#app/phases/pokemon-phase";
|
||||||
|
|
||||||
export class DamagePhase extends PokemonPhase {
|
export class DamageAnimPhase extends PokemonPhase {
|
||||||
private amount: integer;
|
private amount: integer;
|
||||||
private damageResult: DamageResult;
|
private damageResult: DamageResult;
|
||||||
private critical: boolean;
|
private critical: boolean;
|
||||||
@ -25,7 +25,7 @@ export class DamagePhase extends PokemonPhase {
|
|||||||
if (this.scene.moveAnimations) {
|
if (this.scene.moveAnimations) {
|
||||||
this.scene.toggleInvert(true);
|
this.scene.toggleInvert(true);
|
||||||
}
|
}
|
||||||
this.scene.time.delayedCall(Utils.fixedInt(1000), () => {
|
this.scene.time.delayedCall(fixedInt(1000), () => {
|
||||||
this.scene.toggleInvert(false);
|
this.scene.toggleInvert(false);
|
||||||
this.applyDamage();
|
this.applyDamage();
|
||||||
});
|
});
|
@ -14,6 +14,7 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
|||||||
import * as Utils from "#app/utils";
|
import * as Utils from "#app/utils";
|
||||||
import { EggLapsePhase } from "./egg-lapse-phase";
|
import { EggLapsePhase } from "./egg-lapse-phase";
|
||||||
import { EggHatchData } from "#app/data/egg-hatch-data";
|
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||||
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -329,7 +330,12 @@ export class EggHatchPhase extends Phase {
|
|||||||
this.scene.validateAchv(achvs.HATCH_SHINY);
|
this.scene.validateAchv(achvs.HATCH_SHINY);
|
||||||
}
|
}
|
||||||
this.eggContainer.setVisible(false);
|
this.eggContainer.setVisible(false);
|
||||||
this.pokemonSprite.play(this.pokemon.getSpriteKey(true));
|
const spriteKey = this.pokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
this.pokemonSprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
|
this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
|
||||||
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
||||||
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);
|
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);
|
||||||
@ -341,8 +347,7 @@ export class EggHatchPhase extends Phase {
|
|||||||
this.pokemon.cry();
|
this.pokemon.cry();
|
||||||
if (isShiny) {
|
if (isShiny) {
|
||||||
this.scene.time.delayedCall(Utils.fixedInt(500), () => {
|
this.scene.time.delayedCall(Utils.fixedInt(500), () => {
|
||||||
this.pokemonShinySparkle.play(`sparkle${this.pokemon.variant ? `_${this.pokemon.variant + 1}` : ""}`);
|
doShinySparkleAnim(this.scene, this.pokemonShinySparkle, this.pokemon.variant);
|
||||||
this.scene.playSound("se/sparkle");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.scene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => {
|
this.scene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => {
|
||||||
|
@ -34,7 +34,7 @@ export class EggLapsePhase extends Phase {
|
|||||||
if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) {
|
if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) {
|
||||||
this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
|
this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
|
||||||
// show prompt for skip, blocking inputs for 1 second
|
// show prompt for skip, blocking inputs for 1 second
|
||||||
this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0);
|
this.scene.ui.showText(i18next.t("battle:eggSkipPrompt", { eggsToHatch: eggsToHatchCount }), 0);
|
||||||
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
|
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
|
||||||
this.hatchEggsSkipped(eggsToHatch);
|
this.hatchEggsSkipped(eggsToHatch);
|
||||||
this.showSummary();
|
this.showSummary();
|
||||||
|
@ -34,6 +34,7 @@ import { Biome } from "#enums/biome";
|
|||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
|
import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
|
||||||
@ -216,6 +217,11 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) {
|
if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) {
|
||||||
regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD);
|
regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD);
|
||||||
this.scene.generateEnemyModifiers();
|
this.scene.generateEnemyModifiers();
|
||||||
|
overrideModifiers(this.scene, false);
|
||||||
|
this.scene.getEnemyField().forEach(enemy => {
|
||||||
|
overrideHeldItems(this.scene, enemy, false);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
|
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
@ -379,6 +385,9 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
|
|
||||||
if (encounter.onVisualsStart) {
|
if (encounter.onVisualsStart) {
|
||||||
encounter.onVisualsStart(this.scene);
|
encounter.onVisualsStart(this.scene);
|
||||||
|
} else if (encounter.spriteConfigs && introVisuals) {
|
||||||
|
// If the encounter doesn't have any special visual intro, show sparkle for shiny Pokemon
|
||||||
|
introVisuals.playShinySparkles();
|
||||||
}
|
}
|
||||||
|
|
||||||
const doEncounter = () => {
|
const doEncounter = () => {
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||||
import { Phase } from "#app/phase";
|
import { Phase } from "#app/phase";
|
||||||
import BattleScene, { AnySound } from "#app/battle-scene";
|
import BattleScene, { AnySound } from "#app/battle-scene";
|
||||||
import { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
|
import { FusionSpeciesFormEvolution, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
|
||||||
import EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
|
import EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
|
||||||
import * as Utils from "#app/utils";
|
import * as Utils from "#app/utils";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { cos, sin } from "#app/field/anims";
|
import { cos, sin } from "#app/field/anims";
|
||||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
import Pokemon, { LearnMoveSituation, PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { getTypeRgb } from "#app/data/type";
|
import { getTypeRgb } from "#app/data/type";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||||
import { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
|
import { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
|
||||||
|
import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves";
|
||||||
|
|
||||||
export class EvolutionPhase extends Phase {
|
export class EvolutionPhase extends Phase {
|
||||||
protected pokemon: PlayerPokemon;
|
protected pokemon: PlayerPokemon;
|
||||||
@ -20,6 +21,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
private preEvolvedPokemonName: string;
|
private preEvolvedPokemonName: string;
|
||||||
|
|
||||||
private evolution: SpeciesFormEvolution | null;
|
private evolution: SpeciesFormEvolution | null;
|
||||||
|
private fusionSpeciesEvolved: boolean; // Whether the evolution is of the fused species
|
||||||
private evolutionBgm: AnySound;
|
private evolutionBgm: AnySound;
|
||||||
private evolutionHandler: EvolutionSceneHandler;
|
private evolutionHandler: EvolutionSceneHandler;
|
||||||
|
|
||||||
@ -39,6 +41,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
this.pokemon = pokemon;
|
this.pokemon = pokemon;
|
||||||
this.evolution = evolution;
|
this.evolution = evolution;
|
||||||
this.lastLevel = lastLevel;
|
this.lastLevel = lastLevel;
|
||||||
|
this.fusionSpeciesEvolved = evolution instanceof FusionSpeciesFormEvolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(): boolean {
|
validate(): boolean {
|
||||||
@ -102,7 +105,13 @@ export class EvolutionPhase extends Phase {
|
|||||||
this.scene.ui.add(this.evolutionOverlay);
|
this.scene.ui.add(this.evolutionOverlay);
|
||||||
|
|
||||||
[ this.pokemonSprite, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
[ this.pokemonSprite, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
||||||
sprite.play(this.pokemon.getSpriteKey(true));
|
const spriteKey = this.pokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) });
|
sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) });
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
sprite.setPipelineData("ignoreTimeTint", true);
|
||||||
sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
||||||
@ -127,7 +136,13 @@ export class EvolutionPhase extends Phase {
|
|||||||
this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => {
|
this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => {
|
||||||
|
|
||||||
[ this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
[ this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
||||||
sprite.play(evolvedPokemon.getSpriteKey(true));
|
const spriteKey = evolvedPokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
sprite.setPipelineData("ignoreTimeTint", true);
|
||||||
sprite.setPipelineData("spriteKey", evolvedPokemon.getSpriteKey());
|
sprite.setPipelineData("spriteKey", evolvedPokemon.getSpriteKey());
|
||||||
sprite.setPipelineData("shiny", evolvedPokemon.shiny);
|
sprite.setPipelineData("shiny", evolvedPokemon.shiny);
|
||||||
@ -261,7 +276,8 @@ export class EvolutionPhase extends Phase {
|
|||||||
this.evolutionHandler.canCancel = false;
|
this.evolutionHandler.canCancel = false;
|
||||||
|
|
||||||
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => {
|
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => {
|
||||||
const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true);
|
const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved ? LearnMoveSituation.EVOLUTION_FUSED : this.pokemon.fusionSpecies ? LearnMoveSituation.EVOLUTION_FUSED_BASE : LearnMoveSituation.EVOLUTION;
|
||||||
|
const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation).filter(lm => lm[0] === EVOLVE_MOVE);
|
||||||
for (const lm of levelMoves) {
|
for (const lm of levelMoves) {
|
||||||
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.scene.getPlayerParty().indexOf(this.pokemon), lm[1]));
|
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.scene.getPlayerParty().indexOf(this.pokemon), lm[1]));
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||||||
import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
||||||
import { SwitchType } from "#enums/switch-type";
|
import { SwitchType } from "#enums/switch-type";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { DamagePhase } from "./damage-phase";
|
import { DamageAnimPhase } from "./damage-anim-phase";
|
||||||
import { GameOverPhase } from "./game-over-phase";
|
import { GameOverPhase } from "./game-over-phase";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import { SwitchPhase } from "./switch-phase";
|
import { SwitchPhase } from "./switch-phase";
|
||||||
@ -206,7 +206,7 @@ export class FaintPhase extends PokemonPhase {
|
|||||||
} else {
|
} else {
|
||||||
// Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase
|
// Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase
|
||||||
enemy.hp++;
|
enemy.hp++;
|
||||||
this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER));
|
this.scene.unshiftPhase(new DamageAnimPhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER));
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -39,7 +39,13 @@ export class FormChangePhase extends EvolutionPhase {
|
|||||||
this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => {
|
this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => {
|
||||||
|
|
||||||
[ this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
[ this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
||||||
sprite.play(transformedPokemon.getSpriteKey(true));
|
const spriteKey = transformedPokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
sprite.setPipelineData("ignoreTimeTint", true);
|
||||||
sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey());
|
sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey());
|
||||||
sprite.setPipelineData("shiny", transformedPokemon.shiny);
|
sprite.setPipelineData("shiny", transformedPokemon.shiny);
|
||||||
|
@ -23,6 +23,12 @@ import * as Utils from "#app/utils";
|
|||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { SessionSaveData } from "#app/system/game-data";
|
||||||
|
import PersistentModifierData from "#app/system/modifier-data";
|
||||||
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
|
import ChallengeData from "#app/system/challenge-data";
|
||||||
|
import TrainerData from "#app/system/trainer-data";
|
||||||
|
import ArenaData from "#app/system/arena-data";
|
||||||
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
||||||
|
|
||||||
export class GameOverPhase extends BattlePhase {
|
export class GameOverPhase extends BattlePhase {
|
||||||
@ -109,7 +115,7 @@ export class GameOverPhase extends BattlePhase {
|
|||||||
this.scene.gameData.gameStats.dailyRunSessionsWon++;
|
this.scene.gameData.gameStats.dailyRunSessionsWon++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.scene.gameData.saveRunHistory(this.scene, this.scene.gameData.getSessionSaveData(this.scene), this.isVictory);
|
|
||||||
const fadeDuration = this.isVictory ? 10000 : 5000;
|
const fadeDuration = this.isVictory ? 10000 : 5000;
|
||||||
this.scene.fadeOutBgm(fadeDuration, true);
|
this.scene.fadeOutBgm(fadeDuration, true);
|
||||||
const activeBattlers = this.scene.getField().filter(p => p?.isActive(true));
|
const activeBattlers = this.scene.getField().filter(p => p?.isActive(true));
|
||||||
@ -135,8 +141,11 @@ export class GameOverPhase extends BattlePhase {
|
|||||||
this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM));
|
this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase));
|
this.getRunHistoryEntry().then(runHistoryEntry => {
|
||||||
this.end();
|
this.scene.gameData.saveRunHistory(this.scene, runHistoryEntry, this.isVictory);
|
||||||
|
this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase));
|
||||||
|
this.end();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.isVictory && this.scene.gameMode.isClassic) {
|
if (this.isVictory && this.scene.gameMode.isClassic) {
|
||||||
@ -212,5 +221,34 @@ export class GameOverPhase extends BattlePhase {
|
|||||||
this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter)));
|
this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slightly modified version of {@linkcode GameData.getSessionSaveData}.
|
||||||
|
* @returns A promise containing the {@linkcode SessionSaveData}
|
||||||
|
*/
|
||||||
|
private async getRunHistoryEntry(): Promise<SessionSaveData> {
|
||||||
|
const preWaveSessionData = await this.scene.gameData.getSession(this.scene.sessionSlotId);
|
||||||
|
return {
|
||||||
|
seed: this.scene.seed,
|
||||||
|
playTime: this.scene.sessionPlayTime,
|
||||||
|
gameMode: this.scene.gameMode.modeId,
|
||||||
|
party: this.scene.getPlayerParty().map(p => new PokemonData(p)),
|
||||||
|
enemyParty: this.scene.getEnemyParty().map(p => new PokemonData(p)),
|
||||||
|
modifiers: preWaveSessionData ? preWaveSessionData.modifiers : this.scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)),
|
||||||
|
enemyModifiers: preWaveSessionData ? preWaveSessionData.enemyModifiers : this.scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)),
|
||||||
|
arena: new ArenaData(this.scene.arena),
|
||||||
|
pokeballCounts: this.scene.pokeballCounts,
|
||||||
|
money: Math.floor(this.scene.money),
|
||||||
|
score: this.scene.score,
|
||||||
|
waveIndex: this.scene.currentBattle.waveIndex,
|
||||||
|
battleType: this.scene.currentBattle.battleType,
|
||||||
|
trainer: this.scene.currentBattle.trainer ? new TrainerData(this.scene.currentBattle.trainer) : null,
|
||||||
|
gameVersion: this.scene.game.config.gameVersion,
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)),
|
||||||
|
mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType ?? -1,
|
||||||
|
mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData
|
||||||
|
} as SessionSaveData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,11 +4,13 @@ import {
|
|||||||
AddSecondStrikeAbAttr,
|
AddSecondStrikeAbAttr,
|
||||||
AlwaysHitAbAttr,
|
AlwaysHitAbAttr,
|
||||||
applyPostAttackAbAttrs,
|
applyPostAttackAbAttrs,
|
||||||
|
applyPostDamageAbAttrs,
|
||||||
applyPostDefendAbAttrs,
|
applyPostDefendAbAttrs,
|
||||||
applyPreAttackAbAttrs,
|
applyPreAttackAbAttrs,
|
||||||
IgnoreMoveEffectsAbAttr,
|
IgnoreMoveEffectsAbAttr,
|
||||||
MaxMultiHitAbAttr,
|
MaxMultiHitAbAttr,
|
||||||
PostAttackAbAttr,
|
PostAttackAbAttr,
|
||||||
|
PostDamageAbAttr,
|
||||||
PostDefendAbAttr,
|
PostDefendAbAttr,
|
||||||
TypeImmunityAbAttr,
|
TypeImmunityAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/ability";
|
||||||
@ -125,6 +127,14 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||||
|
|
||||||
|
// If the user is acting again (such as due to Instruct), reset hitsLeft/hitCount so that
|
||||||
|
// the move executes correctly (ensures all hits of a multi-hit are properly calculated)
|
||||||
|
if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) {
|
||||||
|
user.turnData.hitsLeft = -1;
|
||||||
|
user.turnData.hitCount = 0;
|
||||||
|
user.turnData.extraTurns--;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this phase is for the first hit of the invoked move,
|
* If this phase is for the first hit of the invoked move,
|
||||||
* resolve the move's total hit count. This block combines the
|
* resolve the move's total hit count. This block combines the
|
||||||
@ -228,9 +238,11 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* If the move missed a target, stop all future hits against that target
|
* If the move missed a target, stop all future hits against that target
|
||||||
* and move on to the next target (if there is one).
|
* and move on to the next target (if there is one).
|
||||||
*/
|
*/
|
||||||
if (isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) {
|
if (target.switchOutStatus || isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) {
|
||||||
this.stopMultiHit(target);
|
this.stopMultiHit(target);
|
||||||
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
|
if (!target.switchOutStatus) {
|
||||||
|
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
|
||||||
|
}
|
||||||
if (moveHistoryEntry.result === MoveResult.PENDING) {
|
if (moveHistoryEntry.result === MoveResult.PENDING) {
|
||||||
moveHistoryEntry.result = MoveResult.MISS;
|
moveHistoryEntry.result = MoveResult.MISS;
|
||||||
}
|
}
|
||||||
@ -299,10 +311,17 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
*/
|
*/
|
||||||
if (lastHit) {
|
if (lastHit) {
|
||||||
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
||||||
|
/**
|
||||||
|
* Multi-Lens, Multi Hit move and Parental Bond check for PostDamageAbAttr
|
||||||
|
* other damage source are calculated in damageAndUpdate in pokemon.ts
|
||||||
|
*/
|
||||||
|
if (user.turnData.hitCount > 1) {
|
||||||
|
applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs.
|
* Create a Promise that applies *all* effects from the invoked move's MoveEffectAttrs.
|
||||||
* These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger
|
* These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger
|
||||||
* type requires different conditions to be met with respect to the move's hit result.
|
* type requires different conditions to be met with respect to the move's hit result.
|
||||||
*/
|
*/
|
||||||
|
@ -27,9 +27,12 @@ export class PostSummonPhase extends PokemonPhase {
|
|||||||
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
|
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end());
|
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon)
|
||||||
|
.then(() => {
|
||||||
|
const field = pokemon.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
|
||||||
|
field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false));
|
||||||
|
|
||||||
const field = pokemon.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
|
this.end();
|
||||||
field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
start() {
|
start() {
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) {
|
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
|
||||||
pokemon.status.incrementTurn();
|
pokemon.status.incrementTurn();
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new Utils.BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
|
@ -43,7 +43,12 @@ export class QuietFormChangePhase extends BattlePhase {
|
|||||||
const getPokemonSprite = () => {
|
const getPokemonSprite = () => {
|
||||||
const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub");
|
const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub");
|
||||||
sprite.setOrigin(0.5, 1);
|
sprite.setOrigin(0.5, 1);
|
||||||
sprite.play(this.pokemon.getBattleSpriteKey()).stop();
|
const spriteKey = this.pokemon.getBattleSpriteKey();
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey).stop();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) });
|
sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) });
|
||||||
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
||||||
if (this.pokemon.summonData?.speciesForm) {
|
if (this.pokemon.summonData?.speciesForm) {
|
||||||
@ -81,7 +86,12 @@ export class QuietFormChangePhase extends BattlePhase {
|
|||||||
this.pokemon.setVisible(false);
|
this.pokemon.setVisible(false);
|
||||||
this.pokemon.changeForm(this.formChange).then(() => {
|
this.pokemon.changeForm(this.formChange).then(() => {
|
||||||
pokemonFormTintSprite.setScale(0.01);
|
pokemonFormTintSprite.setScale(0.01);
|
||||||
pokemonFormTintSprite.play(this.pokemon.getBattleSpriteKey()).stop();
|
const spriteKey = this.pokemon.getBattleSpriteKey();
|
||||||
|
try {
|
||||||
|
pokemonFormTintSprite.play(spriteKey).stop();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
pokemonFormTintSprite.setVisible(true);
|
pokemonFormTintSprite.setVisible(true);
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: pokemonTintSprite,
|
targets: pokemonTintSprite,
|
||||||
|
@ -3,6 +3,7 @@ import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handl
|
|||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { SwitchType } from "#enums/switch-type";
|
import { SwitchType } from "#enums/switch-type";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
|
import { PostSummonPhase } from "./post-summon-phase";
|
||||||
import { SwitchSummonPhase } from "./switch-summon-phase";
|
import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,6 +64,9 @@ export class SwitchPhase extends BattlePhase {
|
|||||||
|
|
||||||
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => {
|
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => {
|
||||||
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
||||||
|
// Remove any pre-existing PostSummonPhase under the same field index.
|
||||||
|
// Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave.
|
||||||
|
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
||||||
const switchType = (option === PartyOption.PASS_BATON) ? SwitchType.BATON_PASS : this.switchType;
|
const switchType = (option === PartyOption.PASS_BATON) ? SwitchType.BATON_PASS : this.switchType;
|
||||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, fieldIndex, slotIndex, this.doReturn));
|
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, fieldIndex, slotIndex, this.doReturn));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr } from "#app/data/ability";
|
import { applyPreSwitchOutAbAttrs, PostDamageForceSwitchAbAttr, PreSwitchOutAbAttr } from "#app/data/ability";
|
||||||
import { allMoves, ForceSwitchOutAttr } from "#app/data/move";
|
import { allMoves, ForceSwitchOutAttr } from "#app/data/move";
|
||||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||||
@ -166,10 +166,11 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
|
|
||||||
const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command;
|
const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command;
|
||||||
const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted();
|
const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted();
|
||||||
|
const lastPokemonHasForceSwitchAbAttr = this.lastPokemon.hasAbilityWithAttr(PostDamageForceSwitchAbAttr) && !this.lastPokemon.isFainted();
|
||||||
|
|
||||||
// Compensate for turn spent summoning
|
// Compensate for turn spent summoning
|
||||||
// Or compensate for force switch move if switched out pokemon is not fainted
|
// Or compensate for force switch move if switched out pokemon is not fainted
|
||||||
if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) {
|
if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted || lastPokemonHasForceSwitchAbAttr) {
|
||||||
pokemon.battleSummonData.turnCount--;
|
pokemon.battleSummonData.turnCount--;
|
||||||
pokemon.battleSummonData.waveTurnCount--;
|
pokemon.battleSummonData.waveTurnCount--;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import { BattlePhase } from "./battle-phase";
|
|||||||
import { ModifierRewardPhase } from "./modifier-reward-phase";
|
import { ModifierRewardPhase } from "./modifier-reward-phase";
|
||||||
import { MoneyRewardPhase } from "./money-reward-phase";
|
import { MoneyRewardPhase } from "./money-reward-phase";
|
||||||
import { TrainerSlot } from "#app/data/trainer-config";
|
import { TrainerSlot } from "#app/data/trainer-config";
|
||||||
|
import { Biome } from "#app/enums/biome";
|
||||||
|
import { achvs } from "#app/system/achv";
|
||||||
|
|
||||||
export class TrainerVictoryPhase extends BattlePhase {
|
export class TrainerVictoryPhase extends BattlePhase {
|
||||||
constructor(scene: BattleScene) {
|
constructor(scene: BattleScene) {
|
||||||
@ -34,11 +36,17 @@ export class TrainerVictoryPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct?
|
const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct?
|
||||||
|
// Validate Voucher for boss trainers
|
||||||
if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
|
if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
|
||||||
if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) {
|
if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) {
|
||||||
this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [ modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType]));
|
this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [ modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Breeders in Space achievement
|
||||||
|
if (this.scene.arena.biomeType === Biome.SPACE
|
||||||
|
&& (trainerType === TrainerType.BREEDER || trainerType === TrainerType.EXPERT_POKEMON_BREEDER)) {
|
||||||
|
this.scene.validateAchv(achvs.BREEDERS_IN_SPACE);
|
||||||
|
}
|
||||||
|
|
||||||
this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => {
|
this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => {
|
||||||
const victoryMessages = this.scene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct?
|
const victoryMessages = this.scene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct?
|
||||||
|
@ -23,22 +23,24 @@ export class TurnEndPhase extends FieldPhase {
|
|||||||
this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn));
|
this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn));
|
||||||
|
|
||||||
const handlePokemon = (pokemon: Pokemon) => {
|
const handlePokemon = (pokemon: Pokemon) => {
|
||||||
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
|
if (!pokemon.switchOutStatus) {
|
||||||
|
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
|
||||||
|
|
||||||
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
|
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
|
||||||
|
|
||||||
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
|
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
|
||||||
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(),
|
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(),
|
||||||
Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true));
|
Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pokemon.isPlayer()) {
|
||||||
|
this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon);
|
||||||
|
this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pokemon.isPlayer()) {
|
|
||||||
this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon);
|
|
||||||
this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
|
|
||||||
|
|
||||||
this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);
|
this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);
|
||||||
|
|
||||||
this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon);
|
this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon);
|
||||||
|
@ -41,7 +41,7 @@ export class VictoryPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) {
|
if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) {
|
||||||
this.scene.pushPhase(new BattleEndPhase(this.scene));
|
this.scene.pushPhase(new BattleEndPhase(this.scene, true));
|
||||||
if (this.scene.currentBattle.battleType === BattleType.TRAINER) {
|
if (this.scene.currentBattle.battleType === BattleType.TRAINER) {
|
||||||
this.scene.pushPhase(new TrainerVictoryPhase(this.scene));
|
this.scene.pushPhase(new TrainerVictoryPhase(this.scene));
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.executeForAll((pokemon: Pokemon) => {
|
this.executeForAll((pokemon: Pokemon) => {
|
||||||
const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length;
|
const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length || pokemon.switchOutStatus;
|
||||||
if (!immune) {
|
if (!immune) {
|
||||||
inflictDamage(pokemon);
|
inflictDamage(pokemon);
|
||||||
}
|
}
|
||||||
@ -59,8 +59,12 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct?
|
this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
|
||||||
this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather));
|
this.executeForAll((pokemon: Pokemon) => {
|
||||||
|
if (!pokemon.switchOutStatus) {
|
||||||
|
applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
super.start();
|
super.start();
|
||||||
});
|
});
|
||||||
|
@ -358,7 +358,7 @@ export const achvs = {
|
|||||||
MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 18 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
|
MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 18 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
|
||||||
FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c, scene) => c instanceof FreshStartChallenge && c.value > 0 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
|
FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c, scene) => c instanceof FreshStartChallenge && c.value > 0 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
|
||||||
INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0),
|
INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0),
|
||||||
BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 100).setSecret(),
|
BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function initAchievements() {
|
export function initAchievements() {
|
||||||
|
@ -171,7 +171,7 @@ export default class PokemonData {
|
|||||||
playerPokemon.nickname = this.nickname;
|
playerPokemon.nickname = this.nickname;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
: scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, this);
|
: scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, false, this);
|
||||||
if (this.summonData) {
|
if (this.summonData) {
|
||||||
ret.primeSummonData(this.summonData);
|
ret.primeSummonData(this.summonData);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BattleScene from "../../battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { hasTouchscreen } from "../../touch-controls";
|
import { hasTouchscreen } from "#app/touch-controls";
|
||||||
import { updateWindowType } from "../../ui/ui-theme";
|
import { updateWindowType } from "#app/ui/ui-theme";
|
||||||
import { CandyUpgradeNotificationChangedEvent } from "../../events/battle-scene";
|
import { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene";
|
||||||
import SettingsUiHandler from "#app/ui/settings/settings-ui-handler";
|
import SettingsUiHandler from "#app/ui/settings/settings-ui-handler";
|
||||||
import { EaseType } from "#enums/ease-type";
|
import { EaseType } from "#enums/ease-type";
|
||||||
import { MoneyFormat } from "#enums/money-format";
|
import { MoneyFormat } from "#enums/money-format";
|
||||||
@ -44,6 +44,7 @@ const OFF_ON: SettingOption[] = [
|
|||||||
label: i18next.t("settings:on")
|
label: i18next.t("settings:on")
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const AUTO_DISABLED: SettingOption[] = [
|
const AUTO_DISABLED: SettingOption[] = [
|
||||||
{
|
{
|
||||||
value: "Auto",
|
value: "Auto",
|
||||||
@ -55,6 +56,19 @@ const AUTO_DISABLED: SettingOption[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TOUCH_CONTROLS_OPTIONS: SettingOption[] = [
|
||||||
|
{
|
||||||
|
value: "Auto",
|
||||||
|
label: i18next.t("settings:auto")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "Disabled",
|
||||||
|
label: i18next.t("settings:disabled"),
|
||||||
|
needConfirmation: true,
|
||||||
|
confirmationMessage: i18next.t("settings:confirmDisableTouch")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [
|
const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [
|
||||||
{
|
{
|
||||||
value: "Rewards",
|
value: "Rewards",
|
||||||
@ -100,7 +114,9 @@ export enum SettingType {
|
|||||||
|
|
||||||
type SettingOption = {
|
type SettingOption = {
|
||||||
value: string,
|
value: string,
|
||||||
label: string
|
label: string,
|
||||||
|
needConfirmation?: boolean,
|
||||||
|
confirmationMessage?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Setting {
|
export interface Setting {
|
||||||
@ -344,13 +360,6 @@ export const Setting: Array<Setting> = [
|
|||||||
default: 1,
|
default: 1,
|
||||||
type: SettingType.GENERAL
|
type: SettingType.GENERAL
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: SettingKeys.Touch_Controls,
|
|
||||||
label: i18next.t("settings:touchControls"),
|
|
||||||
options: AUTO_DISABLED,
|
|
||||||
default: 0,
|
|
||||||
type: SettingType.GENERAL
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: SettingKeys.Vibration,
|
key: SettingKeys.Vibration,
|
||||||
label: i18next.t("settings:vibrations"),
|
label: i18next.t("settings:vibrations"),
|
||||||
@ -358,6 +367,28 @@ export const Setting: Array<Setting> = [
|
|||||||
default: 0,
|
default: 0,
|
||||||
type: SettingType.GENERAL
|
type: SettingType.GENERAL
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: SettingKeys.Touch_Controls,
|
||||||
|
label: i18next.t("settings:touchControls"),
|
||||||
|
options: TOUCH_CONTROLS_OPTIONS,
|
||||||
|
default: 0,
|
||||||
|
type: SettingType.GENERAL,
|
||||||
|
isHidden: () => !hasTouchscreen()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingKeys.Move_Touch_Controls,
|
||||||
|
label: i18next.t("settings:moveTouchControls"),
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "Configure",
|
||||||
|
label: i18next.t("settings:change")
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: 0,
|
||||||
|
type: SettingType.GENERAL,
|
||||||
|
activatable: true,
|
||||||
|
isHidden: () => !hasTouchscreen()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: SettingKeys.Language,
|
key: SettingKeys.Language,
|
||||||
label: i18next.t("settings:language"),
|
label: i18next.t("settings:language"),
|
||||||
@ -643,20 +674,6 @@ export const Setting: Array<Setting> = [
|
|||||||
type: SettingType.AUDIO,
|
type: SettingType.AUDIO,
|
||||||
requireReload: true
|
requireReload: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: SettingKeys.Move_Touch_Controls,
|
|
||||||
label: i18next.t("settings:moveTouchControls"),
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
value: "Configure",
|
|
||||||
label: i18next.t("settings:change")
|
|
||||||
}
|
|
||||||
],
|
|
||||||
default: 0,
|
|
||||||
type: SettingType.GENERAL,
|
|
||||||
activatable: true,
|
|
||||||
isHidden: () => !hasTouchscreen()
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: SettingKeys.Shop_Cursor_Target,
|
key: SettingKeys.Shop_Cursor_Target,
|
||||||
label: i18next.t("settings:shopCursorTarget"),
|
label: i18next.t("settings:shopCursorTarget"),
|
||||||
@ -849,7 +866,7 @@ export function setSetting(scene: BattleScene, setting: string, value: integer):
|
|||||||
if (scene.ui) {
|
if (scene.ui) {
|
||||||
const cancelHandler = () => {
|
const cancelHandler = () => {
|
||||||
scene.ui.revertMode();
|
scene.ui.revertMode();
|
||||||
(scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true);
|
(scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(-1, 0, true);
|
||||||
};
|
};
|
||||||
const changeLocaleHandler = (locale: string): boolean => {
|
const changeLocaleHandler = (locale: string): boolean => {
|
||||||
try {
|
try {
|
||||||
|
81
src/test/abilities/analytic.test.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { isBetween, toDmgValue } from "#app/utils";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Analytic", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.SPLASH, Moves.TACKLE ])
|
||||||
|
.ability(Abilities.ANALYTIC)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.startingLevel(200)
|
||||||
|
.enemyLevel(200)
|
||||||
|
.enemySpecies(Species.SNORLAX)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should increase damage if the user moves last", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.ARCEUS ]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.toNextTurn();
|
||||||
|
const damage1 = enemy.getInverseHp();
|
||||||
|
enemy.hp = enemy.getMaxHp();
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
expect(isBetween(enemy.getInverseHp(), toDmgValue(damage1 * 1.3) - 3, toDmgValue(damage1 * 1.3) + 3)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should increase damage only if the user moves last in doubles", async () => {
|
||||||
|
game.override.battleType("double");
|
||||||
|
await game.classicMode.startBattle([ Species.GENGAR, Species.SHUCKLE ]);
|
||||||
|
|
||||||
|
const [ enemy, ] = game.scene.getEnemyField();
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]);
|
||||||
|
await game.toNextTurn();
|
||||||
|
const damage1 = enemy.getInverseHp();
|
||||||
|
enemy.hp = enemy.getMaxHp();
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER ]);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(isBetween(enemy.getInverseHp(), toDmgValue(damage1 * 1.3) - 3, toDmgValue(damage1 * 1.3) + 3)).toBe(true);
|
||||||
|
enemy.hp = enemy.getMaxHp();
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2 ]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
expect(enemy.getInverseHp()).toBe(damage1);
|
||||||
|
});
|
||||||
|
});
|
@ -1,9 +1,10 @@
|
|||||||
|
import { allAbilities } from "#app/data/ability";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||||
|
|
||||||
describe("Abilities - Arena Trap", () => {
|
describe("Abilities - Arena Trap", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -55,4 +56,39 @@ describe("Abilities - Arena Trap", () => {
|
|||||||
|
|
||||||
expect(game.scene.getEnemyField().length).toBe(2);
|
expect(game.scene.getEnemyField().length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This checks if the Player Pokemon is able to switch out/run away after the Enemy Pokemon with {@linkcode Abilities.ARENA_TRAP}
|
||||||
|
* is forcefully moved out of the field from moves such as Roar {@linkcode Moves.ROAR}
|
||||||
|
*
|
||||||
|
* Note: It should be able to switch out/run away
|
||||||
|
*/
|
||||||
|
it("should lift if pokemon with this ability leaves the field", async () => {
|
||||||
|
game.override
|
||||||
|
.battleType("double")
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.moveset([ Moves.ROAR, Moves.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH);
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP, Species.SUDOWOODO, Species.LUNATONE ]);
|
||||||
|
|
||||||
|
const [ enemy1, enemy2 ] = game.scene.getEnemyField();
|
||||||
|
const [ player1, player2 ] = game.scene.getPlayerField();
|
||||||
|
|
||||||
|
vi.spyOn(enemy1, "getAbility").mockReturnValue(allAbilities[Abilities.ARENA_TRAP]);
|
||||||
|
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
|
||||||
|
// This runs the fist command phase where the moves are selected
|
||||||
|
await game.toNextTurn();
|
||||||
|
// During the next command phase the player pokemons should not be trapped anymore
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player1.isTrapped()).toBe(false);
|
||||||
|
expect(player2.isTrapped()).toBe(false);
|
||||||
|
expect(enemy1.isOnField()).toBe(false);
|
||||||
|
expect(enemy2.isOnField()).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { BattlerIndex } from "#app/battle";
|
|||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/ability";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { WeatherType } from "#app/enums/weather-type";
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
import { MovePhase } from "#app/phases/move-phase";
|
import { MovePhase } from "#app/phases/move-phase";
|
||||||
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||||
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
|
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
|
||||||
@ -273,7 +273,7 @@ describe("Abilities - Forecast", () => {
|
|||||||
const castform = game.scene.getPlayerPokemon()!;
|
const castform = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
// Damage phase should come first
|
// Damage phase should come first
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to(DamageAnimPhase);
|
||||||
expect(castform.hp).toBeLessThan(castform.getMaxHp());
|
expect(castform.hp).toBeLessThan(castform.getMaxHp());
|
||||||
|
|
||||||
// Then change form
|
// Then change form
|
||||||
|
74
src/test/abilities/honey_gather.test.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import type { CommandPhase } from "#app/phases/command-phase";
|
||||||
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Honey Gather", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.SPLASH, Moves.ROAR, Moves.THUNDERBOLT ])
|
||||||
|
.startingLevel(100)
|
||||||
|
.ability(Abilities.HONEY_GATHER)
|
||||||
|
.passiveAbility(Abilities.RUN_AWAY)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should give money when winning a battle", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MILOTIC ]);
|
||||||
|
game.scene.money = 1000;
|
||||||
|
|
||||||
|
game.move.select(Moves.THUNDERBOLT);
|
||||||
|
await game.toNextWave();
|
||||||
|
|
||||||
|
expect(game.scene.money).toBeGreaterThan(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not give money when the enemy pokemon flees", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MILOTIC ]);
|
||||||
|
game.scene.money = 1000;
|
||||||
|
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(game.scene.money).toBe(1000);
|
||||||
|
expect(game.scene.currentBattle.waveIndex).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not give money when the player flees", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MILOTIC ]);
|
||||||
|
game.scene.money = 1000;
|
||||||
|
|
||||||
|
// something weird is going on with the test framework, so this is required to prevent a crash
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "scene", "get").mockReturnValue(game.scene);
|
||||||
|
|
||||||
|
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
|
||||||
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(game.scene.money).toBe(1000);
|
||||||
|
expect(game.scene.currentBattle.waveIndex).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
@ -42,7 +42,7 @@ describe("Abilities - Hustle", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.TACKLE);
|
game.move.select(Moves.TACKLE);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(pikachu.getEffectiveStat).toHaveReturnedWith(Math.floor(atk * 1.5));
|
expect(pikachu.getEffectiveStat).toHaveReturnedWith(Math.floor(atk * 1.5));
|
||||||
});
|
});
|
||||||
@ -68,7 +68,7 @@ describe("Abilities - Hustle", () => {
|
|||||||
vi.spyOn(pikachu, "getAccuracyMultiplier");
|
vi.spyOn(pikachu, "getAccuracyMultiplier");
|
||||||
|
|
||||||
game.move.select(Moves.GIGA_DRAIN);
|
game.move.select(Moves.GIGA_DRAIN);
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(pikachu.getEffectiveStat).toHaveReturnedWith(spatk);
|
expect(pikachu.getEffectiveStat).toHaveReturnedWith(spatk);
|
||||||
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1);
|
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1);
|
||||||
@ -86,7 +86,7 @@ describe("Abilities - Hustle", () => {
|
|||||||
vi.spyOn(allMoves[Moves.FISSURE], "calculateBattleAccuracy");
|
vi.spyOn(allMoves[Moves.FISSURE], "calculateBattleAccuracy");
|
||||||
|
|
||||||
game.move.select(Moves.FISSURE);
|
game.move.select(Moves.FISSURE);
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.turnData.damageTaken).toBe(enemyPokemon.getMaxHp());
|
expect(enemyPokemon.turnData.damageTaken).toBe(enemyPokemon.getMaxHp());
|
||||||
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1);
|
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1);
|
||||||
|
@ -51,7 +51,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.TACKLE);
|
game.move.select(Moves.TACKLE);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
const firstStrikeDamage = enemyStartingHp - enemyPokemon.hp;
|
const firstStrikeDamage = enemyStartingHp - enemyPokemon.hp;
|
||||||
enemyStartingHp = enemyPokemon.hp;
|
enemyStartingHp = enemyPokemon.hp;
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.SELF_DESTRUCT);
|
game.move.select(Moves.SELF_DESTRUCT);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase", false);
|
await game.phaseInterceptor.to("DamageAnimPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(1);
|
expect(leadPokemon.turnData.hitCount).toBe(1);
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
game.move.select(Moves.ROLLOUT);
|
game.move.select(Moves.ROLLOUT);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase", false);
|
await game.phaseInterceptor.to("DamageAnimPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(1);
|
expect(leadPokemon.turnData.hitCount).toBe(1);
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
game.move.select(Moves.COUNTER);
|
game.move.select(Moves.COUNTER);
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
const playerDamage = leadPokemon.getMaxHp() - leadPokemon.hp;
|
const playerDamage = leadPokemon.getMaxHp() - leadPokemon.hp;
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
game.move.select(Moves.EARTHQUAKE);
|
game.move.select(Moves.EARTHQUAKE);
|
||||||
await game.phaseInterceptor.to("DamagePhase", false);
|
await game.phaseInterceptor.to("DamageAnimPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
}
|
}
|
||||||
@ -238,7 +238,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.MIND_BLOWN);
|
game.move.select(Moves.MIND_BLOWN);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase", false);
|
await game.phaseInterceptor.to("DamageAnimPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
|
|
||||||
@ -285,7 +285,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.TACKLE);
|
game.move.select(Moves.TACKLE);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(3);
|
expect(leadPokemon.turnData.hitCount).toBe(3);
|
||||||
}
|
}
|
||||||
@ -307,7 +307,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
game.move.select(Moves.SEISMIC_TOSS);
|
game.move.select(Moves.SEISMIC_TOSS);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(3);
|
expect(leadPokemon.turnData.hitCount).toBe(3);
|
||||||
|
|
||||||
@ -329,7 +329,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
game.move.select(Moves.HYPER_BEAM);
|
game.move.select(Moves.HYPER_BEAM);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeUndefined();
|
expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeUndefined();
|
||||||
@ -353,7 +353,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
game.move.select(Moves.ANCHOR_SHOT);
|
game.move.select(Moves.ANCHOR_SHOT);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined();
|
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined();
|
||||||
@ -380,7 +380,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
game.move.select(Moves.SMACK_DOWN);
|
game.move.select(Moves.SMACK_DOWN);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
|
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
|
||||||
@ -424,7 +424,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
game.move.select(Moves.WAKE_UP_SLAP);
|
game.move.select(Moves.WAKE_UP_SLAP);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP);
|
expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { applyAbAttrs, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability";
|
|
||||||
import { Stat } from "#enums/stat";
|
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|
||||||
import * as Utils from "#app/utils";
|
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { allMoves } from "#app/data/move";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { FlinchAttr } from "#app/data/move";
|
||||||
|
|
||||||
describe("Abilities - Serene Grace", () => {
|
describe("Abilities - Serene Grace", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -27,66 +24,29 @@ describe("Abilities - Serene Grace", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
const movesToUse = [ Moves.AIR_SLASH, Moves.TACKLE ];
|
game.override
|
||||||
game.override.battleType("single");
|
.disableCrits()
|
||||||
game.override.enemySpecies(Species.ONIX);
|
.battleType("single")
|
||||||
game.override.startingLevel(100);
|
.ability(Abilities.SERENE_GRACE)
|
||||||
game.override.moveset(movesToUse);
|
.moveset([ Moves.AIR_SLASH ])
|
||||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
.enemySpecies(Species.ALOLA_GEODUDE)
|
||||||
|
.enemyLevel(10)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset([ Moves.SPLASH ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Move chance without Serene Grace", async () => {
|
it("Serene Grace should double the secondary effect chance of a move", async () => {
|
||||||
const moveToUse = Moves.AIR_SLASH;
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
await game.startBattle([
|
|
||||||
Species.PIDGEOT
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
const airSlashMove = allMoves[Moves.AIR_SLASH];
|
||||||
|
const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0];
|
||||||
|
vi.spyOn(airSlashFlinchAttr, "getMoveChance");
|
||||||
|
|
||||||
game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
|
game.move.select(Moves.AIR_SLASH);
|
||||||
expect(game.scene.getPlayerParty()[0].formIndex).toBe(0);
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
|
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.move.forceHit();
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
// Check chance of Air Slash without Serene Grace
|
expect(airSlashFlinchAttr.getMoveChance).toHaveLastReturnedWith(60);
|
||||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
});
|
||||||
const move = phase.move.getMove();
|
|
||||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
|
||||||
|
|
||||||
const chance = new Utils.IntegerHolder(move.chance);
|
|
||||||
console.log(move.chance + " Their ability is " + phase.getUserPokemon()!.getAbility().name);
|
|
||||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
|
||||||
expect(chance.value).toBe(30);
|
|
||||||
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("Move chance with Serene Grace", async () => {
|
|
||||||
const moveToUse = Moves.AIR_SLASH;
|
|
||||||
game.override.ability(Abilities.SERENE_GRACE);
|
|
||||||
await game.startBattle([
|
|
||||||
Species.TOGEKISS
|
|
||||||
]);
|
|
||||||
|
|
||||||
game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
|
|
||||||
expect(game.scene.getPlayerParty()[0].formIndex).toBe(0);
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
|
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
|
||||||
|
|
||||||
// Check chance of Air Slash with Serene Grace
|
|
||||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
|
||||||
const move = phase.move.getMove();
|
|
||||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
|
||||||
|
|
||||||
const chance = new Utils.IntegerHolder(move.chance);
|
|
||||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
|
||||||
expect(chance.value).toBe(60);
|
|
||||||
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
//TODO King's Rock Interaction Unit Test
|
|
||||||
});
|
});
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { applyAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr } from "#app/data/ability";
|
import { Type } from "#app/enums/type";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|
||||||
import { NumberHolder } from "#app/utils";
|
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { allMoves } from "#app/data/move";
|
import { allMoves, FlinchAttr } from "#app/data/move";
|
||||||
|
|
||||||
describe("Abilities - Sheer Force", () => {
|
describe("Abilities - Sheer Force", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -27,143 +25,91 @@ describe("Abilities - Sheer Force", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
const movesToUse = [ Moves.AIR_SLASH, Moves.BIND, Moves.CRUSH_CLAW, Moves.TACKLE ];
|
game.override
|
||||||
game.override.battleType("single");
|
.battleType("single")
|
||||||
game.override.enemySpecies(Species.ONIX);
|
.ability(Abilities.SHEER_FORCE)
|
||||||
game.override.startingLevel(100);
|
.enemySpecies(Species.ONIX)
|
||||||
game.override.moveset(movesToUse);
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
.enemyMoveset([ Moves.SPLASH ])
|
||||||
|
.disableCrits();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Sheer Force", async () => {
|
const SHEER_FORCE_MULT = 5461 / 4096;
|
||||||
const moveToUse = Moves.AIR_SLASH;
|
|
||||||
game.override.ability(Abilities.SHEER_FORCE);
|
it("Sheer Force should boost the power of the move but disable secondary effects", async () => {
|
||||||
|
game.override.moveset([ Moves.AIR_SLASH ]);
|
||||||
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
|
const airSlashMove = allMoves[Moves.AIR_SLASH];
|
||||||
|
vi.spyOn(airSlashMove, "calculateBattlePower");
|
||||||
|
const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0];
|
||||||
|
vi.spyOn(airSlashFlinchAttr, "getMoveChance");
|
||||||
|
|
||||||
|
game.move.select(Moves.AIR_SLASH);
|
||||||
|
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.move.forceHit();
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
|
expect(airSlashMove.calculateBattlePower).toHaveLastReturnedWith(airSlashMove.power * SHEER_FORCE_MULT);
|
||||||
|
expect(airSlashFlinchAttr.getMoveChance).toHaveLastReturnedWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Sheer Force does not affect the base damage or secondary effects of binding moves", async () => {
|
||||||
|
game.override.moveset([ Moves.BIND ]);
|
||||||
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
|
const bindMove = allMoves[Moves.BIND];
|
||||||
|
vi.spyOn(bindMove, "calculateBattlePower");
|
||||||
|
|
||||||
|
game.move.select(Moves.BIND);
|
||||||
|
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.move.forceHit();
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
|
expect(bindMove.calculateBattlePower).toHaveLastReturnedWith(bindMove.power);
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("Sheer Force does not boost the base damage of moves with no secondary effect", async () => {
|
||||||
|
game.override.moveset([ Moves.TACKLE ]);
|
||||||
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
||||||
|
|
||||||
game.scene.getEnemyPokemon()!.stats[Stat.SPDEF] = 10000;
|
const tackleMove = allMoves[Moves.TACKLE];
|
||||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
vi.spyOn(tackleMove, "calculateBattlePower");
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE);
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.move.forceHit();
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
expect(tackleMove.calculateBattlePower).toHaveLastReturnedWith(tackleMove.power);
|
||||||
const move = phase.move.getMove();
|
});
|
||||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
|
||||||
|
|
||||||
//Verify the move is boosted and has no chance of secondary effects
|
it("Sheer Force can disable the on-hit activation of specific abilities", async () => {
|
||||||
const power = new NumberHolder(move.power);
|
game.override
|
||||||
const chance = new NumberHolder(move.chance);
|
.moveset([ Moves.HEADBUTT ])
|
||||||
|
.enemySpecies(Species.SQUIRTLE)
|
||||||
|
.enemyLevel(10)
|
||||||
|
.enemyAbility(Abilities.COLOR_CHANGE);
|
||||||
|
|
||||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
|
||||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power);
|
|
||||||
|
|
||||||
expect(chance.value).toBe(0);
|
|
||||||
expect(power.value).toBe(move.power * 5461 / 4096);
|
|
||||||
|
|
||||||
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("Sheer Force with exceptions including binding moves", async () => {
|
|
||||||
const moveToUse = Moves.BIND;
|
|
||||||
game.override.ability(Abilities.SHEER_FORCE);
|
|
||||||
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
|
const headbuttMove = allMoves[Moves.HEADBUTT];
|
||||||
|
vi.spyOn(headbuttMove, "calculateBattlePower");
|
||||||
|
const headbuttFlinchAttr = headbuttMove.getAttrs(FlinchAttr)[0];
|
||||||
|
vi.spyOn(headbuttFlinchAttr, "getMoveChance");
|
||||||
|
|
||||||
|
game.move.select(Moves.HEADBUTT);
|
||||||
game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000;
|
|
||||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
|
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.move.forceHit();
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
expect(enemyPokemon?.getTypes()[0]).toBe(Type.WATER);
|
||||||
const move = phase.move.getMove();
|
expect(headbuttMove.calculateBattlePower).toHaveLastReturnedWith(headbuttMove.power * SHEER_FORCE_MULT);
|
||||||
expect(move.id).toBe(Moves.BIND);
|
expect(headbuttFlinchAttr.getMoveChance).toHaveLastReturnedWith(0);
|
||||||
|
});
|
||||||
//Binding moves and other exceptions are not affected by Sheer Force and have a chance.value of -1
|
|
||||||
const power = new NumberHolder(move.power);
|
|
||||||
const chance = new NumberHolder(move.chance);
|
|
||||||
|
|
||||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
|
||||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power);
|
|
||||||
|
|
||||||
expect(chance.value).toBe(-1);
|
|
||||||
expect(power.value).toBe(move.power);
|
|
||||||
|
|
||||||
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("Sheer Force with moves with no secondary effect", async () => {
|
|
||||||
const moveToUse = Moves.TACKLE;
|
|
||||||
game.override.ability(Abilities.SHEER_FORCE);
|
|
||||||
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
|
||||||
|
|
||||||
|
|
||||||
game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000;
|
|
||||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
|
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
|
||||||
|
|
||||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
|
||||||
const move = phase.move.getMove();
|
|
||||||
expect(move.id).toBe(Moves.TACKLE);
|
|
||||||
|
|
||||||
//Binding moves and other exceptions are not affected by Sheer Force and have a chance.value of -1
|
|
||||||
const power = new NumberHolder(move.power);
|
|
||||||
const chance = new NumberHolder(move.chance);
|
|
||||||
|
|
||||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
|
||||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power);
|
|
||||||
|
|
||||||
expect(chance.value).toBe(-1);
|
|
||||||
expect(power.value).toBe(move.power);
|
|
||||||
|
|
||||||
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("Sheer Force Disabling Specific Abilities", async () => {
|
|
||||||
const moveToUse = Moves.CRUSH_CLAW;
|
|
||||||
game.override.enemyAbility(Abilities.COLOR_CHANGE);
|
|
||||||
game.override.startingHeldItems([{ name: "KINGS_ROCK", count: 1 }]);
|
|
||||||
game.override.ability(Abilities.SHEER_FORCE);
|
|
||||||
await game.startBattle([ Species.PIDGEOT ]);
|
|
||||||
|
|
||||||
|
|
||||||
game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000;
|
|
||||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
|
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
|
||||||
|
|
||||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
|
||||||
const move = phase.move.getMove();
|
|
||||||
expect(move.id).toBe(Moves.CRUSH_CLAW);
|
|
||||||
|
|
||||||
//Disable color change due to being hit by Sheer Force
|
|
||||||
const power = new NumberHolder(move.power);
|
|
||||||
const chance = new NumberHolder(move.chance);
|
|
||||||
const user = phase.getUserPokemon()!;
|
|
||||||
const target = phase.getFirstTarget()!;
|
|
||||||
const opponentType = target.getTypes()[0];
|
|
||||||
|
|
||||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, chance, move, target, false);
|
|
||||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, false, power);
|
|
||||||
applyPostDefendAbAttrs(PostDefendTypeChangeAbAttr, target, user, move, target.apply(user, move));
|
|
||||||
|
|
||||||
expect(chance.value).toBe(0);
|
|
||||||
expect(power.value).toBe(move.power * 5461 / 4096);
|
|
||||||
expect(target.getTypes().length).toBe(2);
|
|
||||||
expect(target.getTypes()[0]).toBe(opponentType);
|
|
||||||
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
|
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
|
||||||
const moveToUse = Moves.CRUNCH;
|
const moveToUse = Moves.CRUNCH;
|
||||||
@ -191,5 +137,19 @@ describe("Abilities - Sheer Force", () => {
|
|||||||
expect(onix.getTypes()).toStrictEqual(expectedTypes);
|
expect(onix.getTypes()).toStrictEqual(expectedTypes);
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO King's Rock Interaction Unit Test
|
it("Sheer Force should disable Meloetta's transformation from Relic Song", async () => {
|
||||||
|
game.override
|
||||||
|
.ability(Abilities.SHEER_FORCE)
|
||||||
|
.moveset([ Moves.RELIC_SONG ])
|
||||||
|
.enemyMoveset([ Moves.SPLASH ])
|
||||||
|
.enemyLevel(100);
|
||||||
|
await game.classicMode.startBattle([ Species.MELOETTA ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon();
|
||||||
|
const formKeyStart = playerPokemon?.getFormKey();
|
||||||
|
|
||||||
|
game.move.select(Moves.RELIC_SONG);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
expect(formKeyStart).toBe(playerPokemon?.getFormKey());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,9 @@ describe("Abilities - Speed Boost", () => {
|
|||||||
|
|
||||||
game.override
|
game.override
|
||||||
.battleType("single")
|
.battleType("single")
|
||||||
.enemySpecies(Species.DRAGAPULT)
|
.enemySpecies(Species.SHUCKLE)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyLevel(100)
|
||||||
.ability(Abilities.SPEED_BOOST)
|
.ability(Abilities.SPEED_BOOST)
|
||||||
.enemyMoveset(Moves.SPLASH)
|
.enemyMoveset(Moves.SPLASH)
|
||||||
.moveset([ Moves.SPLASH, Moves.U_TURN ]);
|
.moveset([ Moves.SPLASH, Moves.U_TURN ]);
|
||||||
@ -70,21 +72,23 @@ describe("Abilities - Speed Boost", () => {
|
|||||||
Species.NINJASK
|
Species.NINJASK
|
||||||
]);
|
]);
|
||||||
|
|
||||||
game.move.select(Moves.U_TURN);
|
const [ shuckle, ninjask ] = game.scene.getPlayerParty();
|
||||||
game.doSelectPartyPokemon(1);
|
|
||||||
await game.toNextTurn();
|
|
||||||
let playerPokemon = game.scene.getPlayerPokemon()!;
|
|
||||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
|
||||||
|
|
||||||
game.move.select(Moves.U_TURN);
|
game.move.select(Moves.U_TURN);
|
||||||
game.doSelectPartyPokemon(1);
|
game.doSelectPartyPokemon(1);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
playerPokemon = game.scene.getPlayerPokemon()!;
|
expect(game.scene.getPlayerPokemon()!).toBe(ninjask);
|
||||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
expect(ninjask.getStatStage(Stat.SPD)).toBe(0);
|
||||||
|
|
||||||
|
game.move.select(Moves.U_TURN);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(game.scene.getPlayerPokemon()!).toBe(shuckle);
|
||||||
|
expect(shuckle.getStatStage(Stat.SPD)).toBe(0);
|
||||||
|
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
expect(shuckle.getStatStage(Stat.SPD)).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not trigger this turn if pokemon was switched into combat via normal switch, but the turn after",
|
it("should not trigger this turn if pokemon was switched into combat via normal switch, but the turn after",
|
||||||
|
85
src/test/abilities/stakeout.test.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { isBetween } from "#app/utils";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Stakeout", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.SPLASH, Moves.SURF ])
|
||||||
|
.ability(Abilities.STAKEOUT)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemyLevel(100)
|
||||||
|
.enemySpecies(Species.SNORLAX)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset([ Moves.SPLASH, Moves.FLIP_TURN ])
|
||||||
|
.startingWave(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should do double damage to a pokemon that switched out", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MILOTIC ]);
|
||||||
|
|
||||||
|
const [ enemy1, ] = game.scene.getEnemyParty();
|
||||||
|
|
||||||
|
game.move.select(Moves.SURF);
|
||||||
|
await game.forceEnemyMove(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
const damage1 = enemy1.getInverseHp();
|
||||||
|
enemy1.hp = enemy1.getMaxHp();
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.forceEnemyToSwitch();
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.move.select(Moves.SURF);
|
||||||
|
game.forceEnemyToSwitch();
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemy1.isFainted()).toBe(false);
|
||||||
|
expect(isBetween(enemy1.getInverseHp(), (damage1 * 2) - 5, (damage1 * 2) + 5)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should do double damage to a pokemon that switched out via U-Turn/etc", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MILOTIC ]);
|
||||||
|
|
||||||
|
const [ enemy1, ] = game.scene.getEnemyParty();
|
||||||
|
|
||||||
|
game.move.select(Moves.SURF);
|
||||||
|
await game.forceEnemyMove(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
const damage1 = enemy1.getInverseHp();
|
||||||
|
enemy1.hp = enemy1.getMaxHp();
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.FLIP_TURN);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.move.select(Moves.SURF);
|
||||||
|
await game.forceEnemyMove(Moves.FLIP_TURN);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemy1.isFainted()).toBe(false);
|
||||||
|
expect(isBetween(enemy1.getInverseHp(), (damage1 * 2) - 5, (damage1 * 2) + 5)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
import { EnemyPokemon } from "#app/field/pokemon";
|
import { EnemyPokemon } from "#app/field/pokemon";
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -55,7 +55,7 @@ describe("Abilities - Sturdy", () => {
|
|||||||
enemyPokemon.hp = enemyPokemon.getMaxHp() - 1;
|
enemyPokemon.hp = enemyPokemon.getMaxHp() - 1;
|
||||||
|
|
||||||
game.move.select(Moves.CLOSE_COMBAT);
|
game.move.select(Moves.CLOSE_COMBAT);
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to(DamageAnimPhase);
|
||||||
|
|
||||||
expect(enemyPokemon.hp).toBe(0);
|
expect(enemyPokemon.hp).toBe(0);
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
@ -81,7 +81,7 @@ describe("Abilities - Sturdy", () => {
|
|||||||
|
|
||||||
await game.startBattle();
|
await game.startBattle();
|
||||||
game.move.select(Moves.CLOSE_COMBAT);
|
game.move.select(Moves.CLOSE_COMBAT);
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to(DamageAnimPhase);
|
||||||
|
|
||||||
const enemyPokemon: EnemyPokemon = game.scene.getEnemyParty()[0];
|
const enemyPokemon: EnemyPokemon = game.scene.getEnemyParty()[0];
|
||||||
expect(enemyPokemon.hp).toBe(0);
|
expect(enemyPokemon.hp).toBe(0);
|
||||||
|
@ -632,4 +632,34 @@ describe("Abilities - Wimp Out", () => {
|
|||||||
const hasFled = enemyPokemon.switchOutStatus;
|
const hasFled = enemyPokemon.switchOutStatus;
|
||||||
expect(isVisible && !hasFled).toBe(true);
|
expect(isVisible && !hasFled).toBe(true);
|
||||||
});
|
});
|
||||||
|
it("wimp out will not skip battles when triggered in a double battle", async () => {
|
||||||
|
const wave = 2;
|
||||||
|
game.override
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.enemySpecies(Species.WIMPOD)
|
||||||
|
.enemyAbility(Abilities.WIMP_OUT)
|
||||||
|
.moveset([ Moves.MATCHA_GOTCHA, Moves.FALSE_SWIPE ])
|
||||||
|
.startingLevel(50)
|
||||||
|
.enemyLevel(1)
|
||||||
|
.battleType("double")
|
||||||
|
.startingWave(wave);
|
||||||
|
await game.classicMode.startBattle([
|
||||||
|
Species.RAICHU,
|
||||||
|
Species.PIKACHU
|
||||||
|
]);
|
||||||
|
const [ wimpod0, wimpod1 ] = game.scene.getEnemyField();
|
||||||
|
|
||||||
|
game.move.select(Moves.FALSE_SWIPE, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.MATCHA_GOTCHA, 1);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(wimpod0.hp).toBeGreaterThan(0);
|
||||||
|
expect(wimpod0.switchOutStatus).toBe(true);
|
||||||
|
expect(wimpod0.isFainted()).toBe(false);
|
||||||
|
expect(wimpod1.isFainted()).toBe(true);
|
||||||
|
|
||||||
|
await game.toNextWave();
|
||||||
|
expect(game.scene.currentBattle.waveIndex).toBe(wave + 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import { Stat } from "#enums/stat";
|
|||||||
import { GameModes, getGameMode } from "#app/game-mode";
|
import { GameModes, getGameMode } from "#app/game-mode";
|
||||||
import { BattleEndPhase } from "#app/phases/battle-end-phase";
|
import { BattleEndPhase } from "#app/phases/battle-end-phase";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
import { EncounterPhase } from "#app/phases/encounter-phase";
|
import { EncounterPhase } from "#app/phases/encounter-phase";
|
||||||
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
||||||
import { LoginPhase } from "#app/phases/login-phase";
|
import { LoginPhase } from "#app/phases/login-phase";
|
||||||
@ -267,7 +267,7 @@ describe("Test Battle Phase", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
|
await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
|
||||||
expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
|
expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
|
||||||
await game.phaseInterceptor.to(VictoryPhase, false);
|
await game.phaseInterceptor.to(VictoryPhase, false);
|
||||||
|
@ -102,7 +102,7 @@ describe("Battle Mechanics - Damage Calculation", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.JUMP_KICK);
|
game.move.select(Moves.JUMP_KICK);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||||
|
|
||||||
expect(shedinja.hp).toBe(shedinja.getMaxHp() - 1);
|
expect(shedinja.hp).toBe(shedinja.getMaxHp() - 1);
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import Pokemon from "#app/field/pokemon";
|
import Pokemon from "#app/field/pokemon";
|
||||||
import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags";
|
import { BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags";
|
||||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
|
|
||||||
vi.mock("#app/battle-scene.js");
|
vi.mock("#app/battle-scene.js");
|
||||||
@ -33,30 +32,4 @@ describe("BattlerTag - OctolockTag", () => {
|
|||||||
it ("traps its target (extends TrappedTag)", async () => {
|
it ("traps its target (extends TrappedTag)", async () => {
|
||||||
expect(new OctolockTag(1)).toBeInstanceOf(TrappedTag);
|
expect(new OctolockTag(1)).toBeInstanceOf(TrappedTag);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can be added to pokemon who are not octolocked", async => {
|
|
||||||
const mockPokemon = {
|
|
||||||
getTag: vi.fn().mockReturnValue(undefined) as Pokemon["getTag"],
|
|
||||||
} as Pokemon;
|
|
||||||
|
|
||||||
const subject = new OctolockTag(1);
|
|
||||||
|
|
||||||
expect(subject.canAdd(mockPokemon)).toBeTruthy();
|
|
||||||
|
|
||||||
expect(mockPokemon.getTag).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cannot be added to pokemon who are octolocked", async => {
|
|
||||||
const mockPokemon = {
|
|
||||||
getTag: vi.fn().mockReturnValue(new BattlerTag(null!, null!, null!, null!)) as Pokemon["getTag"],
|
|
||||||
} as Pokemon;
|
|
||||||
|
|
||||||
const subject = new OctolockTag(1);
|
|
||||||
|
|
||||||
expect(subject.canAdd(mockPokemon)).toBeFalsy();
|
|
||||||
|
|
||||||
expect(mockPokemon.getTag).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -48,7 +48,7 @@ describe("Items - Leftovers", () => {
|
|||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
// We should have less hp after the attack
|
// We should have less hp after the attack
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
|
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
|
||||||
|
|
||||||
const leadHpAfterDamage = leadPokemon.hp;
|
const leadHpAfterDamage = leadPokemon.hp;
|
||||||
|
@ -135,4 +135,57 @@ describe("Items - Multi Lens", () => {
|
|||||||
expect(damageResults[0]).toBe(Math.floor(playerPokemon.level * 0.75));
|
expect(damageResults[0]).toBe(Math.floor(playerPokemon.level * 0.75));
|
||||||
expect(damageResults[1]).toBe(Math.floor(playerPokemon.level * 0.25));
|
expect(damageResults[1]).toBe(Math.floor(playerPokemon.level * 0.25));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should result in correct damage for hp% attacks with 1 lens", async () => {
|
||||||
|
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }])
|
||||||
|
.moveset(Moves.SUPER_FANG)
|
||||||
|
.ability(Abilities.COMPOUND_EYES)
|
||||||
|
.enemyLevel(1000)
|
||||||
|
.enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.SUPER_FANG);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should result in correct damage for hp% attacks with 2 lenses", async () => {
|
||||||
|
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }])
|
||||||
|
.moveset(Moves.SUPER_FANG)
|
||||||
|
.ability(Abilities.COMPOUND_EYES)
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.enemyLevel(1000)
|
||||||
|
.enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.SUPER_FANG);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5);
|
||||||
|
});
|
||||||
|
it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => {
|
||||||
|
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }])
|
||||||
|
.moveset(Moves.SUPER_FANG)
|
||||||
|
.ability(Abilities.PARENTAL_BOND)
|
||||||
|
.passiveAbility(Abilities.COMPOUND_EYES)
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.enemyLevel(1000)
|
||||||
|
.enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.SUPER_FANG);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.25, 5);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
|
import { Status } from "#app/data/status-effect";
|
||||||
|
import { Challenges } from "#enums/challenges";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { Type } from "#enums/type";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
@ -193,4 +197,122 @@ describe("Moves - Dragon Tail", () => {
|
|||||||
expect(dratini.hp).toBe(Math.floor(dratini.getMaxHp() / 2));
|
expect(dratini.hp).toBe(Math.floor(dratini.getMaxHp() / 2));
|
||||||
expect(game.scene.getPlayerField().length).toBe(1);
|
expect(game.scene.getPlayerField().length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should force switches randomly", async () => {
|
||||||
|
game.override.enemyMoveset(Moves.DRAGON_TAIL)
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemyLevel(1);
|
||||||
|
await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]);
|
||||||
|
|
||||||
|
const [ bulbasaur, charmander, squirtle ] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
|
// Turn 1: Mock an RNG call that calls for switching to 1st backup Pokemon (Charmander)
|
||||||
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => {
|
||||||
|
return min;
|
||||||
|
});
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.DRAGON_TAIL);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(bulbasaur.isOnField()).toBe(false);
|
||||||
|
expect(charmander.isOnField()).toBe(true);
|
||||||
|
expect(squirtle.isOnField()).toBe(false);
|
||||||
|
expect(bulbasaur.getInverseHp()).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Turn 2: Mock an RNG call that calls for switching to 2nd backup Pokemon (Squirtle)
|
||||||
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => {
|
||||||
|
return min + 1;
|
||||||
|
});
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(bulbasaur.isOnField()).toBe(false);
|
||||||
|
expect(charmander.isOnField()).toBe(false);
|
||||||
|
expect(squirtle.isOnField()).toBe(true);
|
||||||
|
expect(charmander.getInverseHp()).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not force a switch to a challenge-ineligible Pokemon", async () => {
|
||||||
|
game.override.enemyMoveset(Moves.DRAGON_TAIL)
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemyLevel(1);
|
||||||
|
// Mono-Water challenge, Eevee is ineligible
|
||||||
|
game.challengeMode.addChallenge(Challenges.SINGLE_TYPE, Type.WATER + 1, 0);
|
||||||
|
await game.challengeMode.startBattle([ Species.LAPRAS, Species.EEVEE, Species.TOXAPEX, Species.PRIMARINA ]);
|
||||||
|
|
||||||
|
const [ lapras, eevee, toxapex, primarina ] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
|
// Turn 1: Mock an RNG call that would normally call for switching to Eevee, but it is ineligible
|
||||||
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => {
|
||||||
|
return min;
|
||||||
|
});
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(lapras.isOnField()).toBe(false);
|
||||||
|
expect(eevee.isOnField()).toBe(false);
|
||||||
|
expect(toxapex.isOnField()).toBe(true);
|
||||||
|
expect(primarina.isOnField()).toBe(false);
|
||||||
|
expect(lapras.getInverseHp()).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not force a switch to a fainted Pokemon", async () => {
|
||||||
|
game.override.enemyMoveset([ Moves.SPLASH, Moves.DRAGON_TAIL ])
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemyLevel(1);
|
||||||
|
await game.classicMode.startBattle([ Species.LAPRAS, Species.EEVEE, Species.TOXAPEX, Species.PRIMARINA ]);
|
||||||
|
|
||||||
|
const [ lapras, eevee, toxapex, primarina ] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
|
// Turn 1: Eevee faints
|
||||||
|
eevee.hp = 0;
|
||||||
|
eevee.status = new Status(StatusEffect.FAINT);
|
||||||
|
expect(eevee.isFainted()).toBe(true);
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
// Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted
|
||||||
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => {
|
||||||
|
return min;
|
||||||
|
});
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.DRAGON_TAIL);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(lapras.isOnField()).toBe(false);
|
||||||
|
expect(eevee.isOnField()).toBe(false);
|
||||||
|
expect(toxapex.isOnField()).toBe(true);
|
||||||
|
expect(primarina.isOnField()).toBe(false);
|
||||||
|
expect(lapras.getInverseHp()).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not force a switch if there are no available Pokemon to switch into", async () => {
|
||||||
|
game.override.enemyMoveset([ Moves.SPLASH, Moves.DRAGON_TAIL ])
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemyLevel(1);
|
||||||
|
await game.classicMode.startBattle([ Species.LAPRAS, Species.EEVEE ]);
|
||||||
|
|
||||||
|
const [ lapras, eevee ] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
|
// Turn 1: Eevee faints
|
||||||
|
eevee.hp = 0;
|
||||||
|
eevee.status = new Status(StatusEffect.FAINT);
|
||||||
|
expect(eevee.isFainted()).toBe(true);
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
// Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted
|
||||||
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => {
|
||||||
|
return min;
|
||||||
|
});
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.DRAGON_TAIL);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(lapras.isOnField()).toBe(true);
|
||||||
|
expect(eevee.isOnField()).toBe(false);
|
||||||
|
expect(lapras.getInverseHp()).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
@ -51,7 +51,7 @@ describe("Moves - Dynamax Cannon", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ describe("Moves - Dynamax Cannon", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ describe("Moves - Dynamax Cannon", () => {
|
|||||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||||
// Force level cap to be 100
|
// Force level cap to be 100
|
||||||
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(120);
|
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(120);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ describe("Moves - Dynamax Cannon", () => {
|
|||||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||||
// Force level cap to be 100
|
// Force level cap to be 100
|
||||||
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(140);
|
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(140);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ describe("Moves - Dynamax Cannon", () => {
|
|||||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||||
// Force level cap to be 100
|
// Force level cap to be 100
|
||||||
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(160);
|
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(160);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ describe("Moves - Dynamax Cannon", () => {
|
|||||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||||
// Force level cap to be 100
|
// Force level cap to be 100
|
||||||
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(180);
|
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(180);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ describe("Moves - Dynamax Cannon", () => {
|
|||||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||||
// Force level cap to be 100
|
// Force level cap to be 100
|
||||||
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ describe("Moves - Dynamax Cannon", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ import { Abilities } from "#app/enums/abilities";
|
|||||||
import { Moves } from "#app/enums/moves";
|
import { Moves } from "#app/enums/moves";
|
||||||
import { Species } from "#app/enums/species";
|
import { Species } from "#app/enums/species";
|
||||||
import * as Messages from "#app/messages";
|
import * as Messages from "#app/messages";
|
||||||
import { TerastallizeModifier } from "#app/modifier/modifier";
|
import { TerastallizeModifier, overrideHeldItems } from "#app/modifier/modifier";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||||
@ -15,15 +15,17 @@ function testMoveEffectiveness(game: GameManager, move: Moves, targetSpecies: Sp
|
|||||||
expected: number, targetAbility: Abilities = Abilities.BALL_FETCH, teraType?: Type): void {
|
expected: number, targetAbility: Abilities = Abilities.BALL_FETCH, teraType?: Type): void {
|
||||||
// Suppress getPokemonNameWithAffix because it calls on a null battle spec
|
// Suppress getPokemonNameWithAffix because it calls on a null battle spec
|
||||||
vi.spyOn(Messages, "getPokemonNameWithAffix").mockReturnValue("");
|
vi.spyOn(Messages, "getPokemonNameWithAffix").mockReturnValue("");
|
||||||
game.override.enemyAbility(targetAbility);
|
game.override
|
||||||
|
.enemyAbility(targetAbility)
|
||||||
if (teraType !== undefined) {
|
.enemyHeldItems([{ name:"TERA_SHARD", type: teraType }]);
|
||||||
game.override.enemyHeldItems([{ name:"TERA_SHARD", type: teraType }]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = game.scene.addPlayerPokemon(getPokemonSpecies(Species.SNORLAX), 5);
|
const user = game.scene.addPlayerPokemon(getPokemonSpecies(Species.SNORLAX), 5);
|
||||||
const target = game.scene.addEnemyPokemon(getPokemonSpecies(targetSpecies), 5, TrainerSlot.NONE);
|
const target = game.scene.addEnemyPokemon(getPokemonSpecies(targetSpecies), 5, TrainerSlot.NONE);
|
||||||
|
|
||||||
|
if (teraType !== undefined) {
|
||||||
|
overrideHeldItems(game.scene, target, false);
|
||||||
|
}
|
||||||
|
|
||||||
expect(target.getMoveEffectiveness(user, allMoves[move])).toBe(expected);
|
expect(target.getMoveEffectiveness(user, allMoves[move])).toBe(expected);
|
||||||
user.destroy();
|
user.destroy();
|
||||||
target.destroy();
|
target.destroy();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { Species } from "#app/enums/species";
|
import { Species } from "#app/enums/species";
|
||||||
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -56,7 +56,7 @@ describe("Moves - Fissure", () => {
|
|||||||
game.override.enemyAbility(Abilities.FUR_COAT);
|
game.override.enemyAbility(Abilities.FUR_COAT);
|
||||||
|
|
||||||
game.move.select(Moves.FISSURE);
|
game.move.select(Moves.FISSURE);
|
||||||
await game.phaseInterceptor.to(DamagePhase, true);
|
await game.phaseInterceptor.to(DamageAnimPhase, true);
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||||
import { MovePhase } from "#app/phases/move-phase";
|
import { MovePhase } from "#app/phases/move-phase";
|
||||||
@ -58,12 +58,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -81,12 +81,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase);
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
@ -114,7 +114,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase);
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
@ -142,7 +142,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -160,12 +160,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -209,22 +209,22 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -268,22 +268,22 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||||
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
});
|
});
|
||||||
|