Merge branch 'pagefaultgames:beta' into variant_update_oct
@ -1,5 +1,5 @@
|
|||||||
import tseslint from '@typescript-eslint/eslint-plugin';
|
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||||
import stylisticTs from '@stylistic/eslint-plugin-ts'
|
import stylisticTs from '@stylistic/eslint-plugin-ts';
|
||||||
import parser from '@typescript-eslint/parser';
|
import parser from '@typescript-eslint/parser';
|
||||||
import importX from 'eslint-plugin-import-x';
|
import importX from 'eslint-plugin-import-x';
|
||||||
|
|
||||||
@ -16,15 +16,15 @@ export default [
|
|||||||
'@typescript-eslint': tseslint
|
'@typescript-eslint': tseslint
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"eqeqeq": ["error", "always"], // Enforces the use of === and !== instead of == and !=
|
"eqeqeq": ["error", "always"], // Enforces the use of `===` and `!==` instead of `==` and `!=`
|
||||||
"indent": ["error", 2], // Enforces a 2-space indentation
|
"indent": ["error", 2, { "SwitchCase": 1 }], // Enforces a 2-space indentation, enforces indentation of `case ...:` statements
|
||||||
"quotes": ["error", "double"], // Enforces the use of double quotes for strings
|
"quotes": ["error", "double"], // Enforces the use of double quotes for strings
|
||||||
"no-var": "error", // Disallows the use of var, enforcing let or const instead
|
"no-var": "error", // Disallows the use of `var`, enforcing `let` or `const` instead
|
||||||
"prefer-const": "error", // Prefers the use of const for variables that are never reassigned
|
"prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned
|
||||||
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
|
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
|
||||||
"@typescript-eslint/no-unused-vars": [ "error", {
|
"@typescript-eslint/no-unused-vars": [ "error", {
|
||||||
"args": "none", // Allows unused function parameters. Useful for functions with specific signatures where not all parameters are always used.
|
"args": "none", // Allows unused function parameters. Useful for functions with specific signatures where not all parameters are always used.
|
||||||
"ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the rest.
|
"ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the others.
|
||||||
}],
|
}],
|
||||||
"eol-last": ["error", "always"], // Enforces at least one newline at the end of files
|
"eol-last": ["error", "always"], // Enforces at least one newline at the end of files
|
||||||
"@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax
|
"@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax
|
||||||
@ -32,14 +32,14 @@ export default [
|
|||||||
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
|
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
|
||||||
"brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors
|
"brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors
|
||||||
"curly": ["error", "all"], // Enforces the use of curly braces for all control statements
|
"curly": ["error", "all"], // Enforces the use of curly braces for all control statements
|
||||||
"@stylistic/ts/brace-style": ["error", "1tbs"],
|
"@stylistic/ts/brace-style": ["error", "1tbs"], // Enforces the following brace style: https://eslint.style/rules/js/brace-style#_1tbs
|
||||||
"no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines
|
"no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines
|
||||||
"skipBlankLines": false, // Enforces the rule even on blank lines
|
"skipBlankLines": false, // Enforces the rule even on blank lines
|
||||||
"ignoreComments": false // Enforces the rule on lines containing comments
|
"ignoreComments": false // Enforces the rule on lines containing comments
|
||||||
}],
|
}],
|
||||||
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
|
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
|
||||||
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
|
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
|
||||||
"comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after comma
|
"comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after commas
|
||||||
"import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json
|
"import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json
|
||||||
"array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets
|
"array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets
|
||||||
"object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers
|
"object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers
|
||||||
|
@ -3416,12 +3416,12 @@
|
|||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": true,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 24,
|
"w": 32,
|
||||||
"h": 24
|
"h": 32
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 1,
|
"x": 5,
|
||||||
"y": 2,
|
"y": 7,
|
||||||
"w": 22,
|
"w": 22,
|
||||||
"h": 19
|
"h": 19
|
||||||
},
|
},
|
||||||
@ -8415,6 +8415,6 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"app": "https://www.codeandweb.com/texturepacker",
|
"app": "https://www.codeandweb.com/texturepacker",
|
||||||
"version": "3.0",
|
"version": "3.0",
|
||||||
"smartupdate": "$TexturePacker:SmartUpdate:934ea4080bad980d4fea720cc771f133:ed564bc47b79b15a763de57045178e88:110e074689c9edd2c54833ce2e4d9270$"
|
"smartupdate": "$TexturePacker:SmartUpdate:9ef21166268f7487fc9ff8d0f9b996e4:82658ac7bdd4c2b417e1f59168179262:110e074689c9edd2c54833ce2e4d9270$"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 285 B |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.4 KiB |
@ -86,7 +86,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph
|
|||||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
||||||
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
||||||
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
@ -1191,10 +1191,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
if (trainerConfigs[trainerType].doubleOnly) {
|
if (trainerConfigs[trainerType].doubleOnly) {
|
||||||
doubleTrainer = true;
|
doubleTrainer = true;
|
||||||
} else if (trainerConfigs[trainerType].hasDouble) {
|
} else if (trainerConfigs[trainerType].hasDouble) {
|
||||||
const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
doubleTrainer = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
|
||||||
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
|
||||||
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance));
|
|
||||||
doubleTrainer = !Utils.randSeedInt(doubleChance.value);
|
|
||||||
// Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance
|
// Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance
|
||||||
if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) {
|
if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) {
|
||||||
doubleTrainer = false;
|
doubleTrainer = false;
|
||||||
@ -1207,12 +1204,10 @@ export default class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
// Check for mystery encounter
|
// Check for mystery encounter
|
||||||
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
|
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
|
||||||
if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER) {
|
if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||||
newBattleType = BattleType.MYSTERY_ENCOUNTER;
|
newBattleType = BattleType.MYSTERY_ENCOUNTER;
|
||||||
// Reset base spawn weight
|
// Reset to base spawn weight
|
||||||
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||||
} else if (newBattleType === BattleType.WILD) {
|
|
||||||
this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2364,6 +2359,19 @@ export default class BattleScene extends SceneBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found.
|
||||||
|
* @param phaseFilter filter function
|
||||||
|
*/
|
||||||
|
tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean {
|
||||||
|
const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter);
|
||||||
|
if (phaseIndex > -1) {
|
||||||
|
this.phaseQueuePrepend.splice(phaseIndex, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase()
|
* Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase()
|
||||||
* @param phase {@linkcode Phase} the phase to be added
|
* @param phase {@linkcode Phase} the phase to be added
|
||||||
@ -3125,18 +3133,26 @@ export default class BattleScene extends SceneBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if a wave COULD spawn a {@linkcode MysteryEncounter}.
|
||||||
|
* Even if returns `true`, does not guarantee that a wave will actually be a ME.
|
||||||
|
* That check is made in {@linkcode BattleScene.isWaveMysteryEncounter} instead.
|
||||||
|
*/
|
||||||
|
isMysteryEncounterValidForWave(battleType: BattleType, waveIndex: number): boolean {
|
||||||
|
const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves();
|
||||||
|
return this.gameMode.hasMysteryEncounters && battleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}.
|
* Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}.
|
||||||
* Currently, the only modes that MEs are allowed in are Classic and Challenge.
|
* Currently, the only modes that MEs are allowed in are Classic and Challenge.
|
||||||
* Additionally, MEs cannot spawn outside of waves 10-180 in those modes
|
* Additionally, MEs cannot spawn outside of waves 10-180 in those modes
|
||||||
*
|
|
||||||
* @param newBattleType
|
* @param newBattleType
|
||||||
* @param waveIndex
|
* @param waveIndex
|
||||||
* @param sessionDataEncounterType
|
|
||||||
*/
|
*/
|
||||||
private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean {
|
private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number): boolean {
|
||||||
const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves();
|
const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves();
|
||||||
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) {
|
if (this.isMysteryEncounterValidForWave(newBattleType, waveIndex)) {
|
||||||
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
|
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
|
||||||
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
|
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
|
||||||
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;
|
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;
|
||||||
@ -3177,6 +3193,9 @@ export default class BattleScene extends SceneBase {
|
|||||||
let encounter: MysteryEncounter | null;
|
let encounter: MysteryEncounter | null;
|
||||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
|
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
|
||||||
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
|
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
|
||||||
|
if (canBypass) {
|
||||||
|
return encounter;
|
||||||
|
}
|
||||||
} else if (canBypass) {
|
} else if (canBypass) {
|
||||||
encounter = allMysteryEncounters[encounterType ?? -1];
|
encounter = allMysteryEncounters[encounterType ?? -1];
|
||||||
return encounter;
|
return encounter;
|
||||||
|
@ -4200,6 +4200,11 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr {
|
|||||||
|
|
||||||
export class BlockRedirectAbAttr extends AbAttr { }
|
export class BlockRedirectAbAttr extends AbAttr { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by Early Bird, makes the pokemon wake up faster
|
||||||
|
* @param statusEffect - The {@linkcode StatusEffect} to check for
|
||||||
|
* @see {@linkcode apply}
|
||||||
|
*/
|
||||||
export class ReduceStatusEffectDurationAbAttr extends AbAttr {
|
export class ReduceStatusEffectDurationAbAttr extends AbAttr {
|
||||||
private statusEffect: StatusEffect;
|
private statusEffect: StatusEffect;
|
||||||
|
|
||||||
@ -4209,9 +4214,19 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr {
|
|||||||
this.statusEffect = statusEffect;
|
this.statusEffect = statusEffect;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
/**
|
||||||
|
* Reduces the number of sleep turns remaining by an extra 1 when applied
|
||||||
|
* @param args - The args passed to the `AbAttr`:
|
||||||
|
* - `[0]` - The {@linkcode StatusEffect} of the Pokemon
|
||||||
|
* - `[1]` - The number of turns remaining until the status is healed
|
||||||
|
* @returns `true` if the ability was applied
|
||||||
|
*/
|
||||||
|
apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
|
if (!(args[1] instanceof Utils.NumberHolder)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (args[0] === this.statusEffect) {
|
if (args[0] === this.statusEffect) {
|
||||||
(args[1] as Utils.IntegerHolder).value = Utils.toDmgValue((args[1] as Utils.IntegerHolder).value / 2);
|
args[1].value -= 1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4342,6 +4357,30 @@ export class AlwaysHitAbAttr extends AbAttr { }
|
|||||||
/** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */
|
/** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */
|
||||||
export class IgnoreProtectOnContactAbAttr extends AbAttr { }
|
export class IgnoreProtectOnContactAbAttr extends AbAttr { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(Ability) | Infiltrator}.
|
||||||
|
* Allows the source's moves to bypass the effects of opposing Light Screen, Reflect, Aurora Veil, Safeguard, Mist, and Substitute.
|
||||||
|
*/
|
||||||
|
export class InfiltratorAbAttr extends AbAttr {
|
||||||
|
/**
|
||||||
|
* Sets a flag to bypass screens, Substitute, Safeguard, and Mist
|
||||||
|
* @param pokemon n/a
|
||||||
|
* @param passive n/a
|
||||||
|
* @param simulated n/a
|
||||||
|
* @param cancelled n/a
|
||||||
|
* @param args `[0]` a {@linkcode Utils.BooleanHolder | BooleanHolder} containing the flag
|
||||||
|
* @returns `true` if the bypass flag was successfully set; `false` otherwise.
|
||||||
|
*/
|
||||||
|
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): boolean {
|
||||||
|
const bypassed = args[0];
|
||||||
|
if (args[0] instanceof Utils.BooleanHolder) {
|
||||||
|
bypassed.value = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class UncopiableAbilityAbAttr extends AbAttr {
|
export class UncopiableAbilityAbAttr extends AbAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(false);
|
super(false);
|
||||||
@ -4913,8 +4952,7 @@ export function initAbilities() {
|
|||||||
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1)
|
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SHIELD_DUST, 3)
|
new Ability(Abilities.SHIELD_DUST, 3)
|
||||||
.attr(IgnoreMoveEffectsAbAttr)
|
.attr(IgnoreMoveEffectsAbAttr),
|
||||||
.edgeCase(), // Does not work with secret power (unimplemented)
|
|
||||||
new Ability(Abilities.OWN_TEMPO, 3)
|
new Ability(Abilities.OWN_TEMPO, 3)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
||||||
.attr(IntimidateImmunityAbAttr)
|
.attr(IntimidateImmunityAbAttr)
|
||||||
@ -4958,8 +4996,7 @@ export function initAbilities() {
|
|||||||
.attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1)
|
.attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SERENE_GRACE, 3)
|
new Ability(Abilities.SERENE_GRACE, 3)
|
||||||
.attr(MoveEffectChanceMultiplierAbAttr, 2)
|
.attr(MoveEffectChanceMultiplierAbAttr, 2),
|
||||||
.edgeCase(), // does not work with secret power (unimplemented)
|
|
||||||
new Ability(Abilities.SWIFT_SWIM, 3)
|
new Ability(Abilities.SWIFT_SWIM, 3)
|
||||||
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
|
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
|
||||||
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
|
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
|
||||||
@ -5323,7 +5360,8 @@ export function initAbilities() {
|
|||||||
.attr(PostSummonTransformAbAttr)
|
.attr(PostSummonTransformAbAttr)
|
||||||
.attr(UncopiableAbilityAbAttr),
|
.attr(UncopiableAbilityAbAttr),
|
||||||
new Ability(Abilities.INFILTRATOR, 5)
|
new Ability(Abilities.INFILTRATOR, 5)
|
||||||
.unimplemented(),
|
.attr(InfiltratorAbAttr)
|
||||||
|
.partial(), // does not bypass Mist
|
||||||
new Ability(Abilities.MUMMY, 5)
|
new Ability(Abilities.MUMMY, 5)
|
||||||
.attr(PostDefendAbilityGiveAbAttr, Abilities.MUMMY)
|
.attr(PostDefendAbilityGiveAbAttr, Abilities.MUMMY)
|
||||||
.bypassFaint(),
|
.bypassFaint(),
|
||||||
|
@ -7,7 +7,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||||||
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
|
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
|
||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
|
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
|
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -130,7 +130,18 @@ export class MistTag extends ArenaTag {
|
|||||||
* to flag the stat reduction as cancelled
|
* to flag the stat reduction as cancelled
|
||||||
* @returns `true` if a stat reduction was cancelled; `false` otherwise
|
* @returns `true` if a stat reduction was cancelled; `false` otherwise
|
||||||
*/
|
*/
|
||||||
override apply(arena: Arena, simulated: boolean, cancelled: BooleanHolder): boolean {
|
override apply(arena: Arena, simulated: boolean, attacker: Pokemon, cancelled: BooleanHolder): boolean {
|
||||||
|
// `StatStageChangePhase` currently doesn't have a reference to the source of stat drops,
|
||||||
|
// so this code currently has no effect on gameplay.
|
||||||
|
if (attacker) {
|
||||||
|
const bypassed = new BooleanHolder(false);
|
||||||
|
// TODO: Allow this to be simulated
|
||||||
|
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
|
||||||
|
if (bypassed.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cancelled.value = true;
|
cancelled.value = true;
|
||||||
|
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
@ -169,12 +180,18 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
|||||||
*
|
*
|
||||||
* @param arena the {@linkcode Arena} where the move is applied.
|
* @param arena the {@linkcode Arena} where the move is applied.
|
||||||
* @param simulated n/a
|
* @param simulated n/a
|
||||||
|
* @param attacker the attacking {@linkcode Pokemon}
|
||||||
* @param moveCategory the attacking move's {@linkcode MoveCategory}.
|
* @param moveCategory the attacking move's {@linkcode MoveCategory}.
|
||||||
* @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier
|
* @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier
|
||||||
* @returns `true` if the attacking move was weakened; `false` otherwise.
|
* @returns `true` if the attacking move was weakened; `false` otherwise.
|
||||||
*/
|
*/
|
||||||
override apply(arena: Arena, simulated: boolean, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean {
|
override apply(arena: Arena, simulated: boolean, attacker: Pokemon, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean {
|
||||||
if (this.weakenedCategories.includes(moveCategory)) {
|
if (this.weakenedCategories.includes(moveCategory)) {
|
||||||
|
const bypassed = new BooleanHolder(false);
|
||||||
|
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
|
||||||
|
if (bypassed.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5;
|
damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -626,7 +643,7 @@ export class ArenaTrapTag extends ArenaTag {
|
|||||||
* @returns `true` if this hazard affects the given Pokemon; `false` otherwise.
|
* @returns `true` if this hazard affects the given Pokemon; `false` otherwise.
|
||||||
*/
|
*/
|
||||||
override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean {
|
override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean {
|
||||||
if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) {
|
if ((this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,29 +1,44 @@
|
|||||||
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import {
|
||||||
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
|
allAbilities,
|
||||||
import { StatusEffect } from "./status-effect";
|
applyAbAttrs,
|
||||||
import * as Utils from "../utils";
|
BlockNonDirectDamageAbAttr,
|
||||||
import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr, ConsecutiveUseDoublePowerAttr } from "./move";
|
FlinchEffectAbAttr,
|
||||||
import { Type } from "./type";
|
ProtectStatAbAttr,
|
||||||
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
|
ReverseDrainAbAttr
|
||||||
import { TerrainType } from "./terrain";
|
} from "#app/data/ability";
|
||||||
import { WeatherType } from "./weather";
|
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
|
||||||
import { allAbilities } from "./ability";
|
import Move, {
|
||||||
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
|
allMoves,
|
||||||
import { Abilities } from "#enums/abilities";
|
applyMoveAttrs,
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
ChargeAttr,
|
||||||
import { Moves } from "#enums/moves";
|
ConsecutiveUseDoublePowerAttr,
|
||||||
import { Species } from "#enums/species";
|
HealOnAllyAttr,
|
||||||
import i18next from "#app/plugins/i18n";
|
MoveCategory,
|
||||||
import { Stat, type BattleStat, type EffectiveStat, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
|
MoveFlags,
|
||||||
|
StatusCategoryOnAllyAttr
|
||||||
|
} from "#app/data/move";
|
||||||
|
import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms";
|
||||||
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
|
import { TerrainType } from "#app/data/terrain";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
import { WeatherType } from "#app/data/weather";
|
||||||
|
import Pokemon, { HitResult, MoveResult } from "#app/field/pokemon";
|
||||||
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { MovePhase } from "#app/phases/move-phase";
|
import { MovePhase } from "#app/phases/move-phase";
|
||||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||||
import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
|
import { StatStageChangeCallback, StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||||
import { PokemonAnimType } from "#app/enums/pokemon-anim-type";
|
import i18next from "#app/plugins/i18n";
|
||||||
import BattleScene from "#app/battle-scene";
|
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat";
|
||||||
|
|
||||||
export enum BattlerTagLapseType {
|
export enum BattlerTagLapseType {
|
||||||
FAINT,
|
FAINT,
|
||||||
@ -33,6 +48,7 @@ export enum BattlerTagLapseType {
|
|||||||
MOVE_EFFECT,
|
MOVE_EFFECT,
|
||||||
TURN_END,
|
TURN_END,
|
||||||
HIT,
|
HIT,
|
||||||
|
AFTER_HIT,
|
||||||
CUSTOM
|
CUSTOM
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,7 +421,7 @@ export class RechargingTag extends BattlerTag {
|
|||||||
*/
|
*/
|
||||||
export class BeakBlastChargingTag extends BattlerTag {
|
export class BeakBlastChargingTag extends BattlerTag {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1, Moves.BEAK_BLAST);
|
super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1, Moves.BEAK_BLAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
@ -421,16 +437,13 @@ export class BeakBlastChargingTag extends BattlerTag {
|
|||||||
* to be removed after the source makes a move (or the turn ends, whichever comes first)
|
* to be removed after the source makes a move (or the turn ends, whichever comes first)
|
||||||
* @param pokemon {@linkcode Pokemon} the owner of this tag
|
* @param pokemon {@linkcode Pokemon} the owner of this tag
|
||||||
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
|
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
|
||||||
* @returns `true` if invoked with the CUSTOM lapse type; `false` otherwise
|
* @returns `true` if invoked with the `AFTER_HIT` lapse type
|
||||||
*/
|
*/
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
if (lapseType === BattlerTagLapseType.AFTER_HIT) {
|
||||||
const effectPhase = pokemon.scene.getCurrentPhase();
|
const phaseData = getMoveEffectPhaseData(pokemon);
|
||||||
if (effectPhase instanceof MoveEffectPhase) {
|
if (phaseData?.move.hasFlag(MoveFlags.MAKES_CONTACT)) {
|
||||||
const attacker = effectPhase.getPokemon();
|
phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
|
||||||
if (effectPhase.move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
|
|
||||||
attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -444,11 +457,10 @@ export class BeakBlastChargingTag extends BattlerTag {
|
|||||||
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap}
|
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap}
|
||||||
*/
|
*/
|
||||||
export class ShellTrapTag extends BattlerTag {
|
export class ShellTrapTag extends BattlerTag {
|
||||||
public activated: boolean;
|
public activated: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(BattlerTagType.SHELL_TRAP, BattlerTagLapseType.TURN_END, 1);
|
super(BattlerTagType.SHELL_TRAP, [ BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1);
|
||||||
this.activated = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
@ -459,10 +471,14 @@ export class ShellTrapTag extends BattlerTag {
|
|||||||
* "Activates" the shell trap, causing the tag owner to move next.
|
* "Activates" the shell trap, causing the tag owner to move next.
|
||||||
* @param pokemon {@linkcode Pokemon} the owner of this tag
|
* @param pokemon {@linkcode Pokemon} the owner of this tag
|
||||||
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
|
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
|
||||||
* @returns `true` if invoked with the `CUSTOM` lapse type; `false` otherwise
|
* @returns `true` if invoked with the `AFTER_HIT` lapse type
|
||||||
*/
|
*/
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
if (lapseType === BattlerTagLapseType.AFTER_HIT) {
|
||||||
|
const phaseData = getMoveEffectPhaseData(pokemon);
|
||||||
|
|
||||||
|
// Trap should only be triggered by opponent's Physical moves
|
||||||
|
if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) {
|
||||||
const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex(
|
const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex(
|
||||||
phase => phase instanceof MovePhase && phase.pokemon === pokemon
|
phase => phase instanceof MovePhase && phase.pokemon === pokemon
|
||||||
);
|
);
|
||||||
@ -470,14 +486,18 @@ export class ShellTrapTag extends BattlerTag {
|
|||||||
phase => phase instanceof MovePhase
|
phase => phase instanceof MovePhase
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Only shift MovePhase timing if it's not already next up
|
||||||
if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) {
|
if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) {
|
||||||
const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0];
|
const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0];
|
||||||
pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase);
|
pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activated = true;
|
this.activated = true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.lapse(pokemon, lapseType);
|
return super.lapse(pokemon, lapseType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -641,7 +661,7 @@ export class ConfusedTag extends BattlerTag {
|
|||||||
if (pokemon.randSeedInt(3) === 0) {
|
if (pokemon.randSeedInt(3) === 0) {
|
||||||
const atk = pokemon.getEffectiveStat(Stat.ATK);
|
const atk = pokemon.getEffectiveStat(Stat.ATK);
|
||||||
const def = pokemon.getEffectiveStat(Stat.DEF);
|
const def = pokemon.getEffectiveStat(Stat.DEF);
|
||||||
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
|
const damage = toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
|
||||||
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
|
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
|
||||||
pokemon.damageAndUpdate(damage);
|
pokemon.damageAndUpdate(damage);
|
||||||
pokemon.battleData.hitCount++;
|
pokemon.battleData.hitCount++;
|
||||||
@ -812,13 +832,13 @@ export class SeedTag extends BattlerTag {
|
|||||||
if (ret) {
|
if (ret) {
|
||||||
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
||||||
if (source) {
|
if (source) {
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED));
|
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED));
|
||||||
|
|
||||||
const damage = pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8));
|
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
|
||||||
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false);
|
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false);
|
||||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(),
|
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(),
|
||||||
!reverseDrain ? damage : damage * -1,
|
!reverseDrain ? damage : damage * -1,
|
||||||
@ -838,7 +858,7 @@ export class SeedTag extends BattlerTag {
|
|||||||
|
|
||||||
export class NightmareTag extends BattlerTag {
|
export class NightmareTag extends BattlerTag {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.AFTER_MOVE, 1, Moves.NIGHTMARE);
|
super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, Moves.NIGHTMARE);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
@ -860,11 +880,11 @@ export class NightmareTag extends BattlerTag {
|
|||||||
pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
|
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
|
||||||
|
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4));
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1004,7 +1024,7 @@ export class IngrainTag extends TrappedTag {
|
|||||||
new PokemonHealPhase(
|
new PokemonHealPhase(
|
||||||
pokemon.scene,
|
pokemon.scene,
|
||||||
pokemon.getBattlerIndex(),
|
pokemon.getBattlerIndex(),
|
||||||
Utils.toDmgValue(pokemon.getMaxHp() / 16),
|
toDmgValue(pokemon.getMaxHp() / 16),
|
||||||
i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
|
i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
@ -1067,7 +1087,7 @@ export class AquaRingTag extends BattlerTag {
|
|||||||
new PokemonHealPhase(
|
new PokemonHealPhase(
|
||||||
pokemon.scene,
|
pokemon.scene,
|
||||||
pokemon.getBattlerIndex(),
|
pokemon.getBattlerIndex(),
|
||||||
Utils.toDmgValue(pokemon.getMaxHp() / 16),
|
toDmgValue(pokemon.getMaxHp() / 16),
|
||||||
i18next.t("battlerTags:aquaRingLapse", {
|
i18next.t("battlerTags:aquaRingLapse", {
|
||||||
moveName: this.getMoveName(),
|
moveName: this.getMoveName(),
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon)
|
pokemonName: getPokemonNameWithAffix(pokemon)
|
||||||
@ -1161,11 +1181,11 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||||||
);
|
);
|
||||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
|
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
|
||||||
|
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8));
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1356,7 +1376,7 @@ export class ContactDamageProtectedTag extends ProtectedTag {
|
|||||||
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
|
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
|
||||||
const attacker = effectPhase.getPokemon();
|
const attacker = effectPhase.getPokemon();
|
||||||
if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
||||||
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
|
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1709,7 +1729,7 @@ export class SemiInvulnerableTag extends BattlerTag {
|
|||||||
onRemove(pokemon: Pokemon): void {
|
onRemove(pokemon: Pokemon): void {
|
||||||
// Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible
|
// Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible
|
||||||
pokemon.scene.tweens.addCounter({
|
pokemon.scene.tweens.addCounter({
|
||||||
duration: Utils.getFrameMs(2),
|
duration: getFrameMs(2),
|
||||||
onComplete: () => pokemon.setVisible(true)
|
onComplete: () => pokemon.setVisible(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1860,12 +1880,12 @@ export class SaltCuredTag extends BattlerTag {
|
|||||||
if (ret) {
|
if (ret) {
|
||||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE));
|
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE));
|
||||||
|
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER);
|
const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER);
|
||||||
pokemon.damageAndUpdate(Utils.toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8));
|
pokemon.damageAndUpdate(toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8));
|
||||||
|
|
||||||
pokemon.scene.queueMessage(
|
pokemon.scene.queueMessage(
|
||||||
i18next.t("battlerTags:saltCuredLapse", {
|
i18next.t("battlerTags:saltCuredLapse", {
|
||||||
@ -1907,11 +1927,11 @@ export class CursedTag extends BattlerTag {
|
|||||||
if (ret) {
|
if (ret) {
|
||||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE));
|
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE));
|
||||||
|
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4));
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4));
|
||||||
pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2173,7 +2193,7 @@ export class GulpMissileTag extends BattlerTag {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
@ -2289,7 +2309,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
|
|||||||
* @returns `true` if the move cannot be used because the target is an ally
|
* @returns `true` if the move cannot be used because the target is an ally
|
||||||
*/
|
*/
|
||||||
override isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon) {
|
override isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon) {
|
||||||
const moveCategory = new Utils.IntegerHolder(allMoves[move].category);
|
const moveCategory = new NumberHolder(allMoves[move].category);
|
||||||
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory);
|
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory);
|
||||||
if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) {
|
if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) {
|
||||||
return true;
|
return true;
|
||||||
@ -2506,7 +2526,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
|
|||||||
const ret = super.lapse(pokemon, lapseType);
|
const ret = super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
||||||
const cancelled = new Utils.BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
if (pokemon.mysteryEncounterBattleEffects) {
|
if (pokemon.mysteryEncounterBattleEffects) {
|
||||||
@ -2955,3 +2975,22 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag {
|
|||||||
tag.loadTag(source);
|
tag.loadTag(source);
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to verify that the current phase is a MoveEffectPhase and provide quick access to commonly used fields
|
||||||
|
*
|
||||||
|
* @param pokemon {@linkcode Pokemon} The Pokémon used to access the current phase
|
||||||
|
* @returns null if current phase is not MoveEffectPhase, otherwise Object containing the {@linkcode MoveEffectPhase}, and its
|
||||||
|
* corresponding {@linkcode Move} and user {@linkcode Pokemon}
|
||||||
|
*/
|
||||||
|
function getMoveEffectPhaseData(pokemon: Pokemon): {phase: MoveEffectPhase, attacker: Pokemon, move: Move} | null {
|
||||||
|
const phase = pokemon.scene.getCurrentPhase();
|
||||||
|
if (phase instanceof MoveEffectPhase) {
|
||||||
|
return {
|
||||||
|
phase : phase,
|
||||||
|
attacker : phase.getPokemon(),
|
||||||
|
move : phase.move.getMove()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Type } from "#app/data/type";
|
import { Type } from "#app/data/type";
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
|
import { Nature } from "#enums/nature";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data that can customize a Pokemon in non-standard ways from its Species
|
* Data that can customize a Pokemon in non-standard ways from its Species
|
||||||
* Currently only used by Mystery Encounters, may need to be renamed if it becomes more widely used
|
* Currently only used by Mystery Encounters and Mints.
|
||||||
*/
|
*/
|
||||||
export class MysteryEncounterPokemonData {
|
export class CustomPokemonData {
|
||||||
public spriteScale: number;
|
public spriteScale: number;
|
||||||
public ability: Abilities | -1;
|
public ability: Abilities | -1;
|
||||||
public passive: Abilities | -1;
|
public passive: Abilities | -1;
|
||||||
|
public nature: Nature | -1;
|
||||||
public types: Type[];
|
public types: Type[];
|
||||||
|
|
||||||
constructor(data?: MysteryEncounterPokemonData | Partial<MysteryEncounterPokemonData>) {
|
constructor(data?: CustomPokemonData | Partial<CustomPokemonData>) {
|
||||||
if (!isNullOrUndefined(data)) {
|
if (!isNullOrUndefined(data)) {
|
||||||
Object.assign(this, data);
|
Object.assign(this, data);
|
||||||
}
|
}
|
||||||
@ -20,6 +22,7 @@ export class MysteryEncounterPokemonData {
|
|||||||
this.spriteScale = this.spriteScale ?? -1;
|
this.spriteScale = this.spriteScale ?? -1;
|
||||||
this.ability = this.ability ?? -1;
|
this.ability = this.ability ?? -1;
|
||||||
this.passive = this.passive ?? -1;
|
this.passive = this.passive ?? -1;
|
||||||
|
this.nature = this.nature ?? -1;
|
||||||
this.types = this.types ?? [];
|
this.types = this.types ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ import { Constructor, NumberHolder } from "#app/utils";
|
|||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { WeatherType } from "./weather";
|
import { WeatherType } from "./weather";
|
||||||
import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag";
|
import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag";
|
||||||
import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability";
|
import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability";
|
||||||
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
|
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
|
||||||
import { BattlerIndex, BattleType } from "../battle";
|
import { BattlerIndex, BattleType } from "../battle";
|
||||||
import { TerrainType } from "./terrain";
|
import { TerrainType } from "./terrain";
|
||||||
@ -346,7 +346,11 @@ export default class Move implements Localizable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !user.hasAbility(Abilities.INFILTRATOR)
|
const bypassed = new Utils.BooleanHolder(false);
|
||||||
|
// TODO: Allow this to be simulated
|
||||||
|
applyAbAttrs(InfiltratorAbAttr, user, null, false, bypassed);
|
||||||
|
|
||||||
|
return !bypassed.value
|
||||||
&& !this.hasFlag(MoveFlags.SOUND_BASED)
|
&& !this.hasFlag(MoveFlags.SOUND_BASED)
|
||||||
&& !this.hasFlag(MoveFlags.IGNORE_SUBSTITUTE);
|
&& !this.hasFlag(MoveFlags.IGNORE_SUBSTITUTE);
|
||||||
}
|
}
|
||||||
@ -2046,15 +2050,15 @@ export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr {
|
|||||||
|
|
||||||
export class StatusEffectAttr extends MoveEffectAttr {
|
export class StatusEffectAttr extends MoveEffectAttr {
|
||||||
public effect: StatusEffect;
|
public effect: StatusEffect;
|
||||||
public cureTurn: integer | null;
|
public turnsRemaining?: number;
|
||||||
public overrideStatus: boolean;
|
public overrideStatus: boolean = false;
|
||||||
|
|
||||||
constructor(effect: StatusEffect, selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) {
|
constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) {
|
||||||
super(selfTarget, MoveEffectTrigger.HIT);
|
super(selfTarget, MoveEffectTrigger.HIT);
|
||||||
|
|
||||||
this.effect = effect;
|
this.effect = effect;
|
||||||
this.cureTurn = cureTurn!; // TODO: is this bang correct?
|
this.turnsRemaining = turnsRemaining;
|
||||||
this.overrideStatus = !!overrideStatus;
|
this.overrideStatus = overrideStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
@ -2074,14 +2078,14 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user !== target && target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)) {
|
if (user !== target && target.isSafeguarded(user)) {
|
||||||
if (move.category === MoveCategory.STATUS) {
|
if (move.category === MoveCategory.STATUS) {
|
||||||
user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
|
user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
|
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
|
||||||
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
|
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining)) {
|
||||||
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2098,8 +2102,8 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||||||
export class MultiStatusEffectAttr extends StatusEffectAttr {
|
export class MultiStatusEffectAttr extends StatusEffectAttr {
|
||||||
public effects: StatusEffect[];
|
public effects: StatusEffect[];
|
||||||
|
|
||||||
constructor(effects: StatusEffect[], selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) {
|
constructor(effects: StatusEffect[], selfTarget?: boolean, turnsRemaining?: number, overrideStatus?: boolean) {
|
||||||
super(effects[0], selfTarget, cureTurn, overrideStatus);
|
super(effects[0], selfTarget, turnsRemaining, overrideStatus);
|
||||||
this.effects = effects;
|
this.effects = effects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2891,8 +2895,6 @@ export class SecretPowerAttr extends MoveEffectAttr {
|
|||||||
this.effectChanceOverride = move.chance;
|
this.effectChanceOverride = move.chance;
|
||||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget);
|
const moveChance = this.getMoveChance(user, target, move, this.selfTarget);
|
||||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||||
// effectChanceOverride used in the application of the actual secondary effect
|
|
||||||
this.effectChanceOverride = 100;
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@ -2915,6 +2917,8 @@ export class SecretPowerAttr extends MoveEffectAttr {
|
|||||||
const biome = user.scene.arena.biomeType;
|
const biome = user.scene.arena.biomeType;
|
||||||
secondaryEffect = this.determineBiomeEffect(biome);
|
secondaryEffect = this.determineBiomeEffect(biome);
|
||||||
}
|
}
|
||||||
|
// effectChanceOverride used in the application of the actual secondary effect
|
||||||
|
secondaryEffect.effectChanceOverride = 100;
|
||||||
return secondaryEffect.apply(user, target, move, []);
|
return secondaryEffect.apply(user, target, move, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5161,7 +5165,7 @@ export class ConfuseAttr extends AddBattlerTagAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
if (!this.selfTarget && target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)) {
|
if (!this.selfTarget && target.isSafeguarded(user)) {
|
||||||
if (move.category === MoveCategory.STATUS) {
|
if (move.category === MoveCategory.STATUS) {
|
||||||
user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
|
user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
|
||||||
}
|
}
|
||||||
@ -7598,6 +7602,7 @@ export function initMoves() {
|
|||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
new StatusMove(Moves.TRANSFORM, Type.NORMAL, -1, 10, -1, 0, 1)
|
new StatusMove(Moves.TRANSFORM, Type.NORMAL, -1, 10, -1, 0, 1)
|
||||||
.attr(TransformAttr)
|
.attr(TransformAttr)
|
||||||
|
.condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE))
|
||||||
.ignoresProtect(),
|
.ignoresProtect(),
|
||||||
new AttackMove(Moves.BUBBLE, Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
new AttackMove(Moves.BUBBLE, Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
|
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
|
||||||
@ -8028,7 +8033,7 @@ export function initMoves() {
|
|||||||
.attr(RemoveScreensAttr),
|
.attr(RemoveScreensAttr),
|
||||||
new StatusMove(Moves.YAWN, Type.NORMAL, -1, 10, -1, 0, 3)
|
new StatusMove(Moves.YAWN, Type.NORMAL, -1, 10, -1, 0, 3)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true)
|
.attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true)
|
||||||
.condition((user, target, move) => !target.status && !target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)),
|
.condition((user, target, move) => !target.status && !target.isSafeguarded(user)),
|
||||||
new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
|
new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
|
||||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1)
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1)
|
||||||
.attr(RemoveHeldItemAttr, false),
|
.attr(RemoveHeldItemAttr, false),
|
||||||
|
@ -133,8 +133,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
|
|||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||||
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
|
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
|
||||||
new MoveRequirement(EXTORTION_MOVES),
|
new MoveRequirement(EXTORTION_MOVES, true),
|
||||||
new AbilityRequirement(EXTORTION_ABILITIES))
|
new AbilityRequirement(EXTORTION_ABILITIES, true))
|
||||||
)
|
)
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option.2.label`,
|
buttonLabel: `${namespace}:option.2.label`,
|
||||||
|
@ -42,6 +42,8 @@ import {
|
|||||||
AttackTypeBoosterModifier,
|
AttackTypeBoosterModifier,
|
||||||
BypassSpeedChanceModifier,
|
BypassSpeedChanceModifier,
|
||||||
ContactHeldItemTransferChanceModifier,
|
ContactHeldItemTransferChanceModifier,
|
||||||
|
GigantamaxAccessModifier,
|
||||||
|
MegaEvolutionAccessModifier,
|
||||||
PokemonHeldItemModifier
|
PokemonHeldItemModifier
|
||||||
} from "#app/modifier/modifier";
|
} from "#app/modifier/modifier";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -356,10 +358,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
// If player has any evolution/form change items that are valid for their party, will spawn one of those items in addition to a Master Ball
|
// If the player has any evolution/form change items that are valid for their party,
|
||||||
const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)!, generateModifierTypeOption(scene, modifierTypes.MAX_LURE)! ];
|
// spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball
|
||||||
|
const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)! ];
|
||||||
const specialOptions: ModifierTypeOption[] = [];
|
const specialOptions: ModifierTypeOption[] = [];
|
||||||
|
|
||||||
|
if (!scene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) {
|
||||||
|
modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.MEGA_BRACELET)!);
|
||||||
|
}
|
||||||
|
if (!scene.findModifier(m => m instanceof GigantamaxAccessModifier)) {
|
||||||
|
modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.DYNAMAX_BAND)!);
|
||||||
|
}
|
||||||
const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM);
|
const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM);
|
||||||
if (nonRareEvolutionModifier) {
|
if (nonRareEvolutionModifier) {
|
||||||
specialOptions.push(nonRareEvolutionModifier);
|
specialOptions.push(nonRareEvolutionModifier);
|
||||||
|
@ -28,7 +28,7 @@ import { BattlerIndex } from "#app/battle";
|
|||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
||||||
import { MoveCategory } from "#app/data/move";
|
import { MoveCategory } from "#app/data/move";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
import { EncounterAnim } from "#enums/encounter-anims";
|
import { EncounterAnim } from "#enums/encounter-anims";
|
||||||
import { Challenges } from "#enums/challenges";
|
import { Challenges } from "#enums/challenges";
|
||||||
@ -133,7 +133,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
|||||||
},
|
},
|
||||||
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
|
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
|
||||||
species: getPokemonSpecies(Species.BLACEPHALON),
|
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||||
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}),
|
customPokemonData: new CustomPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ]
|
moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ]
|
||||||
},
|
},
|
||||||
@ -353,15 +353,15 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
|||||||
newTypes.push(secondType);
|
newTypes.push(secondType);
|
||||||
|
|
||||||
// Apply the type changes (to both base and fusion, if pokemon is fused)
|
// Apply the type changes (to both base and fusion, if pokemon is fused)
|
||||||
if (!pokemon.mysteryEncounterPokemonData) {
|
if (!pokemon.customPokemonData) {
|
||||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
pokemon.customPokemonData = new CustomPokemonData();
|
||||||
}
|
}
|
||||||
pokemon.mysteryEncounterPokemonData.types = newTypes;
|
pokemon.customPokemonData.types = newTypes;
|
||||||
if (pokemon.isFusion()) {
|
if (pokemon.isFusion()) {
|
||||||
if (!pokemon.fusionMysteryEncounterPokemonData) {
|
if (!pokemon.fusionCustomPokemonData) {
|
||||||
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
pokemon.fusionCustomPokemonData = new CustomPokemonData();
|
||||||
}
|
}
|
||||||
pokemon.fusionMysteryEncounterPokemonData.types = newTypes;
|
pokemon.fusionCustomPokemonData.types = newTypes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -426,15 +426,15 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
|
|||||||
// Do ability swap
|
// Do ability swap
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
if (pokemon.isFusion()) {
|
if (pokemon.isFusion()) {
|
||||||
if (!pokemon.fusionMysteryEncounterPokemonData) {
|
if (!pokemon.fusionCustomPokemonData) {
|
||||||
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
pokemon.fusionCustomPokemonData = new CustomPokemonData();
|
||||||
}
|
}
|
||||||
pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability;
|
pokemon.fusionCustomPokemonData.ability = encounter.misc.ability;
|
||||||
} else {
|
} else {
|
||||||
if (!pokemon.mysteryEncounterPokemonData) {
|
if (!pokemon.customPokemonData) {
|
||||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
pokemon.customPokemonData = new CustomPokemonData();
|
||||||
}
|
}
|
||||||
pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability;
|
pokemon.customPokemonData.ability = encounter.misc.ability;
|
||||||
}
|
}
|
||||||
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
||||||
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
||||||
|
@ -236,7 +236,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
|||||||
.withOption(
|
.withOption(
|
||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||||
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option.3.label`,
|
buttonLabel: `${namespace}:option.3.label`,
|
||||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||||
|
@ -145,7 +145,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
|||||||
.withOption(
|
.withOption(
|
||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option.2.label`,
|
buttonLabel: `${namespace}:option.2.label`,
|
||||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||||
|
@ -227,7 +227,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
|||||||
.withOption(
|
.withOption(
|
||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||||
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option.3.label`,
|
buttonLabel: `${namespace}:option.3.label`,
|
||||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||||
|
@ -138,7 +138,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
|||||||
newNature = randSeedInt(25) as Nature;
|
newNature = randSeedInt(25) as Nature;
|
||||||
}
|
}
|
||||||
|
|
||||||
chosenPokemon.nature = newNature;
|
chosenPokemon.customPokemonData.nature = newNature;
|
||||||
encounter.setDialogueToken("newNature", getNatureName(newNature));
|
encounter.setDialogueToken("newNature", getNatureName(newNature));
|
||||||
queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
|
queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
|
||||||
setEncounterExp(scene, [ chosenPokemon.id ], 100);
|
setEncounterExp(scene, [ chosenPokemon.id ], 100);
|
||||||
|
@ -18,7 +18,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
|||||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
|
|
||||||
/** i18n namespace for the encounter */
|
/** i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/slumberingSnorlax";
|
const namespace = "mysteryEncounters/slumberingSnorlax";
|
||||||
@ -72,7 +72,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
|
|||||||
stackCount: 2
|
stackCount: 2
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }),
|
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||||
aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep
|
aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep
|
||||||
};
|
};
|
||||||
const config: EnemyPartyConfig = {
|
const config: EnemyPartyConfig = {
|
||||||
@ -143,7 +143,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
|
|||||||
.withOption(
|
.withOption(
|
||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES))
|
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true))
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option.3.label`,
|
buttonLabel: `${namespace}:option.3.label`,
|
||||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||||
|
@ -25,6 +25,7 @@ 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 "#app/data/type";
|
import { Type } from "#app/data/type";
|
||||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||||
|
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/theExpertPokemonBreeder";
|
const namespace = "mysteryEncounters/theExpertPokemonBreeder";
|
||||||
@ -163,7 +164,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
if (pokemon2CommonEggs > 0) {
|
if (pokemon2CommonEggs > 0) {
|
||||||
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") });
|
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") });
|
||||||
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
|
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
|
||||||
encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
|
encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
|
||||||
}
|
}
|
||||||
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
|
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
|
||||||
|
|
||||||
@ -221,7 +222,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
encounter.misc.chosenPokemon = pokemon1;
|
encounter.misc.chosenPokemon = pokemon1;
|
||||||
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
|
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
|
||||||
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
|
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
|
||||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene));
|
||||||
|
|
||||||
// Remove all Pokemon from the party except the chosen Pokemon
|
// Remove all Pokemon from the party except the chosen Pokemon
|
||||||
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1);
|
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1);
|
||||||
@ -247,9 +248,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
encounter.onGameOver = onGameOver;
|
encounter.onGameOver = onGameOver;
|
||||||
await initBattleWithEnemyConfig(scene, config);
|
await initBattleWithEnemyConfig(scene, config);
|
||||||
})
|
})
|
||||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
|
||||||
await doPostEncounterCleanup(scene);
|
|
||||||
})
|
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.withOption(
|
.withOption(
|
||||||
@ -273,7 +271,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
encounter.misc.chosenPokemon = pokemon2;
|
encounter.misc.chosenPokemon = pokemon2;
|
||||||
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
|
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
|
||||||
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
|
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
|
||||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene));
|
||||||
|
|
||||||
// Remove all Pokemon from the party except the chosen Pokemon
|
// Remove all Pokemon from the party except the chosen Pokemon
|
||||||
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2);
|
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2);
|
||||||
@ -299,9 +297,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
encounter.onGameOver = onGameOver;
|
encounter.onGameOver = onGameOver;
|
||||||
await initBattleWithEnemyConfig(scene, config);
|
await initBattleWithEnemyConfig(scene, config);
|
||||||
})
|
})
|
||||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
|
||||||
await doPostEncounterCleanup(scene);
|
|
||||||
})
|
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.withOption(
|
.withOption(
|
||||||
@ -325,7 +320,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
encounter.misc.chosenPokemon = pokemon3;
|
encounter.misc.chosenPokemon = pokemon3;
|
||||||
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
|
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
|
||||||
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
|
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
|
||||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene));
|
||||||
|
|
||||||
// Remove all Pokemon from the party except the chosen Pokemon
|
// Remove all Pokemon from the party except the chosen Pokemon
|
||||||
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3);
|
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3);
|
||||||
@ -351,9 +346,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
encounter.onGameOver = onGameOver;
|
encounter.onGameOver = onGameOver;
|
||||||
await initBattleWithEnemyConfig(scene, config);
|
await initBattleWithEnemyConfig(scene, config);
|
||||||
})
|
})
|
||||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
|
||||||
await doPostEncounterCleanup(scene);
|
|
||||||
})
|
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.withOutroDialogue([
|
.withOutroDialogue([
|
||||||
@ -521,19 +513,19 @@ function checkAchievement(scene: BattleScene) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restorePartyAndHeldItems(scene: BattleScene) {
|
function restorePartyAndHeldItems(scene: BattleScene) {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
// Restore original party
|
// Restore original party
|
||||||
scene.getParty().push(...encounter.misc.originalParty);
|
scene.getParty().push(...encounter.misc.originalParty);
|
||||||
|
|
||||||
// Restore held items
|
// Restore held items
|
||||||
const originalHeldItems = encounter.misc.originalPartyHeldItems;
|
const originalHeldItems = encounter.misc.originalPartyHeldItems;
|
||||||
originalHeldItems.forEach(pokemonHeldItemsList => {
|
originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => {
|
||||||
pokemonHeldItemsList.forEach(heldItem => {
|
pokemonHeldItemsList.forEach(heldItem => {
|
||||||
scene.addModifier(heldItem, true, false, false, true);
|
scene.addModifier(heldItem, true, false, false, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await scene.updateModifiers(true);
|
scene.updateModifiers(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGameOver(scene: BattleScene) {
|
function onGameOver(scene: BattleScene) {
|
||||||
@ -609,13 +601,13 @@ function onGameOver(scene: BattleScene) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async 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
|
// Give achievement if in Space biome
|
||||||
checkAchievement(scene);
|
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);
|
||||||
await restorePartyAndHeldItems(scene);
|
restorePartyAndHeldItems(scene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import { BattlerIndex } from "#app/battle";
|
|||||||
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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
@ -79,7 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
|
|||||||
species: getPokemonSpecies(Species.SHUCKLE),
|
species: getPokemonSpecies(Species.SHUCKLE),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 5,
|
bossSegments: 5,
|
||||||
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ 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 ],
|
||||||
modifierConfigs: [
|
modifierConfigs: [
|
||||||
|
@ -210,7 +210,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
.withOption(
|
.withOption(
|
||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||||
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option.3.label`,
|
buttonLabel: `${namespace}:option.3.label`,
|
||||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||||
|
@ -12,7 +12,7 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "
|
|||||||
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
import { achvs } from "#app/system/achv";
|
import { achvs } from "#app/system/achv";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
import i18next from "#app/plugins/i18n";
|
import i18next from "#app/plugins/i18n";
|
||||||
@ -379,10 +379,10 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
|||||||
newType = randSeedInt(18) as Type;
|
newType = randSeedInt(18) as Type;
|
||||||
}
|
}
|
||||||
newTypes.push(newType);
|
newTypes.push(newType);
|
||||||
if (!newPokemon.mysteryEncounterPokemonData) {
|
if (!newPokemon.customPokemonData) {
|
||||||
newPokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
newPokemon.customPokemonData = new CustomPokemonData();
|
||||||
}
|
}
|
||||||
newPokemon.mysteryEncounterPokemonData.types = newTypes;
|
newPokemon.customPokemonData.types = newTypes;
|
||||||
|
|
||||||
for (const item of transformation.heldItems) {
|
for (const item of transformation.heldItems) {
|
||||||
item.pokemonId = newPokemon.id;
|
item.pokemonId = newPokemon.id;
|
||||||
|
@ -15,6 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||||||
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
|
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
|
||||||
import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
|
import { allAbilities } from "#app/data/ability";
|
||||||
|
|
||||||
export interface EncounterRequirement {
|
export interface EncounterRequirement {
|
||||||
meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met
|
meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met
|
||||||
@ -476,9 +477,11 @@ export class MoveRequirement extends EncounterPokemonRequirement {
|
|||||||
requiredMoves: Moves[] = [];
|
requiredMoves: Moves[] = [];
|
||||||
minNumberOfPokemon: number;
|
minNumberOfPokemon: number;
|
||||||
invertQuery: boolean;
|
invertQuery: boolean;
|
||||||
|
excludeDisallowedPokemon: boolean;
|
||||||
|
|
||||||
constructor(moves: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
constructor(moves: Moves | Moves[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||||
super();
|
super();
|
||||||
|
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredMoves = Array.isArray(moves) ? moves : [ moves ];
|
this.requiredMoves = Array.isArray(moves) ? moves : [ moves ];
|
||||||
@ -494,10 +497,15 @@ export class MoveRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||||
if (!this.invertQuery) {
|
if (!this.invertQuery) {
|
||||||
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length > 0).length > 0);
|
// get the Pokemon with at least one move in the required moves list
|
||||||
|
return partyPokemon.filter((pokemon) =>
|
||||||
|
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
|
||||||
|
&& pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId)));
|
||||||
} else {
|
} else {
|
||||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves
|
||||||
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length === 0).length === 0);
|
return partyPokemon.filter((pokemon) =>
|
||||||
|
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
|
||||||
|
&& !pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,9 +567,11 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
|
|||||||
requiredAbilities: Abilities[];
|
requiredAbilities: Abilities[];
|
||||||
minNumberOfPokemon: number;
|
minNumberOfPokemon: number;
|
||||||
invertQuery: boolean;
|
invertQuery: boolean;
|
||||||
|
excludeDisallowedPokemon: boolean;
|
||||||
|
|
||||||
constructor(abilities: Abilities | Abilities[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
constructor(abilities: Abilities | Abilities[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||||
super();
|
super();
|
||||||
|
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ];
|
this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ];
|
||||||
@ -577,16 +587,21 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||||
if (!this.invertQuery) {
|
if (!this.invertQuery) {
|
||||||
return partyPokemon.filter((pokemon) => this.requiredAbilities.some((ability) => pokemon.getAbility().id === ability));
|
return partyPokemon.filter((pokemon) =>
|
||||||
|
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
|
||||||
|
&& this.requiredAbilities.some((ability) => pokemon.hasAbility(ability, false)));
|
||||||
} else {
|
} else {
|
||||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilities
|
||||||
return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability).length === 0);
|
return partyPokemon.filter((pokemon) =>
|
||||||
|
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
|
||||||
|
&& this.requiredAbilities.filter((ability) => pokemon.hasAbility(ability, false)).length === 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
override getDialogueToken(_scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||||
if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) {
|
const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false));
|
||||||
return [ "ability", pokemon.getAbility().name ];
|
if (!isNullOrUndefined(matchingAbility)) {
|
||||||
|
return [ "ability", allAbilities[matchingAbility].name ];
|
||||||
}
|
}
|
||||||
return [ "ability", "" ];
|
return [ "ability", "" ];
|
||||||
}
|
}
|
||||||
|
@ -325,7 +325,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
if (activeMon.length > 0) {
|
if (activeMon.length > 0) {
|
||||||
this.primaryPokemon = activeMon[0];
|
this.primaryPokemon = activeMon[0];
|
||||||
} else {
|
} else {
|
||||||
this.primaryPokemon = scene.getParty().filter(p => !p.isFainted())[0];
|
this.primaryPokemon = scene.getParty().filter(p => p.isAllowedInBattle())[0];
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import { Status, StatusEffect } from "#app/data/status-effect";
|
|||||||
import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config";
|
import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config";
|
||||||
import PokemonSpecies from "#app/data/pokemon-species";
|
import PokemonSpecies from "#app/data/pokemon-species";
|
||||||
import { Egg, IEggOptions } from "#app/data/egg";
|
import { Egg, IEggOptions } from "#app/data/egg";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||||
import { MovePhase } from "#app/phases/move-phase";
|
import { MovePhase } from "#app/phases/move-phase";
|
||||||
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
|
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
|
||||||
@ -71,7 +71,7 @@ export interface EnemyPokemonConfig {
|
|||||||
nickname?: string;
|
nickname?: string;
|
||||||
bossSegments?: number;
|
bossSegments?: number;
|
||||||
bossSegmentModifier?: number; // Additive to the determined segment number
|
bossSegmentModifier?: number; // Additive to the determined segment number
|
||||||
mysteryEncounterPokemonData?: MysteryEncounterPokemonData;
|
customPokemonData?: CustomPokemonData;
|
||||||
formIndex?: number;
|
formIndex?: number;
|
||||||
abilityIndex?: number;
|
abilityIndex?: number;
|
||||||
level?: number;
|
level?: number;
|
||||||
@ -145,7 +145,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
newTrainer.setVisible(false);
|
newTrainer.setVisible(false);
|
||||||
scene.field.add(newTrainer);
|
scene.field.add(newTrainer);
|
||||||
scene.currentBattle.trainer = newTrainer;
|
scene.currentBattle.trainer = newTrainer;
|
||||||
loadEnemyAssets.push(newTrainer.loadAssets());
|
loadEnemyAssets.push(newTrainer.loadAssets().then(() => newTrainer.initSprite()));
|
||||||
|
|
||||||
battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex);
|
battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex);
|
||||||
} else {
|
} else {
|
||||||
@ -250,8 +250,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
|
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
|
||||||
if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) {
|
if (!isNullOrUndefined(config.customPokemonData)) {
|
||||||
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData;
|
enemyPokemon.customPokemonData = config.customPokemonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Boss
|
// Set Boss
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as Utils from "../utils";
|
import { randIntRange } from "#app/utils";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import i18next, { ParseKeys } from "i18next";
|
import i18next, { ParseKeys } from "i18next";
|
||||||
|
|
||||||
@ -6,17 +6,21 @@ export { StatusEffect };
|
|||||||
|
|
||||||
export class Status {
|
export class Status {
|
||||||
public effect: StatusEffect;
|
public effect: StatusEffect;
|
||||||
public turnCount: integer;
|
/** Toxic damage is `1/16 max HP * toxicTurnCount` */
|
||||||
public cureTurn: integer | null;
|
public toxicTurnCount: number = 0;
|
||||||
|
public sleepTurnsRemaining?: number;
|
||||||
|
|
||||||
constructor(effect: StatusEffect, turnCount: integer = 0, cureTurn?: integer) {
|
constructor(effect: StatusEffect, toxicTurnCount: number = 0, sleepTurnsRemaining?: number) {
|
||||||
this.effect = effect;
|
this.effect = effect;
|
||||||
this.turnCount = turnCount === undefined ? 0 : turnCount;
|
this.toxicTurnCount = toxicTurnCount;
|
||||||
this.cureTurn = cureTurn!; // TODO: is this bang correct?
|
this.sleepTurnsRemaining = sleepTurnsRemaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
incrementTurn(): void {
|
incrementTurn(): void {
|
||||||
this.turnCount++;
|
this.toxicTurnCount++;
|
||||||
|
if (this.sleepTurnsRemaining) {
|
||||||
|
this.sleepTurnsRemaining--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isPostTurn(): boolean {
|
isPostTurn(): boolean {
|
||||||
@ -107,7 +111,7 @@ export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect):
|
|||||||
* Returns a random non-volatile StatusEffect
|
* Returns a random non-volatile StatusEffect
|
||||||
*/
|
*/
|
||||||
export function generateRandomStatusEffect(): StatusEffect {
|
export function generateRandomStatusEffect(): StatusEffect {
|
||||||
return Utils.randIntRange(1, 6);
|
return randIntRange(1, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,7 +127,7 @@ export function getRandomStatusEffect(statusEffectA: StatusEffect, statusEffectB
|
|||||||
return statusEffectA;
|
return statusEffectA;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Utils.randIntRange(0, 2) ? statusEffectA : statusEffectB;
|
return randIntRange(0, 2) ? statusEffectA : statusEffectB;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,7 +144,7 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Utils.randIntRange(0, 2) ? statusA : statusB;
|
return randIntRange(0, 2) ? statusA : statusB;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,7 +22,7 @@ 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 "#app/data/weather";
|
import { WeatherType } from "#app/data/weather";
|
||||||
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, ReduceStatusEffectDurationAbAttr, 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 } 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 } 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";
|
||||||
@ -62,7 +62,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph
|
|||||||
import { Challenges } from "#enums/challenges";
|
import { Challenges } from "#enums/challenges";
|
||||||
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
||||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import { SwitchType } from "#enums/switch-type";
|
import { SwitchType } from "#enums/switch-type";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
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";
|
||||||
@ -114,7 +114,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
public fusionVariant: Variant;
|
public fusionVariant: Variant;
|
||||||
public fusionGender: Gender;
|
public fusionGender: Gender;
|
||||||
public fusionLuck: integer;
|
public fusionLuck: integer;
|
||||||
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null;
|
public fusionCustomPokemonData: CustomPokemonData | null;
|
||||||
|
|
||||||
private summonDataPrimer: PokemonSummonData | null;
|
private summonDataPrimer: PokemonSummonData | null;
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
public battleData: PokemonBattleData;
|
public battleData: PokemonBattleData;
|
||||||
public battleSummonData: PokemonBattleSummonData;
|
public battleSummonData: PokemonBattleSummonData;
|
||||||
public turnData: PokemonTurnData;
|
public turnData: PokemonTurnData;
|
||||||
public mysteryEncounterPokemonData: MysteryEncounterPokemonData;
|
public customPokemonData: CustomPokemonData;
|
||||||
|
|
||||||
/** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */
|
/** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */
|
||||||
public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
|
public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
|
||||||
@ -193,7 +193,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
this.nature = dataSource.nature || 0 as Nature;
|
this.nature = dataSource.nature || 0 as Nature;
|
||||||
this.nickname = dataSource.nickname;
|
this.nickname = dataSource.nickname;
|
||||||
this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1;
|
|
||||||
this.moveset = dataSource.moveset;
|
this.moveset = dataSource.moveset;
|
||||||
this.status = dataSource.status!; // TODO: is this bang correct?
|
this.status = dataSource.status!; // TODO: is this bang correct?
|
||||||
this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship;
|
this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship;
|
||||||
@ -212,9 +211,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.fusionVariant = dataSource.fusionVariant || 0;
|
this.fusionVariant = dataSource.fusionVariant || 0;
|
||||||
this.fusionGender = dataSource.fusionGender;
|
this.fusionGender = dataSource.fusionGender;
|
||||||
this.fusionLuck = dataSource.fusionLuck;
|
this.fusionLuck = dataSource.fusionLuck;
|
||||||
this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData;
|
this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData;
|
||||||
this.usedTMs = dataSource.usedTMs ?? [];
|
this.usedTMs = dataSource.usedTMs ?? [];
|
||||||
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData);
|
this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData);
|
||||||
} else {
|
} else {
|
||||||
this.id = Utils.randSeedInt(4294967296);
|
this.id = Utils.randSeedInt(4294967296);
|
||||||
this.ivs = ivs || Utils.getIvsFromId(this.id);
|
this.ivs = ivs || Utils.getIvsFromId(this.id);
|
||||||
@ -235,7 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.variant = this.shiny ? this.generateVariant() : 0;
|
this.variant = this.shiny ? this.generateVariant() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
this.customPokemonData = new CustomPokemonData();
|
||||||
|
|
||||||
if (nature !== undefined) {
|
if (nature !== undefined) {
|
||||||
this.setNature(nature);
|
this.setNature(nature);
|
||||||
@ -243,8 +242,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.generateNature();
|
this.generateNature();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.natureOverride = -1;
|
|
||||||
|
|
||||||
this.friendship = species.baseFriendship;
|
this.friendship = species.baseFriendship;
|
||||||
this.metLevel = level;
|
this.metLevel = level;
|
||||||
this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1;
|
this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1;
|
||||||
@ -593,8 +590,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const formKey = this.getFormKey();
|
const formKey = this.getFormKey();
|
||||||
if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") {
|
if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") {
|
||||||
return 1.5;
|
return 1.5;
|
||||||
} else if (this.mysteryEncounterPokemonData.spriteScale > 0) {
|
} else if (this.customPokemonData.spriteScale > 0) {
|
||||||
return this.mysteryEncounterPokemonData.spriteScale;
|
return this.customPokemonData.spriteScale;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -1023,7 +1020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNature(): Nature {
|
getNature(): Nature {
|
||||||
return this.natureOverride !== -1 ? this.natureOverride : this.nature;
|
return this.customPokemonData.nature !== -1 ? this.customPokemonData.nature : this.nature;
|
||||||
}
|
}
|
||||||
|
|
||||||
setNature(nature: Nature): void {
|
setNature(nature: Nature): void {
|
||||||
@ -1198,15 +1195,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (!types.length || !includeTeraType) {
|
if (!types.length || !includeTeraType) {
|
||||||
if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
|
if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
|
||||||
this.summonData.types.forEach(t => types.push(t));
|
this.summonData.types.forEach(t => types.push(t));
|
||||||
} else if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) {
|
} else if (this.customPokemonData.types && this.customPokemonData.types.length > 0) {
|
||||||
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
|
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
|
||||||
types.push(this.mysteryEncounterPokemonData.types[0]);
|
types.push(this.customPokemonData.types[0]);
|
||||||
|
|
||||||
// Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal
|
// Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal
|
||||||
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
||||||
if (fusionSpeciesForm) {
|
if (fusionSpeciesForm) {
|
||||||
// Check if the fusion Pokemon also had "permanently changed" types
|
// Check if the fusion Pokemon also had "permanently changed" types
|
||||||
const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types;
|
const fusionMETypes = this.fusionCustomPokemonData?.types;
|
||||||
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
|
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
|
||||||
types.push(fusionMETypes[1]);
|
types.push(fusionMETypes[1]);
|
||||||
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
|
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
|
||||||
@ -1218,8 +1215,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (types.length === 1 && this.mysteryEncounterPokemonData.types.length >= 2) {
|
if (types.length === 1 && this.customPokemonData.types.length >= 2) {
|
||||||
types.push(this.mysteryEncounterPokemonData.types[1]);
|
types.push(this.customPokemonData.types[1]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
||||||
@ -1230,7 +1227,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (fusionSpeciesForm) {
|
if (fusionSpeciesForm) {
|
||||||
// Check if the fusion Pokemon also had "permanently changed" types
|
// Check if the fusion Pokemon also had "permanently changed" types
|
||||||
// Otherwise, use standard fusion type logic
|
// Otherwise, use standard fusion type logic
|
||||||
const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types;
|
const fusionMETypes = this.fusionCustomPokemonData?.types;
|
||||||
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
|
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
|
||||||
types.push(fusionMETypes[1]);
|
types.push(fusionMETypes[1]);
|
||||||
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
|
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
|
||||||
@ -1262,6 +1259,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If both types are the same (can happen in weird custom typing scenarios), reduce to single type
|
||||||
|
if (types.length > 1 && types[0] === types[1]) {
|
||||||
|
types.splice(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1288,14 +1290,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
||||||
}
|
}
|
||||||
if (this.isFusion()) {
|
if (this.isFusion()) {
|
||||||
if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) {
|
if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) {
|
||||||
return allAbilities[this.fusionMysteryEncounterPokemonData.ability];
|
return allAbilities[this.fusionCustomPokemonData.ability];
|
||||||
} else {
|
} else {
|
||||||
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) {
|
if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) {
|
||||||
return allAbilities[this.mysteryEncounterPokemonData.ability];
|
return allAbilities[this.customPokemonData.ability];
|
||||||
}
|
}
|
||||||
let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex);
|
let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex);
|
||||||
if (abilityId === Abilities.NONE) {
|
if (abilityId === Abilities.NONE) {
|
||||||
@ -1318,8 +1320,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) {
|
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) {
|
||||||
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
|
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
|
||||||
}
|
}
|
||||||
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.passive) && this.mysteryEncounterPokemonData.passive !== -1) {
|
if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) {
|
||||||
return allAbilities[this.mysteryEncounterPokemonData.passive];
|
return allAbilities[this.customPokemonData.passive];
|
||||||
}
|
}
|
||||||
|
|
||||||
let starterSpeciesId = this.species.speciesId;
|
let starterSpeciesId = this.species.speciesId;
|
||||||
@ -2018,7 +2020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.fusionVariant = 0;
|
this.fusionVariant = 0;
|
||||||
this.fusionGender = 0;
|
this.fusionGender = 0;
|
||||||
this.fusionLuck = 0;
|
this.fusionLuck = 0;
|
||||||
this.fusionMysteryEncounterPokemonData = null;
|
this.fusionCustomPokemonData = null;
|
||||||
|
|
||||||
this.generateName();
|
this.generateName();
|
||||||
this.calculateStats();
|
this.calculateStats();
|
||||||
@ -2187,8 +2189,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.moveset.push(new PokemonMove(movePool[index][0], 0, 0));
|
this.moveset.push(new PokemonMove(movePool[index][0], 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes
|
||||||
|
if (this.isPlayer() || !this.scene.currentBattle?.isBattleMysteryEncounter() || !this.scene.currentBattle?.mysteryEncounter) {
|
||||||
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger);
|
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean {
|
trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean {
|
||||||
const move = this.getMoveset().length > moveIndex
|
const move = this.getMoveset().length > moveIndex
|
||||||
@ -2285,6 +2290,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate);
|
this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares if `this` and {@linkcode target} are on the same team.
|
||||||
|
* @param target the {@linkcode Pokemon} to compare against.
|
||||||
|
* @returns `true` if the two pokemon are allies, `false` otherwise
|
||||||
|
*/
|
||||||
|
public isOpponent(target: Pokemon): boolean {
|
||||||
|
return this.isPlayer() !== target.isPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
getOpponent(targetIndex: integer): Pokemon | null {
|
getOpponent(targetIndex: integer): Pokemon | null {
|
||||||
const ret = this.getOpponents()[targetIndex];
|
const ret = this.getOpponents()[targetIndex];
|
||||||
if (ret.summonData) {
|
if (ret.summonData) {
|
||||||
@ -2605,7 +2619,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */
|
/** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */
|
||||||
const screenMultiplier = new Utils.NumberHolder(1);
|
const screenMultiplier = new Utils.NumberHolder(1);
|
||||||
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, moveCategory, screenMultiplier);
|
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
|
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
|
||||||
@ -3347,13 +3361,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const types = this.getTypes(true, true);
|
if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) {
|
||||||
|
|
||||||
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
|
||||||
if (sourcePokemon && sourcePokemon !== this && this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const types = this.getTypes(true, true);
|
||||||
|
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
case StatusEffect.POISON:
|
case StatusEffect.POISON:
|
||||||
case StatusEffect.TOXIC:
|
case StatusEffect.TOXIC:
|
||||||
@ -3417,7 +3430,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
trySetStatus(effect: StatusEffect | undefined, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, cureTurn: integer | null = 0, sourceText: string | null = null): boolean {
|
trySetStatus(effect?: StatusEffect, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, turnsRemaining: number = 0, sourceText: string | null = null): boolean {
|
||||||
if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) {
|
if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -3431,15 +3444,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (asPhase) {
|
if (asPhase) {
|
||||||
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon));
|
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusCureTurn: Utils.IntegerHolder;
|
let sleepTurnsRemaining: Utils.NumberHolder;
|
||||||
|
|
||||||
if (effect === StatusEffect.SLEEP) {
|
if (effect === StatusEffect.SLEEP) {
|
||||||
statusCureTurn = new Utils.IntegerHolder(this.randSeedIntRange(2, 4));
|
sleepTurnsRemaining = new Utils.NumberHolder(this.randSeedIntRange(2, 4));
|
||||||
applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, false, effect, statusCureTurn);
|
|
||||||
|
|
||||||
this.setFrameRate(4);
|
this.setFrameRate(4);
|
||||||
|
|
||||||
@ -3459,9 +3471,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statusCureTurn = statusCureTurn!; // tell TS compiler it's defined
|
sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
|
||||||
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
|
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
|
||||||
this.status = new Status(effect, 0, statusCureTurn?.value);
|
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
|
||||||
|
|
||||||
if (effect !== StatusEffect.FAINT) {
|
if (effect !== StatusEffect.FAINT) {
|
||||||
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
|
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
|
||||||
@ -3499,6 +3511,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this Pokemon is protected by Safeguard
|
||||||
|
* @param attacker the {@linkcode Pokemon} inflicting status on this Pokemon
|
||||||
|
* @returns `true` if this Pokemon is protected by Safeguard; `false` otherwise.
|
||||||
|
*/
|
||||||
|
isSafeguarded(attacker: Pokemon): boolean {
|
||||||
|
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
|
if (this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
|
||||||
|
const bypassed = new Utils.BooleanHolder(false);
|
||||||
|
if (attacker) {
|
||||||
|
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
|
||||||
|
}
|
||||||
|
return !bypassed.value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
primeSummonData(summonDataPrimer: PokemonSummonData): void {
|
primeSummonData(summonDataPrimer: PokemonSummonData): void {
|
||||||
this.summonDataPrimer = summonDataPrimer;
|
this.summonDataPrimer = summonDataPrimer;
|
||||||
}
|
}
|
||||||
@ -3971,16 +4000,20 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
|
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
|
||||||
|
|
||||||
if (Overrides.STATUS_OVERRIDE) {
|
if (Overrides.STATUS_OVERRIDE) {
|
||||||
this.status = new Status(Overrides.STATUS_OVERRIDE);
|
this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Overrides.SHINY_OVERRIDE) {
|
if (Overrides.SHINY_OVERRIDE) {
|
||||||
this.shiny = true;
|
this.shiny = true;
|
||||||
this.initShinySparkle();
|
this.initShinySparkle();
|
||||||
if (Overrides.VARIANT_OVERRIDE) {
|
} else if (Overrides.SHINY_OVERRIDE === false) {
|
||||||
|
this.shiny = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Overrides.VARIANT_OVERRIDE !== null && this.shiny) {
|
||||||
this.variant = Overrides.VARIANT_OVERRIDE;
|
this.variant = Overrides.VARIANT_OVERRIDE;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
if (this.scene.gameMode.isDaily) {
|
if (this.scene.gameMode.isDaily) {
|
||||||
this.generateAndPopulateMoveset();
|
this.generateAndPopulateMoveset();
|
||||||
@ -4292,12 +4325,33 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
|
|
||||||
changeForm(formChange: SpeciesFormChange): Promise<void> {
|
changeForm(formChange: SpeciesFormChange): Promise<void> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
const previousFormIndex = this.formIndex;
|
||||||
this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0);
|
this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0);
|
||||||
this.generateName();
|
this.generateName();
|
||||||
const abilityCount = this.getSpeciesForm().getAbilityCount();
|
const abilityCount = this.getSpeciesForm().getAbilityCount();
|
||||||
if (this.abilityIndex >= abilityCount) { // Shouldn't happen
|
if (this.abilityIndex >= abilityCount) { // Shouldn't happen
|
||||||
this.abilityIndex = abilityCount - 1;
|
this.abilityIndex = abilityCount - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In cases where a form change updates the type of a Pokemon from its previous form (Arceus, Silvally, Castform, etc.),
|
||||||
|
// persist that type change in customPokemonData if necessary
|
||||||
|
const baseForm = this.species.forms[previousFormIndex];
|
||||||
|
const baseFormTypes = [ baseForm.type1, baseForm.type2 ];
|
||||||
|
if (this.customPokemonData.types.length > 0) {
|
||||||
|
if (this.getSpeciesForm().type1 !== baseFormTypes[0]) {
|
||||||
|
this.customPokemonData.types[0] = this.getSpeciesForm().type1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type2 = this.getSpeciesForm().type2;
|
||||||
|
if (!isNullOrUndefined(type2) && type2 !== baseFormTypes[1]) {
|
||||||
|
if (this.customPokemonData.types.length > 1) {
|
||||||
|
this.customPokemonData.types[1] = type2;
|
||||||
|
} else {
|
||||||
|
this.customPokemonData.types.push(type2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.compatibleTms.splice(0, this.compatibleTms.length);
|
this.compatibleTms.splice(0, this.compatibleTms.length);
|
||||||
this.generateCompatibleTms();
|
this.generateCompatibleTms();
|
||||||
const updateAndResolve = () => {
|
const updateAndResolve = () => {
|
||||||
@ -4334,7 +4388,7 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
this.fusionVariant = pokemon.variant;
|
this.fusionVariant = pokemon.variant;
|
||||||
this.fusionGender = pokemon.gender;
|
this.fusionGender = pokemon.gender;
|
||||||
this.fusionLuck = pokemon.luck;
|
this.fusionLuck = pokemon.luck;
|
||||||
this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData;
|
this.fusionCustomPokemonData = pokemon.customPokemonData;
|
||||||
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
|
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
|
||||||
this.pauseEvolutions = true;
|
this.pauseEvolutions = true;
|
||||||
}
|
}
|
||||||
@ -4426,7 +4480,7 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Overrides.OPP_STATUS_OVERRIDE) {
|
if (Overrides.OPP_STATUS_OVERRIDE) {
|
||||||
this.status = new Status(Overrides.OPP_STATUS_OVERRIDE);
|
this.status = new Status(Overrides.OPP_STATUS_OVERRIDE, 0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Overrides.OPP_GENDER_OVERRIDE) {
|
if (Overrides.OPP_GENDER_OVERRIDE) {
|
||||||
@ -4435,9 +4489,11 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
|
|
||||||
const speciesId = this.species.speciesId;
|
const speciesId = this.species.speciesId;
|
||||||
|
|
||||||
if (speciesId in Overrides.OPP_FORM_OVERRIDES
|
if (
|
||||||
|
speciesId in Overrides.OPP_FORM_OVERRIDES
|
||||||
&& Overrides.OPP_FORM_OVERRIDES[speciesId]
|
&& Overrides.OPP_FORM_OVERRIDES[speciesId]
|
||||||
&& this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]]) {
|
&& this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]]
|
||||||
|
) {
|
||||||
this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId] ?? 0;
|
this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId] ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4448,10 +4504,13 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
if (Overrides.OPP_SHINY_OVERRIDE) {
|
if (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) {
|
||||||
this.variant = this.generateVariant();
|
this.variant = this.generateVariant();
|
||||||
if (Overrides.OPP_VARIANT_OVERRIDE) {
|
if (Overrides.OPP_VARIANT_OVERRIDE !== null) {
|
||||||
this.variant = Overrides.OPP_VARIANT_OVERRIDE;
|
this.variant = Overrides.OPP_VARIANT_OVERRIDE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1126,7 +1126,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
|
class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
|
||||||
constructor(rare: boolean) {
|
constructor(isRareFormChangeItem: boolean) {
|
||||||
super((party: Pokemon[], pregenArgs?: any[]) => {
|
super((party: Pokemon[], pregenArgs?: any[]) => {
|
||||||
if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) {
|
if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) {
|
||||||
return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem);
|
return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem);
|
||||||
@ -1167,7 +1167,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
|
|||||||
}
|
}
|
||||||
return formChangeItemTriggers;
|
return formChangeItemTriggers;
|
||||||
}).flat())
|
}).flat())
|
||||||
].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === rare);
|
].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === isRareFormChangeItem);
|
||||||
// convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party.
|
// convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party.
|
||||||
|
|
||||||
if (!formChangeItemPool.length) {
|
if (!formChangeItemPool.length) {
|
||||||
|
@ -2181,7 +2181,7 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
override apply(playerPokemon: PlayerPokemon): boolean {
|
override apply(playerPokemon: PlayerPokemon): boolean {
|
||||||
playerPokemon.natureOverride = this.nature;
|
playerPokemon.customPokemonData.nature = this.nature;
|
||||||
let speciesId = playerPokemon.species.speciesId;
|
let speciesId = playerPokemon.species.speciesId;
|
||||||
playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
|
playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
|
||||||
|
|
||||||
|
@ -113,8 +113,8 @@ class DefaultOverrides {
|
|||||||
readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
|
readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
|
||||||
readonly GENDER_OVERRIDE: Gender | null = null;
|
readonly GENDER_OVERRIDE: Gender | null = null;
|
||||||
readonly MOVESET_OVERRIDE: Moves | Array<Moves> = [];
|
readonly MOVESET_OVERRIDE: Moves | Array<Moves> = [];
|
||||||
readonly SHINY_OVERRIDE: boolean = false;
|
readonly SHINY_OVERRIDE: boolean | null = null;
|
||||||
readonly VARIANT_OVERRIDE: Variant = 0;
|
readonly VARIANT_OVERRIDE: Variant | null = null;
|
||||||
|
|
||||||
// --------------------------
|
// --------------------------
|
||||||
// OPPONENT / ENEMY OVERRIDES
|
// OPPONENT / ENEMY OVERRIDES
|
||||||
@ -134,8 +134,8 @@ class DefaultOverrides {
|
|||||||
readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
|
readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
|
||||||
readonly OPP_GENDER_OVERRIDE: Gender | null = null;
|
readonly OPP_GENDER_OVERRIDE: Gender | null = null;
|
||||||
readonly OPP_MOVESET_OVERRIDE: Moves | Array<Moves> = [];
|
readonly OPP_MOVESET_OVERRIDE: Moves | Array<Moves> = [];
|
||||||
readonly OPP_SHINY_OVERRIDE: boolean = false;
|
readonly OPP_SHINY_OVERRIDE: boolean | null = null;
|
||||||
readonly OPP_VARIANT_OVERRIDE: Variant = 0;
|
readonly OPP_VARIANT_OVERRIDE: Variant | null = null;
|
||||||
readonly OPP_IVS_OVERRIDE: number | number[] = [];
|
readonly OPP_IVS_OVERRIDE: number | number[] = [];
|
||||||
readonly OPP_FORM_OVERRIDES: Partial<Record<Species, number>> = {};
|
readonly OPP_FORM_OVERRIDES: Partial<Record<Species, number>> = {};
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +35,7 @@ import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-d
|
|||||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||||
import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { Biome } from "#enums/biome";
|
import { Biome } from "#enums/biome";
|
||||||
|
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
|
||||||
export class EncounterPhase extends BattlePhase {
|
export class EncounterPhase extends BattlePhase {
|
||||||
private loaded: boolean;
|
private loaded: boolean;
|
||||||
@ -68,7 +69,7 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
this.scene.executeWithSeedOffset(() => {
|
this.scene.executeWithSeedOffset(() => {
|
||||||
const currentSessionEncounterType = battle.mysteryEncounterType;
|
const currentSessionEncounterType = battle.mysteryEncounterType;
|
||||||
battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType);
|
battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType);
|
||||||
}, battle.waveIndex << 4);
|
}, battle.waveIndex * 16);
|
||||||
}
|
}
|
||||||
const mysteryEncounter = battle.mysteryEncounter;
|
const mysteryEncounter = battle.mysteryEncounter;
|
||||||
if (mysteryEncounter) {
|
if (mysteryEncounter) {
|
||||||
@ -251,6 +252,13 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
this.scene.updateModifiers(true);
|
this.scene.updateModifiers(true);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
const { battleType, waveIndex } = this.scene.currentBattle;
|
||||||
|
if (this.scene.isMysteryEncounterValidForWave(battleType, waveIndex) && !this.scene.currentBattle.isBattleMysteryEncounter()) {
|
||||||
|
// Increment ME spawn chance if an ME could have spawned but did not
|
||||||
|
// Only do this AFTER session has been saved to avoid duplicating increments
|
||||||
|
this.scene.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||||
|
}
|
||||||
|
|
||||||
for (const pokemon of this.scene.getParty()) {
|
for (const pokemon of this.scene.getParty()) {
|
||||||
if (pokemon) {
|
if (pokemon) {
|
||||||
pokemon.resetBattleData();
|
pokemon.resetBattleData();
|
||||||
|
@ -170,13 +170,16 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
|||||||
pokemon.setMove(index, this.moveId);
|
pokemon.setMove(index, this.moveId);
|
||||||
initMoveAnim(this.scene, this.moveId).then(() => {
|
initMoveAnim(this.scene, this.moveId).then(() => {
|
||||||
loadMoveAnimAssets(this.scene, [ this.moveId ], true);
|
loadMoveAnimAssets(this.scene, [ this.moveId ], true);
|
||||||
this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is
|
|
||||||
});
|
});
|
||||||
this.scene.ui.setMode(this.messageMode);
|
this.scene.ui.setMode(this.messageMode);
|
||||||
const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name });
|
const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name });
|
||||||
textMessage = textMessage ? textMessage + "$" + learnMoveText : learnMoveText;
|
if (textMessage) {
|
||||||
await this.scene.ui.showTextPromise(textMessage, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true);
|
await this.scene.ui.showTextPromise(textMessage);
|
||||||
|
}
|
||||||
|
this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is
|
||||||
|
this.scene.ui.showText(learnMoveText, null, () => {
|
||||||
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true);
|
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true);
|
||||||
this.end();
|
this.end();
|
||||||
|
}, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,10 +280,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
|
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
|
||||||
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
|
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
|
||||||
}
|
}
|
||||||
target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING);
|
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||||
if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) {
|
|
||||||
target.lapseTag(BattlerTagType.SHELL_TRAP);
|
|
||||||
}
|
|
||||||
})).then(() => {
|
})).then(() => {
|
||||||
// Apply the user's post-attack ability effects
|
// Apply the user's post-attack ability effects
|
||||||
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {
|
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr, ReduceStatusEffectDurationAbAttr } from "#app/data/ability";
|
||||||
import { CommonAnim } from "#app/data/battle-anims";
|
import { CommonAnim } from "#app/data/battle-anims";
|
||||||
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
||||||
import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move";
|
import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move";
|
||||||
@ -175,7 +175,10 @@ export class MovePhase extends BattlePhase {
|
|||||||
break;
|
break;
|
||||||
case StatusEffect.SLEEP:
|
case StatusEffect.SLEEP:
|
||||||
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
|
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
|
||||||
healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn;
|
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
||||||
|
applyAbAttrs(ReduceStatusEffectDurationAbAttr, this.pokemon, null, false, this.pokemon.status.effect, turnsRemaining);
|
||||||
|
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
|
||||||
|
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
||||||
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
||||||
this.cancelled = activated;
|
this.cancelled = activated;
|
||||||
break;
|
break;
|
||||||
|
@ -402,7 +402,7 @@ export class MysteryEncounterBattlePhase extends Phase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const availablePartyMembers = scene.getParty().filter(p => !p.isFainted());
|
const availablePartyMembers = scene.getParty().filter(p => p.isAllowedInBattle());
|
||||||
|
|
||||||
if (!availablePartyMembers[0].isOnField()) {
|
if (!availablePartyMembers[0].isOnField()) {
|
||||||
scene.pushPhase(new SummonPhase(scene, 0));
|
scene.pushPhase(new SummonPhase(scene, 0));
|
||||||
|
@ -8,26 +8,26 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
private statusEffect?: StatusEffect | undefined;
|
private statusEffect?: StatusEffect;
|
||||||
private cureTurn?: integer | null;
|
private turnsRemaining?: number;
|
||||||
private sourceText?: string | null;
|
private sourceText?: string | null;
|
||||||
private sourcePokemon?: Pokemon | null;
|
private sourcePokemon?: Pokemon | null;
|
||||||
|
|
||||||
constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string | null, sourcePokemon?: Pokemon | null) {
|
constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, turnsRemaining?: number, sourceText?: string | null, sourcePokemon?: Pokemon | null) {
|
||||||
super(scene, battlerIndex);
|
super(scene, battlerIndex);
|
||||||
|
|
||||||
this.statusEffect = statusEffect;
|
this.statusEffect = statusEffect;
|
||||||
this.cureTurn = cureTurn;
|
this.turnsRemaining = turnsRemaining;
|
||||||
this.sourceText = sourceText;
|
this.sourceText = sourceText;
|
||||||
this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect
|
this.sourcePokemon = sourcePokemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
if (pokemon && !pokemon.status) {
|
if (pokemon && !pokemon.status) {
|
||||||
if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
||||||
if (this.cureTurn) {
|
if (this.turnsRemaining) {
|
||||||
pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct?
|
pokemon.status!.sleepTurnsRemaining = this.turnsRemaining;
|
||||||
}
|
}
|
||||||
pokemon.updateInfo(true);
|
pokemon.updateInfo(true);
|
||||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
||||||
|
@ -18,7 +18,7 @@ export class PostSummonPhase extends PokemonPhase {
|
|||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
|
|
||||||
if (pokemon.status?.effect === StatusEffect.TOXIC) {
|
if (pokemon.status?.effect === StatusEffect.TOXIC) {
|
||||||
pokemon.status.turnCount = 0;
|
pokemon.status.toxicTurnCount = 0;
|
||||||
}
|
}
|
||||||
this.scene.arena.applyTags(ArenaTrapTag, false, pokemon);
|
this.scene.arena.applyTags(ArenaTrapTag, false, pokemon);
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
damage.value = Math.max(pokemon.getMaxHp() >> 3, 1);
|
damage.value = Math.max(pokemon.getMaxHp() >> 3, 1);
|
||||||
break;
|
break;
|
||||||
case StatusEffect.TOXIC:
|
case StatusEffect.TOXIC:
|
||||||
damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1);
|
damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.toxicTurnCount), 1);
|
||||||
break;
|
break;
|
||||||
case StatusEffect.BURN:
|
case StatusEffect.BURN:
|
||||||
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
|
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
|
||||||
|
@ -64,7 +64,8 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (!this.selfTarget && stages.value < 0) {
|
if (!this.selfTarget && stages.value < 0) {
|
||||||
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, cancelled);
|
// TODO: add a reference to the source of the stat change to fix Infiltrator interaction
|
||||||
|
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, null, false, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
|
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
|
||||||
|
@ -12,7 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags";
|
|||||||
import { Biome } from "#enums/biome";
|
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 { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
|
|
||||||
export default class PokemonData {
|
export default class PokemonData {
|
||||||
public id: integer;
|
public id: integer;
|
||||||
@ -33,7 +33,6 @@ export default class PokemonData {
|
|||||||
public stats: integer[];
|
public stats: integer[];
|
||||||
public ivs: integer[];
|
public ivs: integer[];
|
||||||
public nature: Nature;
|
public nature: Nature;
|
||||||
public natureOverride: Nature | -1;
|
|
||||||
public moveset: (PokemonMove | null)[];
|
public moveset: (PokemonMove | null)[];
|
||||||
public status: Status | null;
|
public status: Status | null;
|
||||||
public friendship: integer;
|
public friendship: integer;
|
||||||
@ -54,14 +53,20 @@ export default class PokemonData {
|
|||||||
public fusionVariant: Variant;
|
public fusionVariant: Variant;
|
||||||
public fusionGender: Gender;
|
public fusionGender: Gender;
|
||||||
public fusionLuck: integer;
|
public fusionLuck: integer;
|
||||||
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData;
|
|
||||||
|
|
||||||
public boss: boolean;
|
public boss: boolean;
|
||||||
public bossSegments?: integer;
|
public bossSegments?: integer;
|
||||||
|
|
||||||
public summonData: PokemonSummonData;
|
public summonData: PokemonSummonData;
|
||||||
|
|
||||||
/** Data that can customize a Pokemon in non-standard ways from its Species */
|
/** Data that can customize a Pokemon in non-standard ways from its Species */
|
||||||
public mysteryEncounterPokemonData: MysteryEncounterPokemonData;
|
public customPokemonData: CustomPokemonData;
|
||||||
|
public fusionCustomPokemonData: CustomPokemonData;
|
||||||
|
|
||||||
|
// Deprecated attributes, needed for now to allow SessionData migration (see PR#4619 comments)
|
||||||
|
public natureOverride: Nature | -1;
|
||||||
|
public mysteryEncounterPokemonData: CustomPokemonData | null;
|
||||||
|
public fusionMysteryEncounterPokemonData: CustomPokemonData | null;
|
||||||
|
|
||||||
constructor(source: Pokemon | any, forHistory: boolean = false) {
|
constructor(source: Pokemon | any, forHistory: boolean = false) {
|
||||||
const sourcePokemon = source instanceof Pokemon ? source : null;
|
const sourcePokemon = source instanceof Pokemon ? source : null;
|
||||||
@ -107,9 +112,13 @@ export default class PokemonData {
|
|||||||
this.fusionVariant = source.fusionVariant;
|
this.fusionVariant = source.fusionVariant;
|
||||||
this.fusionGender = source.fusionGender;
|
this.fusionGender = source.fusionGender;
|
||||||
this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0);
|
this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0);
|
||||||
|
this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData);
|
||||||
this.usedTMs = source.usedTMs ?? [];
|
this.usedTMs = source.usedTMs ?? [];
|
||||||
|
|
||||||
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(source.mysteryEncounterPokemonData);
|
this.customPokemonData = new CustomPokemonData(source.customPokemonData);
|
||||||
|
|
||||||
|
this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData);
|
||||||
|
this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData);
|
||||||
|
|
||||||
if (!forHistory) {
|
if (!forHistory) {
|
||||||
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
|
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
|
||||||
@ -128,7 +137,7 @@ export default class PokemonData {
|
|||||||
this.moveset = (source.moveset || [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL) ]).filter(m => m).map((m: any) => new PokemonMove(m.moveId, m.ppUsed, m.ppUp));
|
this.moveset = (source.moveset || [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL) ]).filter(m => m).map((m: any) => new PokemonMove(m.moveId, m.ppUsed, m.ppUp));
|
||||||
if (!forHistory) {
|
if (!forHistory) {
|
||||||
this.status = source.status
|
this.status = source.status
|
||||||
? new Status(source.status.effect, source.status.turnCount, source.status.cureTurn)
|
? new Status(source.status.effect, source.status.toxicTurnCount, source.status.sleepTurnsRemaining)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,9 @@ import { version } from "../../../package.json";
|
|||||||
// --- v1.0.4 (and below) PATCHES --- //
|
// --- v1.0.4 (and below) PATCHES --- //
|
||||||
import * as v1_0_4 from "./versions/v1_0_4";
|
import * as v1_0_4 from "./versions/v1_0_4";
|
||||||
|
|
||||||
|
// --- v1.1.0 PATCHES --- //
|
||||||
|
import * as v1_1_0 from "./versions/v1_1_0";
|
||||||
|
|
||||||
const LATEST_VERSION = version.split(".").map(value => parseInt(value));
|
const LATEST_VERSION = version.split(".").map(value => parseInt(value));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,6 +134,10 @@ class SessionVersionConverter extends VersionConverter {
|
|||||||
this.callMigrators(data, v1_0_4.sessionMigrators);
|
this.callMigrators(data, v1_0_4.sessionMigrators);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (curMinor <= 1) {
|
||||||
|
console.log("Applying v1.1.0 session data migration!");
|
||||||
|
this.callMigrators(data, v1_1_0.sessionMigrators);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Session data successfully migrated to v${version}!`);
|
console.log(`Session data successfully migrated to v${version}!`);
|
||||||
@ -153,6 +160,10 @@ class SystemVersionConverter extends VersionConverter {
|
|||||||
this.callMigrators(data, v1_0_4.systemMigrators);
|
this.callMigrators(data, v1_0_4.systemMigrators);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (curMinor <= 1) {
|
||||||
|
console.log("Applying v1.1.0 system data migraton!");
|
||||||
|
this.callMigrators(data, v1_1_0.systemMigrators);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`System data successfully migrated to v${version}!`);
|
console.log(`System data successfully migrated to v${version}!`);
|
||||||
@ -175,6 +186,10 @@ class SettingsVersionConverter extends VersionConverter {
|
|||||||
this.callMigrators(data, v1_0_4.settingsMigrators);
|
this.callMigrators(data, v1_0_4.settingsMigrators);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (curMinor <= 1) {
|
||||||
|
console.log("Applying v1.1.0 settings data migraton!");
|
||||||
|
this.callMigrators(data, v1_1_0.settingsMigrators);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`System data successfully migrated to v${version}!`);
|
console.log(`System data successfully migrated to v${version}!`);
|
||||||
|
32
src/system/version_migration/versions/v1_1_0.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { SessionSaveData } from "../../game-data";
|
||||||
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
|
|
||||||
|
export const systemMigrators = [] as const;
|
||||||
|
|
||||||
|
export const settingsMigrators = [] as const;
|
||||||
|
|
||||||
|
export const sessionMigrators = [
|
||||||
|
/**
|
||||||
|
* Converts old Pokemon natureOverride and mysteryEncounterData
|
||||||
|
* to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead.
|
||||||
|
* @param data {@linkcode SessionSaveData}
|
||||||
|
*/
|
||||||
|
function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) {
|
||||||
|
// Fix Pokemon nature overrides and custom data migration
|
||||||
|
data.party.forEach(pokemon => {
|
||||||
|
if (pokemon["mysteryEncounterPokemonData"]) {
|
||||||
|
pokemon.customPokemonData = new CustomPokemonData(pokemon["mysteryEncounterPokemonData"]);
|
||||||
|
pokemon["mysteryEncounterPokemonData"] = null;
|
||||||
|
}
|
||||||
|
if (pokemon["fusionMysteryEncounterPokemonData"]) {
|
||||||
|
pokemon.fusionCustomPokemonData = new CustomPokemonData(pokemon["fusionMysteryEncounterPokemonData"]);
|
||||||
|
pokemon["fusionMysteryEncounterPokemonData"] = null;
|
||||||
|
}
|
||||||
|
pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData();
|
||||||
|
if (pokemon["natureOverride"] && pokemon["natureOverride"] >= 0) {
|
||||||
|
pokemon.customPokemonData.nature = pokemon["natureOverride"];
|
||||||
|
pokemon["natureOverride"] = -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
] as const;
|
93
src/test/abilities/early_bird.test.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { Status } from "#app/data/status-effect";
|
||||||
|
import { MoveResult } from "#app/field/pokemon";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Early Bird", () => {
|
||||||
|
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.REST, Moves.BELLY_DRUM, Moves.SPLASH ])
|
||||||
|
.ability(Abilities.EARLY_BIRD)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reduces Rest's sleep time to 1 turn", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.BELLY_DRUM);
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.move.select(Moves.REST);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status?.effect).toBeUndefined();
|
||||||
|
expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reduces 3-turn sleep to 1 turn", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
player.status = new Status(StatusEffect.SLEEP, 0, 4);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status?.effect).toBeUndefined();
|
||||||
|
expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reduces 1-turn sleep to 0 turns", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
player.status = new Status(StatusEffect.SLEEP, 0, 2);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status?.effect).toBeUndefined();
|
||||||
|
expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS);
|
||||||
|
});
|
||||||
|
});
|
107
src/test/abilities/infiltrator.test.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
|
import { allMoves } from "#app/data/move";
|
||||||
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
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 - Infiltrator", () => {
|
||||||
|
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.TACKLE, Moves.WATER_GUN, Moves.SPORE, Moves.BABY_DOLL_EYES ])
|
||||||
|
.ability(Abilities.INFILTRATOR)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.SNORLAX)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemyLevel(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ effectName: "Light Screen", tagType: ArenaTagType.LIGHT_SCREEN, move: Moves.WATER_GUN },
|
||||||
|
{ effectName: "Reflect", tagType: ArenaTagType.REFLECT, move: Moves.TACKLE },
|
||||||
|
{ effectName: "Aurora Veil", tagType: ArenaTagType.AURORA_VEIL, move: Moves.TACKLE }
|
||||||
|
])("should bypass the target's $effectName", async ({ tagType, move }) => {
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
const preScreenDmg = enemy.getAttackDamage(player, allMoves[move]).damage;
|
||||||
|
|
||||||
|
game.scene.arena.addTag(tagType, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true);
|
||||||
|
|
||||||
|
const postScreenDmg = enemy.getAttackDamage(player, allMoves[move]).damage;
|
||||||
|
|
||||||
|
expect(postScreenDmg).toBe(preScreenDmg);
|
||||||
|
expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should bypass the target's Safeguard", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.scene.arena.addTag(ArenaTagType.SAFEGUARD, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPORE);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: fix this interaction to pass this test
|
||||||
|
it.skip("should bypass the target's Mist", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.scene.arena.addTag(ArenaTagType.MIST, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true);
|
||||||
|
|
||||||
|
game.move.select(Moves.BABY_DOLL_EYES);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(enemy.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should bypass the target's Substitute", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
enemy.addTag(BattlerTagType.SUBSTITUTE, 1, Moves.NONE, enemy.id);
|
||||||
|
|
||||||
|
game.move.select(Moves.BABY_DOLL_EYES);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(enemy.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR);
|
||||||
|
});
|
||||||
|
});
|
@ -150,7 +150,7 @@ describe("Abilities - Magic Guard", () => {
|
|||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
const toxicStartCounter = enemyPokemon.status!.turnCount;
|
const toxicStartCounter = enemyPokemon.status!.toxicTurnCount;
|
||||||
//should be 0
|
//should be 0
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
@ -162,7 +162,7 @@ describe("Abilities - Magic Guard", () => {
|
|||||||
* - The enemy Pokemon's hypothetical CatchRateMultiplier should be 1.5
|
* - The enemy Pokemon's hypothetical CatchRateMultiplier should be 1.5
|
||||||
*/
|
*/
|
||||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||||
expect(enemyPokemon.status!.turnCount).toBeGreaterThan(toxicStartCounter);
|
expect(enemyPokemon.status!.toxicTurnCount).toBeGreaterThan(toxicStartCounter);
|
||||||
expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status!.effect)).toBe(1.5);
|
expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status!.effect)).toBe(1.5);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Status,
|
||||||
StatusEffect,
|
StatusEffect,
|
||||||
getStatusEffectActivationText,
|
getStatusEffectActivationText,
|
||||||
getStatusEffectDescriptor,
|
getStatusEffectDescriptor,
|
||||||
@ -6,14 +7,19 @@ import {
|
|||||||
getStatusEffectObtainText,
|
getStatusEffectObtainText,
|
||||||
getStatusEffectOverlapText,
|
getStatusEffectOverlapText,
|
||||||
} from "#app/data/status-effect";
|
} from "#app/data/status-effect";
|
||||||
|
import { MoveResult } from "#app/field/pokemon";
|
||||||
|
import GameManager from "#app/test/utils/gameManager";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
import { mockI18next } from "#test/utils/testUtils";
|
import { mockI18next } from "#test/utils/testUtils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const pokemonName = "PKM";
|
const pokemonName = "PKM";
|
||||||
const sourceText = "SOURCE";
|
const sourceText = "SOURCE";
|
||||||
|
|
||||||
describe("status-effect", () => {
|
describe("Status Effect Messages", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
i18next.init();
|
i18next.init();
|
||||||
});
|
});
|
||||||
@ -299,3 +305,59 @@ describe("status-effect", () => {
|
|||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Status Effects - Sleep", () => {
|
||||||
|
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 ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should last the appropriate number of turns", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
player.status = new Status(StatusEffect.SLEEP, 0, 4);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player.status?.effect).toBeUndefined();
|
||||||
|
expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -7,8 +7,6 @@ import GameManager from "#test/utils/gameManager";
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const TIMEOUT = 20 * 1000;
|
|
||||||
|
|
||||||
describe("Items - Toxic orb", () => {
|
describe("Items - Toxic orb", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
let game: GameManager;
|
let game: GameManager;
|
||||||
@ -27,10 +25,10 @@ describe("Items - Toxic orb", () => {
|
|||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.battleType("single")
|
.battleType("single")
|
||||||
.enemySpecies(Species.RATTATA)
|
.enemySpecies(Species.MAGIKARP)
|
||||||
.ability(Abilities.BALL_FETCH)
|
.ability(Abilities.BALL_FETCH)
|
||||||
.enemyAbility(Abilities.BALL_FETCH)
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
.moveset([ Moves.SPLASH ])
|
.moveset(Moves.SPLASH)
|
||||||
.enemyMoveset(Moves.SPLASH)
|
.enemyMoveset(Moves.SPLASH)
|
||||||
.startingHeldItems([{
|
.startingHeldItems([{
|
||||||
name: "TOXIC_ORB",
|
name: "TOXIC_ORB",
|
||||||
@ -39,22 +37,19 @@ describe("Items - Toxic orb", () => {
|
|||||||
vi.spyOn(i18next, "t");
|
vi.spyOn(i18next, "t");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("badly poisons the holder", async () => {
|
it("should badly poison the holder", async () => {
|
||||||
await game.classicMode.startBattle([ Species.MIGHTYENA ]);
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
const player = game.scene.getPlayerField()[0];
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
expect(player.getHeldItems()[0].type.id).toBe("TOXIC_ORB");
|
||||||
|
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
// Toxic orb should trigger here
|
await game.phaseInterceptor.to("MessagePhase");
|
||||||
await game.phaseInterceptor.run("MessagePhase");
|
|
||||||
expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything());
|
expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything());
|
||||||
|
|
||||||
await game.toNextTurn();
|
|
||||||
|
|
||||||
expect(player.status?.effect).toBe(StatusEffect.TOXIC);
|
expect(player.status?.effect).toBe(StatusEffect.TOXIC);
|
||||||
// Damage should not have ticked yet.
|
expect(player.status?.toxicTurnCount).toBe(0);
|
||||||
expect(player.status?.turnCount).toBe(0);
|
});
|
||||||
}, TIMEOUT);
|
|
||||||
});
|
});
|
||||||
|
@ -111,7 +111,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
|||||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
|
|
||||||
if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) {
|
if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) {
|
||||||
defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, move.category, multiplierHolder);
|
defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, attacker, move.category, multiplierHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return move.power * multiplierHolder.value;
|
return move.power * multiplierHolder.value;
|
||||||
|
@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
|||||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
|
|
||||||
if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) {
|
if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) {
|
||||||
defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, move.category, multiplierHolder);
|
defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, attacker, move.category, multiplierHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return move.power * multiplierHolder.value;
|
return move.power * multiplierHolder.value;
|
||||||
|
52
src/test/moves/nightmare.test.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
|
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("Moves - Nightmare", () => {
|
||||||
|
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.battleType("single")
|
||||||
|
.enemySpecies(Species.RATTATA)
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyStatusEffect(StatusEffect.SLEEP)
|
||||||
|
.startingLevel(5)
|
||||||
|
.moveset([ Moves.NIGHTMARE, Moves.SPLASH ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("lowers enemy hp by 1/4 each turn while asleep", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.HYPNO ]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
const enemyMaxHP = enemyPokemon.hp;
|
||||||
|
|
||||||
|
game.move.select(Moves.NIGHTMARE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemyPokemon.hp).toBe(enemyMaxHP - Math.floor(enemyMaxHP / 4));
|
||||||
|
|
||||||
|
// take a second turn to make sure damage occurs again
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemyPokemon.hp).toBe(enemyMaxHP - Math.floor(enemyMaxHP / 4) - Math.floor(enemyMaxHP / 4));
|
||||||
|
});
|
||||||
|
});
|
@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
|||||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
|
|
||||||
if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) {
|
if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) {
|
||||||
defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, move.category, multiplierHolder);
|
defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return move.power * multiplierHolder.value;
|
return move.power * multiplierHolder.value;
|
||||||
|
@ -9,8 +9,6 @@ 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 } from "vitest";
|
||||||
|
|
||||||
const TIMEOUT = 20 * 1000;
|
|
||||||
|
|
||||||
describe("Moves - Toxic Spikes", () => {
|
describe("Moves - Toxic Spikes", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
let game: GameManager;
|
let game: GameManager;
|
||||||
@ -34,10 +32,10 @@ describe("Moves - Toxic Spikes", () => {
|
|||||||
.enemyAbility(Abilities.BALL_FETCH)
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
.ability(Abilities.BALL_FETCH)
|
.ability(Abilities.BALL_FETCH)
|
||||||
.enemyMoveset(Moves.SPLASH)
|
.enemyMoveset(Moves.SPLASH)
|
||||||
.moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR ]);
|
.moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR, Moves.COURT_CHANGE ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not affect the opponent if they do not switch", async() => {
|
it("should not affect the opponent if they do not switch", async () => {
|
||||||
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
|
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
|
||||||
|
|
||||||
const enemy = game.scene.getEnemyField()[0];
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
@ -51,9 +49,9 @@ describe("Moves - Toxic Spikes", () => {
|
|||||||
|
|
||||||
expect(enemy.hp).toBe(enemy.getMaxHp());
|
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||||
expect(enemy.status?.effect).toBeUndefined();
|
expect(enemy.status?.effect).toBeUndefined();
|
||||||
}, TIMEOUT);
|
});
|
||||||
|
|
||||||
it("should poison the opponent if they switch into 1 layer", async() => {
|
it("should poison the opponent if they switch into 1 layer", async () => {
|
||||||
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||||
|
|
||||||
game.move.select(Moves.TOXIC_SPIKES);
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
@ -65,9 +63,9 @@ describe("Moves - Toxic Spikes", () => {
|
|||||||
|
|
||||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
expect(enemy.status?.effect).toBe(StatusEffect.POISON);
|
expect(enemy.status?.effect).toBe(StatusEffect.POISON);
|
||||||
}, TIMEOUT);
|
});
|
||||||
|
|
||||||
it("should badly poison the opponent if they switch into 2 layers", async() => {
|
it("should badly poison the opponent if they switch into 2 layers", async () => {
|
||||||
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||||
|
|
||||||
game.move.select(Moves.TOXIC_SPIKES);
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
@ -80,27 +78,32 @@ describe("Moves - Toxic Spikes", () => {
|
|||||||
const enemy = game.scene.getEnemyField()[0];
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
expect(enemy.status?.effect).toBe(StatusEffect.TOXIC);
|
expect(enemy.status?.effect).toBe(StatusEffect.TOXIC);
|
||||||
}, TIMEOUT);
|
});
|
||||||
|
|
||||||
it("should be removed if a grounded poison pokemon switches in", async() => {
|
it("should be removed if a grounded poison pokemon switches in", async () => {
|
||||||
game.override.enemySpecies(Species.GRIMER);
|
await game.classicMode.runToSummon([ Species.MUK, Species.PIDGEY ]);
|
||||||
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
|
||||||
|
const muk = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
game.move.select(Moves.TOXIC_SPIKES);
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.toNextTurn();
|
||||||
game.move.select(Moves.TOXIC_SPIKES);
|
// also make sure the toxic spikes are removed even if the pokemon
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
// that set them up is the one switching in (https://github.com/pagefaultgames/pokerogue/issues/935)
|
||||||
game.move.select(Moves.ROAR);
|
game.move.select(Moves.COURT_CHANGE);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.toNextTurn();
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
const enemy = game.scene.getEnemyField()[0];
|
await game.toNextTurn();
|
||||||
expect(enemy.hp).toBe(enemy.getMaxHp());
|
game.doSwitchPokemon(1);
|
||||||
expect(enemy.status?.effect).toBeUndefined();
|
await game.toNextTurn();
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(muk.isFullHp()).toBe(true);
|
||||||
|
expect(muk.status?.effect).toBeUndefined();
|
||||||
expect(game.scene.arena.tags.length).toBe(0);
|
expect(game.scene.arena.tags.length).toBe(0);
|
||||||
}, TIMEOUT);
|
});
|
||||||
|
|
||||||
it("shouldn't create multiple layers per use in doubles", async() => {
|
it("shouldn't create multiple layers per use in doubles", async () => {
|
||||||
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
|
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
|
||||||
|
|
||||||
game.move.select(Moves.TOXIC_SPIKES);
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
@ -109,9 +112,9 @@ describe("Moves - Toxic Spikes", () => {
|
|||||||
const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag);
|
const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag);
|
||||||
expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES);
|
expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES);
|
||||||
expect(arenaTags.layers).toBe(1);
|
expect(arenaTags.layers).toBe(1);
|
||||||
}, TIMEOUT);
|
});
|
||||||
|
|
||||||
it("should persist through reload", async() => {
|
it("should persist through reload", async () => {
|
||||||
game.override.startingWave(1);
|
game.override.startingWave(1);
|
||||||
const scene = game.scene;
|
const scene = game.scene;
|
||||||
const gameData = new GameData(scene);
|
const gameData = new GameData(scene);
|
||||||
@ -132,5 +135,5 @@ describe("Moves - Toxic Spikes", () => {
|
|||||||
|
|
||||||
expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags);
|
expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags);
|
||||||
localStorage.removeItem("sessionTestData");
|
localStorage.removeItem("sessionTestData");
|
||||||
}, TIMEOUT);
|
});
|
||||||
});
|
});
|
||||||
|
53
src/test/moves/will_o_wisp.test.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Will-O-Wisp", () => {
|
||||||
|
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.WILL_O_WISP, Moves.SPLASH ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should burn the opponent", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.WILL_O_WISP);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.move.forceHit();
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
});
|
||||||
|
});
|
@ -476,10 +476,11 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
|
|||||||
|
|
||||||
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||||
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||||
expect(modifierSelectHandler.options.length).toEqual(3);
|
expect(modifierSelectHandler.options.length).toEqual(4);
|
||||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MASTER_BALL");
|
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MASTER_BALL");
|
||||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE");
|
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MEGA_BRACELET");
|
||||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("FORM_CHANGE_ITEM");
|
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("DYNAMAX_BAND");
|
||||||
|
expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toBe("FORM_CHANGE_ITEM");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should leave encounter without battle", async () => {
|
it("should leave encounter without battle", async () => {
|
||||||
|
@ -118,11 +118,11 @@ describe("Clowning Around - Mystery Encounter", () => {
|
|||||||
});
|
});
|
||||||
expect(config.pokemonConfigs?.[1]).toEqual({
|
expect(config.pokemonConfigs?.[1]).toEqual({
|
||||||
species: getPokemonSpecies(Species.BLACEPHALON),
|
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||||
mysteryEncounterPokemonData: expect.anything(),
|
customPokemonData: expect.anything(),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ]
|
moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ]
|
||||||
});
|
});
|
||||||
expect(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.types.length).toBe(2);
|
expect(config.pokemonConfigs?.[1].customPokemonData?.types.length).toBe(2);
|
||||||
expect([
|
expect([
|
||||||
Abilities.STURDY,
|
Abilities.STURDY,
|
||||||
Abilities.PICKUP,
|
Abilities.PICKUP,
|
||||||
@ -139,8 +139,8 @@ describe("Clowning Around - Mystery Encounter", () => {
|
|||||||
Abilities.MAGICIAN,
|
Abilities.MAGICIAN,
|
||||||
Abilities.SHEER_FORCE,
|
Abilities.SHEER_FORCE,
|
||||||
Abilities.PRANKSTER
|
Abilities.PRANKSTER
|
||||||
]).toContain(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.ability);
|
]).toContain(config.pokemonConfigs?.[1].customPokemonData?.ability);
|
||||||
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.ability);
|
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].customPokemonData?.ability);
|
||||||
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
|
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
|
||||||
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
|
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
|
||||||
expect(onInitResult).toBe(true);
|
expect(onInitResult).toBe(true);
|
||||||
@ -219,7 +219,7 @@ describe("Clowning Around - Mystery Encounter", () => {
|
|||||||
await game.phaseInterceptor.to(NewBattlePhase, false);
|
await game.phaseInterceptor.to(NewBattlePhase, false);
|
||||||
|
|
||||||
const leadPokemon = scene.getParty()[0];
|
const leadPokemon = scene.getParty()[0];
|
||||||
expect(leadPokemon.mysteryEncounterPokemonData?.ability).toBe(abilityToTrain);
|
expect(leadPokemon.customPokemonData?.ability).toBe(abilityToTrain);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -340,9 +340,9 @@ describe("Clowning Around - Mystery Encounter", () => {
|
|||||||
scene.getParty()[2].moveset = [];
|
scene.getParty()[2].moveset = [];
|
||||||
await runMysteryEncounterToEnd(game, 3);
|
await runMysteryEncounterToEnd(game, 3);
|
||||||
|
|
||||||
const leadTypesAfter = scene.getParty()[0].mysteryEncounterPokemonData?.types;
|
const leadTypesAfter = scene.getParty()[0].customPokemonData?.types;
|
||||||
const secondaryTypesAfter = scene.getParty()[1].mysteryEncounterPokemonData?.types;
|
const secondaryTypesAfter = scene.getParty()[1].customPokemonData?.types;
|
||||||
const thirdTypesAfter = scene.getParty()[2].mysteryEncounterPokemonData?.types;
|
const thirdTypesAfter = scene.getParty()[2].customPokemonData?.types;
|
||||||
|
|
||||||
expect(leadTypesAfter.length).toBe(2);
|
expect(leadTypesAfter.length).toBe(2);
|
||||||
expect(leadTypesAfter[0]).toBe(Type.WATER);
|
expect(leadTypesAfter[0]).toBe(Type.WATER);
|
||||||
|
@ -21,7 +21,7 @@ import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modif
|
|||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { MovePhase } from "#app/phases/move-phase";
|
import { MovePhase } from "#app/phases/move-phase";
|
||||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||||
@ -109,7 +109,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
|||||||
species: getPokemonSpecies(Species.SHUCKLE),
|
species: getPokemonSpecies(Species.SHUCKLE),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 5,
|
bossSegments: 5,
|
||||||
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ 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 ],
|
||||||
modifierConfigs: expect.any(Array),
|
modifierConfigs: expect.any(Array),
|
||||||
|
@ -122,7 +122,7 @@ describe("Weird Dream - Mystery Encounter", () => {
|
|||||||
for (let i = 0; i < pokemonAfter.length; i++) {
|
for (let i = 0; i < pokemonAfter.length; i++) {
|
||||||
const newPokemon = pokemonAfter[i];
|
const newPokemon = pokemonAfter[i];
|
||||||
expect(newPokemon.getSpeciesForm().speciesId).not.toBe(pokemonPrior[i].getSpeciesForm().speciesId);
|
expect(newPokemon.getSpeciesForm().speciesId).not.toBe(pokemonPrior[i].getSpeciesForm().speciesId);
|
||||||
expect(newPokemon.mysteryEncounterPokemonData?.types.length).toBe(2);
|
expect(newPokemon.customPokemonData?.types.length).toBe(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const plus90To110 = bstDiff.filter(bst => bst > 80);
|
const plus90To110 = bstDiff.filter(bst => bst > 80);
|
||||||
|
@ -38,6 +38,10 @@ export class ChallengeModeHelper extends GameManagerHelper {
|
|||||||
async runToSummon(species?: Species[]) {
|
async runToSummon(species?: Species[]) {
|
||||||
await this.game.runToTitle();
|
await this.game.runToTitle();
|
||||||
|
|
||||||
|
if (this.game.override.disableShinies) {
|
||||||
|
this.game.override.shiny(false).enemyShiny(false);
|
||||||
|
}
|
||||||
|
|
||||||
this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||||
this.game.scene.gameMode.challenges = this.challenges;
|
this.game.scene.gameMode.challenges = this.challenges;
|
||||||
const starters = generateStarter(this.game.scene, species);
|
const starters = generateStarter(this.game.scene, species);
|
||||||
@ -47,7 +51,7 @@ export class ChallengeModeHelper extends GameManagerHelper {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.game.phaseInterceptor.run(EncounterPhase);
|
await this.game.phaseInterceptor.run(EncounterPhase);
|
||||||
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) {
|
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||||
this.game.removeEnemyHeldItems();
|
this.game.removeEnemyHeldItems();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,13 @@ export class ClassicModeHelper extends GameManagerHelper {
|
|||||||
* @param species - Optional array of species to summon.
|
* @param species - Optional array of species to summon.
|
||||||
* @returns A promise that resolves when the summon phase is reached.
|
* @returns A promise that resolves when the summon phase is reached.
|
||||||
*/
|
*/
|
||||||
async runToSummon(species?: Species[]) {
|
async runToSummon(species?: Species[]): Promise<void> {
|
||||||
await this.game.runToTitle();
|
await this.game.runToTitle();
|
||||||
|
|
||||||
|
if (this.game.override.disableShinies) {
|
||||||
|
this.game.override.shiny(false).enemyShiny(false);
|
||||||
|
}
|
||||||
|
|
||||||
this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||||
this.game.scene.gameMode = getGameMode(GameModes.CLASSIC);
|
this.game.scene.gameMode = getGameMode(GameModes.CLASSIC);
|
||||||
const starters = generateStarter(this.game.scene, species);
|
const starters = generateStarter(this.game.scene, species);
|
||||||
@ -32,7 +36,7 @@ export class ClassicModeHelper extends GameManagerHelper {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.game.phaseInterceptor.run(EncounterPhase);
|
await this.game.phaseInterceptor.run(EncounterPhase);
|
||||||
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) {
|
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||||
this.game.removeEnemyHeldItems();
|
this.game.removeEnemyHeldItems();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +46,7 @@ export class ClassicModeHelper extends GameManagerHelper {
|
|||||||
* @param species - Optional array of species to start the battle with.
|
* @param species - Optional array of species to start the battle with.
|
||||||
* @returns A promise that resolves when the battle is started.
|
* @returns A promise that resolves when the battle is started.
|
||||||
*/
|
*/
|
||||||
async startBattle(species?: Species[]) {
|
async startBattle(species?: Species[]): Promise<void> {
|
||||||
await this.runToSummon(species);
|
await this.runToSummon(species);
|
||||||
|
|
||||||
if (this.game.scene.battleStyle === BattleStyle.SWITCH) {
|
if (this.game.scene.battleStyle === BattleStyle.SWITCH) {
|
||||||
|
@ -21,6 +21,10 @@ export class DailyModeHelper extends GameManagerHelper {
|
|||||||
async runToSummon() {
|
async runToSummon() {
|
||||||
await this.game.runToTitle();
|
await this.game.runToTitle();
|
||||||
|
|
||||||
|
if (this.game.override.disableShinies) {
|
||||||
|
this.game.override.shiny(false).enemyShiny(false);
|
||||||
|
}
|
||||||
|
|
||||||
this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||||
const titlePhase = new TitlePhase(this.game.scene);
|
const titlePhase = new TitlePhase(this.game.scene);
|
||||||
titlePhase.initDailyRun();
|
titlePhase.initDailyRun();
|
||||||
@ -33,7 +37,7 @@ export class DailyModeHelper extends GameManagerHelper {
|
|||||||
|
|
||||||
await this.game.phaseInterceptor.to(EncounterPhase);
|
await this.game.phaseInterceptor.to(EncounterPhase);
|
||||||
|
|
||||||
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) {
|
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) {
|
||||||
this.game.removeEnemyHeldItems();
|
this.game.removeEnemyHeldItems();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
|||||||
* Helper to handle overrides in tests
|
* Helper to handle overrides in tests
|
||||||
*/
|
*/
|
||||||
export class OverridesHelper extends GameManagerHelper {
|
export class OverridesHelper extends GameManagerHelper {
|
||||||
|
/** If `true`, removes the starting items from enemies at the start of each test; default `true` */
|
||||||
|
public removeEnemyStartingItems: boolean = true;
|
||||||
|
/** If `true`, sets the shiny overrides to disable shinies at the start of each test; default `true` */
|
||||||
|
public disableShinies: boolean = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the starting biome
|
* Override the starting biome
|
||||||
* @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line
|
* @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line
|
||||||
@ -368,23 +373,50 @@ export class OverridesHelper extends GameManagerHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Override player shininess
|
* Override player shininess
|
||||||
* @param shininess Whether the player's Pokemon should be shiny.
|
* @param shininess - `true` or `false` to force the player's pokemon to be shiny or not shiny,
|
||||||
|
* `null` to disable the override and re-enable RNG shinies.
|
||||||
*/
|
*/
|
||||||
shinyLevel(shininess: boolean): this {
|
shiny(shininess: boolean | null): this {
|
||||||
vi.spyOn(Overrides, "SHINY_OVERRIDE", "get").mockReturnValue(shininess);
|
vi.spyOn(Overrides, "SHINY_OVERRIDE", "get").mockReturnValue(shininess);
|
||||||
this.log(`Set player Pokemon as ${shininess ? "" : "not "}shiny!`);
|
if (shininess === null) {
|
||||||
|
this.log("Disabled player Pokemon shiny override!");
|
||||||
|
} else {
|
||||||
|
this.log(`Set player Pokemon to be ${shininess ? "" : "not "}shiny!`);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override player shiny variant
|
* Override player shiny variant
|
||||||
* @param variant The player's shiny variant.
|
* @param variant - The player's shiny variant.
|
||||||
*/
|
*/
|
||||||
variantLevel(variant: Variant): this {
|
shinyVariant(variant: Variant): this {
|
||||||
vi.spyOn(Overrides, "VARIANT_OVERRIDE", "get").mockReturnValue(variant);
|
vi.spyOn(Overrides, "VARIANT_OVERRIDE", "get").mockReturnValue(variant);
|
||||||
this.log(`Set player Pokemon's shiny variant to ${variant}!`);
|
this.log(`Set player Pokemon's shiny variant to ${variant}!`);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override enemy shininess
|
||||||
|
* @param shininess - `true` or `false` to force the enemy's pokemon to be shiny or not shiny,
|
||||||
|
* `null` to disable the override and re-enable RNG shinies.
|
||||||
|
* @param variant - (Optional) The enemy's shiny {@linkcode Variant}.
|
||||||
|
*/
|
||||||
|
enemyShiny(shininess: boolean | null, variant?: Variant): this {
|
||||||
|
vi.spyOn(Overrides, "OPP_SHINY_OVERRIDE", "get").mockReturnValue(shininess);
|
||||||
|
if (shininess === null) {
|
||||||
|
this.log("Disabled enemy Pokemon shiny override!");
|
||||||
|
} else {
|
||||||
|
this.log(`Set enemy Pokemon to be ${shininess ? "" : "not "}shiny!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant !== undefined) {
|
||||||
|
vi.spyOn(Overrides, "OPP_VARIANT_OVERRIDE", "get").mockReturnValue(variant);
|
||||||
|
this.log(`Set enemy shiny variant to be ${variant}!`);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the enemy (Pokemon) to have the given amount of health segments
|
* Override the enemy (Pokemon) to have the given amount of health segments
|
||||||
* @param healthSegments the number of segments to give
|
* @param healthSegments the number of segments to give
|
||||||
|
@ -17,6 +17,9 @@ import { IntegerHolder } from "./../utils";
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
|
|
||||||
export const SHOP_OPTIONS_ROW_LIMIT = 7;
|
export const SHOP_OPTIONS_ROW_LIMIT = 7;
|
||||||
|
const SINGLE_SHOP_ROW_YOFFSET = 12;
|
||||||
|
const DOUBLE_SHOP_ROW_YOFFSET = 24;
|
||||||
|
const OPTION_BUTTON_YPOSITION = -62;
|
||||||
|
|
||||||
export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
||||||
private modifierContainer: Phaser.GameObjects.Container;
|
private modifierContainer: Phaser.GameObjects.Container;
|
||||||
@ -68,7 +71,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||||||
this.checkButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:checkTeam")).width;
|
this.checkButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:checkTeam")).width;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, -64);
|
this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, OPTION_BUTTON_YPOSITION);
|
||||||
this.transferButtonContainer.setName("transfer-btn");
|
this.transferButtonContainer.setName("transfer-btn");
|
||||||
this.transferButtonContainer.setVisible(false);
|
this.transferButtonContainer.setVisible(false);
|
||||||
ui.add(this.transferButtonContainer);
|
ui.add(this.transferButtonContainer);
|
||||||
@ -78,7 +81,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||||||
transferButtonText.setOrigin(1, 0);
|
transferButtonText.setOrigin(1, 0);
|
||||||
this.transferButtonContainer.add(transferButtonText);
|
this.transferButtonContainer.add(transferButtonText);
|
||||||
|
|
||||||
this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, -64);
|
this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, OPTION_BUTTON_YPOSITION);
|
||||||
this.checkButtonContainer.setName("use-btn");
|
this.checkButtonContainer.setName("use-btn");
|
||||||
this.checkButtonContainer.setVisible(false);
|
this.checkButtonContainer.setVisible(false);
|
||||||
ui.add(this.checkButtonContainer);
|
ui.add(this.checkButtonContainer);
|
||||||
@ -88,7 +91,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||||||
checkButtonText.setOrigin(1, 0);
|
checkButtonText.setOrigin(1, 0);
|
||||||
this.checkButtonContainer.add(checkButtonText);
|
this.checkButtonContainer.add(checkButtonText);
|
||||||
|
|
||||||
this.rerollButtonContainer = this.scene.add.container(16, -64);
|
this.rerollButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION);
|
||||||
this.rerollButtonContainer.setName("reroll-brn");
|
this.rerollButtonContainer.setName("reroll-brn");
|
||||||
this.rerollButtonContainer.setVisible(false);
|
this.rerollButtonContainer.setVisible(false);
|
||||||
ui.add(this.rerollButtonContainer);
|
ui.add(this.rerollButtonContainer);
|
||||||
@ -104,7 +107,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||||||
this.rerollCostText.setPositionRelative(rerollButtonText, rerollButtonText.displayWidth + 5, 1);
|
this.rerollCostText.setPositionRelative(rerollButtonText, rerollButtonText.displayWidth + 5, 1);
|
||||||
this.rerollButtonContainer.add(this.rerollCostText);
|
this.rerollButtonContainer.add(this.rerollCostText);
|
||||||
|
|
||||||
this.lockRarityButtonContainer = this.scene.add.container(16, -64);
|
this.lockRarityButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION);
|
||||||
this.lockRarityButtonContainer.setVisible(false);
|
this.lockRarityButtonContainer.setVisible(false);
|
||||||
ui.add(this.lockRarityButtonContainer);
|
ui.add(this.lockRarityButtonContainer);
|
||||||
|
|
||||||
@ -191,7 +194,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||||||
const shopTypeOptions = !removeHealShop
|
const shopTypeOptions = !removeHealShop
|
||||||
? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value)
|
? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value)
|
||||||
: [];
|
: [];
|
||||||
const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24;
|
const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET;
|
||||||
|
|
||||||
for (let m = 0; m < typeOptions.length; m++) {
|
for (let m = 0; m < typeOptions.length; m++) {
|
||||||
const sliceWidth = (this.scene.game.canvas.width / 6) / (typeOptions.length + 2);
|
const sliceWidth = (this.scene.game.canvas.width / 6) / (typeOptions.length + 2);
|
||||||
@ -212,7 +215,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||||||
const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT;
|
const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT;
|
||||||
const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT);
|
const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT);
|
||||||
const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2);
|
const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2);
|
||||||
const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (40 - (28 * row - 1))), shopTypeOptions[m]);
|
const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (42 - (28 * row - 1))), shopTypeOptions[m]);
|
||||||
option.setScale(0.375);
|
option.setScale(0.375);
|
||||||
this.scene.add.existing(option);
|
this.scene.add.existing(option);
|
||||||
this.modifierContainer.add(option);
|
this.modifierContainer.add(option);
|
||||||
@ -456,16 +459,18 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||||||
if (this.rowCursor === 1 && options.length === 0) {
|
if (this.rowCursor === 1 && options.length === 0) {
|
||||||
// Continue button when no shop items
|
// Continue button when no shop items
|
||||||
this.cursorObj.setScale(1.25);
|
this.cursorObj.setScale(1.25);
|
||||||
this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22));
|
this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2));
|
||||||
ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription"));
|
ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription"));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2);
|
const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2);
|
||||||
if (this.rowCursor < 2) {
|
if (this.rowCursor < 2) {
|
||||||
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22));
|
// Cursor on free items
|
||||||
|
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2));
|
||||||
} else {
|
} else {
|
||||||
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-16 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))));
|
// Cursor on paying items
|
||||||
|
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-14 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))));
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = options[this.cursor].modifierTypeOption.type;
|
const type = options[this.cursor].modifierTypeOption.type;
|
||||||
@ -475,16 +480,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
|||||||
this.moveInfoOverlay.show(allMoves[type.moveId]);
|
this.moveInfoOverlay.show(allMoves[type.moveId]);
|
||||||
}
|
}
|
||||||
} else if (cursor === 0) {
|
} else if (cursor === 0) {
|
||||||
this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60);
|
this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? OPTION_BUTTON_YPOSITION - 8 : OPTION_BUTTON_YPOSITION + 4);
|
||||||
ui.showText(i18next.t("modifierSelectUiHandler:rerollDesc"));
|
ui.showText(i18next.t("modifierSelectUiHandler:rerollDesc"));
|
||||||
} else if (cursor === 1) {
|
} else if (cursor === 1) {
|
||||||
this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, -60);
|
this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4);
|
||||||
ui.showText(i18next.t("modifierSelectUiHandler:transferDesc"));
|
ui.showText(i18next.t("modifierSelectUiHandler:transferDesc"));
|
||||||
} else if (cursor === 2) {
|
} else if (cursor === 2) {
|
||||||
this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, -60);
|
this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4);
|
||||||
ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc"));
|
ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc"));
|
||||||
} else {
|
} else {
|
||||||
this.cursorObj.setPosition(6, -60);
|
this.cursorObj.setPosition(6, OPTION_BUTTON_YPOSITION + 4);
|
||||||
ui.showText(i18next.t("modifierSelectUiHandler:lockRaritiesDesc"));
|
ui.showText(i18next.t("modifierSelectUiHandler:lockRaritiesDesc"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|