mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-29 10:16:14 +00:00
Merge branch 'beta' into hebrew-pr
This commit is contained in:
commit
d0f6dd7df7
@ -1146,6 +1146,13 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
}
|
||||
|
||||
getDoubleBattleChance(newWaveIndex: number, playerField: PlayerPokemon[]) {
|
||||
const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
||||
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
||||
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance));
|
||||
return Math.max(doubleChance.value, 1);
|
||||
}
|
||||
|
||||
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounterType?: MysteryEncounterType): Battle | null {
|
||||
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
|
||||
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
|
||||
@ -1229,10 +1236,7 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
if (double === undefined && newWaveIndex > 1) {
|
||||
if (newBattleType === BattleType.WILD && !this.gameMode.isWaveFinal(newWaveIndex)) {
|
||||
const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
||||
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
||||
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance));
|
||||
newDouble = !Utils.randSeedInt(doubleChance.value);
|
||||
newDouble = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
|
||||
} else if (newBattleType === BattleType.TRAINER) {
|
||||
newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
|
||||
}
|
||||
|
@ -165,14 +165,27 @@ export class BlockRecoilDamageAttr extends AbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute for abilities that increase the chance of a double battle
|
||||
* occurring.
|
||||
* @see apply
|
||||
*/
|
||||
export class DoubleBattleChanceAbAttr extends AbAttr {
|
||||
constructor() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
const doubleChance = (args[0] as Utils.IntegerHolder);
|
||||
doubleChance.value = Math.max(doubleChance.value / 2, 1);
|
||||
/**
|
||||
* Increases the chance of a double battle occurring
|
||||
* @param args [0] {@linkcode Utils.NumberHolder} for double battle chance
|
||||
* @returns true if the ability was applied
|
||||
*/
|
||||
apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
const doubleBattleChance = args[0] as Utils.NumberHolder;
|
||||
// This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt
|
||||
// A double battle will initiate if the generated number is 0
|
||||
doubleBattleChance.value = doubleBattleChance.value / 4;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -4831,11 +4844,9 @@ export function initAbilities() {
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.VOLT_ABSORB, 3)
|
||||
.attr(TypeImmunityHealAbAttr, Type.ELECTRIC)
|
||||
.partial() // Healing not blocked by Heal Block
|
||||
.ignorable(),
|
||||
new Ability(Abilities.WATER_ABSORB, 3)
|
||||
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
||||
.partial() // Healing not blocked by Heal Block
|
||||
.ignorable(),
|
||||
new Ability(Abilities.OBLIVIOUS, 3)
|
||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
|
||||
@ -4948,8 +4959,7 @@ export function initAbilities() {
|
||||
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED))
|
||||
.ignorable(),
|
||||
new Ability(Abilities.RAIN_DISH, 3)
|
||||
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
|
||||
.partial(), // Healing not blocked by Heal Block
|
||||
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN),
|
||||
new Ability(Abilities.SAND_STREAM, 3)
|
||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SANDSTORM)
|
||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SANDSTORM),
|
||||
@ -5080,7 +5090,6 @@ export function initAbilities() {
|
||||
.attr(PostWeatherLapseHealAbAttr, 2, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
|
||||
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 1.25)
|
||||
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
||||
.partial() // Healing not blocked by Heal Block
|
||||
.ignorable(),
|
||||
new Ability(Abilities.DOWNLOAD, 4)
|
||||
.attr(DownloadAbAttr),
|
||||
@ -5161,8 +5170,7 @@ export function initAbilities() {
|
||||
.ignorable(),
|
||||
new Ability(Abilities.ICE_BODY, 4)
|
||||
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
|
||||
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW)
|
||||
.partial(), // Healing not blocked by Heal Block
|
||||
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW),
|
||||
new Ability(Abilities.SOLID_ROCK, 4)
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
|
||||
.ignorable(),
|
||||
@ -5332,8 +5340,7 @@ export function initAbilities() {
|
||||
.ignorable()
|
||||
.unimplemented(),
|
||||
new Ability(Abilities.CHEEK_POUCH, 6)
|
||||
.attr(HealFromBerryUseAbAttr, 1/3)
|
||||
.partial(), // Healing not blocked by Heal Block
|
||||
.attr(HealFromBerryUseAbAttr, 1/3),
|
||||
new Ability(Abilities.PROTEAN, 6)
|
||||
.attr(PokemonTypeChangeAbAttr),
|
||||
//.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.PROTEAN)), //Gen 9 Implementation
|
||||
|
@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "../messages";
|
||||
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
|
||||
import { StatusEffect } from "./status-effect";
|
||||
import * as Utils from "../utils";
|
||||
import { ChargeAttr, MoveFlags, allMoves } from "./move";
|
||||
import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr } from "./move";
|
||||
import { Type } from "./type";
|
||||
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
|
||||
import { TerrainType } from "./terrain";
|
||||
@ -141,6 +141,18 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
||||
*/
|
||||
abstract isMoveRestricted(move: Moves): boolean;
|
||||
|
||||
/**
|
||||
* Checks if this tag is restricting a move based on a user's decisions during the target selection phase
|
||||
*
|
||||
* @param {Moves} move {@linkcode Moves} move ID to check restriction for
|
||||
* @param {Pokemon} user {@linkcode Pokemon} the user of the above move
|
||||
* @param {Pokemon} target {@linkcode Pokemon} the target of the above move
|
||||
* @returns {boolean} `false` unless overridden by the child tag
|
||||
*/
|
||||
isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text to display when the player attempts to select a move that is restricted by this tag.
|
||||
*
|
||||
@ -2178,6 +2190,74 @@ export class ExposedTag extends BattlerTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag that prevents HP recovery from held items and move effects. It also blocks the usage of recovery moves.
|
||||
* Applied by moves: {@linkcode Moves.HEAL_BLOCK | Heal Block (5 turns)}, {@linkcode Moves.PSYCHIC_NOISE | Psychic Noise (2 turns)}
|
||||
*
|
||||
* @extends MoveRestrictionBattlerTag
|
||||
*/
|
||||
export class HealBlockTag extends MoveRestrictionBattlerTag {
|
||||
constructor(turnCount: number, sourceMove: Moves) {
|
||||
super(BattlerTagType.HEAL_BLOCK, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], turnCount, sourceMove);
|
||||
}
|
||||
|
||||
onActivation(pokemon: Pokemon): string {
|
||||
return i18next.t("battle:battlerTagsHealBlock", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a move is disabled under Heal Block
|
||||
* @param {Moves} move {@linkcode Moves} the move ID
|
||||
* @returns `true` if the move has a TRIAGE_MOVE flag and is a status move
|
||||
*/
|
||||
override isMoveRestricted(move: Moves): boolean {
|
||||
if (allMoves[move].hasFlag(MoveFlags.TRIAGE_MOVE) && allMoves[move].category === MoveCategory.STATUS) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a move is disabled under Heal Block because of its choice of target
|
||||
* Implemented b/c of Pollen Puff
|
||||
* @param {Moves} move {@linkcode Moves} the move ID
|
||||
* @param {Pokemon} user {@linkcode Pokemon} the move user
|
||||
* @param {Pokemon} target {@linkcode Pokemon} the target of the move
|
||||
* @returns `true` if the move cannot be used because the target is an ally
|
||||
*/
|
||||
override isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon) {
|
||||
const moveCategory = new Utils.IntegerHolder(allMoves[move].category);
|
||||
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory);
|
||||
if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses DisabledTag's selectionDeniedText() message
|
||||
*/
|
||||
override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name });
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move
|
||||
* @param {Moves} move {@linkcode Moves} ID of the move being interrupted
|
||||
* @returns {string} text to display when the move is interrupted
|
||||
*/
|
||||
override interruptedText(pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
|
||||
}
|
||||
|
||||
override onRemove(pokemon: Pokemon): void {
|
||||
super.onRemove(pokemon);
|
||||
|
||||
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHealBlockOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag that doubles the type effectiveness of Fire-type moves.
|
||||
* @extends BattlerTag
|
||||
@ -2490,6 +2570,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||
return new SubstituteTag(sourceMove, sourceId);
|
||||
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
||||
return new MysteryEncounterPostSummonTag();
|
||||
case BattlerTagType.HEAL_BLOCK:
|
||||
return new HealBlockTag(turnCount, sourceMove);
|
||||
case BattlerTagType.NONE:
|
||||
default:
|
||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||
|
@ -867,9 +867,17 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:star_grunt.encounter.1",
|
||||
"dialogue:star_grunt.encounter.2",
|
||||
"dialogue:star_grunt.encounter.3",
|
||||
"dialogue:star_grunt.encounter.4",
|
||||
"dialogue:star_grunt.encounter.5",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:star_grunt.victory.1",
|
||||
"dialogue:star_grunt.victory.2",
|
||||
"dialogue:star_grunt.victory.3",
|
||||
"dialogue:star_grunt.victory.4",
|
||||
"dialogue:star_grunt.victory.5",
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -877,9 +885,11 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:giacomo.encounter.1",
|
||||
"dialogue:giacomo.encounter.2",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:giacomo.victory.1",
|
||||
"dialogue:giacomo.victory.2",
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -887,9 +897,11 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:mela.encounter.1",
|
||||
"dialogue:mela.encounter.2",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:mela.victory.1",
|
||||
"dialogue:mela.victory.2",
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -897,9 +909,11 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:atticus.encounter.1",
|
||||
"dialogue:atticus.encounter.2",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:atticus.victory.1",
|
||||
"dialogue:atticus.victory.2",
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -907,9 +921,11 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:ortega.encounter.1",
|
||||
"dialogue:ortega.encounter.2",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:ortega.victory.1",
|
||||
"dialogue:ortega.victory.2",
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -917,9 +933,11 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:eri.encounter.1",
|
||||
"dialogue:eri.encounter.2",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:eri.victory.1",
|
||||
"dialogue:eri.victory.2",
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -4539,6 +4539,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
||||
case BattlerTagType.NIGHTMARE:
|
||||
case BattlerTagType.DROWSY:
|
||||
case BattlerTagType.DISABLED:
|
||||
case BattlerTagType.HEAL_BLOCK:
|
||||
return -5;
|
||||
case BattlerTagType.SEEDED:
|
||||
case BattlerTagType.SALT_CURED:
|
||||
@ -7826,8 +7827,8 @@ export function initMoves() {
|
||||
.makesContact()
|
||||
.attr(LessPPMorePowerAttr),
|
||||
new StatusMove(Moves.HEAL_BLOCK, Type.PSYCHIC, 100, 15, -1, 0, 4)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||
.unimplemented(),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, true, 5)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.WRING_OUT, Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 4)
|
||||
.attr(OpponentHighHpPowerAttr, 120)
|
||||
.makesContact(),
|
||||
@ -9609,7 +9610,7 @@ export function initMoves() {
|
||||
.recklessMove(),
|
||||
new AttackMove(Moves.PSYCHIC_NOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, -1, 0, 9)
|
||||
.soundBased()
|
||||
.partial(),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, false, 2),
|
||||
new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
|
||||
.attr(FlinchAttr)
|
||||
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].priority > 0 ) // TODO: is this bang correct?
|
||||
|
@ -431,14 +431,13 @@ function getPokemonTradeOptions(scene: BattleScene): Map<number, EnemyPokemon[]>
|
||||
|
||||
function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?: number): PokemonSpecies {
|
||||
let newSpecies: PokemonSpecies | undefined;
|
||||
let bstCap = 9999;
|
||||
let bstMin = 0;
|
||||
if (originalBst) {
|
||||
bstCap = originalBst + 100;
|
||||
bstMin = originalBst - 100;
|
||||
}
|
||||
while (isNullOrUndefined(newSpecies)) {
|
||||
let bstCap = 9999;
|
||||
let bstMin = 0;
|
||||
if (originalBst) {
|
||||
bstCap = originalBst + 100;
|
||||
bstMin = originalBst - 100;
|
||||
}
|
||||
|
||||
// Get all non-legendary species that fall within the Bst range requirements
|
||||
let validSpecies = allSpecies
|
||||
.filter(s => {
|
||||
|
@ -2261,8 +2261,8 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
[TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form
|
||||
p.generateAndPopulateMoveset();
|
||||
p.pokeball = PokeballType.ULTRA_BALL;
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ENTEI, Species.RAIKOU, Species.SUICUNE ], TrainerSlot.TRAINER, true, p => {
|
||||
|
@ -80,4 +80,5 @@ export enum BattlerTagType {
|
||||
BURNED_UP = "BURNED_UP",
|
||||
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
|
||||
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
||||
HEAL_BLOCK = "HEAL_BLOCK",
|
||||
}
|
||||
|
@ -2971,16 +2971,40 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return this.getRestrictingTag(moveId) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the given move is currently disabled for the user based on the player's target selection
|
||||
*
|
||||
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||
* @param {Pokemon} user {@linkcode Pokemon} the move user
|
||||
* @param {Pokemon} target {@linkcode Pokemon} the target of the move
|
||||
*
|
||||
* @returns {boolean} `true` if the move is disabled for this Pokemon due to the player's target selection
|
||||
*
|
||||
* @see {@linkcode MoveRestrictionBattlerTag}
|
||||
*/
|
||||
isMoveTargetRestricted(moveId: Moves, user: Pokemon, target: Pokemon): boolean {
|
||||
for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) {
|
||||
if ((tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) {
|
||||
return (tag as MoveRestrictionBattlerTag !== null);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists.
|
||||
*
|
||||
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||
* @param {Pokemon} user {@linkcode Pokemon} the move user, optional and used when the target is a factor in the move's restricted status
|
||||
* @param {Pokemon} target {@linkcode Pokemon} the target of the move, optional and used when the target is a factor in the move's restricted status
|
||||
* @returns {MoveRestrictionBattlerTag | null} the first tag on this Pokemon that restricts the move, or `null` if the move is not restricted.
|
||||
*/
|
||||
getRestrictingTag(moveId: Moves): MoveRestrictionBattlerTag | null {
|
||||
getRestrictingTag(moveId: Moves, user?: Pokemon, target?: Pokemon): MoveRestrictionBattlerTag | null {
|
||||
for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) {
|
||||
if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) {
|
||||
return tag as MoveRestrictionBattlerTag;
|
||||
} else if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) {
|
||||
return tag as MoveRestrictionBattlerTag;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -425,14 +425,32 @@ export default class Trainer extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
|
||||
if (retry && (attempt || 0) < 10) {
|
||||
// Prompts reroll of party member species if species already present in the enemy party
|
||||
if (this.checkDuplicateSpecies(ret)) {
|
||||
console.log("Duplicate species detected, prompting reroll...");
|
||||
retry = true;
|
||||
}
|
||||
|
||||
if (retry && (attempt ?? 0) < 10) {
|
||||
console.log("Rerolling party member...");
|
||||
ret = this.genNewPartyMemberSpecies(level, strength, (attempt || 0) + 1);
|
||||
ret = this.genNewPartyMemberSpecies(level, strength, (attempt ?? 0) + 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the enemy trainer already has the Pokemon species in their party
|
||||
* @param {PokemonSpecies} species {@linkcode PokemonSpecies}
|
||||
* @returns `true` if the species is already present in the party
|
||||
*/
|
||||
checkDuplicateSpecies(species: PokemonSpecies): boolean {
|
||||
const currentPartySpecies = this.scene.getEnemyParty().map(p => {
|
||||
return p.species.speciesId;
|
||||
});
|
||||
return currentPartySpecies.includes(species.speciesId);
|
||||
}
|
||||
|
||||
getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [integer, integer][] {
|
||||
if (trainerSlot && !this.isDouble()) {
|
||||
trainerSlot = TrainerSlot.NONE;
|
||||
|
@ -96,5 +96,7 @@
|
||||
"unlockedSomething": "{{unlockedThing}} wurde freigeschaltet.",
|
||||
"congratulations": "Glückwunsch!",
|
||||
"beatModeFirstTime": "{{speciesName}} hat den {{gameMode}} Modus zum ersten Mal beendet! Du erhältst {{newModifier}}!",
|
||||
"eggSkipPrompt": "Zur Ei-Zusammenfassung springen?"
|
||||
"eggSkipPrompt": "Zur Ei-Zusammenfassung springen?",
|
||||
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} kann nicht geheilt werden, da die Heilung blockiert wird!",
|
||||
"battlerTagsHealBlockOnRemove": "{{pokemonNameWithAffix}} kann wieder geheilt werden!"
|
||||
}
|
||||
|
@ -105,5 +105,7 @@
|
||||
"congratulations": "Congratulations!",
|
||||
"beatModeFirstTime": "{{speciesName}} beat {{gameMode}} Mode for the first time!\nYou received {{newModifier}}!",
|
||||
"ppReduced": "It reduced the PP of {{targetName}}'s\n{{moveName}} by {{reduction}}!",
|
||||
"mysteryEncounterAppeared": "What's this?"
|
||||
"mysteryEncounterAppeared": "What's this?",
|
||||
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} can't restore its HP!",
|
||||
"battlerTagsHealBlockOnRemove": "{{pokemonNameWithAffix}} can restore its HP again!"
|
||||
}
|
@ -85,5 +85,7 @@
|
||||
"statSeverelyFell_one": "¡El {{stats}} de {{pokemonNameWithAffix}} ha bajado muchísimo!",
|
||||
"statSeverelyFell_other": "¡{{stats}} de\n{{pokemonNameWithAffix}} han bajado muchísimo!",
|
||||
"statWontGoAnyLower_one": "¡El {{stats}} de {{pokemonNameWithAffix}} no puede bajar más!",
|
||||
"statWontGoAnyLower_other": "¡{{stats}} de\n{{pokemonNameWithAffix}} no pueden bajar más!"
|
||||
"statWontGoAnyLower_other": "¡{{stats}} de\n{{pokemonNameWithAffix}} no pueden bajar más!",
|
||||
"battlerTagsHealBlock": "¡{{pokemonNameWithAffix}} no puede restaurar sus PS!",
|
||||
"battlerTagsHealBlockOnRemove": "¡{{pokemonNameWithAffix}} ya puede recuperar PS!"
|
||||
}
|
||||
|
@ -99,5 +99,7 @@
|
||||
"unlockedSomething": "{{unlockedThing}}\na été débloqué.",
|
||||
"congratulations": "Félicitations !",
|
||||
"beatModeFirstTime": "{{speciesName}} a battu le mode {{gameMode}} pour la première fois !\nVous avez reçu {{newModifier}} !",
|
||||
"eggSkipPrompt": "Aller directement au résumé des Œufs éclos ?"
|
||||
"eggSkipPrompt": "Aller directement au résumé des Œufs éclos ?",
|
||||
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} ne peut pas guérir !",
|
||||
"battlerTagsHealBlockOnRemove": "Le blocage de soins qui affectait\n{{pokemonNameWithAffix}} s’est dissipé !"
|
||||
}
|
||||
|
@ -96,5 +96,7 @@
|
||||
"retryBattle": "Você gostaria de tentar novamente desde o início da batalha?",
|
||||
"unlockedSomething": "{{unlockedThing}}\nfoi desbloqueado.",
|
||||
"congratulations": "Parabéns!",
|
||||
"beatModeFirstTime": "{{speciesName}} venceu o Modo {{gameMode}} pela primeira vez!\nVocê recebeu {{newModifier}}!"
|
||||
"beatModeFirstTime": "{{speciesName}} venceu o Modo {{gameMode}} pela primeira vez!\nVocê recebeu {{newModifier}}!",
|
||||
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} não pode restaurar seus PS!",
|
||||
"battlerTagsHealBlockOnRemove": "{{pokemonNameWithAffix}} pode restaurar seus PS novamente!"
|
||||
}
|
||||
|
@ -413,7 +413,7 @@ export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the chance of a double battle occurring
|
||||
* Increases the chance of a double battle occurring
|
||||
* @param args [0] {@linkcode Utils.NumberHolder} for double battle chance
|
||||
* @returns true if the modifier was applied
|
||||
*/
|
||||
@ -421,7 +421,7 @@ export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier
|
||||
const doubleBattleChance = args[0] as Utils.NumberHolder;
|
||||
// This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt
|
||||
// A double battle will initiate if the generated number is 0
|
||||
doubleBattleChance.value = Math.ceil(doubleBattleChance.value / 4);
|
||||
doubleBattleChance.value = doubleBattleChance.value / 4;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import { HealAchv } from "#app/system/achv";
|
||||
import i18next from "i18next";
|
||||
import * as Utils from "#app/utils";
|
||||
import { CommonAnimPhase } from "./common-anim-phase";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { HealBlockTag } from "#app/data/battler-tags";
|
||||
|
||||
export class PokemonHealPhase extends CommonAnimPhase {
|
||||
private hpHealed: integer;
|
||||
@ -50,9 +52,14 @@ export class PokemonHealPhase extends CommonAnimPhase {
|
||||
|
||||
const hasMessage = !!this.message;
|
||||
const healOrDamage = (!pokemon.isFullHp() || this.hpHealed < 0);
|
||||
const healBlock = pokemon.getTag(BattlerTagType.HEAL_BLOCK) as HealBlockTag;
|
||||
let lastStatusEffect = StatusEffect.NONE;
|
||||
|
||||
if (healOrDamage) {
|
||||
if (healBlock && this.hpHealed > 0) {
|
||||
this.scene.queueMessage(healBlock.onActivation(pokemon));
|
||||
this.message = null;
|
||||
super.end();
|
||||
} else if (healOrDamage) {
|
||||
const hpRestoreMultiplier = new Utils.IntegerHolder(1);
|
||||
if (!this.revive) {
|
||||
this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
|
||||
|
@ -4,6 +4,8 @@ import { Command } from "#app/ui/command-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { CommandPhase } from "./command-phase";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { allMoves } from "#app/data/move";
|
||||
|
||||
export class SelectTargetPhase extends PokemonPhase {
|
||||
constructor(scene: BattleScene, fieldIndex: integer) {
|
||||
@ -17,6 +19,14 @@ export class SelectTargetPhase extends PokemonPhase {
|
||||
const move = turnCommand?.move?.move;
|
||||
this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => {
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
const fieldSide = this.scene.getField();
|
||||
const user = fieldSide[this.fieldIndex];
|
||||
const moveObject = allMoves[move!];
|
||||
if (moveObject && user.isMoveTargetRestricted(moveObject.id, user, fieldSide[targets[0]])) {
|
||||
const errorMessage = user.getRestrictingTag(move!, user, fieldSide[targets[0]])!.selectionDeniedText(user, moveObject.id);
|
||||
user.scene.queueMessage(i18next.t(errorMessage, { moveName: moveObject.name }), 0, true);
|
||||
targets = [];
|
||||
}
|
||||
if (targets.length < 1) {
|
||||
this.scene.currentBattle.turnCommands[this.fieldIndex] = null;
|
||||
this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex));
|
||||
|
59
src/test/abilities/arena_trap.test.ts
Normal file
59
src/test/abilities/arena_trap.test.ts
Normal file
@ -0,0 +1,59 @@
|
||||
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, it, expect } from "vitest";
|
||||
|
||||
describe("Abilities - Arena Trap", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset(Moves.SPLASH)
|
||||
.ability(Abilities.ARENA_TRAP)
|
||||
.enemySpecies(Species.RALTS)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.TELEPORT);
|
||||
});
|
||||
|
||||
// TODO: Enable test when Issue #935 is addressed
|
||||
it.todo("should not allow grounded Pokémon to flee", async () => {
|
||||
game.override.battleType("single");
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon();
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(enemy).toBe(game.scene.getEnemyPokemon());
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should guarantee double battle with any one LURE", async () => {
|
||||
game.override
|
||||
.startingModifier([
|
||||
{ name: "LURE" },
|
||||
])
|
||||
.startingWave(2);
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
expect(game.scene.getEnemyField().length).toBe(2);
|
||||
}, TIMEOUT);
|
||||
});
|
59
src/test/abilities/illuminate.test.ts
Normal file
59
src/test/abilities/illuminate.test.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
|
||||
|
||||
describe("Abilities - Illuminate", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset(Moves.SPLASH)
|
||||
.ability(Abilities.ILLUMINATE)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SAND_ATTACK);
|
||||
});
|
||||
|
||||
it("should prevent ACC stat stage from being lowered", async () => {
|
||||
game.override.battleType("single");
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
|
||||
expect(player.getStatStage(Stat.ACC)).toBe(0);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(player.getStatStage(Stat.ACC)).toBe(0);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should guarantee double battle with any one LURE", async () => {
|
||||
game.override
|
||||
.startingModifier([
|
||||
{ name: "LURE" },
|
||||
])
|
||||
.startingWave(2);
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
expect(game.scene.getEnemyField().length).toBe(2);
|
||||
}, TIMEOUT);
|
||||
});
|
68
src/test/abilities/no_guard.test.ts
Normal file
68
src/test/abilities/no_guard.test.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
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, it, expect, vi } from "vitest";
|
||||
|
||||
describe("Abilities - No Guard", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset(Moves.ZAP_CANNON)
|
||||
.ability(Abilities.NO_GUARD)
|
||||
.enemyLevel(200)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("should make moves always hit regardless of move accuracy", async () => {
|
||||
game.override.battleType("single");
|
||||
|
||||
await game.classicMode.startBattle([
|
||||
Species.REGIELEKI
|
||||
]);
|
||||
|
||||
game.move.select(Moves.ZAP_CANNON);
|
||||
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
const moveEffectPhase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
vi.spyOn(moveEffectPhase, "hitCheck");
|
||||
|
||||
await game.phaseInterceptor.to(MoveEndPhase);
|
||||
|
||||
expect(moveEffectPhase.hitCheck).toHaveReturnedWith(true);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should guarantee double battle with any one LURE", async () => {
|
||||
game.override
|
||||
.startingModifier([
|
||||
{ name: "LURE" },
|
||||
])
|
||||
.startingWave(2);
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
expect(game.scene.getEnemyField().length).toBe(2);
|
||||
}, TIMEOUT);
|
||||
});
|
153
src/test/moves/heal_block.test.ts
Normal file
153
src/test/moves/heal_block.test.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||
import { WeatherType } from "#app/data/weather";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
// Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Heal_Block_(move)
|
||||
describe("Moves - Heal Block", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.ABSORB, Moves.WISH, Moves.SPLASH, Moves.AQUA_RING])
|
||||
.enemyMoveset(Moves.HEAL_BLOCK)
|
||||
.ability(Abilities.NO_GUARD)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemySpecies(Species.BLISSEY)
|
||||
.disableCrits();
|
||||
});
|
||||
|
||||
it("shouldn't stop damage from HP-drain attacks, just HP restoration", async() => {
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
player.damageAndUpdate(enemy.getMaxHp() - 1);
|
||||
|
||||
game.move.select(Moves.ABSORB);
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(player.hp).toBe(1);
|
||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||
}, TIMEOUT
|
||||
);
|
||||
|
||||
it("shouldn't stop Liquid Ooze from dealing damage", async() => {
|
||||
game.override.enemyAbility(Abilities.LIQUID_OOZE);
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.ABSORB);
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(player.isFullHp()).toBe(false);
|
||||
expect(enemy.isFullHp()).toBe(false);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should stop delayed heals, such as from Wish", async() => {
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
|
||||
player.damageAndUpdate(player.getMaxHp() - 1);
|
||||
|
||||
game.move.select(Moves.WISH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.WISH, ArenaTagSide.PLAYER)).toBeDefined();
|
||||
while (game.scene.arena.getTagOnSide(ArenaTagType.WISH, ArenaTagSide.PLAYER)) {
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
}
|
||||
|
||||
expect(player.hp).toBe(1);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should prevent Grassy Terrain from restoring HP", async() => {
|
||||
game.override.enemyAbility(Abilities.GRASSY_SURGE);
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
|
||||
player.damageAndUpdate(player.getMaxHp() - 1);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(player.hp).toBe(1);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should prevent healing from heal-over-time moves", async() => {
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
|
||||
player.damageAndUpdate(player.getMaxHp() - 1);
|
||||
|
||||
game.move.select(Moves.AQUA_RING);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(player.getTag(BattlerTagType.AQUA_RING)).toBeDefined();
|
||||
expect(player.hp).toBe(1);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should prevent abilities from restoring HP", async() => {
|
||||
game.override
|
||||
.weather(WeatherType.RAIN)
|
||||
.ability(Abilities.RAIN_DISH);
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
|
||||
player.damageAndUpdate(player.getMaxHp() - 1);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(player.hp).toBe(1);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should stop healing from items", async() => {
|
||||
game.override.startingHeldItems([{name: "LEFTOVERS"}]);
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
player.damageAndUpdate(player.getMaxHp() - 1);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(player.hp).toBe(1);
|
||||
}, TIMEOUT);
|
||||
});
|
@ -69,6 +69,20 @@ describe("Global Trade System - Mystery Encounter", () => {
|
||||
expect(GlobalTradeSystemEncounter.options.length).toBe(4);
|
||||
});
|
||||
|
||||
it("should not loop infinitely when generating trade options for extreme BST non-legendaries", async () => {
|
||||
const extremeBstTeam = [Species.SLAKING, Species.WISHIWASHI, Species.SUNKERN];
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, extremeBstTeam);
|
||||
|
||||
expect(GlobalTradeSystemEncounter.encounterType).toBe(MysteryEncounterType.GLOBAL_TRADE_SYSTEM);
|
||||
expect(GlobalTradeSystemEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
|
||||
expect(GlobalTradeSystemEncounter.dialogue).toBeDefined();
|
||||
expect(GlobalTradeSystemEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
|
||||
expect(GlobalTradeSystemEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
|
||||
expect(GlobalTradeSystemEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
|
||||
expect(GlobalTradeSystemEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
|
||||
expect(GlobalTradeSystemEncounter.options.length).toBe(4);
|
||||
});
|
||||
|
||||
it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => {
|
||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||
game.override.startingBiome(Biome.VOLCANO);
|
||||
|
Loading…
Reference in New Issue
Block a user