Compare commits

..

No commits in common. "e66d7026d5daa3ad3433ef16e2233cd4fc519eb3" and "7ae09a31a50501049e2d39190b97c6529830be59" have entirely different histories.

10 changed files with 108 additions and 225 deletions

View File

@ -93,8 +93,10 @@ export default class Battle {
private initBattleSpec(): void { private initBattleSpec(): void {
let spec = BattleSpec.DEFAULT; let spec = BattleSpec.DEFAULT;
if (this.gameMode.isWaveFinal(this.waveIndex) && this.gameMode.isClassic) if (this.gameMode.isClassic) {
spec = BattleSpec.FINAL_BOSS; if (this.waveIndex === 200)
spec = BattleSpec.FINAL_BOSS;
}
this.battleSpec = spec; this.battleSpec = spec;
} }
@ -103,7 +105,7 @@ export default class Battle {
let baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2); let baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2);
const bossMultiplier = 1.2; const bossMultiplier = 1.2;
if (this.gameMode.isBoss(this.waveIndex)) { if (!(this.waveIndex % 10)) {
const ret = Math.floor(baseLevel * bossMultiplier); const ret = Math.floor(baseLevel * bossMultiplier);
if (this.battleSpec === BattleSpec.FINAL_BOSS || !(this.waveIndex % 250)) if (this.battleSpec === BattleSpec.FINAL_BOSS || !(this.waveIndex % 250))
return Math.ceil(ret / 25) * 25; return Math.ceil(ret / 25) * 25;

View File

@ -13,7 +13,7 @@ import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, RecoilAttr, Stat
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { ArenaTagType } from "./enums/arena-tag-type"; import { ArenaTagType } from "./enums/arena-tag-type";
import { Stat } from "./pokemon-stat"; import { Stat } from "./pokemon-stat";
import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { PokemonHeldItemModifier } from "../modifier/modifier";
import { Moves } from "./enums/moves"; import { Moves } from "./enums/moves";
import { TerrainType } from "./terrain"; import { TerrainType } from "./terrain";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms"; import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
@ -23,7 +23,6 @@ import { Command } from "../ui/command-ui-handler";
import Battle from "#app/battle.js"; import Battle from "#app/battle.js";
import { ability } from "#app/locales/en/ability.js"; import { ability } from "#app/locales/en/ability.js";
import { PokeballType, getPokeballName } from "./pokeball"; import { PokeballType, getPokeballName } from "./pokeball";
import { BerryModifierType } from "#app/modifier/modifier-type";
export class Ability implements Localizable { export class Ability implements Localizable {
public id: Abilities; public id: Abilities;
@ -128,7 +127,7 @@ export abstract class AbAttr {
return null; return null;
} }
getCondition(): AbAttrCondition | null { getCondition(): AbAttrCondition {
return this.extraCondition || null; return this.extraCondition || null;
} }
@ -2229,71 +2228,6 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
} }
} }
/**
* After the turn ends, try to create an extra item
*/
export class PostTurnLootAbAttr extends PostTurnAbAttr {
/**
* @param itemType - The type of item to create
* @param procChance - Chance to create an item
* @see {@linkcode applyPostTurn()}
*/
constructor(
/** Extend itemType to add more options */
private itemType: "EATEN_BERRIES" | "HELD_BERRIES",
private procChance: (pokemon: Pokemon) => number
) {
super();
}
applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
const pass = Phaser.Math.RND.realInRange(0, 1);
// Clamp procChance to [0, 1]. Skip if didn't proc (less than pass)
if (Math.max(Math.min(this.procChance(pokemon), 1), 0) < pass) {
return false;
}
if (this.itemType === "EATEN_BERRIES") {
return this.createEatenBerry(pokemon);
} else {
return false;
}
}
/**
* Create a new berry chosen randomly from the berries the pokemon ate this battle
* @param pokemon The pokemon with this ability
* @returns whether a new berry was created
*/
createEatenBerry(pokemon: Pokemon): boolean {
const berriesEaten = pokemon.battleData.berriesEaten;
if (!berriesEaten.length) {
return false;
}
const randomIdx = Utils.randSeedInt(berriesEaten.length);
const chosenBerry = new BerryModifierType(berriesEaten[randomIdx]);
berriesEaten.splice(randomIdx) // Remove berry from memory
const berryModifier = pokemon.scene.findModifier(
(m) => m instanceof BerryModifier && m.berryType === berriesEaten[randomIdx],
pokemon.isPlayer()
) as BerryModifier | undefined;
if (!berryModifier) {
pokemon.scene.addModifier(new BerryModifier(chosenBerry, pokemon.id, berriesEaten[randomIdx], 1));
} else {
berryModifier.stackCount++;
}
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` harvested one ${chosenBerry.name}!`));
pokemon.scene.updateModifiers(pokemon.isPlayer());
return true;
}
}
export class MoodyAbAttr extends PostTurnAbAttr { export class MoodyAbAttr extends PostTurnAbAttr {
constructor() { constructor() {
super(true); super(true);
@ -3484,13 +3418,7 @@ export function initAbilities() {
new Ability(Abilities.FLARE_BOOST, 5) new Ability(Abilities.FLARE_BOOST, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user.status?.effect === StatusEffect.BURN, 1.5), .attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user.status?.effect === StatusEffect.BURN, 1.5),
new Ability(Abilities.HARVEST, 5) new Ability(Abilities.HARVEST, 5)
.attr( .unimplemented(),
PostTurnLootAbAttr,
"EATEN_BERRIES",
/** Rate is doubled when under sun {@link https://dex.pokemonshowdown.com/abilities/harvest} */
(pokemon) => 0.5 * (getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)(pokemon) ? 2 : 1)
)
.partial(),
new Ability(Abilities.TELEPATHY, 5) new Ability(Abilities.TELEPATHY, 5)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.getMove() instanceof AttackMove) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.getMove() instanceof AttackMove)
.ignorable(), .ignorable(),

View File

@ -4892,7 +4892,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.FREEZE), .attr(StatusEffectAttr, StatusEffect.FREEZE),
new AttackMove(Moves.BLIZZARD, Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 10, 0, 1) new AttackMove(Moves.BLIZZARD, Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 10, 0, 1)
.attr(BlizzardAccuracyAttr) .attr(BlizzardAccuracyAttr)
.attr(StatusEffectAttr, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.FREEZE) // TODO: 30% chance to hit protect/detect in hail
.windMove() .windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES), .target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.PSYBEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1) new AttackMove(Moves.PSYBEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1)

View File

@ -147,14 +147,8 @@ export class GameMode implements GameModeConfig {
return null; return null;
} }
/** isWaveFinal(waveIndex: integer): boolean {
* Checks if wave provided is the final for current or specified game mode switch (this.modeId) {
* @param waveIndex
* @param modeId game mode
* @returns if the current wave is final for classic or daily OR a minor boss in endless
*/
isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean {
switch (modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
return waveIndex === 200; return waveIndex === 200;
case GameModes.ENDLESS: case GameModes.ENDLESS:
@ -165,45 +159,6 @@ export class GameMode implements GameModeConfig {
} }
} }
/**
* Every 10 waves is a boss battle
* @returns true if waveIndex is a multiple of 10
*/
isBoss(waveIndex: integer): boolean {
return waveIndex % 10 === 0;
}
/**
* Every 50 waves of an Endless mode is a boss
* At this time it is paradox pokemon
* @returns true if waveIndex is a multiple of 50 in Endless
*/
isEndlessBoss(waveIndex: integer): boolean {
return waveIndex % 50 &&
(this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS);
}
/**
* Every 250 waves of an Endless mode is a minor boss
* At this time it is Eternatus
* @returns true if waveIndex is a multiple of 250 in Endless
*/
isEndlessMinorBoss(waveIndex: integer): boolean {
return waveIndex % 250 === 0 &&
(this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS);
}
/**
* Every 1000 waves of an Endless mode is a major boss
* At this time it is Eternamax Eternatus
* @returns true if waveIndex is a multiple of 1000 in Endless
*/
isEndlessMajorBoss(waveIndex: integer): boolean {
return waveIndex % 1000 === 0 &&
(this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS);
}
getClearScoreBonus(): integer { getClearScoreBonus(): integer {
switch (this.modeId) { switch (this.modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:

View File

@ -1,11 +1,11 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n"; import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const voucher: SimpleTranslationEntries = { export const voucher: SimpleTranslationEntries = {
"vouchers": "Gutschein", "vouchers": "Vouchers",
"eggVoucher": "Ei-Gutschein", "eggVoucher": "Egg Voucher",
"eggVoucherPlus": "Ei-Gutschein Plus", "eggVoucherPlus": "Egg Voucher Plus",
"eggVoucherPremium": "Ei-Gutschein Premium", "eggVoucherPremium": "Egg Voucher Premium",
"eggVoucherGold": "Ei-Gutschein Gold", "eggVoucherGold": "Egg Voucher Gold",
"locked": "Gesperrt", "locked": "Locked",
"defeatTrainer": "Besiege {{trainerName}}" "defeatTrainer": "Defeat {{trainerName}}"
} as const; } as const;

View File

@ -1,10 +1,10 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n"; import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const battleMessageUiHandler: SimpleTranslationEntries = { export const battleMessageUiHandler: SimpleTranslationEntries = {
"ivBest": "Inmejorable", "ivBest": "Best",
"ivFantastic": "Fantástico", "ivFantastic": "Fantastic",
"ivVeryGood": "Notable", "ivVeryGood": "Very Good",
"ivPrettyGood": "Genial", "ivPrettyGood": "Pretty Good",
"ivDecent": "No está mal", "ivDecent": "Decent",
"ivNoGood": "Cojea un poco", "ivNoGood": "No Good",
} as const; } as const;

View File

@ -2,47 +2,47 @@ import { BerryTranslationEntries } from "#app/plugins/i18n";
export const berry: BerryTranslationEntries = { export const berry: BerryTranslationEntries = {
"SITRUS": { "SITRUS": {
name: "Baya Zidra", name: "Sitrus Berry",
effect: "Restaura 25% PS si estos caen por debajo del 50%", effect: "Restores 25% HP if HP is below 50%",
}, },
"LUM": { "LUM": {
name: "Baya Ziuela", name: "Lum Berry",
effect: "Cura cualquier problema de estado", effect: "Cures any non-volatile status condition and confusion",
}, },
"ENIGMA": { "ENIGMA": {
name: "Baya Enigma", name: "Enigma Berry",
effect: "Restaura 25% PS si le alcanza un ataque supereficaz", effect: "Restores 25% HP if hit by a super effective move",
}, },
"LIECHI": { "LIECHI": {
name: "Baya Lichi", name: "Liechi Berry",
effect: "Aumenta el ataque si los PS están por debajo de 25%", effect: "Raises Attack if HP is below 25%",
}, },
"GANLON": { "GANLON": {
name: "Baya Gonlan", name: "Ganlon Berry",
effect: "Aumenta la defensa si los PS están por debajo de 25%", effect: "Raises Defense if HP is below 25%",
}, },
"PETAYA": { "PETAYA": {
name: "Baya Yapati", name: "Petaya Berry",
effect: "Aumenta el ataque especial si los PS están por debajo de 25%", effect: "Raises Sp. Atk if HP is below 25%",
}, },
"APICOT": { "APICOT": {
name: "Baya Aricoc", name: "Apicot Berry",
effect: "Aumenta la defensa especial si los PS están por debajo de 25%", effect: "Raises Sp. Def if HP is below 25%",
}, },
"SALAC": { "SALAC": {
name: "Baya Aslac", name: "Salac Berry",
effect: "Aumenta la velocidad si los PS están por debajo de 25%", effect: "Raises Speed if HP is below 25%",
}, },
"LANSAT": { "LANSAT": {
name: "Baya Zonlan", name: "Lansat Berry",
effect: "Aumenta el índice de golpe crítico si los PS están por debajo de 25%", effect: "Raises critical hit ratio if HP is below 25%",
}, },
"STARF": { "STARF": {
name: "Baya Arabol", name: "Starf Berry",
effect: "Aumenta mucho una estadística al azar si los PS están por debajo de 25%", effect: "Sharply raises a random stat if HP is below 25%",
}, },
"LEPPA": { "LEPPA": {
name: "Baya Zanama", name: "Leppa Berry",
effect: "Restaura 10 PP del primer movimiento cuyos PP bajen a 0", effect: "Restores 10 PP to a move if its PP reaches 0",
}, },
} as const; } as const;

View File

@ -9,7 +9,7 @@ export const menuUiHandler: SimpleTranslationEntries = {
"EGG_GACHA": "Gacha de Huevos", "EGG_GACHA": "Gacha de Huevos",
"MANAGE_DATA": "Gestionar Datos", "MANAGE_DATA": "Gestionar Datos",
"COMMUNITY": "Comunidad", "COMMUNITY": "Comunidad",
"SAVE_AND_QUIT": "Guardar y Salir", "SAVE_AND_QUIT": "Save and Quit",
"LOG_OUT": "Cerrar Sesión", "LOG_OUT": "Cerrar Sesión",
"slot": "Ranura {{slotNumber}}", "slot": "Ranura {{slotNumber}}",
"importSession": "Importar Sesión", "importSession": "Importar Sesión",
@ -20,4 +20,4 @@ export const menuUiHandler: SimpleTranslationEntries = {
"exportData": "Exportar Datos", "exportData": "Exportar Datos",
"cancel": "Cancelar", "cancel": "Cancelar",
"losingProgressionWarning": "Perderás cualquier progreso desde el inicio de la batalla. ¿Continuar?" "losingProgressionWarning": "Perderás cualquier progreso desde el inicio de la batalla. ¿Continuar?"
} as const; } as const;

View File

@ -2,8 +2,8 @@ import { PokemonInfoTranslationEntries } from "#app/plugins/i18n";
export const pokemonInfo: PokemonInfoTranslationEntries = { export const pokemonInfo: PokemonInfoTranslationEntries = {
Stat: { Stat: {
"HP": "PS", "HP": "PV",
"HPshortened": "PS", "HPshortened": "PV",
"ATK": "Ataque", "ATK": "Ataque",
"ATKshortened": "Ata", "ATKshortened": "Ata",
"DEF": "Defensa", "DEF": "Defensa",
@ -17,25 +17,25 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
}, },
Type: { Type: {
"UNKNOWN": "Desconocido", "UNKNOWN": "Unknown",
"NORMAL": "Normal", "NORMAL": "Normal",
"FIGHTING": "Lucha", "FIGHTING": "Fighting",
"FLYING": "Volador", "FLYING": "Flying",
"POISON": "Veneno", "POISON": "Poison",
"GROUND": "Tierra", "GROUND": "Ground",
"ROCK": "Roca", "ROCK": "Rock",
"BUG": "Bicho", "BUG": "Bug",
"GHOST": "Fantasma", "GHOST": "Ghost",
"STEEL": "Acero", "STEEL": "Steel",
"FIRE": "Fuego", "FIRE": "Fire",
"WATER": "Agua", "WATER": "Water",
"GRASS": "Planta", "GRASS": "Grass",
"ELECTRIC": "Eléctrico", "ELECTRIC": "Electric",
"PSYCHIC": "Psíquico", "PSYCHIC": "Psychic",
"ICE": "Hielo", "ICE": "Ice",
"DRAGON": "Dragón", "DRAGON": "Dragon",
"DARK": "Siniestro", "DARK": "Dark",
"FAIRY": "Hada", "FAIRY": "Fairy",
"STELLAR": "Astral", "STELLAR": "Stellar",
}, },
} as const; } as const;

View File

@ -2076,8 +2076,6 @@ export class TurnStartPhase extends FieldPhase {
} }
} }
this.scene.pushPhase(new BerryPhase(this.scene));
if (this.scene.arena.weather) if (this.scene.arena.weather)
this.scene.pushPhase(new WeatherEffectPhase(this.scene, this.scene.arena.weather)); this.scene.pushPhase(new WeatherEffectPhase(this.scene, this.scene.arena.weather));
@ -2092,42 +2090,6 @@ export class TurnStartPhase extends FieldPhase {
} }
} }
/** The phase after attacks where the pokemon eat berries */
export class BerryPhase extends FieldPhase {
start() {
super.start();
this.executeForAll((pokemon) => {
const hasUsableBerry = !!this.scene.findModifier((m) => m instanceof BerryModifier && m.shouldApply([pokemon]), pokemon.isPlayer());
if (hasUsableBerry) {
const cancelled = new Utils.BooleanHolder(false);
pokemon.getOpponents().map((opp) => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
if (cancelled.value) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is too\nnervous to eat berries!"));
} else {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM));
for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon) as BerryModifier[]) {
if (berryModifier.consumed) {
if (!--berryModifier.stackCount) {
this.scene.removeModifier(berryModifier);
} else {
berryModifier.consumed = false;
}
}
}
this.scene.updateModifiers(pokemon.isPlayer());
}
}
});
this.end();
}
}
export class TurnEndPhase extends FieldPhase { export class TurnEndPhase extends FieldPhase {
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
super(scene); super(scene);
@ -2146,6 +2108,10 @@ export class TurnEndPhase extends FieldPhase {
pokemon.summonData.disabledMove = Moves.NONE; pokemon.summonData.disabledMove = Moves.NONE;
} }
const hasUsableBerry = !!this.scene.findModifier(m => m instanceof BerryModifier && m.shouldApply([ pokemon ]), pokemon.isPlayer());
if (hasUsableBerry)
this.scene.unshiftPhase(new BerryPhase(this.scene, pokemon.getBattlerIndex()));
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
@ -4127,6 +4093,38 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
} }
} }
export class BerryPhase extends CommonAnimPhase {
constructor(scene: BattleScene, battlerIndex: BattlerIndex) {
super(scene, battlerIndex, undefined, CommonAnim.USE_ITEM);
}
start() {
let berryModifiers: BerryModifier[];
const pokemon = this.getPokemon();
const cancelled = new Utils.BooleanHolder(false);
pokemon.getOpponents().map(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
if (cancelled.value)
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is too\nnervous to eat berries!'));
else if ((berryModifiers = this.scene.applyModifiers(BerryModifier, this.player, pokemon) as BerryModifier[])) {
for (let berryModifier of berryModifiers) {
if (berryModifier.consumed) {
if (!--berryModifier.stackCount)
this.scene.removeModifier(berryModifier);
else
berryModifier.consumed = false;
this.scene.updateModifiers(this.player);
}
}
return super.start();
}
this.end();
}
}
export class PokemonHealPhase extends CommonAnimPhase { export class PokemonHealPhase extends CommonAnimPhase {
private hpHealed: integer; private hpHealed: integer;
private message: string; private message: string;