mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-26 16:56:11 +00:00
Merge branch 'beta' into hebrew-pr
This commit is contained in:
commit
b817253053
14
global.d.ts
vendored
Normal file
14
global.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import type { SetupServerApi } from "msw/node";
|
||||
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
/**
|
||||
* Only used in testing.
|
||||
* Can technically be undefined/null but for ease of use we are going to assume it is always defined.
|
||||
* Used to load i18n files exclusively.
|
||||
*
|
||||
* To set up your own server in a test see `game_data.test.ts`
|
||||
*/
|
||||
var i18nServer: SetupServerApi;
|
||||
}
|
@ -399,13 +399,13 @@
|
||||
"x": 0,
|
||||
"y": 6,
|
||||
"w": 36,
|
||||
"h": 55
|
||||
"h": 54
|
||||
},
|
||||
"frame": {
|
||||
"x": 72,
|
||||
"y": 55,
|
||||
"w": 36,
|
||||
"h": 55
|
||||
"h": 54
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -420,13 +420,13 @@
|
||||
"x": 0,
|
||||
"y": 6,
|
||||
"w": 36,
|
||||
"h": 55
|
||||
"h": 54
|
||||
},
|
||||
"frame": {
|
||||
"x": 72,
|
||||
"y": 55,
|
||||
"w": 36,
|
||||
"h": 55
|
||||
"h": 54
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -399,13 +399,13 @@
|
||||
"x": 0,
|
||||
"y": 6,
|
||||
"w": 36,
|
||||
"h": 55
|
||||
"h": 54
|
||||
},
|
||||
"frame": {
|
||||
"x": 72,
|
||||
"y": 55,
|
||||
"w": 36,
|
||||
"h": 55
|
||||
"h": 54
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -420,13 +420,13 @@
|
||||
"x": 0,
|
||||
"y": 6,
|
||||
"w": 36,
|
||||
"h": 55
|
||||
"h": 54
|
||||
},
|
||||
"frame": {
|
||||
"x": 72,
|
||||
"y": 55,
|
||||
"w": 36,
|
||||
"h": 55
|
||||
"h": 54
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit b44ee2173788018ffd5dc6b7b7fa159be5b9d514
|
||||
Subproject commit fc4a1effd5170def3c8314208a52cd0d8e6913ef
|
@ -2676,7 +2676,7 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all modifiers from enemy of PersistentModifier type
|
||||
* Removes all modifiers from enemy pokemon of {@linkcode PersistentModifier} type
|
||||
*/
|
||||
clearEnemyModifiers(): void {
|
||||
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PersistentModifier);
|
||||
@ -2687,10 +2687,11 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all modifiers from enemy of PokemonHeldItemModifier type
|
||||
* Removes all modifiers from enemy pokemon of {@linkcode PokemonHeldItemModifier} type
|
||||
* @param pokemon - If specified, only removes held items from that {@linkcode Pokemon}
|
||||
*/
|
||||
clearEnemyHeldItemModifiers(): void {
|
||||
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier);
|
||||
clearEnemyHeldItemModifiers(pokemon?: Pokemon): void {
|
||||
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon(this) === pokemon));
|
||||
for (const m of modifiersToRemove) {
|
||||
this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1);
|
||||
}
|
||||
@ -3161,13 +3162,17 @@ export default class BattleScene extends SceneBase {
|
||||
/**
|
||||
* Loads or generates a mystery encounter
|
||||
* @param encounterType used to load session encounter when restarting game, etc.
|
||||
* @param canBypass optional boolean to indicate that the request is coming from a function that needs to access a Mystery Encounter outside of gameplay requirements
|
||||
* @returns
|
||||
*/
|
||||
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
|
||||
getMysteryEncounter(encounterType?: MysteryEncounterType, canBypass?: boolean): MysteryEncounter {
|
||||
// Loading override or session encounter
|
||||
let encounter: MysteryEncounter | null;
|
||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
|
||||
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
|
||||
} else if (canBypass) {
|
||||
encounter = allMysteryEncounters[encounterType ?? -1];
|
||||
return encounter;
|
||||
} else {
|
||||
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null;
|
||||
}
|
||||
|
@ -634,15 +634,15 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
|
||||
* Examples include: Absorb, Draining Kiss, Bitter Blade, etc.
|
||||
* Also displays a message to show this ability was activated.
|
||||
* @param pokemon {@linkcode Pokemon} with this ability
|
||||
* @param passive N/A
|
||||
* @param _passive N/A
|
||||
* @param attacker {@linkcode Pokemon} that is attacking this Pokemon
|
||||
* @param move {@linkcode PokemonMove} that is being used
|
||||
* @param hitResult N/A
|
||||
* @args N/A
|
||||
* @param _hitResult N/A
|
||||
* @param _args N/A
|
||||
* @returns true if healing should be reversed on a healing move, false otherwise.
|
||||
*/
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.hasAttr(HitHealAttr)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) }));
|
||||
}
|
||||
@ -669,8 +669,8 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
this.allOthers = allOthers;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (simulated) {
|
||||
return true;
|
||||
}
|
||||
@ -707,13 +707,13 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
this.selfTarget = selfTarget;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate);
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
|
||||
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
|
||||
const damageReceived = lastAttackReceived?.damage || 0;
|
||||
|
||||
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) {
|
||||
if (!simulated ) {
|
||||
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages));
|
||||
}
|
||||
return true;
|
||||
@ -734,8 +734,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
|
||||
this.tagType = tagType;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag;
|
||||
if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) {
|
||||
if (!simulated) {
|
||||
@ -758,8 +758,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
|
||||
this.tagType = tagType;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!pokemon.getTag(this.tagType) && !simulated) {
|
||||
pokemon.addTag(this.tagType, undefined, undefined, pokemon.id);
|
||||
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }));
|
||||
@ -771,8 +771,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
|
||||
}
|
||||
|
||||
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (simulated) {
|
||||
return true;
|
||||
}
|
||||
@ -787,7 +787,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:postDefendTypeChange", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
abilityName,
|
||||
@ -805,8 +805,8 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
|
||||
this.terrainType = terrainType;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (simulated) {
|
||||
return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined);
|
||||
} else {
|
||||
@ -829,8 +829,9 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status
|
||||
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
|
||||
if (simulated) {
|
||||
return attacker.canSetStatus(effect, true, false, pokemon);
|
||||
@ -869,8 +870,8 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
||||
this.turnCount = turnCount;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (simulated) {
|
||||
return attacker.canAddTag(this.tagType);
|
||||
} else {
|
||||
@ -893,7 +894,11 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
this.stages = stages;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.hitsSubstitute(attacker, pokemon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!simulated) {
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
|
||||
}
|
||||
@ -901,7 +906,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
return true;
|
||||
}
|
||||
|
||||
getCondition(): AbAttrCondition {
|
||||
override getCondition(): AbAttrCondition {
|
||||
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
|
||||
}
|
||||
}
|
||||
@ -915,8 +920,9 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
||||
this.damageRatio = damageRatio;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
|
||||
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
|
||||
attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio));
|
||||
return true;
|
||||
@ -925,7 +931,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:postDefendContactDamage", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
abilityName
|
||||
@ -948,8 +954,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
|
||||
this.turns = turns;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) {
|
||||
return false;
|
||||
} else {
|
||||
@ -963,24 +969,24 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName });
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
|
||||
private weatherType: WeatherType;
|
||||
protected condition: PokemonDefendCondition | null;
|
||||
protected condition?: PokemonDefendCondition;
|
||||
|
||||
constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) {
|
||||
super();
|
||||
|
||||
this.weatherType = weatherType;
|
||||
this.condition = condition ?? null;
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (this.condition !== null && !this.condition(pokemon, attacker, move)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) {
|
||||
return false;
|
||||
}
|
||||
if (!pokemon.scene.arena.weather?.isImmutable()) {
|
||||
@ -999,8 +1005,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
|
||||
super();
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
|
||||
&& !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
const tempAbilityId = attacker.getAbility().id;
|
||||
attacker.summonData.ability = pokemon.getAbility().id;
|
||||
@ -1012,7 +1019,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
|
||||
}
|
||||
}
|
||||
@ -1025,8 +1032,9 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
|
||||
this.ability = ability;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr)
|
||||
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
attacker.summonData.ability = this.ability;
|
||||
}
|
||||
@ -1037,7 +1045,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return i18next.t("abilityTriggers:postDefendAbilityGive", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
abilityName
|
||||
@ -1056,8 +1064,8 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (attacker.getTag(BattlerTagType.DISABLED) === null) {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
|
||||
if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
|
||||
if (simulated) {
|
||||
return true;
|
||||
@ -1724,17 +1732,17 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
|
||||
}
|
||||
|
||||
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
private condition: PokemonDefendCondition | null;
|
||||
private condition?: PokemonDefendCondition;
|
||||
|
||||
constructor(condition?: PokemonDefendCondition) {
|
||||
super();
|
||||
|
||||
this.condition = condition ?? null;
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) {
|
||||
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable);
|
||||
if (heldItems.length) {
|
||||
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
|
||||
@ -4476,7 +4484,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC
|
||||
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
||||
private multiplier: number;
|
||||
private tagType: BattlerTagType;
|
||||
private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined;
|
||||
private recoilDamageFunc?: ((pokemon: Pokemon) => number);
|
||||
private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string;
|
||||
|
||||
constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) {
|
||||
@ -4492,16 +4500,16 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
||||
* Applies the pre-defense ability to the Pokémon.
|
||||
* Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form.
|
||||
*
|
||||
* @param {Pokemon} pokemon The Pokémon with the ability.
|
||||
* @param {boolean} passive n/a
|
||||
* @param {Pokemon} attacker The attacking Pokémon.
|
||||
* @param {PokemonMove} move The move being used.
|
||||
* @param {Utils.BooleanHolder} cancelled n/a
|
||||
* @param {any[]} args Additional arguments.
|
||||
* @returns {boolean} Whether the immunity was applied.
|
||||
* @param pokemon The Pokémon with the ability.
|
||||
* @param _passive n/a
|
||||
* @param attacker The attacking Pokémon.
|
||||
* @param move The move being used.
|
||||
* @param _cancelled n/a
|
||||
* @param args Additional arguments.
|
||||
* @returns `true` if the immunity was applied.
|
||||
*/
|
||||
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move)) {
|
||||
override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
|
||||
if (!simulated) {
|
||||
(args[0] as Utils.NumberHolder).value = this.multiplier;
|
||||
pokemon.removeTag(this.tagType);
|
||||
@ -4517,12 +4525,12 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
||||
|
||||
/**
|
||||
* Gets the message triggered when the Pokémon avoids damage using the form-changing ability.
|
||||
* @param {Pokemon} pokemon The Pokémon with the ability.
|
||||
* @param {string} abilityName The name of the ability.
|
||||
* @param {...any} args n/a
|
||||
* @returns {string} The trigger message.
|
||||
* @param pokemon The Pokémon with the ability.
|
||||
* @param abilityName The name of the ability.
|
||||
* @param _args n/a
|
||||
* @returns The trigger message.
|
||||
*/
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
||||
return this.triggerMessageFunc(pokemon, abilityName);
|
||||
}
|
||||
}
|
||||
@ -5503,7 +5511,8 @@ export function initAbilities() {
|
||||
.attr(NoFusionAbilityAbAttr)
|
||||
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
|
||||
.attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
|
||||
.attr(FormBlockDamageAbAttr,
|
||||
(target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
|
||||
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
|
||||
(pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8))
|
||||
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
||||
@ -5665,7 +5674,8 @@ export function initAbilities() {
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0)
|
||||
// When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE
|
||||
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
|
||||
.attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
|
||||
.attr(FormBlockDamageAbAttr,
|
||||
(target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
|
||||
(pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }))
|
||||
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
||||
.bypassFaint()
|
||||
|
@ -28,20 +28,13 @@ export enum ArenaTagSide {
|
||||
}
|
||||
|
||||
export abstract class ArenaTag {
|
||||
public tagType: ArenaTagType;
|
||||
public turnCount: integer;
|
||||
public sourceMove?: Moves;
|
||||
public sourceId?: integer;
|
||||
public side: ArenaTagSide;
|
||||
|
||||
|
||||
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
|
||||
this.tagType = tagType;
|
||||
this.turnCount = turnCount;
|
||||
this.sourceMove = sourceMove;
|
||||
this.sourceId = sourceId;
|
||||
this.side = side;
|
||||
}
|
||||
constructor(
|
||||
public tagType: ArenaTagType,
|
||||
public turnCount: number,
|
||||
public sourceMove?: Moves,
|
||||
public sourceId?: number,
|
||||
public side: ArenaTagSide = ArenaTagSide.BOTH
|
||||
) {}
|
||||
|
||||
apply(arena: Arena, args: any[]): boolean {
|
||||
return true;
|
||||
@ -66,6 +59,18 @@ export abstract class ArenaTag {
|
||||
? allMoves[this.sourceMove].name
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* When given a arena tag or json representing one, load the data for it.
|
||||
* This is meant to be inherited from by any arena tag with custom attributes
|
||||
* @param {ArenaTag | any} source An arena tag
|
||||
*/
|
||||
loadTag(source : ArenaTag | any) : void {
|
||||
this.turnCount = source.turnCount;
|
||||
this.sourceMove = source.sourceMove;
|
||||
this.sourceId = source.sourceId;
|
||||
this.side = source.side;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +78,7 @@ export abstract class ArenaTag {
|
||||
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
|
||||
*/
|
||||
export class MistTag extends ArenaTag {
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
|
||||
}
|
||||
|
||||
@ -117,7 +122,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
||||
* @param side - The side (player or enemy) the tag affects.
|
||||
* @param weakenedCategories - The categories of moves that are weakened by this tag.
|
||||
*/
|
||||
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
|
||||
constructor(tagType: ArenaTagType, turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
|
||||
super(tagType, turnCount, sourceMove, sourceId, side);
|
||||
|
||||
this.weakenedCategories = weakenedCategories;
|
||||
@ -148,7 +153,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
||||
* Used by {@linkcode Moves.REFLECT}
|
||||
*/
|
||||
class ReflectTag extends WeakenMoveScreenTag {
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [ MoveCategory.PHYSICAL ]);
|
||||
}
|
||||
|
||||
@ -164,7 +169,7 @@ class ReflectTag extends WeakenMoveScreenTag {
|
||||
* Used by {@linkcode Moves.LIGHT_SCREEN}
|
||||
*/
|
||||
class LightScreenTag extends WeakenMoveScreenTag {
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [ MoveCategory.SPECIAL ]);
|
||||
}
|
||||
|
||||
@ -180,7 +185,7 @@ class LightScreenTag extends WeakenMoveScreenTag {
|
||||
* Used by {@linkcode Moves.AURORA_VEIL}
|
||||
*/
|
||||
class AuroraVeilTag extends WeakenMoveScreenTag {
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ]);
|
||||
}
|
||||
|
||||
@ -203,7 +208,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
/** Does this apply to all moves, including those that ignore other forms of protection? */
|
||||
protected ignoresBypass: boolean;
|
||||
|
||||
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) {
|
||||
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) {
|
||||
super(tagType, 1, sourceMove, sourceId, side);
|
||||
|
||||
this.protectConditionFunc = condition;
|
||||
@ -265,7 +270,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
*/
|
||||
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
||||
const move = allMoves[moveId];
|
||||
const priority = new Utils.IntegerHolder(move.priority);
|
||||
const priority = new Utils.NumberHolder(move.priority);
|
||||
const effectPhase = arena.scene.getCurrentPhase();
|
||||
|
||||
if (effectPhase instanceof MoveEffectPhase) {
|
||||
@ -281,7 +286,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
||||
* Condition: The incoming move has increased priority.
|
||||
*/
|
||||
class QuickGuardTag extends ConditionalProtectTag {
|
||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
|
||||
}
|
||||
}
|
||||
@ -312,7 +317,7 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =
|
||||
* can be an ally or enemy.
|
||||
*/
|
||||
class WideGuardTag extends ConditionalProtectTag {
|
||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
|
||||
}
|
||||
}
|
||||
@ -334,7 +339,7 @@ const MatBlockConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =>
|
||||
* Condition: The incoming move is a Physical or Special attack move.
|
||||
*/
|
||||
class MatBlockTag extends ConditionalProtectTag {
|
||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
|
||||
}
|
||||
|
||||
@ -372,7 +377,7 @@ const CraftyShieldConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
||||
* not target all Pokemon or sides of the field.
|
||||
*/
|
||||
class CraftyShieldTag extends ConditionalProtectTag {
|
||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.CRAFTY_SHIELD, Moves.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true);
|
||||
}
|
||||
}
|
||||
@ -384,12 +389,12 @@ class CraftyShieldTag extends ConditionalProtectTag {
|
||||
export class NoCritTag extends ArenaTag {
|
||||
/**
|
||||
* Constructor method for the NoCritTag class
|
||||
* @param turnCount `integer` the number of turns this effect lasts
|
||||
* @param turnCount `number` the number of turns this effect lasts
|
||||
* @param sourceMove {@linkcode Moves} the move that created this effect
|
||||
* @param sourceId `integer` the ID of the {@linkcode Pokemon} that created this effect
|
||||
* @param sourceId `number` the ID of the {@linkcode Pokemon} that created this effect
|
||||
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
|
||||
*/
|
||||
constructor(turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
|
||||
}
|
||||
|
||||
@ -419,7 +424,7 @@ class WishTag extends ArenaTag {
|
||||
private triggerMessage: string;
|
||||
private healHp: number;
|
||||
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.WISH, turnCount, Moves.WISH, sourceId, side);
|
||||
}
|
||||
|
||||
@ -460,7 +465,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
|
||||
* @param sourceMove - The move that created the tag.
|
||||
* @param sourceId - The ID of the source of the tag.
|
||||
*/
|
||||
constructor(tagType: ArenaTagType, turnCount: integer, type: Type, sourceMove: Moves, sourceId: integer) {
|
||||
constructor(tagType: ArenaTagType, turnCount: number, type: Type, sourceMove: Moves, sourceId: number) {
|
||||
super(tagType, turnCount, sourceMove, sourceId);
|
||||
|
||||
this.weakenedType = type;
|
||||
@ -481,7 +486,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
|
||||
* Weakens Electric type moves for a set amount of turns, usually 5.
|
||||
*/
|
||||
class MudSportTag extends WeakenMoveTypeTag {
|
||||
constructor(turnCount: integer, sourceId: integer) {
|
||||
constructor(turnCount: number, sourceId: number) {
|
||||
super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId);
|
||||
}
|
||||
|
||||
@ -499,7 +504,7 @@ class MudSportTag extends WeakenMoveTypeTag {
|
||||
* Weakens Fire type moves for a set amount of turns, usually 5.
|
||||
*/
|
||||
class WaterSportTag extends WeakenMoveTypeTag {
|
||||
constructor(turnCount: integer, sourceId: integer) {
|
||||
constructor(turnCount: number, sourceId: number) {
|
||||
super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId);
|
||||
}
|
||||
|
||||
@ -550,8 +555,8 @@ export class IonDelugeTag extends ArenaTag {
|
||||
* Abstract class to implement arena traps.
|
||||
*/
|
||||
export class ArenaTrapTag extends ArenaTag {
|
||||
public layers: integer;
|
||||
public maxLayers: integer;
|
||||
public layers: number;
|
||||
public maxLayers: number;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the ArenaTrapTag class.
|
||||
@ -562,7 +567,7 @@ export class ArenaTrapTag extends ArenaTag {
|
||||
* @param side - The side (player or enemy) the tag affects.
|
||||
* @param maxLayers - The maximum amount of layers this tag can have.
|
||||
*/
|
||||
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) {
|
||||
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, maxLayers: number) {
|
||||
super(tagType, 0, sourceMove, sourceId, side);
|
||||
|
||||
this.layers = 1;
|
||||
@ -593,6 +598,12 @@ export class ArenaTrapTag extends ArenaTag {
|
||||
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
||||
return pokemon.isGrounded() ? 1 : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
|
||||
}
|
||||
|
||||
loadTag(source: any): void {
|
||||
super.loadTag(source);
|
||||
this.layers = source.layers;
|
||||
this.maxLayers = source.maxLayers;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -601,7 +612,7 @@ export class ArenaTrapTag extends ArenaTag {
|
||||
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
|
||||
*/
|
||||
class SpikesTag extends ArenaTrapTag {
|
||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
|
||||
}
|
||||
|
||||
@ -645,7 +656,7 @@ class SpikesTag extends ArenaTrapTag {
|
||||
class ToxicSpikesTag extends ArenaTrapTag {
|
||||
private neutralized: boolean;
|
||||
|
||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
|
||||
this.neutralized = false;
|
||||
}
|
||||
@ -703,7 +714,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
||||
class DelayedAttackTag extends ArenaTag {
|
||||
public targetIndex: BattlerIndex;
|
||||
|
||||
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: integer, targetIndex: BattlerIndex) {
|
||||
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: number, targetIndex: BattlerIndex) {
|
||||
super(tagType, 3, sourceMove, sourceId);
|
||||
|
||||
this.targetIndex = targetIndex;
|
||||
@ -728,7 +739,7 @@ class DelayedAttackTag extends ArenaTag {
|
||||
* who is summoned into the trap, based on the Rock type's type effectiveness.
|
||||
*/
|
||||
class StealthRockTag extends ArenaTrapTag {
|
||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
|
||||
}
|
||||
|
||||
@ -804,7 +815,7 @@ class StealthRockTag extends ArenaTrapTag {
|
||||
* to any Pokémon who is summoned into this trap.
|
||||
*/
|
||||
class StickyWebTag extends ArenaTrapTag {
|
||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1);
|
||||
}
|
||||
|
||||
@ -838,7 +849,7 @@ class StickyWebTag extends ArenaTrapTag {
|
||||
* also reversing the turn order for all Pokémon on the field as well.
|
||||
*/
|
||||
export class TrickRoomTag extends ArenaTag {
|
||||
constructor(turnCount: integer, sourceId: integer) {
|
||||
constructor(turnCount: number, sourceId: number) {
|
||||
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
|
||||
}
|
||||
|
||||
@ -866,7 +877,7 @@ export class TrickRoomTag extends ArenaTag {
|
||||
* {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns.
|
||||
*/
|
||||
export class GravityTag extends ArenaTag {
|
||||
constructor(turnCount: integer) {
|
||||
constructor(turnCount: number) {
|
||||
super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY);
|
||||
}
|
||||
|
||||
@ -890,7 +901,7 @@ export class GravityTag extends ArenaTag {
|
||||
* Applies this arena tag for 4 turns (including the turn the move was used).
|
||||
*/
|
||||
class TailwindTag extends ArenaTag {
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side);
|
||||
}
|
||||
|
||||
@ -928,7 +939,7 @@ class TailwindTag extends ArenaTag {
|
||||
* Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}.
|
||||
*/
|
||||
class HappyHourTag extends ArenaTag {
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side);
|
||||
}
|
||||
|
||||
@ -942,7 +953,7 @@ class HappyHourTag extends ArenaTag {
|
||||
}
|
||||
|
||||
class SafeguardTag extends ArenaTag {
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
|
||||
}
|
||||
|
||||
@ -955,6 +966,11 @@ class SafeguardTag extends ArenaTag {
|
||||
}
|
||||
}
|
||||
|
||||
class NoneTag extends ArenaTag {
|
||||
constructor() {
|
||||
super(ArenaTagType.NONE, 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This arena tag facilitates the application of the move Imprison
|
||||
* Imprison remains in effect as long as the source Pokemon is active and present on the field.
|
||||
@ -1102,7 +1118,8 @@ class GrassWaterPledgeTag extends ArenaTag {
|
||||
}
|
||||
}
|
||||
|
||||
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
||||
// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter
|
||||
export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
||||
switch (tagType) {
|
||||
case ArenaTagType.MIST:
|
||||
return new MistTag(turnCount, sourceId, side);
|
||||
@ -1163,3 +1180,16 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When given a battler tag or json representing one, creates an actual ArenaTag object with the same data.
|
||||
* @param {ArenaTag | any} source An arena tag
|
||||
* @return {ArenaTag} The valid arena tag
|
||||
*/
|
||||
export function loadArenaTag(source: ArenaTag | any): ArenaTag {
|
||||
const tag = getArenaTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId, source.targetIndex, source.side)
|
||||
?? new NoneTag();
|
||||
tag.loadTag(source);
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
603
src/data/balance/species-egg-tiers.ts
Normal file
603
src/data/balance/species-egg-tiers.ts
Normal file
@ -0,0 +1,603 @@
|
||||
import { Species } from "#enums/species";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
|
||||
/**
|
||||
* Map of all starters and their respective {@linkcode EggTier}, which determines the type of egg the starter hatches from.
|
||||
*/
|
||||
export const speciesEggTiers = {
|
||||
[Species.BULBASAUR]: EggTier.COMMON,
|
||||
[Species.CHARMANDER]: EggTier.COMMON,
|
||||
[Species.SQUIRTLE]: EggTier.COMMON,
|
||||
[Species.CATERPIE]: EggTier.COMMON,
|
||||
[Species.WEEDLE]: EggTier.COMMON,
|
||||
[Species.PIDGEY]: EggTier.COMMON,
|
||||
[Species.RATTATA]: EggTier.COMMON,
|
||||
[Species.SPEAROW]: EggTier.COMMON,
|
||||
[Species.EKANS]: EggTier.COMMON,
|
||||
[Species.PIKACHU]: EggTier.COMMON,
|
||||
[Species.SANDSHREW]: EggTier.COMMON,
|
||||
[Species.NIDORAN_F]: EggTier.COMMON,
|
||||
[Species.NIDORAN_M]: EggTier.COMMON,
|
||||
[Species.CLEFAIRY]: EggTier.COMMON,
|
||||
[Species.VULPIX]: EggTier.COMMON,
|
||||
[Species.JIGGLYPUFF]: EggTier.COMMON,
|
||||
[Species.ZUBAT]: EggTier.COMMON,
|
||||
[Species.ODDISH]: EggTier.COMMON,
|
||||
[Species.PARAS]: EggTier.COMMON,
|
||||
[Species.VENONAT]: EggTier.COMMON,
|
||||
[Species.DIGLETT]: EggTier.COMMON,
|
||||
[Species.MEOWTH]: EggTier.COMMON,
|
||||
[Species.PSYDUCK]: EggTier.COMMON,
|
||||
[Species.MANKEY]: EggTier.RARE,
|
||||
[Species.GROWLITHE]: EggTier.RARE,
|
||||
[Species.POLIWAG]: EggTier.COMMON,
|
||||
[Species.ABRA]: EggTier.RARE,
|
||||
[Species.MACHOP]: EggTier.COMMON,
|
||||
[Species.BELLSPROUT]: EggTier.COMMON,
|
||||
[Species.TENTACOOL]: EggTier.COMMON,
|
||||
[Species.GEODUDE]: EggTier.COMMON,
|
||||
[Species.PONYTA]: EggTier.COMMON,
|
||||
[Species.SLOWPOKE]: EggTier.COMMON,
|
||||
[Species.MAGNEMITE]: EggTier.RARE,
|
||||
[Species.FARFETCHD]: EggTier.COMMON,
|
||||
[Species.DODUO]: EggTier.COMMON,
|
||||
[Species.SEEL]: EggTier.COMMON,
|
||||
[Species.GRIMER]: EggTier.COMMON,
|
||||
[Species.SHELLDER]: EggTier.RARE,
|
||||
[Species.GASTLY]: EggTier.RARE,
|
||||
[Species.ONIX]: EggTier.COMMON,
|
||||
[Species.DROWZEE]: EggTier.COMMON,
|
||||
[Species.KRABBY]: EggTier.COMMON,
|
||||
[Species.VOLTORB]: EggTier.COMMON,
|
||||
[Species.EXEGGCUTE]: EggTier.COMMON,
|
||||
[Species.CUBONE]: EggTier.COMMON,
|
||||
[Species.HITMONLEE]: EggTier.RARE,
|
||||
[Species.HITMONCHAN]: EggTier.RARE,
|
||||
[Species.LICKITUNG]: EggTier.COMMON,
|
||||
[Species.KOFFING]: EggTier.COMMON,
|
||||
[Species.RHYHORN]: EggTier.COMMON,
|
||||
[Species.CHANSEY]: EggTier.COMMON,
|
||||
[Species.TANGELA]: EggTier.COMMON,
|
||||
[Species.KANGASKHAN]: EggTier.RARE,
|
||||
[Species.HORSEA]: EggTier.COMMON,
|
||||
[Species.GOLDEEN]: EggTier.COMMON,
|
||||
[Species.STARYU]: EggTier.COMMON,
|
||||
[Species.MR_MIME]: EggTier.COMMON,
|
||||
[Species.SCYTHER]: EggTier.RARE,
|
||||
[Species.JYNX]: EggTier.RARE,
|
||||
[Species.ELECTABUZZ]: EggTier.RARE,
|
||||
[Species.MAGMAR]: EggTier.RARE,
|
||||
[Species.PINSIR]: EggTier.RARE,
|
||||
[Species.TAUROS]: EggTier.RARE,
|
||||
[Species.MAGIKARP]: EggTier.RARE,
|
||||
[Species.LAPRAS]: EggTier.RARE,
|
||||
[Species.DITTO]: EggTier.COMMON,
|
||||
[Species.EEVEE]: EggTier.COMMON,
|
||||
[Species.PORYGON]: EggTier.RARE,
|
||||
[Species.OMANYTE]: EggTier.COMMON,
|
||||
[Species.KABUTO]: EggTier.COMMON,
|
||||
[Species.AERODACTYL]: EggTier.RARE,
|
||||
[Species.SNORLAX]: EggTier.RARE,
|
||||
[Species.ARTICUNO]: EggTier.EPIC,
|
||||
[Species.ZAPDOS]: EggTier.EPIC,
|
||||
[Species.MOLTRES]: EggTier.EPIC,
|
||||
[Species.DRATINI]: EggTier.RARE,
|
||||
[Species.MEWTWO]: EggTier.LEGENDARY,
|
||||
[Species.MEW]: EggTier.EPIC,
|
||||
|
||||
[Species.CHIKORITA]: EggTier.COMMON,
|
||||
[Species.CYNDAQUIL]: EggTier.COMMON,
|
||||
[Species.TOTODILE]: EggTier.COMMON,
|
||||
[Species.SENTRET]: EggTier.COMMON,
|
||||
[Species.HOOTHOOT]: EggTier.COMMON,
|
||||
[Species.LEDYBA]: EggTier.COMMON,
|
||||
[Species.SPINARAK]: EggTier.COMMON,
|
||||
[Species.CHINCHOU]: EggTier.COMMON,
|
||||
[Species.PICHU]: EggTier.COMMON,
|
||||
[Species.CLEFFA]: EggTier.COMMON,
|
||||
[Species.IGGLYBUFF]: EggTier.COMMON,
|
||||
[Species.TOGEPI]: EggTier.COMMON,
|
||||
[Species.NATU]: EggTier.COMMON,
|
||||
[Species.MAREEP]: EggTier.COMMON,
|
||||
[Species.MARILL]: EggTier.RARE,
|
||||
[Species.SUDOWOODO]: EggTier.COMMON,
|
||||
[Species.HOPPIP]: EggTier.COMMON,
|
||||
[Species.AIPOM]: EggTier.COMMON,
|
||||
[Species.SUNKERN]: EggTier.COMMON,
|
||||
[Species.YANMA]: EggTier.COMMON,
|
||||
[Species.WOOPER]: EggTier.COMMON,
|
||||
[Species.MURKROW]: EggTier.COMMON,
|
||||
[Species.MISDREAVUS]: EggTier.COMMON,
|
||||
[Species.UNOWN]: EggTier.COMMON,
|
||||
[Species.WOBBUFFET]: EggTier.COMMON,
|
||||
[Species.GIRAFARIG]: EggTier.COMMON,
|
||||
[Species.PINECO]: EggTier.COMMON,
|
||||
[Species.DUNSPARCE]: EggTier.COMMON,
|
||||
[Species.GLIGAR]: EggTier.COMMON,
|
||||
[Species.SNUBBULL]: EggTier.COMMON,
|
||||
[Species.QWILFISH]: EggTier.COMMON,
|
||||
[Species.SHUCKLE]: EggTier.COMMON,
|
||||
[Species.HERACROSS]: EggTier.RARE,
|
||||
[Species.SNEASEL]: EggTier.RARE,
|
||||
[Species.TEDDIURSA]: EggTier.RARE,
|
||||
[Species.SLUGMA]: EggTier.COMMON,
|
||||
[Species.SWINUB]: EggTier.COMMON,
|
||||
[Species.CORSOLA]: EggTier.COMMON,
|
||||
[Species.REMORAID]: EggTier.COMMON,
|
||||
[Species.DELIBIRD]: EggTier.COMMON,
|
||||
[Species.MANTINE]: EggTier.COMMON,
|
||||
[Species.SKARMORY]: EggTier.RARE,
|
||||
[Species.HOUNDOUR]: EggTier.COMMON,
|
||||
[Species.PHANPY]: EggTier.COMMON,
|
||||
[Species.STANTLER]: EggTier.COMMON,
|
||||
[Species.SMEARGLE]: EggTier.COMMON,
|
||||
[Species.TYROGUE]: EggTier.COMMON,
|
||||
[Species.SMOOCHUM]: EggTier.COMMON,
|
||||
[Species.ELEKID]: EggTier.COMMON,
|
||||
[Species.MAGBY]: EggTier.COMMON,
|
||||
[Species.MILTANK]: EggTier.RARE,
|
||||
[Species.RAIKOU]: EggTier.EPIC,
|
||||
[Species.ENTEI]: EggTier.EPIC,
|
||||
[Species.SUICUNE]: EggTier.EPIC,
|
||||
[Species.LARVITAR]: EggTier.RARE,
|
||||
[Species.LUGIA]: EggTier.LEGENDARY,
|
||||
[Species.HO_OH]: EggTier.LEGENDARY,
|
||||
[Species.CELEBI]: EggTier.EPIC,
|
||||
|
||||
[Species.TREECKO]: EggTier.COMMON,
|
||||
[Species.TORCHIC]: EggTier.RARE,
|
||||
[Species.MUDKIP]: EggTier.COMMON,
|
||||
[Species.POOCHYENA]: EggTier.COMMON,
|
||||
[Species.ZIGZAGOON]: EggTier.COMMON,
|
||||
[Species.WURMPLE]: EggTier.COMMON,
|
||||
[Species.LOTAD]: EggTier.COMMON,
|
||||
[Species.SEEDOT]: EggTier.COMMON,
|
||||
[Species.TAILLOW]: EggTier.COMMON,
|
||||
[Species.WINGULL]: EggTier.COMMON,
|
||||
[Species.RALTS]: EggTier.COMMON,
|
||||
[Species.SURSKIT]: EggTier.COMMON,
|
||||
[Species.SHROOMISH]: EggTier.COMMON,
|
||||
[Species.SLAKOTH]: EggTier.RARE,
|
||||
[Species.NINCADA]: EggTier.RARE,
|
||||
[Species.WHISMUR]: EggTier.COMMON,
|
||||
[Species.MAKUHITA]: EggTier.COMMON,
|
||||
[Species.AZURILL]: EggTier.RARE,
|
||||
[Species.NOSEPASS]: EggTier.COMMON,
|
||||
[Species.SKITTY]: EggTier.COMMON,
|
||||
[Species.SABLEYE]: EggTier.COMMON,
|
||||
[Species.MAWILE]: EggTier.COMMON,
|
||||
[Species.ARON]: EggTier.COMMON,
|
||||
[Species.MEDITITE]: EggTier.COMMON,
|
||||
[Species.ELECTRIKE]: EggTier.COMMON,
|
||||
[Species.PLUSLE]: EggTier.COMMON,
|
||||
[Species.MINUN]: EggTier.COMMON,
|
||||
[Species.VOLBEAT]: EggTier.COMMON,
|
||||
[Species.ILLUMISE]: EggTier.COMMON,
|
||||
[Species.ROSELIA]: EggTier.COMMON,
|
||||
[Species.GULPIN]: EggTier.COMMON,
|
||||
[Species.CARVANHA]: EggTier.COMMON,
|
||||
[Species.WAILMER]: EggTier.COMMON,
|
||||
[Species.NUMEL]: EggTier.COMMON,
|
||||
[Species.TORKOAL]: EggTier.COMMON,
|
||||
[Species.SPOINK]: EggTier.COMMON,
|
||||
[Species.SPINDA]: EggTier.COMMON,
|
||||
[Species.TRAPINCH]: EggTier.COMMON,
|
||||
[Species.CACNEA]: EggTier.COMMON,
|
||||
[Species.SWABLU]: EggTier.COMMON,
|
||||
[Species.ZANGOOSE]: EggTier.RARE,
|
||||
[Species.SEVIPER]: EggTier.COMMON,
|
||||
[Species.LUNATONE]: EggTier.COMMON,
|
||||
[Species.SOLROCK]: EggTier.COMMON,
|
||||
[Species.BARBOACH]: EggTier.COMMON,
|
||||
[Species.CORPHISH]: EggTier.COMMON,
|
||||
[Species.BALTOY]: EggTier.COMMON,
|
||||
[Species.LILEEP]: EggTier.COMMON,
|
||||
[Species.ANORITH]: EggTier.COMMON,
|
||||
[Species.FEEBAS]: EggTier.RARE,
|
||||
[Species.CASTFORM]: EggTier.COMMON,
|
||||
[Species.KECLEON]: EggTier.COMMON,
|
||||
[Species.SHUPPET]: EggTier.COMMON,
|
||||
[Species.DUSKULL]: EggTier.COMMON,
|
||||
[Species.TROPIUS]: EggTier.COMMON,
|
||||
[Species.CHIMECHO]: EggTier.COMMON,
|
||||
[Species.ABSOL]: EggTier.RARE,
|
||||
[Species.WYNAUT]: EggTier.COMMON,
|
||||
[Species.SNORUNT]: EggTier.COMMON,
|
||||
[Species.SPHEAL]: EggTier.COMMON,
|
||||
[Species.CLAMPERL]: EggTier.COMMON,
|
||||
[Species.RELICANTH]: EggTier.COMMON,
|
||||
[Species.LUVDISC]: EggTier.COMMON,
|
||||
[Species.BAGON]: EggTier.RARE,
|
||||
[Species.BELDUM]: EggTier.RARE,
|
||||
[Species.REGIROCK]: EggTier.EPIC,
|
||||
[Species.REGICE]: EggTier.EPIC,
|
||||
[Species.REGISTEEL]: EggTier.EPIC,
|
||||
[Species.LATIAS]: EggTier.EPIC,
|
||||
[Species.LATIOS]: EggTier.EPIC,
|
||||
[Species.KYOGRE]: EggTier.LEGENDARY,
|
||||
[Species.GROUDON]: EggTier.LEGENDARY,
|
||||
[Species.RAYQUAZA]: EggTier.LEGENDARY,
|
||||
[Species.JIRACHI]: EggTier.EPIC,
|
||||
[Species.DEOXYS]: EggTier.EPIC,
|
||||
|
||||
[Species.TURTWIG]: EggTier.COMMON,
|
||||
[Species.CHIMCHAR]: EggTier.COMMON,
|
||||
[Species.PIPLUP]: EggTier.COMMON,
|
||||
[Species.STARLY]: EggTier.COMMON,
|
||||
[Species.BIDOOF]: EggTier.COMMON,
|
||||
[Species.KRICKETOT]: EggTier.COMMON,
|
||||
[Species.SHINX]: EggTier.COMMON,
|
||||
[Species.BUDEW]: EggTier.COMMON,
|
||||
[Species.CRANIDOS]: EggTier.COMMON,
|
||||
[Species.SHIELDON]: EggTier.COMMON,
|
||||
[Species.BURMY]: EggTier.COMMON,
|
||||
[Species.COMBEE]: EggTier.COMMON,
|
||||
[Species.PACHIRISU]: EggTier.COMMON,
|
||||
[Species.BUIZEL]: EggTier.COMMON,
|
||||
[Species.CHERUBI]: EggTier.COMMON,
|
||||
[Species.SHELLOS]: EggTier.COMMON,
|
||||
[Species.DRIFLOON]: EggTier.COMMON,
|
||||
[Species.BUNEARY]: EggTier.COMMON,
|
||||
[Species.GLAMEOW]: EggTier.COMMON,
|
||||
[Species.CHINGLING]: EggTier.COMMON,
|
||||
[Species.STUNKY]: EggTier.COMMON,
|
||||
[Species.BRONZOR]: EggTier.COMMON,
|
||||
[Species.BONSLY]: EggTier.COMMON,
|
||||
[Species.MIME_JR]: EggTier.COMMON,
|
||||
[Species.HAPPINY]: EggTier.COMMON,
|
||||
[Species.CHATOT]: EggTier.COMMON,
|
||||
[Species.SPIRITOMB]: EggTier.RARE,
|
||||
[Species.GIBLE]: EggTier.RARE,
|
||||
[Species.MUNCHLAX]: EggTier.RARE,
|
||||
[Species.RIOLU]: EggTier.COMMON,
|
||||
[Species.HIPPOPOTAS]: EggTier.COMMON,
|
||||
[Species.SKORUPI]: EggTier.COMMON,
|
||||
[Species.CROAGUNK]: EggTier.COMMON,
|
||||
[Species.CARNIVINE]: EggTier.COMMON,
|
||||
[Species.FINNEON]: EggTier.COMMON,
|
||||
[Species.MANTYKE]: EggTier.COMMON,
|
||||
[Species.SNOVER]: EggTier.COMMON,
|
||||
[Species.ROTOM]: EggTier.RARE,
|
||||
[Species.UXIE]: EggTier.EPIC,
|
||||
[Species.MESPRIT]: EggTier.EPIC,
|
||||
[Species.AZELF]: EggTier.EPIC,
|
||||
[Species.DIALGA]: EggTier.LEGENDARY,
|
||||
[Species.PALKIA]: EggTier.LEGENDARY,
|
||||
[Species.HEATRAN]: EggTier.EPIC,
|
||||
[Species.REGIGIGAS]: EggTier.EPIC,
|
||||
[Species.GIRATINA]: EggTier.LEGENDARY,
|
||||
[Species.CRESSELIA]: EggTier.EPIC,
|
||||
[Species.PHIONE]: EggTier.RARE,
|
||||
[Species.MANAPHY]: EggTier.EPIC,
|
||||
[Species.DARKRAI]: EggTier.EPIC,
|
||||
[Species.SHAYMIN]: EggTier.EPIC,
|
||||
[Species.ARCEUS]: EggTier.LEGENDARY,
|
||||
|
||||
[Species.VICTINI]: EggTier.EPIC,
|
||||
[Species.SNIVY]: EggTier.COMMON,
|
||||
[Species.TEPIG]: EggTier.COMMON,
|
||||
[Species.OSHAWOTT]: EggTier.COMMON,
|
||||
[Species.PATRAT]: EggTier.COMMON,
|
||||
[Species.LILLIPUP]: EggTier.COMMON,
|
||||
[Species.PURRLOIN]: EggTier.COMMON,
|
||||
[Species.PANSAGE]: EggTier.COMMON,
|
||||
[Species.PANSEAR]: EggTier.COMMON,
|
||||
[Species.PANPOUR]: EggTier.COMMON,
|
||||
[Species.MUNNA]: EggTier.COMMON,
|
||||
[Species.PIDOVE]: EggTier.COMMON,
|
||||
[Species.BLITZLE]: EggTier.COMMON,
|
||||
[Species.ROGGENROLA]: EggTier.COMMON,
|
||||
[Species.WOOBAT]: EggTier.COMMON,
|
||||
[Species.DRILBUR]: EggTier.RARE,
|
||||
[Species.AUDINO]: EggTier.COMMON,
|
||||
[Species.TIMBURR]: EggTier.RARE,
|
||||
[Species.TYMPOLE]: EggTier.COMMON,
|
||||
[Species.THROH]: EggTier.RARE,
|
||||
[Species.SAWK]: EggTier.RARE,
|
||||
[Species.SEWADDLE]: EggTier.COMMON,
|
||||
[Species.VENIPEDE]: EggTier.COMMON,
|
||||
[Species.COTTONEE]: EggTier.COMMON,
|
||||
[Species.PETILIL]: EggTier.COMMON,
|
||||
[Species.BASCULIN]: EggTier.RARE,
|
||||
[Species.SANDILE]: EggTier.RARE,
|
||||
[Species.DARUMAKA]: EggTier.RARE,
|
||||
[Species.MARACTUS]: EggTier.COMMON,
|
||||
[Species.DWEBBLE]: EggTier.COMMON,
|
||||
[Species.SCRAGGY]: EggTier.COMMON,
|
||||
[Species.SIGILYPH]: EggTier.RARE,
|
||||
[Species.YAMASK]: EggTier.COMMON,
|
||||
[Species.TIRTOUGA]: EggTier.COMMON,
|
||||
[Species.ARCHEN]: EggTier.COMMON,
|
||||
[Species.TRUBBISH]: EggTier.COMMON,
|
||||
[Species.ZORUA]: EggTier.COMMON,
|
||||
[Species.MINCCINO]: EggTier.COMMON,
|
||||
[Species.GOTHITA]: EggTier.COMMON,
|
||||
[Species.SOLOSIS]: EggTier.COMMON,
|
||||
[Species.DUCKLETT]: EggTier.COMMON,
|
||||
[Species.VANILLITE]: EggTier.COMMON,
|
||||
[Species.DEERLING]: EggTier.COMMON,
|
||||
[Species.EMOLGA]: EggTier.COMMON,
|
||||
[Species.KARRABLAST]: EggTier.COMMON,
|
||||
[Species.FOONGUS]: EggTier.COMMON,
|
||||
[Species.FRILLISH]: EggTier.COMMON,
|
||||
[Species.ALOMOMOLA]: EggTier.RARE,
|
||||
[Species.JOLTIK]: EggTier.COMMON,
|
||||
[Species.FERROSEED]: EggTier.COMMON,
|
||||
[Species.KLINK]: EggTier.COMMON,
|
||||
[Species.TYNAMO]: EggTier.COMMON,
|
||||
[Species.ELGYEM]: EggTier.COMMON,
|
||||
[Species.LITWICK]: EggTier.COMMON,
|
||||
[Species.AXEW]: EggTier.RARE,
|
||||
[Species.CUBCHOO]: EggTier.COMMON,
|
||||
[Species.CRYOGONAL]: EggTier.RARE,
|
||||
[Species.SHELMET]: EggTier.COMMON,
|
||||
[Species.STUNFISK]: EggTier.COMMON,
|
||||
[Species.MIENFOO]: EggTier.COMMON,
|
||||
[Species.DRUDDIGON]: EggTier.RARE,
|
||||
[Species.GOLETT]: EggTier.COMMON,
|
||||
[Species.PAWNIARD]: EggTier.RARE,
|
||||
[Species.BOUFFALANT]: EggTier.RARE,
|
||||
[Species.RUFFLET]: EggTier.COMMON,
|
||||
[Species.VULLABY]: EggTier.COMMON,
|
||||
[Species.HEATMOR]: EggTier.COMMON,
|
||||
[Species.DURANT]: EggTier.RARE,
|
||||
[Species.DEINO]: EggTier.RARE,
|
||||
[Species.LARVESTA]: EggTier.RARE,
|
||||
[Species.COBALION]: EggTier.EPIC,
|
||||
[Species.TERRAKION]: EggTier.EPIC,
|
||||
[Species.VIRIZION]: EggTier.EPIC,
|
||||
[Species.TORNADUS]: EggTier.EPIC,
|
||||
[Species.THUNDURUS]: EggTier.EPIC,
|
||||
[Species.RESHIRAM]: EggTier.LEGENDARY,
|
||||
[Species.ZEKROM]: EggTier.LEGENDARY,
|
||||
[Species.LANDORUS]: EggTier.EPIC,
|
||||
[Species.KYUREM]: EggTier.LEGENDARY,
|
||||
[Species.KELDEO]: EggTier.EPIC,
|
||||
[Species.MELOETTA]: EggTier.EPIC,
|
||||
[Species.GENESECT]: EggTier.EPIC,
|
||||
|
||||
[Species.CHESPIN]: EggTier.COMMON,
|
||||
[Species.FENNEKIN]: EggTier.COMMON,
|
||||
[Species.FROAKIE]: EggTier.RARE,
|
||||
[Species.BUNNELBY]: EggTier.COMMON,
|
||||
[Species.FLETCHLING]: EggTier.COMMON,
|
||||
[Species.SCATTERBUG]: EggTier.COMMON,
|
||||
[Species.LITLEO]: EggTier.COMMON,
|
||||
[Species.FLABEBE]: EggTier.COMMON,
|
||||
[Species.SKIDDO]: EggTier.COMMON,
|
||||
[Species.PANCHAM]: EggTier.COMMON,
|
||||
[Species.FURFROU]: EggTier.COMMON,
|
||||
[Species.ESPURR]: EggTier.COMMON,
|
||||
[Species.HONEDGE]: EggTier.RARE,
|
||||
[Species.SPRITZEE]: EggTier.COMMON,
|
||||
[Species.SWIRLIX]: EggTier.COMMON,
|
||||
[Species.INKAY]: EggTier.COMMON,
|
||||
[Species.BINACLE]: EggTier.COMMON,
|
||||
[Species.SKRELP]: EggTier.COMMON,
|
||||
[Species.CLAUNCHER]: EggTier.COMMON,
|
||||
[Species.HELIOPTILE]: EggTier.COMMON,
|
||||
[Species.TYRUNT]: EggTier.COMMON,
|
||||
[Species.AMAURA]: EggTier.COMMON,
|
||||
[Species.HAWLUCHA]: EggTier.RARE,
|
||||
[Species.DEDENNE]: EggTier.COMMON,
|
||||
[Species.CARBINK]: EggTier.COMMON,
|
||||
[Species.GOOMY]: EggTier.RARE,
|
||||
[Species.KLEFKI]: EggTier.COMMON,
|
||||
[Species.PHANTUMP]: EggTier.COMMON,
|
||||
[Species.PUMPKABOO]: EggTier.COMMON,
|
||||
[Species.BERGMITE]: EggTier.COMMON,
|
||||
[Species.NOIBAT]: EggTier.COMMON,
|
||||
[Species.XERNEAS]: EggTier.LEGENDARY,
|
||||
[Species.YVELTAL]: EggTier.LEGENDARY,
|
||||
[Species.ZYGARDE]: EggTier.LEGENDARY,
|
||||
[Species.DIANCIE]: EggTier.EPIC,
|
||||
[Species.HOOPA]: EggTier.EPIC,
|
||||
[Species.VOLCANION]: EggTier.EPIC,
|
||||
[Species.ETERNAL_FLOETTE]: EggTier.RARE,
|
||||
|
||||
[Species.ROWLET]: EggTier.COMMON,
|
||||
[Species.LITTEN]: EggTier.COMMON,
|
||||
[Species.POPPLIO]: EggTier.RARE,
|
||||
[Species.PIKIPEK]: EggTier.COMMON,
|
||||
[Species.YUNGOOS]: EggTier.COMMON,
|
||||
[Species.GRUBBIN]: EggTier.COMMON,
|
||||
[Species.CRABRAWLER]: EggTier.COMMON,
|
||||
[Species.ORICORIO]: EggTier.COMMON,
|
||||
[Species.CUTIEFLY]: EggTier.COMMON,
|
||||
[Species.ROCKRUFF]: EggTier.COMMON,
|
||||
[Species.WISHIWASHI]: EggTier.COMMON,
|
||||
[Species.MAREANIE]: EggTier.COMMON,
|
||||
[Species.MUDBRAY]: EggTier.COMMON,
|
||||
[Species.DEWPIDER]: EggTier.COMMON,
|
||||
[Species.FOMANTIS]: EggTier.COMMON,
|
||||
[Species.MORELULL]: EggTier.COMMON,
|
||||
[Species.SALANDIT]: EggTier.COMMON,
|
||||
[Species.STUFFUL]: EggTier.COMMON,
|
||||
[Species.BOUNSWEET]: EggTier.COMMON,
|
||||
[Species.COMFEY]: EggTier.RARE,
|
||||
[Species.ORANGURU]: EggTier.RARE,
|
||||
[Species.PASSIMIAN]: EggTier.RARE,
|
||||
[Species.WIMPOD]: EggTier.COMMON,
|
||||
[Species.SANDYGAST]: EggTier.COMMON,
|
||||
[Species.PYUKUMUKU]: EggTier.COMMON,
|
||||
[Species.TYPE_NULL]: EggTier.RARE,
|
||||
[Species.MINIOR]: EggTier.RARE,
|
||||
[Species.KOMALA]: EggTier.COMMON,
|
||||
[Species.TURTONATOR]: EggTier.RARE,
|
||||
[Species.TOGEDEMARU]: EggTier.COMMON,
|
||||
[Species.MIMIKYU]: EggTier.RARE,
|
||||
[Species.BRUXISH]: EggTier.RARE,
|
||||
[Species.DRAMPA]: EggTier.RARE,
|
||||
[Species.DHELMISE]: EggTier.RARE,
|
||||
[Species.JANGMO_O]: EggTier.RARE,
|
||||
[Species.TAPU_KOKO]: EggTier.EPIC,
|
||||
[Species.TAPU_LELE]: EggTier.EPIC,
|
||||
[Species.TAPU_BULU]: EggTier.EPIC,
|
||||
[Species.TAPU_FINI]: EggTier.EPIC,
|
||||
[Species.COSMOG]: EggTier.EPIC,
|
||||
[Species.NIHILEGO]: EggTier.EPIC,
|
||||
[Species.BUZZWOLE]: EggTier.EPIC,
|
||||
[Species.PHEROMOSA]: EggTier.EPIC,
|
||||
[Species.XURKITREE]: EggTier.EPIC,
|
||||
[Species.CELESTEELA]: EggTier.EPIC,
|
||||
[Species.KARTANA]: EggTier.EPIC,
|
||||
[Species.GUZZLORD]: EggTier.EPIC,
|
||||
[Species.NECROZMA]: EggTier.LEGENDARY,
|
||||
[Species.MAGEARNA]: EggTier.EPIC,
|
||||
[Species.MARSHADOW]: EggTier.EPIC,
|
||||
[Species.POIPOLE]: EggTier.EPIC,
|
||||
[Species.STAKATAKA]: EggTier.EPIC,
|
||||
[Species.BLACEPHALON]: EggTier.EPIC,
|
||||
[Species.ZERAORA]: EggTier.EPIC,
|
||||
[Species.MELTAN]: EggTier.EPIC,
|
||||
[Species.ALOLA_RATTATA]: EggTier.COMMON,
|
||||
[Species.ALOLA_SANDSHREW]: EggTier.COMMON,
|
||||
[Species.ALOLA_VULPIX]: EggTier.COMMON,
|
||||
[Species.ALOLA_DIGLETT]: EggTier.COMMON,
|
||||
[Species.ALOLA_MEOWTH]: EggTier.COMMON,
|
||||
[Species.ALOLA_GEODUDE]: EggTier.COMMON,
|
||||
[Species.ALOLA_GRIMER]: EggTier.COMMON,
|
||||
|
||||
[Species.GROOKEY]: EggTier.COMMON,
|
||||
[Species.SCORBUNNY]: EggTier.RARE,
|
||||
[Species.SOBBLE]: EggTier.COMMON,
|
||||
[Species.SKWOVET]: EggTier.COMMON,
|
||||
[Species.ROOKIDEE]: EggTier.COMMON,
|
||||
[Species.BLIPBUG]: EggTier.COMMON,
|
||||
[Species.NICKIT]: EggTier.COMMON,
|
||||
[Species.GOSSIFLEUR]: EggTier.COMMON,
|
||||
[Species.WOOLOO]: EggTier.COMMON,
|
||||
[Species.CHEWTLE]: EggTier.COMMON,
|
||||
[Species.YAMPER]: EggTier.COMMON,
|
||||
[Species.ROLYCOLY]: EggTier.COMMON,
|
||||
[Species.APPLIN]: EggTier.COMMON,
|
||||
[Species.SILICOBRA]: EggTier.COMMON,
|
||||
[Species.CRAMORANT]: EggTier.COMMON,
|
||||
[Species.ARROKUDA]: EggTier.COMMON,
|
||||
[Species.TOXEL]: EggTier.COMMON,
|
||||
[Species.SIZZLIPEDE]: EggTier.COMMON,
|
||||
[Species.CLOBBOPUS]: EggTier.COMMON,
|
||||
[Species.SINISTEA]: EggTier.COMMON,
|
||||
[Species.HATENNA]: EggTier.COMMON,
|
||||
[Species.IMPIDIMP]: EggTier.COMMON,
|
||||
[Species.MILCERY]: EggTier.COMMON,
|
||||
[Species.FALINKS]: EggTier.RARE,
|
||||
[Species.PINCURCHIN]: EggTier.COMMON,
|
||||
[Species.SNOM]: EggTier.COMMON,
|
||||
[Species.STONJOURNER]: EggTier.COMMON,
|
||||
[Species.EISCUE]: EggTier.COMMON,
|
||||
[Species.INDEEDEE]: EggTier.RARE,
|
||||
[Species.MORPEKO]: EggTier.COMMON,
|
||||
[Species.CUFANT]: EggTier.COMMON,
|
||||
[Species.DRACOZOLT]: EggTier.RARE,
|
||||
[Species.ARCTOZOLT]: EggTier.RARE,
|
||||
[Species.DRACOVISH]: EggTier.RARE,
|
||||
[Species.ARCTOVISH]: EggTier.RARE,
|
||||
[Species.DURALUDON]: EggTier.RARE,
|
||||
[Species.DREEPY]: EggTier.RARE,
|
||||
[Species.ZACIAN]: EggTier.LEGENDARY,
|
||||
[Species.ZAMAZENTA]: EggTier.LEGENDARY,
|
||||
[Species.ETERNATUS]: EggTier.COMMON,
|
||||
[Species.KUBFU]: EggTier.EPIC,
|
||||
[Species.ZARUDE]: EggTier.EPIC,
|
||||
[Species.REGIELEKI]: EggTier.EPIC,
|
||||
[Species.REGIDRAGO]: EggTier.EPIC,
|
||||
[Species.GLASTRIER]: EggTier.EPIC,
|
||||
[Species.SPECTRIER]: EggTier.EPIC,
|
||||
[Species.CALYREX]: EggTier.LEGENDARY,
|
||||
[Species.GALAR_MEOWTH]: EggTier.COMMON,
|
||||
[Species.GALAR_PONYTA]: EggTier.COMMON,
|
||||
[Species.GALAR_SLOWPOKE]: EggTier.COMMON,
|
||||
[Species.GALAR_FARFETCHD]: EggTier.COMMON,
|
||||
[Species.GALAR_CORSOLA]: EggTier.COMMON,
|
||||
[Species.GALAR_ZIGZAGOON]: EggTier.COMMON,
|
||||
[Species.GALAR_DARUMAKA]: EggTier.RARE,
|
||||
[Species.GALAR_YAMASK]: EggTier.COMMON,
|
||||
[Species.GALAR_STUNFISK]: EggTier.COMMON,
|
||||
[Species.GALAR_MR_MIME]: EggTier.COMMON,
|
||||
[Species.GALAR_ARTICUNO]: EggTier.EPIC,
|
||||
[Species.GALAR_ZAPDOS]: EggTier.EPIC,
|
||||
[Species.GALAR_MOLTRES]: EggTier.EPIC,
|
||||
[Species.HISUI_GROWLITHE]: EggTier.RARE,
|
||||
[Species.HISUI_VOLTORB]: EggTier.COMMON,
|
||||
[Species.HISUI_QWILFISH]: EggTier.RARE,
|
||||
[Species.HISUI_SNEASEL]: EggTier.RARE,
|
||||
[Species.HISUI_ZORUA]: EggTier.COMMON,
|
||||
[Species.ENAMORUS]: EggTier.EPIC,
|
||||
|
||||
[Species.SPRIGATITO]: EggTier.RARE,
|
||||
[Species.FUECOCO]: EggTier.RARE,
|
||||
[Species.QUAXLY]: EggTier.RARE,
|
||||
[Species.LECHONK]: EggTier.COMMON,
|
||||
[Species.TAROUNTULA]: EggTier.COMMON,
|
||||
[Species.NYMBLE]: EggTier.COMMON,
|
||||
[Species.PAWMI]: EggTier.COMMON,
|
||||
[Species.TANDEMAUS]: EggTier.RARE,
|
||||
[Species.FIDOUGH]: EggTier.COMMON,
|
||||
[Species.SMOLIV]: EggTier.COMMON,
|
||||
[Species.SQUAWKABILLY]: EggTier.COMMON,
|
||||
[Species.NACLI]: EggTier.RARE,
|
||||
[Species.CHARCADET]: EggTier.RARE,
|
||||
[Species.TADBULB]: EggTier.COMMON,
|
||||
[Species.WATTREL]: EggTier.COMMON,
|
||||
[Species.MASCHIFF]: EggTier.COMMON,
|
||||
[Species.SHROODLE]: EggTier.COMMON,
|
||||
[Species.BRAMBLIN]: EggTier.COMMON,
|
||||
[Species.TOEDSCOOL]: EggTier.COMMON,
|
||||
[Species.KLAWF]: EggTier.COMMON,
|
||||
[Species.CAPSAKID]: EggTier.COMMON,
|
||||
[Species.RELLOR]: EggTier.COMMON,
|
||||
[Species.FLITTLE]: EggTier.COMMON,
|
||||
[Species.TINKATINK]: EggTier.RARE,
|
||||
[Species.WIGLETT]: EggTier.COMMON,
|
||||
[Species.BOMBIRDIER]: EggTier.COMMON,
|
||||
[Species.FINIZEN]: EggTier.COMMON,
|
||||
[Species.VAROOM]: EggTier.RARE,
|
||||
[Species.CYCLIZAR]: EggTier.RARE,
|
||||
[Species.ORTHWORM]: EggTier.RARE,
|
||||
[Species.GLIMMET]: EggTier.RARE,
|
||||
[Species.GREAVARD]: EggTier.COMMON,
|
||||
[Species.FLAMIGO]: EggTier.RARE,
|
||||
[Species.CETODDLE]: EggTier.COMMON,
|
||||
[Species.VELUZA]: EggTier.RARE,
|
||||
[Species.DONDOZO]: EggTier.RARE,
|
||||
[Species.TATSUGIRI]: EggTier.RARE,
|
||||
[Species.GREAT_TUSK]: EggTier.EPIC,
|
||||
[Species.SCREAM_TAIL]: EggTier.EPIC,
|
||||
[Species.BRUTE_BONNET]: EggTier.EPIC,
|
||||
[Species.FLUTTER_MANE]: EggTier.EPIC,
|
||||
[Species.SLITHER_WING]: EggTier.EPIC,
|
||||
[Species.SANDY_SHOCKS]: EggTier.EPIC,
|
||||
[Species.IRON_TREADS]: EggTier.EPIC,
|
||||
[Species.IRON_BUNDLE]: EggTier.EPIC,
|
||||
[Species.IRON_HANDS]: EggTier.EPIC,
|
||||
[Species.IRON_JUGULIS]: EggTier.EPIC,
|
||||
[Species.IRON_MOTH]: EggTier.EPIC,
|
||||
[Species.IRON_THORNS]: EggTier.EPIC,
|
||||
[Species.FRIGIBAX]: EggTier.RARE,
|
||||
[Species.GIMMIGHOUL]: EggTier.RARE,
|
||||
[Species.WO_CHIEN]: EggTier.EPIC,
|
||||
[Species.CHIEN_PAO]: EggTier.EPIC,
|
||||
[Species.TING_LU]: EggTier.EPIC,
|
||||
[Species.CHI_YU]: EggTier.EPIC,
|
||||
[Species.ROARING_MOON]: EggTier.EPIC,
|
||||
[Species.IRON_VALIANT]: EggTier.EPIC,
|
||||
[Species.KORAIDON]: EggTier.LEGENDARY,
|
||||
[Species.MIRAIDON]: EggTier.LEGENDARY,
|
||||
[Species.WALKING_WAKE]: EggTier.EPIC,
|
||||
[Species.IRON_LEAVES]: EggTier.EPIC,
|
||||
[Species.POLTCHAGEIST]: EggTier.RARE,
|
||||
[Species.OKIDOGI]: EggTier.EPIC,
|
||||
[Species.MUNKIDORI]: EggTier.EPIC,
|
||||
[Species.FEZANDIPITI]: EggTier.EPIC,
|
||||
[Species.OGERPON]: EggTier.EPIC,
|
||||
[Species.GOUGING_FIRE]: EggTier.EPIC,
|
||||
[Species.RAGING_BOLT]: EggTier.EPIC,
|
||||
[Species.IRON_BOULDER]: EggTier.EPIC,
|
||||
[Species.IRON_CROWN]: EggTier.EPIC,
|
||||
[Species.TERAPAGOS]: EggTier.LEGENDARY,
|
||||
[Species.PECHARUNT]: EggTier.EPIC,
|
||||
[Species.PALDEA_TAUROS]: EggTier.RARE,
|
||||
[Species.PALDEA_WOOPER]: EggTier.COMMON,
|
||||
[Species.BLOODMOON_URSALUNA]: EggTier.EPIC,
|
||||
};
|
@ -1376,7 +1376,7 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
|
||||
const effectPhase = pokemon.scene.getCurrentPhase();
|
||||
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
|
||||
const attacker = effectPhase.getPokemon();
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ this.stat ], this.levels));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2139,6 +2139,10 @@ export class GulpMissileTag extends BattlerTag {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
|
||||
|
||||
@ -2636,16 +2640,16 @@ export class ImprisonTag extends MoveRestrictionBattlerTag {
|
||||
/**
|
||||
* Battler Tag that applies the effects of Syrup Bomb to the target Pokemon.
|
||||
* For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1.
|
||||
* The tag can also expire by taking the target Pokemon off the field.
|
||||
* The tag can also expire by taking the target Pokemon off the field, or the Pokemon that originally used the move.
|
||||
*/
|
||||
export class SyrupBombTag extends BattlerTag {
|
||||
constructor() {
|
||||
super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB);
|
||||
constructor(sourceId: number) {
|
||||
super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB, sourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Syrup Bomb battler tag to the target Pokemon.
|
||||
* @param {Pokemon} pokemon the target Pokemon
|
||||
* @param pokemon - The target {@linkcode Pokemon}
|
||||
*/
|
||||
override onAdd(pokemon: Pokemon) {
|
||||
super.onAdd(pokemon);
|
||||
@ -2654,15 +2658,16 @@ export class SyrupBombTag extends BattlerTag {
|
||||
|
||||
/**
|
||||
* Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count
|
||||
* @param {Pokemon} pokemon the target Pokemon
|
||||
* @param {BattlerTagLapseType} _lapseType
|
||||
* @returns `true` if the turnCount is still greater than 0 | `false` if the turnCount is 0 or the target Pokemon has been removed from the field
|
||||
* @param pokemon - The target {@linkcode Pokemon}
|
||||
* @param _lapseType - N/A
|
||||
* @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field
|
||||
*/
|
||||
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
||||
if (!pokemon.isActive(true)) {
|
||||
if (this.sourceId && !pokemon.scene.getPokemonById(this.sourceId)?.isActive(true)) {
|
||||
return false;
|
||||
}
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Custom message in lieu of an animation in mainline
|
||||
// Custom message in lieu of an animation in mainline
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(
|
||||
pokemon.scene, pokemon.getBattlerIndex(), true,
|
||||
[ Stat.SPD ], -1, true, false, true
|
||||
@ -2673,12 +2678,8 @@ export class SyrupBombTag extends BattlerTag {
|
||||
|
||||
/**
|
||||
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
|
||||
*
|
||||
* @param {BattlerTagType} tagType the type of the {@linkcode BattlerTagType}.
|
||||
* @param turnCount the turn count.
|
||||
* @param {Moves} sourceMove the source {@linkcode Moves}.
|
||||
* @param sourceId the source ID.
|
||||
* @returns {BattlerTag} the corresponding {@linkcode BattlerTag} object.
|
||||
* @param sourceId - The ID of the pokemon adding the tag
|
||||
* @returns The corresponding {@linkcode BattlerTag} object.
|
||||
*/
|
||||
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
|
||||
switch (tagType) {
|
||||
@ -2847,7 +2848,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||
case BattlerTagType.IMPRISON:
|
||||
return new ImprisonTag(sourceId);
|
||||
case BattlerTagType.SYRUP_BOMB:
|
||||
return new SyrupBombTag();
|
||||
return new SyrupBombTag(sourceId);
|
||||
case BattlerTagType.NONE:
|
||||
default:
|
||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||
|
@ -11,6 +11,7 @@ import { EggTier } from "#enums/egg-type";
|
||||
import { Species } from "#enums/species";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates";
|
||||
import { speciesEggTiers } from "#app/data/balance/species-egg-tiers";
|
||||
|
||||
export const EGG_SEED = 1073741824;
|
||||
|
||||
@ -160,7 +161,7 @@ export class Egg {
|
||||
|
||||
// Override egg tier and hatchwaves if species was given
|
||||
if (eggOptions?.species) {
|
||||
this._tier = this.getEggTierFromSpeciesStarterValue();
|
||||
this._tier = this.getEggTier();
|
||||
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
|
||||
}
|
||||
// If species has no variant, set variantTier to common. This needs to
|
||||
@ -261,11 +262,11 @@ export class Egg {
|
||||
return "Manaphy";
|
||||
}
|
||||
switch (this.tier) {
|
||||
case EggTier.GREAT:
|
||||
case EggTier.RARE:
|
||||
return i18next.t("egg:greatTier");
|
||||
case EggTier.ULTRA:
|
||||
case EggTier.EPIC:
|
||||
return i18next.t("egg:ultraTier");
|
||||
case EggTier.MASTER:
|
||||
case EggTier.LEGENDARY:
|
||||
return i18next.t("egg:masterTier");
|
||||
default:
|
||||
return i18next.t("egg:defaultTier");
|
||||
@ -336,9 +337,9 @@ export class Egg {
|
||||
switch (eggTier ?? this._tier) {
|
||||
case EggTier.COMMON:
|
||||
return HATCH_WAVES_COMMON_EGG;
|
||||
case EggTier.GREAT:
|
||||
case EggTier.RARE:
|
||||
return HATCH_WAVES_RARE_EGG;
|
||||
case EggTier.ULTRA:
|
||||
case EggTier.EPIC:
|
||||
return HATCH_WAVES_EPIC_EGG;
|
||||
}
|
||||
return HATCH_WAVES_LEGENDARY_EGG;
|
||||
@ -347,7 +348,7 @@ export class Egg {
|
||||
private rollEggTier(): EggTier {
|
||||
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
|
||||
const tierValue = Utils.randInt(256);
|
||||
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.GREAT : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER;
|
||||
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.RARE : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.EPIC : EggTier.LEGENDARY;
|
||||
}
|
||||
|
||||
private rollSpecies(scene: BattleScene): Species | null {
|
||||
@ -367,7 +368,7 @@ export class Egg {
|
||||
*/
|
||||
const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1);
|
||||
return rand ? Species.PHIONE : Species.MANAPHY;
|
||||
} else if (this.tier === EggTier.MASTER
|
||||
} else if (this.tier === EggTier.LEGENDARY
|
||||
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) {
|
||||
if (!Utils.randSeedInt(2)) {
|
||||
return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp);
|
||||
@ -378,15 +379,15 @@ export class Egg {
|
||||
let maxStarterValue: integer;
|
||||
|
||||
switch (this.tier) {
|
||||
case EggTier.GREAT:
|
||||
case EggTier.RARE:
|
||||
minStarterValue = 4;
|
||||
maxStarterValue = 5;
|
||||
break;
|
||||
case EggTier.ULTRA:
|
||||
case EggTier.EPIC:
|
||||
minStarterValue = 6;
|
||||
maxStarterValue = 7;
|
||||
break;
|
||||
case EggTier.MASTER:
|
||||
case EggTier.LEGENDARY:
|
||||
minStarterValue = 8;
|
||||
maxStarterValue = 9;
|
||||
break;
|
||||
@ -398,8 +399,8 @@ export class Egg {
|
||||
|
||||
const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ];
|
||||
|
||||
let speciesPool = Object.keys(speciesStarterCosts)
|
||||
.filter(s => speciesStarterCosts[s] >= minStarterValue && speciesStarterCosts[s] <= maxStarterValue)
|
||||
let speciesPool = Object.keys(speciesEggTiers)
|
||||
.filter(s => speciesEggTiers[s] === this.tier)
|
||||
.map(s => parseInt(s) as Species)
|
||||
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
|
||||
|
||||
@ -430,7 +431,9 @@ export class Egg {
|
||||
let totalWeight = 0;
|
||||
const speciesWeights : number[] = [];
|
||||
for (const speciesId of speciesPool) {
|
||||
let weight = Math.floor((((maxStarterValue - speciesStarterCosts[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
|
||||
// Accounts for species that have starter costs outside of the normal range for their EggTier
|
||||
const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue);
|
||||
let weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
|
||||
const species = getPokemonSpecies(speciesId);
|
||||
if (species.isRegional()) {
|
||||
weight = Math.floor(weight / 2);
|
||||
@ -498,16 +501,16 @@ export class Egg {
|
||||
|
||||
private checkForPityTierOverrides(scene: BattleScene): void {
|
||||
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
|
||||
scene.gameData.eggPity[EggTier.GREAT] += 1;
|
||||
scene.gameData.eggPity[EggTier.ULTRA] += 1;
|
||||
scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset;
|
||||
scene.gameData.eggPity[EggTier.RARE] += 1;
|
||||
scene.gameData.eggPity[EggTier.EPIC] += 1;
|
||||
scene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset;
|
||||
// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered.
|
||||
if (scene.gameData.eggPity[EggTier.MASTER] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
|
||||
this._tier = EggTier.MASTER;
|
||||
} else if (scene.gameData.eggPity[EggTier.ULTRA] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
|
||||
this._tier = EggTier.ULTRA;
|
||||
} else if (scene.gameData.eggPity[EggTier.GREAT] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) {
|
||||
this._tier = EggTier.GREAT;
|
||||
if (scene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
|
||||
this._tier = EggTier.LEGENDARY;
|
||||
} else if (scene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
|
||||
this._tier = EggTier.EPIC;
|
||||
} else if (scene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) {
|
||||
this._tier = EggTier.RARE;
|
||||
}
|
||||
scene.gameData.eggPity[this._tier] = 0;
|
||||
}
|
||||
@ -516,38 +519,24 @@ export class Egg {
|
||||
scene.gameData.gameStats.eggsPulled++;
|
||||
if (this.isManaphyEgg()) {
|
||||
scene.gameData.gameStats.manaphyEggsPulled++;
|
||||
this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.ULTRA);
|
||||
this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.EPIC);
|
||||
return;
|
||||
}
|
||||
switch (this.tier) {
|
||||
case EggTier.GREAT:
|
||||
case EggTier.RARE:
|
||||
scene.gameData.gameStats.rareEggsPulled++;
|
||||
break;
|
||||
case EggTier.ULTRA:
|
||||
case EggTier.EPIC:
|
||||
scene.gameData.gameStats.epicEggsPulled++;
|
||||
break;
|
||||
case EggTier.MASTER:
|
||||
case EggTier.LEGENDARY:
|
||||
scene.gameData.gameStats.legendaryEggsPulled++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private getEggTierFromSpeciesStarterValue(): EggTier {
|
||||
const speciesStartValue = speciesStarterCosts[this.species];
|
||||
if (speciesStartValue >= 1 && speciesStartValue <= 3) {
|
||||
return EggTier.COMMON;
|
||||
}
|
||||
if (speciesStartValue >= 4 && speciesStartValue <= 5) {
|
||||
return EggTier.GREAT;
|
||||
}
|
||||
if (speciesStartValue >= 6 && speciesStartValue <= 7) {
|
||||
return EggTier.ULTRA;
|
||||
}
|
||||
if (speciesStartValue >= 8) {
|
||||
return EggTier.MASTER;
|
||||
}
|
||||
|
||||
return EggTier.COMMON;
|
||||
private getEggTier(): EggTier {
|
||||
return speciesEggTiers[this.species];
|
||||
}
|
||||
|
||||
////
|
||||
@ -556,8 +545,8 @@ export class Egg {
|
||||
}
|
||||
|
||||
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species {
|
||||
const legendarySpecies = Object.entries(speciesStarterCosts)
|
||||
.filter(s => s[1] >= 8 && s[1] <= 9)
|
||||
const legendarySpecies = Object.entries(speciesEggTiers)
|
||||
.filter(s => s[1] === EggTier.LEGENDARY)
|
||||
.map(s => parseInt(s[0]))
|
||||
.filter(s => getPokemonSpecies(s).isObtainable());
|
||||
|
||||
@ -579,17 +568,9 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta
|
||||
|
||||
/**
|
||||
* Check for a given species EggTier Value
|
||||
* @param species - Species for wich we will check the egg tier it belongs to
|
||||
* @param pokemonSpecies - Species for wich we will check the egg tier it belongs to
|
||||
* @returns The egg tier of a given pokemon species
|
||||
*/
|
||||
export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier {
|
||||
const speciesBaseValue = speciesStarterCosts[pokemonSpecies.getRootSpeciesId()];
|
||||
if (speciesBaseValue <= 3) {
|
||||
return EggTier.COMMON;
|
||||
} else if (speciesBaseValue <= 5) {
|
||||
return EggTier.GREAT;
|
||||
} else if (speciesBaseValue <= 7) {
|
||||
return EggTier.ULTRA;
|
||||
}
|
||||
return EggTier.MASTER;
|
||||
return speciesEggTiers[pokemonSpecies.getRootSpeciesId()];
|
||||
}
|
||||
|
@ -970,13 +970,16 @@ export class MoveEffectAttr extends MoveAttr {
|
||||
public lastHitOnly: boolean;
|
||||
/** Should this effect only apply on the first target hit? */
|
||||
public firstTargetOnly: boolean;
|
||||
/** Overrides the secondary effect chance for this attr if set. */
|
||||
public effectChanceOverride?: number;
|
||||
|
||||
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false) {
|
||||
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false, effectChanceOverride?: number) {
|
||||
super(selfTarget);
|
||||
this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY;
|
||||
this.trigger = trigger ?? MoveEffectTrigger.POST_APPLY;
|
||||
this.firstHitOnly = firstHitOnly;
|
||||
this.lastHitOnly = lastHitOnly;
|
||||
this.firstTargetOnly = firstTargetOnly;
|
||||
this.effectChanceOverride = effectChanceOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1001,15 +1004,15 @@ export class MoveEffectAttr extends MoveAttr {
|
||||
|
||||
/**
|
||||
* Gets the used move's additional effect chance.
|
||||
* If user's ability has MoveEffectChanceMultiplierAbAttr or IgnoreMoveEffectsAbAttr modifies the base chance.
|
||||
* Chance is modified by {@linkcode MoveEffectChanceMultiplierAbAttr} and {@linkcode IgnoreMoveEffectsAbAttr}.
|
||||
* @param user {@linkcode Pokemon} using this move
|
||||
* @param target {@linkcode Pokemon} target of this move
|
||||
* @param target {@linkcode Pokemon | Target} of this move
|
||||
* @param move {@linkcode Move} being used
|
||||
* @param selfEffect {@linkcode Boolean} if move targets user.
|
||||
* @returns Move chance value.
|
||||
* @param selfEffect `true` if move targets user.
|
||||
* @returns Move effect chance value.
|
||||
*/
|
||||
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): integer {
|
||||
const moveChance = new Utils.NumberHolder(move.chance);
|
||||
const moveChance = new Utils.NumberHolder(this.effectChanceOverride ?? move.chance);
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, moveChance, move, target, selfEffect, showAbility);
|
||||
|
||||
@ -1935,12 +1938,21 @@ export class IncrementMovePriorityAttr extends MoveAttr {
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class MultiHitAttr extends MoveAttr {
|
||||
/** This move's intrinsic multi-hit type. It should never be modified. */
|
||||
private readonly intrinsicMultiHitType: MultiHitType;
|
||||
/** This move's current multi-hit type. It may be temporarily modified by abilities (e.g., Battle Bond). */
|
||||
private multiHitType: MultiHitType;
|
||||
|
||||
constructor(multiHitType?: MultiHitType) {
|
||||
super();
|
||||
|
||||
this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5;
|
||||
this.intrinsicMultiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5;
|
||||
this.multiHitType = this.intrinsicMultiHitType;
|
||||
}
|
||||
|
||||
// Currently used by `battle_bond.test.ts`
|
||||
getMultiHitType(): MultiHitType {
|
||||
return this.multiHitType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1954,7 +1966,7 @@ export class MultiHitAttr extends MoveAttr {
|
||||
* @returns True
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const hitType = new Utils.NumberHolder(this.multiHitType);
|
||||
const hitType = new Utils.NumberHolder(this.intrinsicMultiHitType);
|
||||
applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType);
|
||||
this.multiHitType = hitType.value;
|
||||
|
||||
@ -2752,14 +2764,17 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
|
||||
|
||||
/**
|
||||
* Attribute used for moves that change stat stages
|
||||
* @param stats {@linkcode BattleStat} array of stats to be changed
|
||||
* @param stages stages by which to change the stats, from -6 to 6
|
||||
* @param selfTarget whether the changes are applied to the user (true) or the target (false)
|
||||
* @param condition {@linkcode MoveConditionFunc} optional condition to trigger the stat change
|
||||
* @param firstHitOnly whether the stat change only applies on the first hit of a multi hit move
|
||||
* @param moveEffectTrigger {@linkcode MoveEffectTrigger} the trigger for the effect to take place
|
||||
* @param firstTargetOnly whether, if this is a multi target move, to only apply the effect after the first target is hit, rather than once for each target
|
||||
* @param lastHitOnly whether the effect should only apply after the last hit of a multi hit move
|
||||
*
|
||||
* @param stats {@linkcode BattleStat} Array of stat(s) to change
|
||||
* @param stages How many stages to change the stat(s) by, [-6, 6]
|
||||
* @param selfTarget `true` if the move is self-targetting
|
||||
* @param condition {@linkcode MoveConditionFunc} Optional condition to be checked in order to apply the changes
|
||||
* @param showMessage `true` to display a message; default `true`
|
||||
* @param firstHitOnly `true` if only the first hit of a multi hit move should cause a stat stage change; default `false`
|
||||
* @param moveEffectTrigger {@linkcode MoveEffectTrigger} When the stat change should trigger; default {@linkcode MoveEffectTrigger.HIT}
|
||||
* @param firstTargetOnly `true` if a move that hits multiple pokemon should only trigger the stat change if it hits at least one pokemon, rather than once per hit pokemon; default `false`
|
||||
* @param lastHitOnly `true` if the effect should only apply after the last hit of a multi hit move; default `false`
|
||||
* @param effectChanceOverride Will override the move's normal secondary effect chance if specified
|
||||
*
|
||||
* @extends MoveEffectAttr
|
||||
* @see {@linkcode apply}
|
||||
@ -2767,14 +2782,14 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
|
||||
export class StatStageChangeAttr extends MoveEffectAttr {
|
||||
public stats: BattleStat[];
|
||||
public stages: integer;
|
||||
private condition: MoveConditionFunc | null;
|
||||
private condition?: MoveConditionFunc | null;
|
||||
private showMessage: boolean;
|
||||
|
||||
constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false) {
|
||||
super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly);
|
||||
constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false, effectChanceOverride?: number) {
|
||||
super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly, effectChanceOverride);
|
||||
this.stats = stats;
|
||||
this.stages = stages;
|
||||
this.condition = condition!; // TODO: is this bang correct?
|
||||
this.condition = condition;
|
||||
this.showMessage = showMessage;
|
||||
}
|
||||
|
||||
@ -4101,11 +4116,11 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr {
|
||||
* @param user {@linkcode Pokemon} using the move
|
||||
* @param target {@linkcode Pokemon} target of the move
|
||||
* @param move {@linkcode Move} with this attribute
|
||||
* @param args [0] {@linkcode Utils.IntegerHolder} The category of the move
|
||||
* @param args [0] {@linkcode Utils.NumberHolder} The category of the move
|
||||
* @returns true if the function succeeds
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const category = (args[0] as Utils.IntegerHolder);
|
||||
const category = (args[0] as Utils.NumberHolder);
|
||||
|
||||
if (user.getAlly() === target) {
|
||||
category.value = MoveCategory.STATUS;
|
||||
@ -4829,14 +4844,14 @@ export class GulpMissileTagAttr extends MoveEffectAttr {
|
||||
|
||||
/**
|
||||
* Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio.
|
||||
* @param {Pokemon} user The Pokemon using the move.
|
||||
* @param {Pokemon} target The Pokemon being targeted by the move.
|
||||
* @param {Move} move The move being used.
|
||||
* @param {any[]} args Additional arguments, if any.
|
||||
* @param user The Pokemon using the move.
|
||||
* @param _target N/A
|
||||
* @param move The move being used.
|
||||
* @param _args N/A
|
||||
* @returns Whether the BattlerTag is applied.
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean {
|
||||
if (!super.apply(user, _target, move, _args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -9556,9 +9571,8 @@ export function initMoves() {
|
||||
new AttackMove(Moves.TRIPLE_ARROWS, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8)
|
||||
.makesContact(false)
|
||||
.attr(HighCritAttr)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1)
|
||||
.attr(FlinchAttr)
|
||||
.partial(),
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 50)
|
||||
.attr(FlinchAttr),
|
||||
new AttackMove(Moves.INFERNAL_PARADE, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 15, 30, 0, 8)
|
||||
.attr(StatusEffectAttr, StatusEffect.BURN)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status ? 2 : 1),
|
||||
|
@ -128,6 +128,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
@ -149,7 +150,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: encounter.misc.trainerEggDescription,
|
||||
tier: EggTier.ULTRA
|
||||
tier: EggTier.EPIC
|
||||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
|
||||
@ -171,7 +172,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: encounter.misc.trainerEggDescription,
|
||||
tier: EggTier.GREAT
|
||||
tier: EggTier.RARE
|
||||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
|
||||
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]);
|
||||
|
@ -166,6 +166,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -64,6 +64,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -110,6 +110,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -276,6 +276,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -148,6 +148,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -102,6 +102,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -117,6 +117,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
||||
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
|
||||
.withCatchAllowed(true)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -84,6 +84,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -51,6 +51,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -52,6 +52,7 @@ export const FieldTripEncounter: MysteryEncounter =
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -122,6 +122,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -120,6 +120,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -76,6 +76,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -96,6 +96,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -50,6 +50,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -125,6 +125,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -61,6 +61,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -69,6 +69,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -54,6 +54,7 @@ export const SafariZoneEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -62,6 +62,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -88,6 +88,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -58,6 +58,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -196,6 +196,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
@ -493,7 +494,7 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number)
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: eggDescription,
|
||||
tier: EggTier.GREAT
|
||||
tier: EggTier.RARE
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -117,6 +117,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -94,6 +94,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -52,6 +52,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -54,6 +54,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -125,6 +125,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -125,6 +125,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
|
@ -190,7 +190,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
secondaryPokemon?: PlayerPokemon[];
|
||||
|
||||
// #region Post-construct / Auto-populated params
|
||||
|
||||
localizationKey: string;
|
||||
/**
|
||||
* Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter
|
||||
*/
|
||||
@ -264,6 +264,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
Object.assign(this, encounter);
|
||||
}
|
||||
this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON;
|
||||
this.localizationKey = this.localizationKey ?? "";
|
||||
this.dialogue = this.dialogue ?? {};
|
||||
this.spriteConfigs = this.spriteConfigs ? [ ...this.spriteConfigs ] : [];
|
||||
// Default max is 1 for ROGUE encounters, 2 for others
|
||||
@ -528,6 +529,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
|
||||
enemyPartyConfigs: EnemyPartyConfig[] = [];
|
||||
|
||||
localizationKey: string = "";
|
||||
dialogue: MysteryEncounterDialogue = {};
|
||||
requirements: EncounterSceneRequirement[] = [];
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
@ -632,6 +634,16 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the localization key used by the encounter
|
||||
* @param localizationKey the string used as the key
|
||||
* @returns `this`
|
||||
*/
|
||||
setLocalizationKey(localizationKey: string): this {
|
||||
this.localizationKey = localizationKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* OPTIONAL
|
||||
*/
|
||||
|
@ -1,6 +1,6 @@
|
||||
export enum EggTier {
|
||||
COMMON,
|
||||
GREAT,
|
||||
ULTRA,
|
||||
MASTER
|
||||
RARE,
|
||||
EPIC,
|
||||
LEGENDARY
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boo
|
||||
return isIncrease ? "battle:statRose" : "battle:statFell";
|
||||
} else if (stages === 2) {
|
||||
return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell";
|
||||
} else if (stages <= 6) {
|
||||
} else if (stages > 2 && stages <= 6) {
|
||||
return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell";
|
||||
}
|
||||
return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower";
|
||||
|
@ -983,7 +983,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
|
||||
}
|
||||
|
||||
statHolder.value = Utils.clampInt(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
|
||||
statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
|
||||
|
||||
this.setStat(s, statHolder.value);
|
||||
}
|
||||
@ -1417,10 +1417,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @returns {boolean} Whether the ability is present and active
|
||||
*/
|
||||
hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean {
|
||||
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).id === ability) {
|
||||
if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) {
|
||||
return true;
|
||||
}
|
||||
if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().id === ability) {
|
||||
if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -2684,7 +2684,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*/
|
||||
apply(source: Pokemon, move: Move): HitResult {
|
||||
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
if (move.category === MoveCategory.STATUS) {
|
||||
const moveCategory = new Utils.NumberHolder(move.category);
|
||||
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory);
|
||||
if (moveCategory.value === MoveCategory.STATUS) {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled);
|
||||
|
||||
@ -2758,7 +2760,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
if (damage > 0) {
|
||||
if (source.isPlayer()) {
|
||||
this.scene.validateAchvs(DamageAchv, damage);
|
||||
this.scene.validateAchvs(DamageAchv, new Utils.NumberHolder(damage));
|
||||
if (damage > this.scene.gameData.gameStats.highestDamage) {
|
||||
this.scene.gameData.gameStats.highestDamage = damage;
|
||||
}
|
||||
|
@ -3084,11 +3084,12 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
|
||||
* Steals an item from a set of target Pokemon.
|
||||
* This prioritizes high-tier held items when selecting the item to steal.
|
||||
* @param pokemon The {@linkcode Pokemon} holding this item
|
||||
* @param target The {@linkcode Pokemon} to steal from (optional)
|
||||
* @param _args N/A
|
||||
* @returns `true` if an item was stolen; false otherwise.
|
||||
*/
|
||||
override apply(pokemon: Pokemon, ..._args: unknown[]): boolean {
|
||||
const opponents = this.getTargets(pokemon);
|
||||
override apply(pokemon: Pokemon, target?: Pokemon, ..._args: unknown[]): boolean {
|
||||
const opponents = this.getTargets(pokemon, target);
|
||||
|
||||
if (!opponents.length) {
|
||||
return false;
|
||||
@ -3187,7 +3188,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier {
|
||||
* @see {@linkcode HeldItemTransferModifier}
|
||||
*/
|
||||
export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier {
|
||||
private chance: number;
|
||||
public readonly chance: number;
|
||||
|
||||
constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) {
|
||||
super(type, pokemonId, stackCount);
|
||||
@ -3634,7 +3635,7 @@ export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer
|
||||
}
|
||||
|
||||
if (!isPlayer) {
|
||||
scene.clearEnemyHeldItemModifiers();
|
||||
scene.clearEnemyHeldItemModifiers(pokemon);
|
||||
}
|
||||
|
||||
heldItemsOverride.forEach(item => {
|
||||
|
@ -156,6 +156,7 @@ class DefaultOverrides {
|
||||
readonly EGG_VARIANT_OVERRIDE: VariantTier | null = null;
|
||||
readonly EGG_FREE_GACHA_PULLS_OVERRIDE: boolean = false;
|
||||
readonly EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0;
|
||||
readonly UNLIMITED_EGG_COUNT_OVERRIDE: boolean = false;
|
||||
|
||||
// -------------------------
|
||||
// MYSTERY ENCOUNTER OVERRIDES
|
||||
|
23
src/phases/check-status-effect-phase.ts
Normal file
23
src/phases/check-status-effect-phase.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
|
||||
import { Phase } from "#app/phase";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
|
||||
export class CheckStatusEffectPhase extends Phase {
|
||||
private order : BattlerIndex[];
|
||||
constructor(scene : BattleScene, order : BattlerIndex[]) {
|
||||
super(scene);
|
||||
this.scene = scene;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
start() {
|
||||
const field = this.scene.getField();
|
||||
for (const o of this.order) {
|
||||
if (field[o].status && field[o].status.isPostTurn()) {
|
||||
this.scene.unshiftPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
||||
}
|
||||
}
|
||||
this.end();
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase {
|
||||
this.scene.gameData.gameStats.highestLevel = this.level;
|
||||
}
|
||||
|
||||
this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level));
|
||||
this.scene.validateAchvs(LevelAchv, new Utils.NumberHolder(this.level));
|
||||
|
||||
const pokemon = this.getPokemon();
|
||||
const prevStats = pokemon.stats.slice(0);
|
||||
|
@ -8,10 +8,6 @@ import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms";
|
||||
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
|
||||
import { Type } from "#app/data/type";
|
||||
import { getTerrainBlockMessage } from "#app/data/weather";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { StatusEffect } from "#app/enums/status-effect";
|
||||
import { MoveUsedEvent } from "#app/events/battle-scene";
|
||||
import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
@ -20,7 +16,11 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||
import * as Utils from "#app/utils";
|
||||
import { BooleanHolder, NumberHolder } from "#app/utils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class MovePhase extends BattlePhase {
|
||||
@ -89,7 +89,7 @@ export class MovePhase extends BattlePhase {
|
||||
this.cancelled = true;
|
||||
}
|
||||
|
||||
public start() {
|
||||
public start(): void {
|
||||
super.start();
|
||||
|
||||
console.log(Moves[this.move.moveId]);
|
||||
@ -140,7 +140,7 @@ export class MovePhase extends BattlePhase {
|
||||
}
|
||||
|
||||
/** Check for cancellation edge cases - no targets remaining, or {@linkcode Moves.NONE} is in the queue */
|
||||
protected resolveFinalPreMoveCancellationChecks() {
|
||||
protected resolveFinalPreMoveCancellationChecks(): void {
|
||||
const targets = this.getActiveTargetPokemon();
|
||||
const moveQueue = this.pokemon.getMoveQueue();
|
||||
|
||||
@ -150,14 +150,14 @@ export class MovePhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
|
||||
public getActiveTargetPokemon() {
|
||||
public getActiveTargetPokemon(): Pokemon[] {
|
||||
return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects.
|
||||
*/
|
||||
protected resolvePreMoveStatusEffects() {
|
||||
protected resolvePreMoveStatusEffects(): void {
|
||||
if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) {
|
||||
this.pokemon.status.incrementTurn();
|
||||
let activated = false;
|
||||
@ -198,7 +198,7 @@ export class MovePhase extends BattlePhase {
|
||||
* Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed.
|
||||
* Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful.
|
||||
*/
|
||||
protected lapsePreMoveAndMoveTags() {
|
||||
protected lapsePreMoveAndMoveTags(): void {
|
||||
this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE);
|
||||
|
||||
// TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked?
|
||||
@ -207,7 +207,7 @@ export class MovePhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
|
||||
protected useMove() {
|
||||
protected useMove(): void {
|
||||
const targets = this.getActiveTargetPokemon();
|
||||
const moveQueue = this.pokemon.getMoveQueue();
|
||||
|
||||
@ -217,7 +217,8 @@ export class MovePhase extends BattlePhase {
|
||||
this.showMoveText();
|
||||
|
||||
// TODO: Clean up implementation of two-turn moves.
|
||||
if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used
|
||||
if (moveQueue.length > 0) {
|
||||
// Using .shift here clears out two turn moves once they've been used
|
||||
this.ignorePp = moveQueue.shift()?.ignorePP ?? false;
|
||||
}
|
||||
|
||||
@ -226,7 +227,7 @@ export class MovePhase extends BattlePhase {
|
||||
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
|
||||
|
||||
this.move.usePp(ppUsed);
|
||||
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
|
||||
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
|
||||
}
|
||||
|
||||
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
|
||||
@ -275,7 +276,7 @@ export class MovePhase extends BattlePhase {
|
||||
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
|
||||
|
||||
let failedText: string | undefined;
|
||||
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new Utils.BooleanHolder(false));
|
||||
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false));
|
||||
|
||||
if (failureMessage) {
|
||||
failedText = failureMessage;
|
||||
@ -299,7 +300,7 @@ export class MovePhase extends BattlePhase {
|
||||
* Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`,
|
||||
* then ends the phase.
|
||||
*/
|
||||
public end() {
|
||||
public end(): void {
|
||||
if (!this.followUp && this.canMove()) {
|
||||
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex()));
|
||||
}
|
||||
@ -313,7 +314,7 @@ export class MovePhase extends BattlePhase {
|
||||
*
|
||||
* TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability.
|
||||
*/
|
||||
public getPpIncreaseFromPressure(targets: Pokemon[]) {
|
||||
public getPpIncreaseFromPressure(targets: Pokemon[]): number {
|
||||
const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr));
|
||||
return foesWithPressure.length;
|
||||
}
|
||||
@ -323,10 +324,10 @@ export class MovePhase extends BattlePhase {
|
||||
* - Move redirection abilities, effects, etc.
|
||||
* - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`).
|
||||
*/
|
||||
protected resolveRedirectTarget() {
|
||||
protected resolveRedirectTarget(): void {
|
||||
if (this.targets.length === 1) {
|
||||
const currentTarget = this.targets[0];
|
||||
const redirectTarget = new Utils.NumberHolder(currentTarget);
|
||||
const redirectTarget = new NumberHolder(currentTarget);
|
||||
|
||||
// check move redirection abilities of every pokemon *except* the user.
|
||||
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget));
|
||||
@ -372,7 +373,7 @@ export class MovePhase extends BattlePhase {
|
||||
* If there is no last attacker, or they are no longer on the field, a message is displayed and the
|
||||
* move is marked for failure.
|
||||
*/
|
||||
protected resolveCounterAttackTarget() {
|
||||
protected resolveCounterAttackTarget(): void {
|
||||
if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) {
|
||||
if (this.pokemon.turnData.attacksReceived.length) {
|
||||
this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex;
|
||||
@ -411,7 +412,7 @@ export class MovePhase extends BattlePhase {
|
||||
*
|
||||
* TODO: handle charge moves more gracefully
|
||||
*/
|
||||
protected handlePreMoveFailures() {
|
||||
protected handlePreMoveFailures(): void {
|
||||
if (this.cancelled || this.failed) {
|
||||
if (this.failed) {
|
||||
const ppUsed = this.ignorePp ? 0 : 1;
|
||||
|
@ -6,7 +6,6 @@ import { StatusEffect } from "#app/enums/status-effect";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
||||
|
||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||
private statusEffect?: StatusEffect | undefined;
|
||||
@ -33,9 +32,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||
pokemon.updateInfo(true);
|
||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
||||
this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined));
|
||||
if (pokemon.status?.isPostTurn()) {
|
||||
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex));
|
||||
}
|
||||
this.end();
|
||||
});
|
||||
return;
|
||||
|
@ -196,7 +196,7 @@ export class TitlePhase extends Phase {
|
||||
this.scene.gameMode = getGameMode(GameModes.DAILY);
|
||||
|
||||
this.scene.setSeed(seed);
|
||||
this.scene.resetSeed(1);
|
||||
this.scene.resetSeed(0);
|
||||
|
||||
this.scene.money = this.scene.gameMode.getStartingMoney();
|
||||
|
||||
|
@ -13,10 +13,10 @@ import { BerryPhase } from "./berry-phase";
|
||||
import { FieldPhase } from "./field-phase";
|
||||
import { MoveHeaderPhase } from "./move-header-phase";
|
||||
import { MovePhase } from "./move-phase";
|
||||
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
||||
import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||
import { TurnEndPhase } from "./turn-end-phase";
|
||||
import { WeatherEffectPhase } from "./weather-effect-phase";
|
||||
import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { TrickRoomTag } from "#app/data/arena-tag";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
@ -158,7 +158,7 @@ export class TurnStartPhase extends FieldPhase {
|
||||
if (!queuedMove) {
|
||||
continue;
|
||||
}
|
||||
const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move);
|
||||
const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move);
|
||||
if (move.getMove().hasAttr(MoveHeaderAttr)) {
|
||||
this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move));
|
||||
}
|
||||
@ -206,11 +206,8 @@ export class TurnStartPhase extends FieldPhase {
|
||||
|
||||
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
|
||||
|
||||
for (const o of moveOrder) {
|
||||
if (field[o].status && field[o].status.isPostTurn()) {
|
||||
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
||||
}
|
||||
}
|
||||
/** Add a new phase to check who should be taking status damage */
|
||||
this.scene.pushPhase(new CheckStatusEffectPhase(this.scene, moveOrder));
|
||||
|
||||
this.scene.pushPhase(new BerryPhase(this.scene));
|
||||
this.scene.pushPhase(new TurnEndPhase(this.scene));
|
||||
|
@ -81,6 +81,8 @@ const namespaceMap = {
|
||||
miscDialogue: "dialogue-misc",
|
||||
battleSpecDialogue: "dialogue-final-boss",
|
||||
doubleBattleDialogue: "dialogue-double-battle",
|
||||
splashMessages: "splash-texts",
|
||||
mysteryEncounterMessages: "mystery-encounter-texts",
|
||||
};
|
||||
|
||||
//#region Functions
|
||||
|
@ -109,7 +109,7 @@ export class DamageAchv extends Achv {
|
||||
damageAmount: integer;
|
||||
|
||||
constructor(localizationKey: string, name: string, damageAmount: integer, iconImage: string, score: integer) {
|
||||
super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.damageAmount);
|
||||
super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.damageAmount);
|
||||
this.damageAmount = damageAmount;
|
||||
}
|
||||
}
|
||||
@ -118,7 +118,7 @@ export class HealAchv extends Achv {
|
||||
healAmount: integer;
|
||||
|
||||
constructor(localizationKey: string, name: string, healAmount: integer, iconImage: string, score: integer) {
|
||||
super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.healAmount);
|
||||
super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.healAmount);
|
||||
this.healAmount = healAmount;
|
||||
}
|
||||
}
|
||||
@ -127,7 +127,7 @@ export class LevelAchv extends Achv {
|
||||
level: integer;
|
||||
|
||||
constructor(localizationKey: string, name: string, level: integer, iconImage: string, score: integer) {
|
||||
super(localizationKey, name, "", iconImage, score, (scene: BattleScene, args: any[]) => (args[0] as Utils.IntegerHolder).value >= this.level);
|
||||
super(localizationKey, name, "", iconImage, score, (scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.level);
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Arena } from "../field/arena";
|
||||
import { ArenaTag } from "../data/arena-tag";
|
||||
import { ArenaTag, loadArenaTag } from "../data/arena-tag";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { Weather } from "../data/weather";
|
||||
import { Terrain } from "#app/data/terrain";
|
||||
@ -15,6 +15,10 @@ export default class ArenaData {
|
||||
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
|
||||
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
|
||||
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
|
||||
this.tags = sourceArena ? sourceArena.tags : [];
|
||||
this.tags = [];
|
||||
|
||||
if (source.tags) {
|
||||
this.tags = source.tags.map(t => loadArenaTag(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import { TrainerVariant } from "#app/field/trainer";
|
||||
import { Variant } from "#app/data/variant";
|
||||
import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad";
|
||||
import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard";
|
||||
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||
import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||
import * as Modifier from "#app/modifier/modifier";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import ChallengeData from "#app/system/challenge-data";
|
||||
@ -50,6 +50,7 @@ import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatch
|
||||
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
|
||||
import { ArenaTrapTag } from "#app/data/arena-tag";
|
||||
|
||||
export const defaultStarterSpecies: Species[] = [
|
||||
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
|
||||
@ -1085,8 +1086,18 @@ export class GameData {
|
||||
|
||||
scene.arena.terrain = sessionData.arena.terrain;
|
||||
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct?
|
||||
// TODO
|
||||
//scene.arena.tags = sessionData.arena.tags;
|
||||
|
||||
scene.arena.tags = sessionData.arena.tags;
|
||||
if (scene.arena.tags) {
|
||||
for (const tag of scene.arena.tags) {
|
||||
if (tag instanceof ArenaTrapTag) {
|
||||
const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag;
|
||||
scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers));
|
||||
} else {
|
||||
scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const modifierData of sessionData.modifiers) {
|
||||
const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]);
|
||||
@ -1125,10 +1136,16 @@ export class GameData {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the session data at the given slot when overwriting a save file
|
||||
* For deleting the session of a finished run, use {@linkcode tryClearSession}
|
||||
* @param slotId the slot to clear
|
||||
* @returns Promise with result `true` if the session was deleted successfully, `false` otherwise
|
||||
*/
|
||||
deleteSession(slotId: integer): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
if (bypassLogin) {
|
||||
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`);
|
||||
localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`);
|
||||
return resolve(true);
|
||||
}
|
||||
|
||||
@ -1139,7 +1156,7 @@ export class GameData {
|
||||
Utils.apiFetch(`savedata/session/delete?slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => {
|
||||
if (response.ok) {
|
||||
loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct?
|
||||
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`);
|
||||
localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`);
|
||||
resolve(true);
|
||||
}
|
||||
return response.text();
|
||||
@ -1190,7 +1207,9 @@ export class GameData {
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to clear session data. After session data is removed, attempt to update user info so the menu updates
|
||||
* Attempt to clear session data after the end of a run
|
||||
* After session data is removed, attempt to update user info so the menu updates
|
||||
* To delete an unfinished run instead, use {@linkcode deleteSession}
|
||||
*/
|
||||
async tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> {
|
||||
let result: [boolean, boolean] = [ false, false ];
|
||||
@ -1204,7 +1223,7 @@ export class GameData {
|
||||
|
||||
if (response.ok) {
|
||||
loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct?
|
||||
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`);
|
||||
localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`);
|
||||
}
|
||||
|
||||
const jsonResponse: PokerogueApiClearSessionData = await response.json();
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { BattleStyle } from "#app/enums/battle-style";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
||||
import i18next, { initI18n } from "#app/plugins/i18n";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
|
||||
describe("Ability Timing", () => {
|
||||
@ -32,11 +32,10 @@ describe("Ability Timing", () => {
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.INTIMIDATE)
|
||||
.ability(Abilities.BALL_FETCH);
|
||||
vi.spyOn(i18next, "t");
|
||||
});
|
||||
|
||||
it("should trigger after switch check", async () => {
|
||||
initI18n();
|
||||
i18next.changeLanguage("en");
|
||||
game.settings.battleStyle = BattleStyle.SWITCH;
|
||||
await game.classicMode.runToSummon([ Species.EEVEE, Species.FEEBAS ]);
|
||||
|
||||
@ -46,7 +45,6 @@ describe("Ability Timing", () => {
|
||||
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
|
||||
|
||||
await game.phaseInterceptor.to("MessagePhase");
|
||||
const message = game.textInterceptor.getLatestMessage();
|
||||
expect(message).toContain("battle:statFell");
|
||||
expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 }));
|
||||
}, 5000);
|
||||
});
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move";
|
||||
import { Status, StatusEffect } from "#app/data/status-effect";
|
||||
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
|
||||
describe("Abilities - BATTLE BOND", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
const baseForm = 1;
|
||||
const ashForm = 2;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
@ -24,40 +26,68 @@ describe("Abilities - BATTLE BOND", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const moveToUse = Moves.SPLASH;
|
||||
game.override.battleType("single");
|
||||
game.override.ability(Abilities.BATTLE_BOND);
|
||||
game.override.moveset([ moveToUse ]);
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
||||
game.override.battleType("single")
|
||||
.startingWave(4) // Leads to arena reset on Wave 5 trainer battle
|
||||
.ability(Abilities.BATTLE_BOND)
|
||||
.starterForms({ [Species.GRENINJA]: ashForm, })
|
||||
.moveset([ Moves.SPLASH, Moves.WATER_SHURIKEN ])
|
||||
.enemySpecies(Species.BULBASAUR)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.startingLevel(100) // Avoid levelling up
|
||||
.enemyLevel(1000); // Avoid opponent dying before `doKillOpponents()`
|
||||
});
|
||||
|
||||
test(
|
||||
"check if fainted pokemon switches to base form on arena reset",
|
||||
async () => {
|
||||
const baseForm = 1;
|
||||
const ashForm = 2;
|
||||
game.override.startingWave(4);
|
||||
game.override.starterForms({
|
||||
[Species.GRENINJA]: ashForm,
|
||||
});
|
||||
it("check if fainted pokemon switches to base form on arena reset", async () => {
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP, Species.GRENINJA ]);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP, Species.GRENINJA ]);
|
||||
const greninja = game.scene.getParty()[1];
|
||||
expect(greninja.formIndex).toBe(ashForm);
|
||||
|
||||
const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA);
|
||||
expect(greninja).toBeDefined();
|
||||
expect(greninja!.formIndex).toBe(ashForm);
|
||||
greninja.hp = 0;
|
||||
greninja.status = new Status(StatusEffect.FAINT);
|
||||
expect(greninja.isFainted()).toBe(true);
|
||||
|
||||
greninja!.hp = 0;
|
||||
greninja!.status = new Status(StatusEffect.FAINT);
|
||||
expect(greninja!.isFainted()).toBe(true);
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.doKillOpponents();
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.doSelectModifier();
|
||||
await game.phaseInterceptor.to("QuietFormChangePhase");
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.doKillOpponents();
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
game.doSelectModifier();
|
||||
await game.phaseInterceptor.to(QuietFormChangePhase);
|
||||
expect(greninja.formIndex).toBe(baseForm);
|
||||
});
|
||||
|
||||
expect(greninja!.formIndex).toBe(baseForm);
|
||||
},
|
||||
);
|
||||
it("should not keep buffing Water Shuriken after Greninja switches to base form", async () => {
|
||||
await game.classicMode.startBattle([ Species.GRENINJA ]);
|
||||
|
||||
const waterShuriken = allMoves[Moves.WATER_SHURIKEN];
|
||||
vi.spyOn(waterShuriken, "calculateBattlePower");
|
||||
|
||||
let actualMultiHitType: MultiHitType | null = null;
|
||||
const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0];
|
||||
vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => {
|
||||
actualMultiHitType = multiHitAttr.getMultiHitType();
|
||||
return 3;
|
||||
});
|
||||
|
||||
// Wave 4: Use Water Shuriken in Ash form
|
||||
let expectedBattlePower = 20;
|
||||
let expectedMultiHitType = MultiHitType._3;
|
||||
|
||||
game.move.select(Moves.WATER_SHURIKEN);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower);
|
||||
expect(actualMultiHitType).toBe(expectedMultiHitType);
|
||||
|
||||
await game.doKillOpponents();
|
||||
await game.toNextWave();
|
||||
|
||||
// Wave 5: Use Water Shuriken in base form
|
||||
expectedBattlePower = 15;
|
||||
expectedMultiHitType = MultiHitType._2_TO_5;
|
||||
|
||||
game.move.select(Moves.WATER_SHURIKEN);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower);
|
||||
expect(actualMultiHitType).toBe(expectedMultiHitType);
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { toDmgValue } from "#app/utils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
@ -222,4 +223,17 @@ describe("Abilities - Disguise", () => {
|
||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||
expect(mimikyu.hp).toBe(maxHp - disguiseDamage);
|
||||
});
|
||||
|
||||
it("doesn't trigger if user is behind a substitute", async () => {
|
||||
game.override
|
||||
.enemyMoveset(Moves.SUBSTITUTE)
|
||||
.moveset(Moves.POWER_TRIP);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.POWER_TRIP);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.formIndex).toBe(disguisedForm);
|
||||
});
|
||||
});
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { Stat } from "#enums/stat";
|
||||
|
||||
describe("Abilities - Gulp Missile", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -40,8 +41,9 @@ describe("Abilities - Gulp Missile", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.disableCrits()
|
||||
.battleType("single")
|
||||
.moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH ])
|
||||
.moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH, Moves.SUBSTITUTE ])
|
||||
.enemySpecies(Species.SNORLAX)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
@ -234,6 +236,25 @@ describe("Abilities - Gulp Missile", () => {
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(-1);
|
||||
});
|
||||
|
||||
it("doesn't trigger if user is behind a substitute", async () => {
|
||||
game.override
|
||||
.enemyAbility(Abilities.STURDY)
|
||||
.enemyMoveset([ Moves.SPLASH, Moves.POWER_TRIP ]);
|
||||
await game.classicMode.startBattle([ Species.CRAMORANT ]);
|
||||
|
||||
game.move.select(Moves.SURF);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM);
|
||||
|
||||
game.move.select(Moves.SUBSTITUTE);
|
||||
await game.forceEnemyMove(Moves.POWER_TRIP);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM);
|
||||
});
|
||||
|
||||
it("cannot be suppressed", async () => {
|
||||
game.override.enemyMoveset(Moves.GASTRO_ACID);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
|
||||
@ -36,7 +37,7 @@ describe("Abilities - Ice Face", () => {
|
||||
});
|
||||
|
||||
it("takes no damage from physical move and transforms to Noice", async () => {
|
||||
await game.startBattle([ Species.HITMONLEE ]);
|
||||
await game.classicMode.startBattle([ Species.HITMONLEE ]);
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
|
||||
@ -52,7 +53,7 @@ describe("Abilities - Ice Face", () => {
|
||||
it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => {
|
||||
game.override.moveset([ Moves.SURGING_STRIKES ]);
|
||||
game.override.enemyLevel(1);
|
||||
await game.startBattle([ Species.HITMONLEE ]);
|
||||
await game.classicMode.startBattle([ Species.HITMONLEE ]);
|
||||
|
||||
game.move.select(Moves.SURGING_STRIKES);
|
||||
|
||||
@ -78,7 +79,7 @@ describe("Abilities - Ice Face", () => {
|
||||
});
|
||||
|
||||
it("takes damage from special moves", async () => {
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.ICE_BEAM);
|
||||
|
||||
@ -92,7 +93,7 @@ describe("Abilities - Ice Face", () => {
|
||||
});
|
||||
|
||||
it("takes effects from status moves", async () => {
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.TOXIC_THREAD);
|
||||
|
||||
@ -108,7 +109,7 @@ describe("Abilities - Ice Face", () => {
|
||||
game.override.moveset([ Moves.QUICK_ATTACK ]);
|
||||
game.override.enemyMoveset([ Moves.HAIL, Moves.HAIL, Moves.HAIL, Moves.HAIL ]);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.QUICK_ATTACK);
|
||||
|
||||
@ -130,7 +131,7 @@ describe("Abilities - Ice Face", () => {
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
||||
game.override.moveset([ Moves.SNOWSCAPE ]);
|
||||
|
||||
await game.startBattle([ Species.EISCUE, Species.NINJASK ]);
|
||||
await game.classicMode.startBattle([ Species.EISCUE, Species.NINJASK ]);
|
||||
|
||||
game.move.select(Moves.SNOWSCAPE);
|
||||
|
||||
@ -157,7 +158,7 @@ describe("Abilities - Ice Face", () => {
|
||||
game.override.enemySpecies(Species.SHUCKLE);
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
||||
|
||||
await game.startBattle([ Species.EISCUE ]);
|
||||
await game.classicMode.startBattle([ Species.EISCUE ]);
|
||||
|
||||
game.move.select(Moves.HAIL);
|
||||
const eiscue = game.scene.getPlayerPokemon()!;
|
||||
@ -176,7 +177,7 @@ describe("Abilities - Ice Face", () => {
|
||||
it("persists form change when switched out", async () => {
|
||||
game.override.enemyMoveset([ Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK ]);
|
||||
|
||||
await game.startBattle([ Species.EISCUE, Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.EISCUE, Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.ICE_BEAM);
|
||||
|
||||
@ -205,7 +206,7 @@ describe("Abilities - Ice Face", () => {
|
||||
[Species.EISCUE]: noiceForm,
|
||||
});
|
||||
|
||||
await game.startBattle([ Species.EISCUE ]);
|
||||
await game.classicMode.startBattle([ Species.EISCUE ]);
|
||||
|
||||
const eiscue = game.scene.getPlayerPokemon()!;
|
||||
|
||||
@ -222,10 +223,23 @@ describe("Abilities - Ice Face", () => {
|
||||
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
|
||||
});
|
||||
|
||||
it("doesn't trigger if user is behind a substitute", async () => {
|
||||
game.override
|
||||
.enemyMoveset(Moves.SUBSTITUTE)
|
||||
.moveset(Moves.POWER_TRIP);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.POWER_TRIP);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.formIndex).toBe(icefaceForm);
|
||||
});
|
||||
|
||||
it("cannot be suppressed", async () => {
|
||||
game.override.moveset([ Moves.GASTRO_ACID ]);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.GASTRO_ACID);
|
||||
|
||||
@ -241,7 +255,7 @@ describe("Abilities - Ice Face", () => {
|
||||
it("cannot be swapped with another ability", async () => {
|
||||
game.override.moveset([ Moves.SKILL_SWAP ]);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.SKILL_SWAP);
|
||||
|
||||
@ -257,7 +271,7 @@ describe("Abilities - Ice Face", () => {
|
||||
it("cannot be copied", async () => {
|
||||
game.override.ability(Abilities.TRACE);
|
||||
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
game.move.select(Moves.SIMPLE_BEAM);
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { allMoves } from "#app/data/move";
|
||||
|
||||
|
||||
describe("Abilities - Sheer Force", () => {
|
||||
@ -174,5 +175,31 @@ describe("Abilities - Sheer Force", () => {
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
|
||||
const moveToUse = Moves.CRUNCH;
|
||||
game.override.enemyAbility(Abilities.COLOR_CHANGE)
|
||||
.ability(Abilities.COLOR_CHANGE)
|
||||
.moveset(moveToUse)
|
||||
.enemyMoveset(moveToUse);
|
||||
|
||||
await game.classicMode.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
|
||||
const pidgeot = game.scene.getParty()[0];
|
||||
const onix = game.scene.getEnemyParty()[0];
|
||||
|
||||
pidgeot.stats[Stat.DEF] = 10000;
|
||||
onix.stats[Stat.DEF] = 10000;
|
||||
|
||||
game.move.select(moveToUse);
|
||||
await game.toNextTurn();
|
||||
|
||||
// Check that both Pokemon's Color Change activated
|
||||
const expectedTypes = [ allMoves[moveToUse].type ];
|
||||
expect(pidgeot.getTypes()).toStrictEqual(expectedTypes);
|
||||
expect(onix.getTypes()).toStrictEqual(expectedTypes);
|
||||
});
|
||||
|
||||
//TODO King's Rock Interaction Unit Test
|
||||
});
|
||||
|
@ -55,7 +55,7 @@ describe("Egg Generation Tests", () => {
|
||||
let gachaSpeciesCount = 0;
|
||||
|
||||
for (let i = 0; i < EGG_HATCH_COUNT; i++) {
|
||||
const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId;
|
||||
const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.LEGENDARY }).generatePlayerPokemon(scene).species.speciesId;
|
||||
if (result === expectedSpecies) {
|
||||
gachaSpeciesCount++;
|
||||
}
|
||||
@ -82,7 +82,7 @@ describe("Egg Generation Tests", () => {
|
||||
});
|
||||
it("should return an rare tier egg", () => {
|
||||
const scene = game.scene;
|
||||
const expectedTier = EggTier.GREAT;
|
||||
const expectedTier = EggTier.RARE;
|
||||
|
||||
const result = new Egg({ scene, tier: expectedTier }).tier;
|
||||
|
||||
@ -90,7 +90,7 @@ describe("Egg Generation Tests", () => {
|
||||
});
|
||||
it("should return an epic tier egg", () => {
|
||||
const scene = game.scene;
|
||||
const expectedTier = EggTier.ULTRA;
|
||||
const expectedTier = EggTier.EPIC;
|
||||
|
||||
const result = new Egg({ scene, tier: expectedTier }).tier;
|
||||
|
||||
@ -98,7 +98,7 @@ describe("Egg Generation Tests", () => {
|
||||
});
|
||||
it("should return an legendary tier egg", () => {
|
||||
const scene = game.scene;
|
||||
const expectedTier = EggTier.MASTER;
|
||||
const expectedTier = EggTier.LEGENDARY;
|
||||
|
||||
const result = new Egg({ scene, tier: expectedTier }).tier;
|
||||
|
||||
@ -200,7 +200,7 @@ describe("Egg Generation Tests", () => {
|
||||
const scene = game.scene;
|
||||
const expectedEggTier = EggTier.COMMON;
|
||||
|
||||
const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).tier;
|
||||
const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).tier;
|
||||
|
||||
expect(result).toBe(expectedEggTier);
|
||||
});
|
||||
@ -208,7 +208,7 @@ describe("Egg Generation Tests", () => {
|
||||
const scene = game.scene;
|
||||
const expectedHatchWaves = 10;
|
||||
|
||||
const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).hatchWaves;
|
||||
const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).hatchWaves;
|
||||
|
||||
expect(result).toBe(expectedHatchWaves);
|
||||
});
|
||||
@ -229,7 +229,7 @@ describe("Egg Generation Tests", () => {
|
||||
|
||||
const result = new EggData(legacyEgg).toEgg();
|
||||
|
||||
expect(result.tier).toBe(EggTier.GREAT);
|
||||
expect(result.tier).toBe(EggTier.RARE);
|
||||
expect(result.id).toBe(legacyEgg.id);
|
||||
expect(result.timestamp).toBe(legacyEgg.timestamp);
|
||||
expect(result.hatchWaves).toBe(legacyEgg.hatchWaves);
|
||||
@ -241,9 +241,9 @@ describe("Egg Generation Tests", () => {
|
||||
|
||||
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.COMMON });
|
||||
|
||||
expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 1);
|
||||
});
|
||||
it("should increase legendary egg pity by two", () => {
|
||||
const scene = game.scene;
|
||||
@ -251,9 +251,9 @@ describe("Egg Generation Tests", () => {
|
||||
|
||||
new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true, tier: EggTier.COMMON });
|
||||
|
||||
expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 2);
|
||||
expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1);
|
||||
expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 2);
|
||||
});
|
||||
it("should not increase manaphy egg count if bulbasaurs are pulled", () => {
|
||||
const scene = game.scene;
|
||||
@ -277,7 +277,7 @@ describe("Egg Generation Tests", () => {
|
||||
const scene = game.scene;
|
||||
const startingRareEggsPulled = scene.gameData.gameStats.rareEggsPulled;
|
||||
|
||||
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.GREAT });
|
||||
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.RARE });
|
||||
|
||||
expect(scene.gameData.gameStats.rareEggsPulled).toBe(startingRareEggsPulled + 1);
|
||||
});
|
||||
@ -285,7 +285,7 @@ describe("Egg Generation Tests", () => {
|
||||
const scene = game.scene;
|
||||
const startingEpicEggsPulled = scene.gameData.gameStats.epicEggsPulled;
|
||||
|
||||
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.ULTRA });
|
||||
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.EPIC });
|
||||
|
||||
expect(scene.gameData.gameStats.epicEggsPulled).toBe(startingEpicEggsPulled + 1);
|
||||
});
|
||||
@ -293,7 +293,7 @@ describe("Egg Generation Tests", () => {
|
||||
const scene = game.scene;
|
||||
const startingLegendaryEggsPulled = scene.gameData.gameStats.legendaryEggsPulled;
|
||||
|
||||
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.MASTER });
|
||||
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.LEGENDARY });
|
||||
|
||||
expect(scene.gameData.gameStats.legendaryEggsPulled).toBe(startingLegendaryEggsPulled + 1);
|
||||
});
|
||||
@ -301,8 +301,8 @@ describe("Egg Generation Tests", () => {
|
||||
vi.spyOn(Utils, "randInt").mockReturnValue(1);
|
||||
|
||||
const scene = game.scene;
|
||||
const expectedTier1 = EggTier.MASTER;
|
||||
const expectedTier2 = EggTier.ULTRA;
|
||||
const expectedTier1 = EggTier.LEGENDARY;
|
||||
const expectedTier2 = EggTier.EPIC;
|
||||
|
||||
const result1 = new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true }).tier;
|
||||
const result2 = new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true }).tier;
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { BerryType } from "#app/enums/berry-type";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phase from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// 20 seconds
|
||||
|
||||
describe("Items - Grip Claw", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
@ -30,39 +28,85 @@ describe("Items - Grip Claw", () => {
|
||||
|
||||
game.override
|
||||
.battleType("double")
|
||||
.moveset([ Moves.POPULATION_BOMB, Moves.SPLASH ])
|
||||
.moveset([ Moves.TACKLE, Moves.SPLASH, Moves.ATTRACT ])
|
||||
.startingHeldItems([
|
||||
{ name: "GRIP_CLAW", count: 5 }, // TODO: Find a way to mock the steal chance of grip claw
|
||||
{ name: "MULTI_LENS", count: 3 },
|
||||
{ name: "GRIP_CLAW", count: 1 },
|
||||
])
|
||||
.enemySpecies(Species.SNORLAX)
|
||||
.ability(Abilities.KLUTZ)
|
||||
.enemyAbility(Abilities.UNNERVE)
|
||||
.ability(Abilities.UNNERVE)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemyHeldItems([
|
||||
{ name: "BERRY", type: BerryType.SITRUS, count: 2 },
|
||||
{ name: "BERRY", type: BerryType.LUM, count: 2 },
|
||||
])
|
||||
.startingLevel(100)
|
||||
.enemyLevel(100);
|
||||
|
||||
vi.spyOn(allMoves[Moves.POPULATION_BOMB], "accuracy", "get").mockReturnValue(100);
|
||||
});
|
||||
|
||||
it(
|
||||
"should only steal items from the attack target",
|
||||
async () => {
|
||||
await game.startBattle([ Species.PANSEAR, Species.ROWLET ]);
|
||||
it("should steal items on contact and only from the attack target", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyField();
|
||||
const [ playerPokemon, ] = game.scene.getPlayerField();
|
||||
|
||||
const enemyHeldItemCt = enemyPokemon.map(p => p.getHeldItems.length);
|
||||
const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier;
|
||||
vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100);
|
||||
|
||||
game.move.select(Moves.POPULATION_BOMB, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
const enemyPokemon = game.scene.getEnemyField();
|
||||
|
||||
await game.phaseInterceptor.to(MoveEndPhase, false);
|
||||
const playerHeldItemCount = getHeldItemCount(playerPokemon);
|
||||
const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]);
|
||||
const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]);
|
||||
expect(enemy2HeldItemCount).toBeGreaterThan(0);
|
||||
|
||||
expect(enemyPokemon[1].getHeldItems.length).toBe(enemyHeldItemCt[1]);
|
||||
}
|
||||
);
|
||||
game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY_2);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
const playerHeldItemCountAfter = getHeldItemCount(playerPokemon);
|
||||
const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]);
|
||||
const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]);
|
||||
|
||||
expect(playerHeldItemCountAfter).toBe(playerHeldItemCount + 1);
|
||||
expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount);
|
||||
expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount - 1);
|
||||
});
|
||||
|
||||
it("should not steal items when using a targetted, non attack move", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]);
|
||||
|
||||
const [ playerPokemon, ] = game.scene.getPlayerField();
|
||||
|
||||
const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier;
|
||||
vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyField();
|
||||
|
||||
const playerHeldItemCount = getHeldItemCount(playerPokemon);
|
||||
const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]);
|
||||
const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]);
|
||||
expect(enemy2HeldItemCount).toBeGreaterThan(0);
|
||||
|
||||
game.move.select(Moves.ATTRACT, 0, BattlerIndex.ENEMY_2);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
const playerHeldItemCountAfter = getHeldItemCount(playerPokemon);
|
||||
const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]);
|
||||
const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]);
|
||||
|
||||
expect(playerHeldItemCountAfter).toBe(playerHeldItemCount);
|
||||
expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount);
|
||||
expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* Gets the total number of items a Pokemon holds
|
||||
*/
|
||||
function getHeldItemCount(pokemon: Pokemon) {
|
||||
return pokemon.getHeldItems().reduce((currentTotal, item) => currentTotal + item.getStackCount(), 0);
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
||||
import { MessagePhase } from "#app/phases/message-phase";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
import i18next, { initI18n } from "#app/plugins/i18n";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
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";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Items - Toxic orb", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -27,41 +25,36 @@ describe("Items - Toxic orb", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const moveToUse = Moves.GROWTH;
|
||||
const oppMoveToUse = Moves.TACKLE;
|
||||
game.override.battleType("single");
|
||||
game.override.enemySpecies(Species.RATTATA);
|
||||
game.override.ability(Abilities.INSOMNIA);
|
||||
game.override.enemyAbility(Abilities.INSOMNIA);
|
||||
game.override.startingLevel(2000);
|
||||
game.override.moveset([ moveToUse ]);
|
||||
game.override.enemyMoveset([ oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse ]);
|
||||
game.override.startingHeldItems([{
|
||||
name: "TOXIC_ORB",
|
||||
}]);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.RATTATA)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.moveset([ Moves.SPLASH ])
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.startingHeldItems([{
|
||||
name: "TOXIC_ORB",
|
||||
}]);
|
||||
|
||||
vi.spyOn(i18next, "t");
|
||||
});
|
||||
|
||||
it("TOXIC ORB", async () => {
|
||||
initI18n();
|
||||
i18next.changeLanguage("en");
|
||||
const moveToUse = Moves.GROWTH;
|
||||
await game.startBattle([
|
||||
Species.MIGHTYENA,
|
||||
Species.MIGHTYENA,
|
||||
]);
|
||||
expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB");
|
||||
it("badly poisons the holder", async () => {
|
||||
await game.classicMode.startBattle([ Species.MIGHTYENA ]);
|
||||
|
||||
game.move.select(moveToUse);
|
||||
const player = game.scene.getPlayerField()[0];
|
||||
|
||||
// will run the 13 phase from enemyCommandPhase to TurnEndPhase
|
||||
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
|
||||
game.move.select(Moves.SPLASH);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
// Toxic orb should trigger here
|
||||
await game.phaseInterceptor.run(MessagePhase);
|
||||
const message = game.textInterceptor.getLatestMessage();
|
||||
expect(message).toContain("statusEffect:toxic.obtainSource");
|
||||
await game.phaseInterceptor.run(MessagePhase);
|
||||
const message2 = game.textInterceptor.getLatestMessage();
|
||||
expect(message2).toBe("statusEffect:toxic.activation");
|
||||
expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC);
|
||||
}, 20000);
|
||||
await game.phaseInterceptor.run("MessagePhase");
|
||||
expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything());
|
||||
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(player.status?.effect).toBe(StatusEffect.TOXIC);
|
||||
// Damage should not have ticked yet.
|
||||
expect(player.status?.turnCount).toBe(0);
|
||||
}, TIMEOUT);
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
@ -22,13 +23,15 @@ describe("Moves - Obstruct", () => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyMoveset(Moves.TACKLE)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.moveset([ Moves.OBSTRUCT ]);
|
||||
.moveset([ Moves.OBSTRUCT ])
|
||||
.starterSpecies(Species.FEEBAS);
|
||||
});
|
||||
|
||||
it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
@ -42,7 +45,6 @@ describe("Moves - Obstruct", () => {
|
||||
});
|
||||
|
||||
it("bypasses accuracy checks when applying protection and defense reduction", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
@ -59,7 +61,7 @@ describe("Moves - Obstruct", () => {
|
||||
);
|
||||
|
||||
it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.WATER_GUN));
|
||||
game.override.enemyMoveset(Moves.WATER_GUN);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
@ -73,7 +75,7 @@ describe("Moves - Obstruct", () => {
|
||||
});
|
||||
|
||||
it("doesn't protect from status moves", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.GROWL));
|
||||
game.override.enemyMoveset(Moves.GROWL);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
@ -83,4 +85,14 @@ describe("Moves - Obstruct", () => {
|
||||
|
||||
expect(player.getStatStage(Stat.ATK)).toBe(-1);
|
||||
});
|
||||
|
||||
it("doesn't reduce the stats of an opponent with Clear Body/etc", async () => {
|
||||
game.override.enemyAbility(Abilities.CLEAR_BODY);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.OBSTRUCT);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
53
src/test/moves/sketch.test.ts
Normal file
53
src/test/moves/sketch.test.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Sketch", () => {
|
||||
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
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("Sketch should not fail even if a previous Sketch failed to retrieve a valid move and ran out of PP", async () => {
|
||||
game.override.moveset([ Moves.SKETCH, Moves.SKETCH ]);
|
||||
|
||||
await game.classicMode.startBattle([ Species.REGIELEKI ]);
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
|
||||
game.move.select(Moves.SKETCH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
const moveSlot0 = playerPokemon?.getMoveset()[0];
|
||||
expect(moveSlot0?.moveId).toBe(Moves.SKETCH);
|
||||
expect(moveSlot0?.getPpRatio()).toBe(0);
|
||||
|
||||
await game.toNextTurn();
|
||||
game.move.select(Moves.SKETCH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
// Can't verify if the player Pokemon's moveset was successfully changed because of overrides.
|
||||
});
|
||||
});
|
@ -1,4 +1,3 @@
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
@ -7,7 +6,7 @@ import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - SYRUP BOMB", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -26,20 +25,21 @@ describe("Moves - SYRUP BOMB", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.starterSpecies(Species.MAGIKARP)
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.SNORLAX)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.startingLevel(30)
|
||||
.enemyLevel(100)
|
||||
.moveset([ Moves.SYRUP_BOMB, Moves.SPLASH ])
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
vi.spyOn(allMoves[Moves.SYRUP_BOMB], "accuracy", "get").mockReturnValue(100);
|
||||
});
|
||||
|
||||
//Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/syrup_bomb_(move)
|
||||
|
||||
it("decreases the target Pokemon's speed stat once per turn for 3 turns",
|
||||
async () => {
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
const targetPokemon = game.scene.getEnemyPokemon()!;
|
||||
expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||
@ -66,7 +66,7 @@ describe("Moves - SYRUP BOMB", () => {
|
||||
it("does not affect Pokemon with the ability Bulletproof",
|
||||
async () => {
|
||||
game.override.enemyAbility(Abilities.BULLETPROOF);
|
||||
await game.startBattle([ Species.MAGIKARP ]);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
const targetPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
@ -79,4 +79,18 @@ describe("Moves - SYRUP BOMB", () => {
|
||||
expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||
}
|
||||
);
|
||||
|
||||
it("stops lowering the target's speed if the user leaves the field", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]);
|
||||
|
||||
game.move.select(Moves.SYRUP_BOMB);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.move.forceHit();
|
||||
await game.toNextTurn();
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPD)).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
136
src/test/moves/toxic_spikes.test.ts
Normal file
136
src/test/moves/toxic_spikes.test.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { decrypt, encrypt, GameData, SessionSaveData } from "#app/system/game-data";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
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";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Moves - Toxic Spikes", () => {
|
||||
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")
|
||||
.startingWave(5)
|
||||
.enemySpecies(Species.RATTATA)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR ]);
|
||||
});
|
||||
|
||||
it("should not affect the opponent if they do not switch", async() => {
|
||||
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
|
||||
|
||||
const enemy = game.scene.getEnemyField()[0];
|
||||
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.doSwitchPokemon(1);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||
expect(enemy.status?.effect).toBeUndefined();
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should poison the opponent if they switch into 1 layer", async() => {
|
||||
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.move.select(Moves.ROAR);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
const enemy = game.scene.getEnemyField()[0];
|
||||
|
||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||
expect(enemy.status?.effect).toBe(StatusEffect.POISON);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should badly poison the opponent if they switch into 2 layers", async() => {
|
||||
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.move.select(Moves.ROAR);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
const enemy = game.scene.getEnemyField()[0];
|
||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||
expect(enemy.status?.effect).toBe(StatusEffect.TOXIC);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should be removed if a grounded poison pokemon switches in", async() => {
|
||||
game.override.enemySpecies(Species.GRIMER);
|
||||
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.move.select(Moves.ROAR);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
const enemy = game.scene.getEnemyField()[0];
|
||||
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||
expect(enemy.status?.effect).toBeUndefined();
|
||||
|
||||
expect(game.scene.arena.tags.length).toBe(0);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("shouldn't create multiple layers per use in doubles", async() => {
|
||||
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
|
||||
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag);
|
||||
expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES);
|
||||
expect(arenaTags.layers).toBe(1);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should persist through reload", async() => {
|
||||
game.override.startingWave(1);
|
||||
const scene = game.scene;
|
||||
const gameData = new GameData(scene);
|
||||
|
||||
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||
|
||||
game.move.select(Moves.TOXIC_SPIKES);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.doKillOpponents();
|
||||
await game.phaseInterceptor.to("BattleEndPhase");
|
||||
await game.toNextWave();
|
||||
|
||||
const sessionData : SessionSaveData = gameData["getSessionSaveData"](game.scene);
|
||||
localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true));
|
||||
const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true));
|
||||
gameData.loadSession(game.scene, 0, recoveredData);
|
||||
|
||||
expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags);
|
||||
localStorage.removeItem("sessionTestData");
|
||||
}, TIMEOUT);
|
||||
});
|
60
src/test/moves/triple_arrows.test.ts
Normal file
60
src/test/moves/triple_arrows.test.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { allMoves, FlinchAttr, StatStageChangeAttr } from "#app/data/move";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Moves - Triple Arrows", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
const tripleArrows = allMoves[Moves.TRIPLE_ARROWS];
|
||||
const flinchAttr = tripleArrows.getAttrs(FlinchAttr)[0];
|
||||
const defDropAttr = tripleArrows.getAttrs(StatStageChangeAttr)[0];
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.moveset([ Moves.TRIPLE_ARROWS ])
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.STURDY)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
|
||||
vi.spyOn(flinchAttr, "getMoveChance");
|
||||
vi.spyOn(defDropAttr, "getMoveChance");
|
||||
});
|
||||
|
||||
it("has a 30% flinch chance and 50% defense drop chance", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
game.move.select(Moves.TRIPLE_ARROWS);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(flinchAttr.getMoveChance).toHaveReturnedWith(30);
|
||||
expect(defDropAttr.getMoveChance).toHaveReturnedWith(50);
|
||||
});
|
||||
|
||||
it("is affected normally by Serene Grace", async () => {
|
||||
game.override.ability(Abilities.SERENE_GRACE);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
game.move.select(Moves.TRIPLE_ARROWS);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(flinchAttr.getMoveChance).toHaveReturnedWith(60);
|
||||
expect(defDropAttr.getMoveChance).toHaveReturnedWith(100);
|
||||
});
|
||||
});
|
@ -16,6 +16,7 @@ import { EggTier } from "#enums/egg-type";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||
import i18next from "i18next";
|
||||
|
||||
const namespace = "mysteryEncounters/aTrainersTest";
|
||||
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
||||
@ -106,7 +107,8 @@ describe("A Trainer's Test - Mystery Encounter", () => {
|
||||
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
|
||||
expect(enemyField.length).toBe(1);
|
||||
expect(scene.currentBattle.trainer).toBeDefined();
|
||||
expect([ "trainerNames:buck", "trainerNames:cheryl", "trainerNames:marley", "trainerNames:mira", "trainerNames:riley" ].includes(scene.currentBattle.trainer!.config.name)).toBeTruthy();
|
||||
expect([ i18next.t("trainerNames:buck"), i18next.t("trainerNames:cheryl"), i18next.t("trainerNames:marley"), i18next.t("trainerNames:mira"), i18next.t("trainerNames:riley") ]
|
||||
.map(name => name.toLowerCase()).includes(scene.currentBattle.trainer!.config.name)).toBeTruthy();
|
||||
expect(enemyField[0]).toBeDefined();
|
||||
});
|
||||
|
||||
@ -126,7 +128,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
|
||||
expect(eggsAfter).toBeDefined();
|
||||
expect(eggsBeforeLength + 1).toBe(eggsAfter.length);
|
||||
const eggTier = eggsAfter[eggsAfter.length - 1].tier;
|
||||
expect(eggTier === EggTier.ULTRA || eggTier === EggTier.MASTER).toBeTruthy();
|
||||
expect(eggTier === EggTier.EPIC || eggTier === EggTier.LEGENDARY).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -174,7 +176,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
|
||||
expect(eggsAfter).toBeDefined();
|
||||
expect(eggsBeforeLength + 1).toBe(eggsAfter.length);
|
||||
const eggTier = eggsAfter[eggsAfter.length - 1].tier;
|
||||
expect(eggTier).toBe(EggTier.GREAT);
|
||||
expect(eggTier).toBe(EggTier.RARE);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
|
@ -16,6 +16,7 @@ import { Moves } from "#enums/moves";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { MovePhase } from "#app/phases/move-phase";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import i18next from "i18next";
|
||||
|
||||
const namespace = "mysteryEncounters/absoluteAvarice";
|
||||
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
||||
@ -146,7 +147,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
|
||||
const pokemonId = partyPokemon.id;
|
||||
const pokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& (m as PokemonHeldItemModifier).pokemonId === pokemonId, true) as PokemonHeldItemModifier[];
|
||||
const revSeed = pokemonItems.find(i => i.type.name === "modifierType:ModifierType.REVIVER_SEED.name");
|
||||
const revSeed = pokemonItems.find(i => i.type.name === i18next.t("modifierType:ModifierType.REVIVER_SEED.name"));
|
||||
expect(revSeed).toBeDefined;
|
||||
expect(revSeed?.stackCount).toBe(1);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { ShinyRateBoosterModifier } from "#app/modifier/modifier";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import i18next from "i18next";
|
||||
|
||||
const namespace = "mysteryEncounters/anOfferYouCantRefuse";
|
||||
/** Gyarados for Indimidate */
|
||||
@ -93,8 +94,8 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
|
||||
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined();
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined();
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe("ability:intimidate.name");
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe("ability:intimidate.name");
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe(i18next.t("ability:intimidate.name"));
|
||||
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe(i18next.t("ability:intimidate.name"));
|
||||
expect(AnOfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy();
|
||||
expect(AnOfferYouCantRefuseEncounter.misc?.price?.toString()).toBe(AnOfferYouCantRefuseEncounter.dialogueTokens?.price);
|
||||
expect(onInitResult).toBe(true);
|
||||
|
@ -103,11 +103,11 @@ describe("Field Trip - Mystery Encounter", () => {
|
||||
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||
expect(modifierSelectHandler.options.length).toEqual(5);
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_attack");
|
||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_defense");
|
||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed");
|
||||
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name");
|
||||
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name");
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_attack"));
|
||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_defense"));
|
||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed"));
|
||||
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name"));
|
||||
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name"));
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
@ -150,11 +150,11 @@ describe("Field Trip - Mystery Encounter", () => {
|
||||
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||
expect(modifierSelectHandler.options.length).toEqual(5);
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_atk");
|
||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_def");
|
||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed");
|
||||
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name");
|
||||
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name");
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_atk"));
|
||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_def"));
|
||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed"));
|
||||
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name"));
|
||||
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name"));
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
@ -198,12 +198,12 @@ describe("Field Trip - Mystery Encounter", () => {
|
||||
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||
expect(modifierSelectHandler.options.length).toEqual(5);
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_accuracy");
|
||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed");
|
||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:ModifierType.AddPokeballModifierType.name");
|
||||
expect(i18next.t).toHaveBeenCalledWith("modifierType:ModifierType.AddPokeballModifierType.name", expect.objectContaining({ modifierCount: 5 }));
|
||||
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.IV_SCANNER.name");
|
||||
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name");
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_accuracy"));
|
||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed"));
|
||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { modifierCount: 5, pokeballName: i18next.t("pokeball:greatBall") }));
|
||||
expect(i18next.t).toHaveBeenCalledWith(("modifierType:ModifierType.AddPokeballModifierType.name"), expect.objectContaining({ modifierCount: 5 }));
|
||||
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.IV_SCANNER.name"));
|
||||
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name"));
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
|
@ -22,6 +22,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { MovePhase } from "#app/phases/move-phase";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import i18next from "i18next";
|
||||
|
||||
const namespace = "mysteryEncounters/fieryFallout";
|
||||
/** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */
|
||||
@ -205,7 +206,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
||||
|
||||
const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE));
|
||||
const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE));
|
||||
expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe("pokemon:gengar");
|
||||
expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe(i18next.t("pokemon:gengar"));
|
||||
burnablePokemon.forEach((pkm) => {
|
||||
expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2));
|
||||
});
|
||||
|
@ -14,6 +14,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import { PartyExpPhase } from "#app/phases/party-exp-phase";
|
||||
import i18next from "i18next";
|
||||
|
||||
|
||||
const namespace = "mysteryEncounters/lostAtSea";
|
||||
@ -86,8 +87,8 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
||||
const onInitResult = onInit!(scene);
|
||||
|
||||
expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25");
|
||||
expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe("move:surf.name");
|
||||
expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe("move:fly.name");
|
||||
expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(i18next.t("move:surf.name"));
|
||||
expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe(i18next.t("move:fly.name"));
|
||||
expect(onInitResult).toBe(true);
|
||||
});
|
||||
|
||||
|
@ -1,21 +1,22 @@
|
||||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter";
|
||||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import i18next from "i18next";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const namespace = "mysteryEncounters/teleportingHijinks";
|
||||
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
||||
@ -300,8 +301,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
|
||||
|
||||
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||
expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.metal_coat")).toBe(true);
|
||||
expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.magnet")).toBe(true);
|
||||
expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.metal_coat"))).toBe(true);
|
||||
expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.magnet"))).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -155,7 +155,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
||||
expect(eggsAfter).toBeDefined();
|
||||
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
|
||||
|
||||
game.phaseInterceptor.superEndPhase();
|
||||
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
|
||||
@ -213,7 +213,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
||||
expect(eggsAfter).toBeDefined();
|
||||
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
|
||||
|
||||
game.phaseInterceptor.superEndPhase();
|
||||
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
|
||||
@ -271,7 +271,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
||||
expect(eggsAfter).toBeDefined();
|
||||
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs);
|
||||
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
|
||||
|
||||
game.phaseInterceptor.superEndPhase();
|
||||
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
|
||||
|
@ -9,6 +9,7 @@ import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import MessageUiHandler from "#app/ui/message-ui-handler";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import i18next from "i18next";
|
||||
|
||||
describe("Mystery Encounter Phases", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -78,9 +79,9 @@ describe("Mystery Encounter Phases", () => {
|
||||
expect(ui.getMode()).toBe(Mode.MESSAGE);
|
||||
expect(ui.showDialogue).toHaveBeenCalledTimes(1);
|
||||
expect(ui.showText).toHaveBeenCalledTimes(2);
|
||||
expect(ui.showDialogue).toHaveBeenCalledWith("battle:mysteryEncounterAppeared", "???", null, expect.any(Function));
|
||||
expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:intro", null, expect.any(Function), 750, true);
|
||||
expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:option.selected", null, expect.any(Function), 300, true);
|
||||
expect(ui.showDialogue).toHaveBeenCalledWith(i18next.t("battle:mysteryEncounterAppeared"), "???", null, expect.any(Function));
|
||||
expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:intro"), null, expect.any(Function), 750, true);
|
||||
expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:option.selected"), null, expect.any(Function), 300, true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -11,13 +11,15 @@ import * as account from "../../account";
|
||||
|
||||
const apiBase = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8001";
|
||||
|
||||
export const server = setupServer();
|
||||
/** We need a custom server. For some reasons I can't extend the listeners of {@linkcode global.i18nServer} with {@linkcode global.i18nServer.use} */
|
||||
const server = setupServer();
|
||||
|
||||
describe("System - Game Data", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
global.i18nServer.close();
|
||||
server.listen();
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
@ -26,6 +28,7 @@ describe("System - Game Data", () => {
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
global.i18nServer.listen();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -14,6 +14,7 @@ import { Abilities } from "#enums/abilities";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import i18next from "i18next";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
@ -66,11 +67,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
@ -127,11 +128,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
@ -191,11 +192,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
@ -254,11 +255,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
@ -315,11 +316,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
@ -376,11 +377,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
@ -436,11 +437,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
@ -496,11 +497,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
let starterSelectUiHandler: StarterSelectUiHandler;
|
||||
@ -561,11 +562,11 @@ describe("UI - Starter select", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true);
|
||||
expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true);
|
||||
expect(options.some(option => option.label === "menu:cancel")).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true);
|
||||
expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true);
|
||||
optionSelectUiHandler?.processInput(Button.ACTION);
|
||||
|
||||
let starterSelectUiHandler: StarterSelectUiHandler | undefined;
|
||||
|
@ -7,7 +7,8 @@ import { Mode } from "#app/ui/ui";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import MockText from "../utils/mocks/mocksContainer/mockText";
|
||||
import MockText from "#test/utils/mocks/mocksContainer/mockText";
|
||||
import i18next from "i18next";
|
||||
|
||||
describe("UI - Type Hints", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -53,7 +54,7 @@ describe("UI - Type Hints", () => {
|
||||
const movesContainer = ui.getByName<Phaser.GameObjects.Container>(FightUiHandler.MOVES_CONTAINER_NAME);
|
||||
const dragonClawText = movesContainer
|
||||
.getAll<Phaser.GameObjects.Text>()
|
||||
.find((text) => text.text === "move:dragonClaw.name")! as unknown as MockText;
|
||||
.find((text) => text.text === i18next.t("move:dragonClaw.name"))! as unknown as MockText;
|
||||
|
||||
expect.soft(dragonClawText.color).toBe("#929292");
|
||||
ui.getHandler().processInput(Button.ACTION);
|
||||
@ -78,7 +79,7 @@ describe("UI - Type Hints", () => {
|
||||
const movesContainer = ui.getByName<Phaser.GameObjects.Container>(FightUiHandler.MOVES_CONTAINER_NAME);
|
||||
const growlText = movesContainer
|
||||
.getAll<Phaser.GameObjects.Text>()
|
||||
.find((text) => text.text === "move:growl.name")! as unknown as MockText;
|
||||
.find((text) => text.text === i18next.t("move:growl.name"))! as unknown as MockText;
|
||||
|
||||
expect.soft(growlText.color).toBe(undefined);
|
||||
ui.getHandler().processInput(Button.ACTION);
|
||||
|
@ -86,7 +86,7 @@ export function waitUntil(truth) {
|
||||
export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) {
|
||||
const playerPokemon = scene.getPlayerField()[pokemonIndex];
|
||||
const moveSet = playerPokemon.getMoveset();
|
||||
const index = moveSet.findIndex((m) => m?.moveId === move);
|
||||
const index = moveSet.findIndex((m) => m?.moveId === move && m?.ppUsed < m?.getMovePp());
|
||||
console.log(`Move position for ${Moves[move]} (=${move}):`, index);
|
||||
return index;
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ import {
|
||||
} from "#app/phases/mystery-encounter-phases";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
import { PartyExpPhase } from "#app/phases/party-exp-phase";
|
||||
import { ExpPhase } from "#app/phases/exp-phase";
|
||||
|
||||
export interface PromptHandler {
|
||||
phaseTarget?: string;
|
||||
@ -61,7 +62,114 @@ export interface PromptHandler {
|
||||
expireFn?: () => void;
|
||||
awaitingActionInput?: boolean;
|
||||
}
|
||||
import { ExpPhase } from "#app/phases/exp-phase";
|
||||
|
||||
type PhaseClass =
|
||||
| typeof LoginPhase
|
||||
| typeof TitlePhase
|
||||
| typeof SelectGenderPhase
|
||||
| typeof EncounterPhase
|
||||
| typeof NewBiomeEncounterPhase
|
||||
| typeof SelectStarterPhase
|
||||
| typeof PostSummonPhase
|
||||
| typeof SummonPhase
|
||||
| typeof ToggleDoublePositionPhase
|
||||
| typeof CheckSwitchPhase
|
||||
| typeof ShowAbilityPhase
|
||||
| typeof MessagePhase
|
||||
| typeof TurnInitPhase
|
||||
| typeof CommandPhase
|
||||
| typeof EnemyCommandPhase
|
||||
| typeof TurnStartPhase
|
||||
| typeof MovePhase
|
||||
| typeof MoveEffectPhase
|
||||
| typeof DamagePhase
|
||||
| typeof FaintPhase
|
||||
| typeof BerryPhase
|
||||
| typeof TurnEndPhase
|
||||
| typeof BattleEndPhase
|
||||
| typeof EggLapsePhase
|
||||
| typeof SelectModifierPhase
|
||||
| typeof NextEncounterPhase
|
||||
| typeof NewBattlePhase
|
||||
| typeof VictoryPhase
|
||||
| typeof LearnMovePhase
|
||||
| typeof MoveEndPhase
|
||||
| typeof StatStageChangePhase
|
||||
| typeof ShinySparklePhase
|
||||
| typeof SelectTargetPhase
|
||||
| typeof UnavailablePhase
|
||||
| typeof QuietFormChangePhase
|
||||
| typeof SwitchPhase
|
||||
| typeof SwitchSummonPhase
|
||||
| typeof PartyHealPhase
|
||||
| typeof EvolutionPhase
|
||||
| typeof EndEvolutionPhase
|
||||
| typeof LevelCapPhase
|
||||
| typeof AttemptRunPhase
|
||||
| typeof SelectBiomePhase
|
||||
| typeof MysteryEncounterPhase
|
||||
| typeof MysteryEncounterOptionSelectedPhase
|
||||
| typeof MysteryEncounterBattlePhase
|
||||
| typeof MysteryEncounterRewardsPhase
|
||||
| typeof PostMysteryEncounterPhase
|
||||
| typeof ModifierRewardPhase
|
||||
| typeof PartyExpPhase
|
||||
| typeof ExpPhase;
|
||||
|
||||
type PhaseString =
|
||||
| "LoginPhase"
|
||||
| "TitlePhase"
|
||||
| "SelectGenderPhase"
|
||||
| "EncounterPhase"
|
||||
| "NewBiomeEncounterPhase"
|
||||
| "SelectStarterPhase"
|
||||
| "PostSummonPhase"
|
||||
| "SummonPhase"
|
||||
| "ToggleDoublePositionPhase"
|
||||
| "CheckSwitchPhase"
|
||||
| "ShowAbilityPhase"
|
||||
| "MessagePhase"
|
||||
| "TurnInitPhase"
|
||||
| "CommandPhase"
|
||||
| "EnemyCommandPhase"
|
||||
| "TurnStartPhase"
|
||||
| "MovePhase"
|
||||
| "MoveEffectPhase"
|
||||
| "DamagePhase"
|
||||
| "FaintPhase"
|
||||
| "BerryPhase"
|
||||
| "TurnEndPhase"
|
||||
| "BattleEndPhase"
|
||||
| "EggLapsePhase"
|
||||
| "SelectModifierPhase"
|
||||
| "NextEncounterPhase"
|
||||
| "NewBattlePhase"
|
||||
| "VictoryPhase"
|
||||
| "LearnMovePhase"
|
||||
| "MoveEndPhase"
|
||||
| "StatStageChangePhase"
|
||||
| "ShinySparklePhase"
|
||||
| "SelectTargetPhase"
|
||||
| "UnavailablePhase"
|
||||
| "QuietFormChangePhase"
|
||||
| "SwitchPhase"
|
||||
| "SwitchSummonPhase"
|
||||
| "PartyHealPhase"
|
||||
| "EvolutionPhase"
|
||||
| "EndEvolutionPhase"
|
||||
| "LevelCapPhase"
|
||||
| "AttemptRunPhase"
|
||||
| "SelectBiomePhase"
|
||||
| "MysteryEncounterPhase"
|
||||
| "MysteryEncounterOptionSelectedPhase"
|
||||
| "MysteryEncounterBattlePhase"
|
||||
| "MysteryEncounterRewardsPhase"
|
||||
| "PostMysteryEncounterPhase"
|
||||
| "ModifierRewardPhase"
|
||||
| "PartyExpPhase"
|
||||
| "ExpPhase";
|
||||
|
||||
type PhaseInterceptorPhase = PhaseClass | PhaseString;
|
||||
|
||||
export default class PhaseInterceptor {
|
||||
public scene;
|
||||
@ -172,7 +280,7 @@ export default class PhaseInterceptor {
|
||||
* @param phaseFrom - The phase to start from.
|
||||
* @returns The instance of the PhaseInterceptor.
|
||||
*/
|
||||
runFrom(phaseFrom) {
|
||||
runFrom(phaseFrom: PhaseInterceptorPhase): PhaseInterceptor {
|
||||
this.phaseFrom = phaseFrom;
|
||||
return this;
|
||||
}
|
||||
@ -180,9 +288,10 @@ export default class PhaseInterceptor {
|
||||
/**
|
||||
* Method to transition to a target phase.
|
||||
* @param phaseTo - The phase to transition to.
|
||||
* @param runTarget - Whether or not to run the target phase.
|
||||
* @returns A promise that resolves when the transition is complete.
|
||||
*/
|
||||
async to(phaseTo, runTarget: boolean = true): Promise<void> {
|
||||
async to(phaseTo: PhaseInterceptorPhase, runTarget: boolean = true): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
ErrorInterceptor.getInstance().add(this);
|
||||
if (this.phaseFrom) {
|
||||
@ -219,7 +328,7 @@ export default class PhaseInterceptor {
|
||||
* @param skipFn - Optional skip function.
|
||||
* @returns A promise that resolves when the phase is run.
|
||||
*/
|
||||
run(phaseTarget, skipFn?): Promise<void> {
|
||||
run(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise<void> {
|
||||
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
|
||||
this.scene.moveAnimations = null; // Mandatory to avoid crash
|
||||
return new Promise(async (resolve, reject) => {
|
||||
@ -253,7 +362,7 @@ export default class PhaseInterceptor {
|
||||
});
|
||||
}
|
||||
|
||||
whenAboutToRun(phaseTarget, skipFn?): Promise<void> {
|
||||
whenAboutToRun(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise<void> {
|
||||
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
|
||||
this.scene.moveAnimations = null; // Mandatory to avoid crash
|
||||
return new Promise(async (resolve, reject) => {
|
||||
@ -311,7 +420,7 @@ export default class PhaseInterceptor {
|
||||
* Method to start a phase and log it.
|
||||
* @param phase - The phase to start.
|
||||
*/
|
||||
startPhase(phase) {
|
||||
startPhase(phase: PhaseClass) {
|
||||
this.log.push(phase.name);
|
||||
const instance = this.scene.getCurrentPhase();
|
||||
this.onHold.push({
|
||||
@ -340,9 +449,10 @@ export default class PhaseInterceptor {
|
||||
|
||||
/**
|
||||
* m2m to set mode.
|
||||
* @param phase - The phase to start.
|
||||
* @param mode - The {@linkcode Mode} to set.
|
||||
* @param args - Additional arguments to pass to the original method.
|
||||
*/
|
||||
setMode(mode: Mode, ...args: any[]): Promise<void> {
|
||||
setMode(mode: Mode, ...args: unknown[]): Promise<void> {
|
||||
const currentPhase = this.scene.getCurrentPhase();
|
||||
const instance = this.scene.ui;
|
||||
console.log("setMode", `${Mode[mode]} (=${mode})`, args);
|
||||
|
@ -4,16 +4,17 @@ import { initLoggedInUser } from "#app/account";
|
||||
import { initAbilities } from "#app/data/ability";
|
||||
import { initBiomes } from "#app/data/balance/biomes";
|
||||
import { initEggMoves } from "#app/data/balance/egg-moves";
|
||||
import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
||||
import { initMoves } from "#app/data/move";
|
||||
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
||||
import { initPokemonForms } from "#app/data/pokemon-forms";
|
||||
import { initSpecies } from "#app/data/pokemon-species";
|
||||
import { initAchievements } from "#app/system/achv";
|
||||
import { initVouchers } from "#app/system/voucher";
|
||||
import { initStatsKeys } from "#app/ui/game-stats-ui-handler";
|
||||
import { beforeAll, vi } from "vitest";
|
||||
import { afterAll, beforeAll, vi } from "vitest";
|
||||
|
||||
/** Set the timezone to UTC for tests. */
|
||||
process.env.TZ = "UTC";
|
||||
|
||||
/** Mock the override import to always return default values, ignoring any custom overrides. */
|
||||
@ -26,26 +27,36 @@ vi.mock("#app/overrides", async (importOriginal) => {
|
||||
} satisfies typeof import("#app/overrides");
|
||||
});
|
||||
|
||||
vi.mock("i18next", () => ({
|
||||
default: {
|
||||
use: () => {},
|
||||
t: (key: string) => key,
|
||||
changeLanguage: () => Promise.resolve(),
|
||||
init: () => Promise.resolve(),
|
||||
resolvedLanguage: "en",
|
||||
exists: () => true,
|
||||
getDataByLanguage:() => ({
|
||||
en: {
|
||||
keys: [ "foo" ]
|
||||
},
|
||||
}),
|
||||
services: {
|
||||
formatter: {
|
||||
add: () => {},
|
||||
/**
|
||||
* This is a hacky way to mock the i18n backend requests (with the help of {@link https://mswjs.io/ | msw}).
|
||||
* The reason to put it inside of a mock is to elevate it.
|
||||
* This is necessary because how our code is structured.
|
||||
* Do NOT try to put any of this code into external functions, it won't work as it's elevated during runtime.
|
||||
*/
|
||||
vi.mock("i18next", async (importOriginal) => {
|
||||
console.log("Mocking i18next");
|
||||
const { setupServer } = await import("msw/node");
|
||||
const { http, HttpResponse } = await import("msw");
|
||||
|
||||
global.i18nServer = setupServer(
|
||||
http.get("/locales/en/*", async (req) => {
|
||||
const filename = req.params[0];
|
||||
|
||||
try {
|
||||
const json = await import(`../../public/locales/en/${req.params[0]}`);
|
||||
console.log("Loaded locale", filename);
|
||||
return HttpResponse.json(json);
|
||||
} catch (err) {
|
||||
console.log(`Failed to load locale ${filename}!`, err);
|
||||
return HttpResponse.json({});
|
||||
}
|
||||
},
|
||||
},
|
||||
}));
|
||||
})
|
||||
);
|
||||
global.i18nServer.listen({ onUnhandledRequest: "error" });
|
||||
console.log("i18n MSW server listening!");
|
||||
|
||||
return await importOriginal();
|
||||
});
|
||||
|
||||
initVouchers();
|
||||
initAchievements();
|
||||
@ -70,3 +81,8 @@ beforeAll(() => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
global.i18nServer.close();
|
||||
console.log("Closing i18n MSW server!");
|
||||
});
|
||||
|
@ -40,7 +40,9 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
||||
private iconsBg: Phaser.GameObjects.NineSlice;
|
||||
private icons: Phaser.GameObjects.Sprite[];
|
||||
|
||||
private titleBg: Phaser.GameObjects.NineSlice;
|
||||
private titleText: Phaser.GameObjects.Text;
|
||||
private scoreContainer: Phaser.GameObjects.Container;
|
||||
private scoreText: Phaser.GameObjects.Text;
|
||||
private unlockText: Phaser.GameObjects.Text;
|
||||
|
||||
@ -114,29 +116,31 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
||||
|
||||
const titleBg = addWindow(this.scene, 0, this.headerBg.height + this.iconsBg.height, 174, 24);
|
||||
titleBg.setOrigin(0, 0);
|
||||
this.titleBg = titleBg;
|
||||
|
||||
this.titleText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW);
|
||||
const textSize = languageSettings[i18next.language]?.TextSize ?? this.titleText.style.fontSize;
|
||||
this.titleText.setFontSize(textSize);
|
||||
this.titleText.setOrigin(0, 0);
|
||||
const titleBgCenterX = titleBg.x + titleBg.width / 2;
|
||||
const titleBgCenterY = titleBg.y + titleBg.height / 2;
|
||||
this.titleText.setOrigin(0.5, 0.5);
|
||||
this.titleText.setPosition(titleBgCenterX, titleBgCenterY);
|
||||
|
||||
const scoreBg = addWindow(this.scene, titleBg.x + titleBg.width, titleBg.y, 46, 24);
|
||||
this.scoreContainer = this.scene.add.container(titleBg.x + titleBg.width, titleBg.y);
|
||||
const scoreBg = addWindow(this.scene, 0, 0, 46, 24);
|
||||
scoreBg.setOrigin(0, 0);
|
||||
this.scoreContainer.add(scoreBg);
|
||||
|
||||
this.scoreText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW);
|
||||
this.scoreText.setOrigin(0, 0);
|
||||
this.scoreText.setPositionRelative(scoreBg, 8, 4);
|
||||
this.scoreText = addTextObject(this.scene, scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW);
|
||||
this.scoreText.setOrigin(0.5, 0.5);
|
||||
this.scoreContainer.add(this.scoreText);
|
||||
|
||||
const unlockBg = addWindow(this.scene, scoreBg.x + scoreBg.width, scoreBg.y, 98, 24);
|
||||
const unlockBg = addWindow(this.scene, this.scoreContainer.x + scoreBg.width, titleBg.y, 98, 24);
|
||||
unlockBg.setOrigin(0, 0);
|
||||
|
||||
this.unlockText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW);
|
||||
this.unlockText.setOrigin(0, 0);
|
||||
this.unlockText.setPositionRelative(unlockBg, 8, 4);
|
||||
this.unlockText.setOrigin(0.5, 0.5);
|
||||
this.unlockText.setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2);
|
||||
|
||||
const descriptionBg = addWindow(this.scene, 0, titleBg.y + titleBg.height, (this.scene.game.canvas.width / 6) - 2, 42);
|
||||
descriptionBg.setOrigin(0, 0);
|
||||
@ -157,8 +161,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
||||
this.mainContainer.add(this.iconsContainer);
|
||||
this.mainContainer.add(titleBg);
|
||||
this.mainContainer.add(this.titleText);
|
||||
this.mainContainer.add(scoreBg);
|
||||
this.mainContainer.add(this.scoreText);
|
||||
this.mainContainer.add(this.scoreContainer);
|
||||
this.mainContainer.add(unlockBg);
|
||||
this.mainContainer.add(this.unlockText);
|
||||
this.mainContainer.add(descriptionBg);
|
||||
@ -167,8 +170,6 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
||||
ui.add(this.mainContainer);
|
||||
|
||||
this.currentPage = Page.ACHIEVEMENTS;
|
||||
this.setCursor(0);
|
||||
this.setScrollCursor(0);
|
||||
|
||||
this.mainContainer.setVisible(false);
|
||||
}
|
||||
@ -316,9 +317,19 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
||||
if (update || pageChange) {
|
||||
switch (this.currentPage) {
|
||||
case Page.ACHIEVEMENTS:
|
||||
if (pageChange) {
|
||||
this.titleBg.width = 174;
|
||||
this.titleText.x = this.titleBg.width / 2;
|
||||
this.scoreContainer.setVisible(true);
|
||||
}
|
||||
this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]);
|
||||
break;
|
||||
case Page.VOUCHERS:
|
||||
if (pageChange) {
|
||||
this.titleBg.width = 220;
|
||||
this.titleText.x = this.titleBg.width / 2;
|
||||
this.scoreContainer.setVisible(false);
|
||||
}
|
||||
this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]);
|
||||
break;
|
||||
}
|
||||
@ -442,6 +453,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
||||
this.currentPage = Page.ACHIEVEMENTS;
|
||||
this.mainContainer.setVisible(false);
|
||||
this.setScrollCursor(0);
|
||||
this.setCursor(0, true);
|
||||
this.eraseCursor();
|
||||
}
|
||||
|
||||
|
@ -593,7 +593,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
||||
};
|
||||
|
||||
const updatePokemonHp = () => {
|
||||
let duration = !instant ? Utils.clampInt(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0;
|
||||
let duration = !instant ? Phaser.Math.Clamp(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0;
|
||||
const speed = (this.scene as BattleScene).hpBarSpeed;
|
||||
if (speed) {
|
||||
duration = speed >= 3 ? 0 : duration / Math.pow(2, speed);
|
||||
|
@ -34,6 +34,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
private cursorObj: Phaser.GameObjects.Image;
|
||||
private transitioning: boolean;
|
||||
private transitionCancelled: boolean;
|
||||
private summaryFinished: boolean;
|
||||
private defaultText: string;
|
||||
|
||||
private scale: number = 0.1666666667;
|
||||
@ -470,16 +471,21 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
getGuaranteedEggTierFromPullCount(pullCount: number): EggTier {
|
||||
switch (pullCount) {
|
||||
case 10:
|
||||
return EggTier.GREAT;
|
||||
return EggTier.RARE;
|
||||
case 25:
|
||||
return EggTier.ULTRA;
|
||||
return EggTier.EPIC;
|
||||
default:
|
||||
return EggTier.COMMON;
|
||||
}
|
||||
}
|
||||
|
||||
showSummary(eggs: Egg[]): void {
|
||||
this.transitioning = false;
|
||||
// the overlay will appear faster if the egg pulling animation was skipped
|
||||
const overlayEaseInDuration = this.getDelayValue(750);
|
||||
|
||||
this.summaryFinished = false;
|
||||
this.transitionCancelled = false;
|
||||
this.setTransitioning(true);
|
||||
this.eggGachaSummaryContainer.setVisible(true);
|
||||
|
||||
const eggScale = eggs.length < 20 ? 1 : 0.5;
|
||||
@ -488,12 +494,14 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
targets: this.eggGachaOverlay,
|
||||
alpha: 0.5,
|
||||
ease: "Sine.easeOut",
|
||||
duration: 750,
|
||||
duration: overlayEaseInDuration,
|
||||
onComplete: () => {
|
||||
const rowItems = 5;
|
||||
const rows = Math.ceil(eggs.length / rowItems);
|
||||
const cols = Math.min(eggs.length, rowItems);
|
||||
const height = this.eggGachaOverlay.displayHeight - this.eggGachaMessageBox.displayHeight;
|
||||
|
||||
// Create sprites for each egg
|
||||
const eggContainers = eggs.map((egg, t) => {
|
||||
const col = t % rowItems;
|
||||
const row = Math.floor(t / rowItems);
|
||||
@ -508,21 +516,31 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
|
||||
const eggText = addTextObject(this.scene, 0, 14, egg.getEggDescriptor(), TextStyle.PARTY, { align: "center" });
|
||||
eggText.setOrigin(0.5, 0);
|
||||
eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.ULTRA));
|
||||
eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.EPIC));
|
||||
ret.add(eggText);
|
||||
|
||||
this.eggGachaSummaryContainer.addAt(ret, 0);
|
||||
return ret;
|
||||
});
|
||||
|
||||
eggContainers.forEach((eggContainer, e) => {
|
||||
this.scene.tweens.add({
|
||||
targets: eggContainer,
|
||||
delay: this.getDelayValue(e * 100),
|
||||
duration: this.getDelayValue(350),
|
||||
scale: eggScale,
|
||||
ease: "Sine.easeOut"
|
||||
});
|
||||
// If action/cancel was pressed when the overlay was easing in, show all eggs at once
|
||||
// Otherwise show the eggs one by one with a small delay between each
|
||||
eggContainers.forEach((eggContainer, index) => {
|
||||
const delay = !this.transitionCancelled ? this.getDelayValue(index * 100) : 0;
|
||||
this.scene.time.delayedCall(delay, () =>
|
||||
this.scene.tweens.add({
|
||||
targets: eggContainer,
|
||||
duration: this.getDelayValue(350),
|
||||
scale: eggScale,
|
||||
ease: "Sine.easeOut",
|
||||
onComplete: () => {
|
||||
if (index === eggs.length - 1) {
|
||||
this.setTransitioning(false);
|
||||
this.summaryFinished = true;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -540,6 +558,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
this.eggGachaSummaryContainer.setAlpha(1);
|
||||
this.eggGachaSummaryContainer.removeAll(true);
|
||||
this.setTransitioning(false);
|
||||
this.summaryFinished = false;
|
||||
this.eggGachaOptionsContainer.setVisible(true);
|
||||
}
|
||||
});
|
||||
@ -613,7 +632,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
} else {
|
||||
|
||||
if (this.eggGachaSummaryContainer.visible) {
|
||||
if (button === Button.ACTION || button === Button.CANCEL) {
|
||||
if (this.summaryFinished && (button === Button.ACTION || button === Button.CANCEL)) {
|
||||
this.hideSummary();
|
||||
success = true;
|
||||
}
|
||||
@ -625,7 +644,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) {
|
||||
error = true;
|
||||
this.showError(i18next.t("egg:notEnoughVouchers"));
|
||||
} else if (this.scene.gameData.eggs.length < 99) {
|
||||
} else if (this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) {
|
||||
if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) {
|
||||
this.consumeVouchers(VoucherType.REGULAR, 1);
|
||||
}
|
||||
@ -640,7 +659,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) {
|
||||
error = true;
|
||||
this.showError(i18next.t("egg:notEnoughVouchers"));
|
||||
} else if (this.scene.gameData.eggs.length < 95) {
|
||||
} else if (this.scene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) {
|
||||
if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) {
|
||||
this.consumeVouchers(VoucherType.PLUS, 1);
|
||||
}
|
||||
@ -657,7 +676,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
|| (this.cursor === 3 && !this.scene.gameData.voucherCounts[VoucherType.PREMIUM] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE)) {
|
||||
error = true;
|
||||
this.showError(i18next.t("egg:notEnoughVouchers"));
|
||||
} else if (this.scene.gameData.eggs.length < 90) {
|
||||
} else if (this.scene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) {
|
||||
if (this.cursor === 3) {
|
||||
if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) {
|
||||
this.consumeVouchers(VoucherType.PREMIUM, 1);
|
||||
@ -678,7 +697,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
||||
if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) {
|
||||
error = true;
|
||||
this.showError(i18next.t("egg:notEnoughVouchers"));
|
||||
} else if (this.scene.gameData.eggs.length < 75) {
|
||||
} else if (this.scene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) {
|
||||
if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) {
|
||||
this.consumeVouchers(VoucherType.GOLDEN, 1);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import { BattleType } from "../battle";
|
||||
import { RunEntry } from "../system/game-data";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { TrainerVariant } from "../field/trainer";
|
||||
import { RunDisplayMode } from "#app/ui/run-info-ui-handler";
|
||||
|
||||
export type RunSelectCallback = (cursor: number) => void;
|
||||
|
||||
@ -104,7 +105,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler {
|
||||
if (button === Button.ACTION) {
|
||||
const cursor = this.cursor + this.scrollCursor;
|
||||
if (this.runs[cursor]) {
|
||||
this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, true);
|
||||
this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, RunDisplayMode.RUN_HISTORY, true);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { SessionSaveData } from "../system/game-data";
|
||||
import { TextStyle, addTextObject, addBBCodeTextObject, getTextColor } from "./text";
|
||||
import { Mode } from "./ui";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import { getPokeballAtlasKey } from "#app/data/pokeball";
|
||||
import * as Utils from "../utils";
|
||||
import PokemonData from "../system/pokemon-data";
|
||||
import i18next from "i18next";
|
||||
@ -22,6 +23,8 @@ import * as Modifier from "../modifier/modifier";
|
||||
import { Species } from "#enums/species";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { SettingKeyboard } from "#app/system/settings/settings-keyboard";
|
||||
import { getBiomeName } from "#app/data/balance/biomes";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
||||
/**
|
||||
* RunInfoUiMode indicates possible overlays of RunInfoUiHandler.
|
||||
@ -34,6 +37,11 @@ enum RunInfoUiMode {
|
||||
ENDING_ART
|
||||
}
|
||||
|
||||
export enum RunDisplayMode {
|
||||
RUN_HISTORY,
|
||||
SESSION_PREVIEW
|
||||
}
|
||||
|
||||
/**
|
||||
* Some variables are protected because this UI class will most likely be extended in the future to display more information.
|
||||
* These variables will most likely be shared across 'classes' aka pages.
|
||||
@ -41,6 +49,7 @@ enum RunInfoUiMode {
|
||||
* For now, I leave as is.
|
||||
*/
|
||||
export default class RunInfoUiHandler extends UiHandler {
|
||||
protected runDisplayMode: RunDisplayMode;
|
||||
protected runInfo: SessionSaveData;
|
||||
protected isVictory: boolean;
|
||||
protected pageMode: RunInfoUiMode;
|
||||
@ -66,6 +75,7 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
// The import of the modifiersModule is loaded here to sidestep async/await issues.
|
||||
this.modifiersModule = Modifier;
|
||||
this.runContainer.setVisible(false);
|
||||
this.scene.loadImage("encounter_exclaim", "mystery-encounters");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,9 +97,15 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
this.runContainer.add(gameStatsBg);
|
||||
|
||||
const run = args[0];
|
||||
this.runDisplayMode = args[1];
|
||||
if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) {
|
||||
this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry));
|
||||
this.isVictory = run.isVictory ?? false;
|
||||
} else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) {
|
||||
this.runInfo = args[0];
|
||||
}
|
||||
// Assigning information necessary for the UI's creation
|
||||
this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry));
|
||||
this.isVictory = run.isVictory;
|
||||
|
||||
this.pageMode = RunInfoUiMode.MAIN;
|
||||
|
||||
// Creates Header and adds to this.runContainer
|
||||
@ -102,7 +118,11 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
const runResultWindow = addWindow(this.scene, 0, 0, this.statsBgWidth - 11, 65);
|
||||
runResultWindow.setOrigin(0, 0);
|
||||
this.runResultContainer.add(runResultWindow);
|
||||
this.parseRunResult();
|
||||
if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) {
|
||||
this.parseRunResult();
|
||||
} else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) {
|
||||
this.parseRunStatus();
|
||||
}
|
||||
|
||||
// Creates Run Info Container
|
||||
this.runInfoContainer = this.scene.add.container(0, 89);
|
||||
@ -226,6 +246,66 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
this.runContainer.add(this.runResultContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used when the Run Info UI is used to preview a Session.
|
||||
* It edits {@linkcode runResultContainer}, but most importantly - does not display the negative results of a Mystery Encounter or any details of a trainer's party.
|
||||
* Trainer Parties are replaced with their sprites, names, and their party size.
|
||||
* Mystery Encounters contain sprites associated with MEs + the title of the specific ME.
|
||||
*/
|
||||
private parseRunStatus() {
|
||||
const runStatusText = addTextObject(this.scene, 6, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex} - ${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize : "65px", lineSpacing: 0.1 });
|
||||
|
||||
const enemyContainer = this.scene.add.container(0, 0);
|
||||
this.runResultContainer.add(enemyContainer);
|
||||
if (this.runInfo.battleType === BattleType.WILD) {
|
||||
if (this.runInfo.enemyParty.length === 1) {
|
||||
this.parseWildSingleDefeat(enemyContainer);
|
||||
} else if (this.runInfo.enemyParty.length === 2) {
|
||||
this.parseWildDoubleDefeat(enemyContainer);
|
||||
}
|
||||
} else if (this.runInfo.battleType === BattleType.TRAINER) {
|
||||
this.showTrainerSprites(enemyContainer);
|
||||
const row_limit = 3;
|
||||
this.runInfo.enemyParty.forEach((p, i) => {
|
||||
const pokeball = this.scene.add.sprite(0, 0, "pb");
|
||||
pokeball.setFrame(getPokeballAtlasKey(p.pokeball));
|
||||
pokeball.setScale(0.5);
|
||||
pokeball.setPosition(52 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25);
|
||||
enemyContainer.add(pokeball);
|
||||
});
|
||||
const trainerObj = this.runInfo.trainer.toTrainer(this.scene);
|
||||
const RIVAL_TRAINER_ID_THRESHOLD = 375;
|
||||
let trainerName = "";
|
||||
if (this.runInfo.trainer.trainerType >= RIVAL_TRAINER_ID_THRESHOLD) {
|
||||
trainerName = (trainerObj.variant === TrainerVariant.FEMALE) ? i18next.t("trainerNames:rival_female") : i18next.t("trainerNames:rival");
|
||||
} else {
|
||||
trainerName = trainerObj.getName(0, true);
|
||||
}
|
||||
const boxString = i18next.t(trainerObj.variant !== TrainerVariant.DOUBLE ? "battle:trainerAppeared" : "battle:trainerAppearedDouble", { trainerName: trainerName }).replace(/\n/g, " ");
|
||||
const descContainer = this.scene.add.container(0, 0);
|
||||
const textBox = addTextObject(this.scene, 0, 0, boxString, TextStyle.WINDOW, { fontSize : "35px", wordWrap: { width: 200 }});
|
||||
descContainer.add(textBox);
|
||||
descContainer.setPosition(52, 29);
|
||||
this.runResultContainer.add(descContainer);
|
||||
} else if (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||
const encounterExclaim = this.scene.add.sprite(0, 0, "encounter_exclaim");
|
||||
encounterExclaim.setPosition(34, 26);
|
||||
encounterExclaim.setScale(0.65);
|
||||
const subSprite = this.scene.add.sprite(56, -106, "pkmn__sub");
|
||||
subSprite.setScale(0.65);
|
||||
subSprite.setPosition(34, 46);
|
||||
const mysteryEncounterTitle = i18next.t(this.scene.getMysteryEncounter(this.runInfo.mysteryEncounterType as MysteryEncounterType, true).localizationKey + ":title");
|
||||
const descContainer = this.scene.add.container(0, 0);
|
||||
const textBox = addTextObject(this.scene, 0, 0, mysteryEncounterTitle, TextStyle.WINDOW, { fontSize : "45px", wordWrap: { width: 160 }});
|
||||
descContainer.add(textBox);
|
||||
descContainer.setPosition(47, 37);
|
||||
this.runResultContainer.add([ encounterExclaim, subSprite, descContainer ]);
|
||||
}
|
||||
|
||||
this.runResultContainer.add(runStatusText);
|
||||
this.runContainer.add(this.runResultContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called to edit an enemyContainer to represent a loss from a defeat by a wild single Pokemon battle.
|
||||
* @param enemyContainer - container holding enemy visual and level information
|
||||
@ -278,40 +358,58 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* This edits a container to represent a loss from a defeat by a trainer battle.
|
||||
* @param enemyContainer - container holding enemy visuals and level information
|
||||
* The trainers are placed to the left of their party.
|
||||
* Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...)
|
||||
*
|
||||
* Party Pokemon have their icons, terastalization status, and level shown.
|
||||
* This loads the enemy sprites, positions, and scales them according to the current display mode of the RunInfo UI and then adds them to the container parameter.
|
||||
* Used by {@linkcode parseRunStatus} and {@linkcode parseTrainerDefeat}
|
||||
* @param enemyContainer a Phaser Container that should hold enemy sprites
|
||||
*/
|
||||
private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) {
|
||||
private showTrainerSprites(enemyContainer: Phaser.GameObjects.Container) {
|
||||
// Creating the trainer sprite and adding it to enemyContainer
|
||||
const tObj = this.runInfo.trainer.toTrainer(this.scene);
|
||||
|
||||
// Loads trainer assets on demand, as they are not loaded by default in the scene
|
||||
tObj.config.loadAssets(this.scene, this.runInfo.trainer.variant).then(() => {
|
||||
const tObjSpriteKey = tObj.config.getSpriteKey(this.runInfo.trainer.variant === TrainerVariant.FEMALE, false);
|
||||
const tObjSprite = this.scene.add.sprite(0, 5, tObjSpriteKey);
|
||||
if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE) {
|
||||
if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE && !tObj.config.doubleOnly) {
|
||||
const doubleContainer = this.scene.add.container(5, 8);
|
||||
tObjSprite.setPosition(-3, -3);
|
||||
const tObjPartnerSpriteKey = tObj.config.getSpriteKey(true, true);
|
||||
const tObjPartnerSprite = this.scene.add.sprite(5, -3, tObjPartnerSpriteKey);
|
||||
// Double Trainers have smaller sprites than Single Trainers
|
||||
tObjPartnerSprite.setScale(0.20);
|
||||
tObjSprite.setScale(0.20);
|
||||
doubleContainer.add(tObjSprite);
|
||||
doubleContainer.add(tObjPartnerSprite);
|
||||
doubleContainer.setPosition(12, 38);
|
||||
if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) {
|
||||
tObjPartnerSprite.setScale(0.20);
|
||||
tObjSprite.setScale(0.20);
|
||||
doubleContainer.add(tObjSprite);
|
||||
doubleContainer.add(tObjPartnerSprite);
|
||||
doubleContainer.setPosition(12, 38);
|
||||
} else {
|
||||
tObjSprite.setScale(0.55);
|
||||
tObjSprite.setPosition(-9, -3);
|
||||
tObjPartnerSprite.setScale(0.55);
|
||||
doubleContainer.add([ tObjSprite, tObjPartnerSprite ]);
|
||||
doubleContainer.setPosition(28, 40);
|
||||
}
|
||||
enemyContainer.add(doubleContainer);
|
||||
} else {
|
||||
tObjSprite.setScale(0.35, 0.35);
|
||||
tObjSprite.setPosition(12, 28);
|
||||
const scale = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? 0.35 : 0.65;
|
||||
const position = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? [ 12, 28 ] : [ 32, 36 ];
|
||||
tObjSprite.setScale(scale, scale);
|
||||
tObjSprite.setPosition(position[0], position[1]);
|
||||
enemyContainer.add(tObjSprite);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This edits a container to represent a loss from a defeat by a trainer battle.
|
||||
* The trainers are placed to the left of their party.
|
||||
* Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...)
|
||||
*
|
||||
* Party Pokemon have their icons, terastalization status, and level shown.
|
||||
* @param enemyContainer - container holding enemy visuals and level information
|
||||
*/
|
||||
private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) {
|
||||
// Loads and adds trainer sprites to the UI
|
||||
this.showTrainerSprites(enemyContainer);
|
||||
// Determining which Terastallize Modifier belongs to which Pokemon
|
||||
// Creates a dictionary {PokemonId: TeraShardType}
|
||||
const teraPokemon = {};
|
||||
|
@ -10,8 +10,10 @@ import MessageUiHandler from "./message-ui-handler";
|
||||
import { TextStyle, addTextObject } from "./text";
|
||||
import { Mode } from "./ui";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import { RunDisplayMode } from "#app/ui/run-info-ui-handler";
|
||||
|
||||
const sessionSlotCount = 5;
|
||||
const SESSION_SLOTS_COUNT = 5;
|
||||
const SLOTS_ON_SCREEN = 3;
|
||||
|
||||
export enum SaveSlotUiMode {
|
||||
LOAD,
|
||||
@ -33,7 +35,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler {
|
||||
|
||||
private scrollCursor: integer = 0;
|
||||
|
||||
private cursorObj: Phaser.GameObjects.NineSlice | null;
|
||||
private cursorObj: Phaser.GameObjects.Container | null;
|
||||
|
||||
private sessionSlotsContainerInitialY: number;
|
||||
|
||||
@ -84,9 +86,9 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler {
|
||||
|
||||
this.saveSlotSelectContainer.setVisible(true);
|
||||
this.populateSessionSlots();
|
||||
|
||||
this.setScrollCursor(0);
|
||||
this.setCursor(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -147,21 +149,28 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler {
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
const cursorPosition = this.cursor + this.scrollCursor;
|
||||
switch (button) {
|
||||
case Button.UP:
|
||||
if (this.cursor) {
|
||||
success = this.setCursor(this.cursor - 1);
|
||||
// Check to prevent cursor from accessing a negative index
|
||||
success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition);
|
||||
} else if (this.scrollCursor) {
|
||||
success = this.setScrollCursor(this.scrollCursor - 1);
|
||||
success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition);
|
||||
}
|
||||
break;
|
||||
case Button.DOWN:
|
||||
if (this.cursor < 2) {
|
||||
success = this.setCursor(this.cursor + 1);
|
||||
} else if (this.scrollCursor < sessionSlotCount - 3) {
|
||||
success = this.setScrollCursor(this.scrollCursor + 1);
|
||||
if (this.cursor < (SLOTS_ON_SCREEN - 1)) {
|
||||
success = this.setCursor(this.cursor + 1, cursorPosition);
|
||||
} else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) {
|
||||
success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition);
|
||||
}
|
||||
break;
|
||||
case Button.RIGHT:
|
||||
if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) {
|
||||
this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,12 +184,18 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
|
||||
populateSessionSlots() {
|
||||
for (let s = 0; s < sessionSlotCount; s++) {
|
||||
for (let s = 0; s < SESSION_SLOTS_COUNT; s++) {
|
||||
const sessionSlot = new SessionSlot(this.scene, s);
|
||||
sessionSlot.load();
|
||||
this.scene.add.existing(sessionSlot);
|
||||
this.sessionSlotsContainer.add(sessionSlot);
|
||||
this.sessionSlots.push(sessionSlot);
|
||||
sessionSlot.load().then((success) => {
|
||||
// If the cursor was moved to this slot while the session was loading
|
||||
// call setCursor again to shift the slot position and show the arrow for save preview
|
||||
if (success && (this.cursor + this.scrollCursor) === s) {
|
||||
this.setCursor(s);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,25 +213,79 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler {
|
||||
this.saveSlotSelectMessageBoxContainer.setVisible(!!text?.length);
|
||||
}
|
||||
|
||||
setCursor(cursor: integer): boolean {
|
||||
/**
|
||||
* Move the cursor to a new position and update the view accordingly
|
||||
* @param cursor the new cursor position, between `0` and `SLOTS_ON_SCREEN - 1`
|
||||
* @param prevSlotIndex index of the previous session occupied by the cursor, between `0` and `SESSION_SLOTS_COUNT - 1` - optional
|
||||
* @returns `true` if the cursor position has changed | `false` if it has not
|
||||
*/
|
||||
override setCursor(cursor: integer, prevSlotIndex?: integer): boolean {
|
||||
const changed = super.setCursor(cursor);
|
||||
|
||||
if (!this.cursorObj) {
|
||||
this.cursorObj = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6);
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
this.cursorObj = this.scene.add.container(0, 0);
|
||||
const cursorBox = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6);
|
||||
const rightArrow = this.scene.add.image(0, 0, "cursor");
|
||||
rightArrow.setPosition(160, 0);
|
||||
rightArrow.setName("rightArrow");
|
||||
this.cursorObj.add([ cursorBox, rightArrow ]);
|
||||
this.sessionSlotsContainer.add(this.cursorObj);
|
||||
}
|
||||
this.cursorObj.setPosition(4, 4 + (cursor + this.scrollCursor) * 56);
|
||||
const cursorPosition = cursor + this.scrollCursor;
|
||||
const cursorIncrement = cursorPosition * 56;
|
||||
if (this.sessionSlots[cursorPosition] && this.cursorObj) {
|
||||
const hasData = this.sessionSlots[cursorPosition].hasData;
|
||||
// If the session slot lacks session data, it does not move from its default, central position.
|
||||
// Only session slots with session data will move leftwards and have a visible arrow.
|
||||
if (!hasData) {
|
||||
this.cursorObj.setPosition(151, 26 + cursorIncrement);
|
||||
this.sessionSlots[cursorPosition].setPosition(0, cursorIncrement);
|
||||
} else {
|
||||
this.cursorObj.setPosition(145, 26 + cursorIncrement);
|
||||
this.sessionSlots[cursorPosition].setPosition(-6, cursorIncrement);
|
||||
}
|
||||
this.setArrowVisibility(hasData);
|
||||
}
|
||||
if (!Utils.isNullOrUndefined(prevSlotIndex)) {
|
||||
this.revertSessionSlot(prevSlotIndex);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
setScrollCursor(scrollCursor: integer): boolean {
|
||||
/**
|
||||
* Helper function that resets the given session slot to its default central position
|
||||
*/
|
||||
revertSessionSlot(slotIndex: integer): void {
|
||||
const sessionSlot = this.sessionSlots[slotIndex];
|
||||
if (sessionSlot) {
|
||||
sessionSlot.setPosition(0, slotIndex * 56);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that checks if the session slot involved holds data or not
|
||||
* @param hasData `true` if session slot contains data | 'false' if not
|
||||
*/
|
||||
setArrowVisibility(hasData: boolean): void {
|
||||
if (this.cursorObj) {
|
||||
const rightArrow = this.cursorObj?.getByName("rightArrow") as Phaser.GameObjects.Image;
|
||||
rightArrow.setVisible(hasData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the scrolling cursor to a new position and update the view accordingly
|
||||
* @param scrollCursor the new cursor position, between `0` and `SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN`
|
||||
* @param prevSlotIndex index of the previous slot occupied by the cursor, between `0` and `SESSION_SLOTS_COUNT-1` - optional
|
||||
* @returns `true` if the cursor position has changed | `false` if it has not
|
||||
*/
|
||||
setScrollCursor(scrollCursor: integer, prevSlotIndex?: integer): boolean {
|
||||
const changed = scrollCursor !== this.scrollCursor;
|
||||
|
||||
if (changed) {
|
||||
this.scrollCursor = scrollCursor;
|
||||
this.setCursor(this.cursor);
|
||||
this.setCursor(this.cursor, prevSlotIndex);
|
||||
this.scene.tweens.add({
|
||||
targets: this.sessionSlotsContainer,
|
||||
y: this.sessionSlotsContainerInitialY - 56 * scrollCursor,
|
||||
@ -231,6 +300,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler {
|
||||
clear() {
|
||||
super.clear();
|
||||
this.saveSlotSelectContainer.setVisible(false);
|
||||
this.setScrollCursor(0);
|
||||
this.eraseCursor();
|
||||
this.saveSlotSelectCallback = null;
|
||||
this.clearSessionSlots();
|
||||
@ -254,6 +324,8 @@ class SessionSlot extends Phaser.GameObjects.Container {
|
||||
public hasData: boolean;
|
||||
private loadingLabel: Phaser.GameObjects.Text;
|
||||
|
||||
public saveData: SessionSaveData;
|
||||
|
||||
constructor(scene: BattleScene, slotId: integer) {
|
||||
super(scene, 0, slotId * 56);
|
||||
|
||||
@ -330,6 +402,10 @@ class SessionSlot extends Phaser.GameObjects.Container {
|
||||
load(): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
this.scene.gameData.getSession(this.slotId).then(async sessionData => {
|
||||
// Ignore the results if the view was exited
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
if (!sessionData) {
|
||||
this.hasData = false;
|
||||
this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty"));
|
||||
@ -337,6 +413,7 @@ class SessionSlot extends Phaser.GameObjects.Container {
|
||||
return;
|
||||
}
|
||||
this.hasData = true;
|
||||
this.saveData = sessionData;
|
||||
await this.setupWithData(sessionData);
|
||||
resolve(true);
|
||||
});
|
||||
|
@ -1793,7 +1793,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
options.push({
|
||||
label: `x${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`,
|
||||
handler: () => {
|
||||
if (this.scene.gameData.eggs.length < 99 && (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost)) {
|
||||
if ((this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE)
|
||||
&& (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost)) {
|
||||
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
|
||||
starterData.candyCount -= sameSpeciesEggCost;
|
||||
}
|
||||
|
@ -356,11 +356,11 @@ export function getEggTierTextTint(tier: EggTier): integer {
|
||||
switch (tier) {
|
||||
case EggTier.COMMON:
|
||||
return getModifierTierTextTint(ModifierTier.COMMON);
|
||||
case EggTier.GREAT:
|
||||
case EggTier.RARE:
|
||||
return getModifierTierTextTint(ModifierTier.GREAT);
|
||||
case EggTier.ULTRA:
|
||||
case EggTier.EPIC:
|
||||
return getModifierTierTextTint(ModifierTier.ULTRA);
|
||||
case EggTier.MASTER:
|
||||
case EggTier.LEGENDARY:
|
||||
return getModifierTierTextTint(ModifierTier.MASTER);
|
||||
}
|
||||
}
|
||||
|
@ -38,10 +38,6 @@ export function shiftCharCodes(str: string, shiftCount: integer) {
|
||||
return newStr;
|
||||
}
|
||||
|
||||
export function clampInt(value: integer, min: integer, max: integer): integer {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
export function randGauss(stdev: number, mean: number = 0): number {
|
||||
if (!stdev) {
|
||||
return 0;
|
||||
|
Loading…
Reference in New Issue
Block a user