Compare commits

...

6 Commits

Author SHA1 Message Date
Jannik Tappert
e66d7026d5
Added German Localization to Voucher.ts (#1249) 2024-05-22 14:40:53 -05:00
nedpfeiffer
b72b432ce3
Spanish translations (#1084)
* spanish translations

* shortened translation
2024-05-22 13:01:29 -05:00
Matthew Olker
99557b318f Merge branch 'main' of https://github.com/pagefaultgames/pokerogue 2024-05-22 11:06:49 -04:00
Matthew Olker
aed9b56a7b Add helper functions to game mode 2024-05-22 11:06:24 -04:00
Ethan
ecc17c5342
Fixes Issue #805, the TODO was for a bug and was fixed in Platinum (#1238) 2024-05-22 09:59:29 -05:00
Dmitriy K
cd7e818a01
Mostly implement the Harvest ability (#1081)
* add harvest ability, move berry phase before turn end phase

* readd removing berry from memory after harvest
2024-05-22 08:52:45 -04:00
10 changed files with 225 additions and 108 deletions

View File

@ -93,10 +93,8 @@ export default class Battle {
private initBattleSpec(): void { private initBattleSpec(): void {
let spec = BattleSpec.DEFAULT; let spec = BattleSpec.DEFAULT;
if (this.gameMode.isClassic) { if (this.gameMode.isWaveFinal(this.waveIndex) && this.gameMode.isClassic)
if (this.waveIndex === 200) spec = BattleSpec.FINAL_BOSS;
spec = BattleSpec.FINAL_BOSS;
}
this.battleSpec = spec; this.battleSpec = spec;
} }
@ -105,7 +103,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.waveIndex % 10)) { if (this.gameMode.isBoss(this.waveIndex)) {
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 { PokemonHeldItemModifier } from "../modifier/modifier"; import { BerryModifier, 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,6 +23,7 @@ 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;
@ -127,7 +128,7 @@ export abstract class AbAttr {
return null; return null;
} }
getCondition(): AbAttrCondition { getCondition(): AbAttrCondition | null {
return this.extraCondition || null; return this.extraCondition || null;
} }
@ -2228,6 +2229,71 @@ 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);
@ -3418,7 +3484,13 @@ 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)
.unimplemented(), .attr(
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) // TODO: 30% chance to hit protect/detect in hail .attr(StatusEffectAttr, StatusEffect.FREEZE)
.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,8 +147,14 @@ export class GameMode implements GameModeConfig {
return null; return null;
} }
isWaveFinal(waveIndex: integer): boolean { /**
switch (this.modeId) { * Checks if wave provided is the final for current or specified game mode
* @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:
@ -159,6 +165,45 @@ 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": "Vouchers", "vouchers": "Gutschein",
"eggVoucher": "Egg Voucher", "eggVoucher": "Ei-Gutschein",
"eggVoucherPlus": "Egg Voucher Plus", "eggVoucherPlus": "Ei-Gutschein Plus",
"eggVoucherPremium": "Egg Voucher Premium", "eggVoucherPremium": "Ei-Gutschein Premium",
"eggVoucherGold": "Egg Voucher Gold", "eggVoucherGold": "Ei-Gutschein Gold",
"locked": "Locked", "locked": "Gesperrt",
"defeatTrainer": "Defeat {{trainerName}}" "defeatTrainer": "Besiege {{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": "Best", "ivBest": "Inmejorable",
"ivFantastic": "Fantastic", "ivFantastic": "Fantástico",
"ivVeryGood": "Very Good", "ivVeryGood": "Notable",
"ivPrettyGood": "Pretty Good", "ivPrettyGood": "Genial",
"ivDecent": "Decent", "ivDecent": "No está mal",
"ivNoGood": "No Good", "ivNoGood": "Cojea un poco",
} 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: "Sitrus Berry", name: "Baya Zidra",
effect: "Restores 25% HP if HP is below 50%", effect: "Restaura 25% PS si estos caen por debajo del 50%",
}, },
"LUM": { "LUM": {
name: "Lum Berry", name: "Baya Ziuela",
effect: "Cures any non-volatile status condition and confusion", effect: "Cura cualquier problema de estado",
}, },
"ENIGMA": { "ENIGMA": {
name: "Enigma Berry", name: "Baya Enigma",
effect: "Restores 25% HP if hit by a super effective move", effect: "Restaura 25% PS si le alcanza un ataque supereficaz",
}, },
"LIECHI": { "LIECHI": {
name: "Liechi Berry", name: "Baya Lichi",
effect: "Raises Attack if HP is below 25%", effect: "Aumenta el ataque si los PS están por debajo de 25%",
}, },
"GANLON": { "GANLON": {
name: "Ganlon Berry", name: "Baya Gonlan",
effect: "Raises Defense if HP is below 25%", effect: "Aumenta la defensa si los PS están por debajo de 25%",
}, },
"PETAYA": { "PETAYA": {
name: "Petaya Berry", name: "Baya Yapati",
effect: "Raises Sp. Atk if HP is below 25%", effect: "Aumenta el ataque especial si los PS están por debajo de 25%",
}, },
"APICOT": { "APICOT": {
name: "Apicot Berry", name: "Baya Aricoc",
effect: "Raises Sp. Def if HP is below 25%", effect: "Aumenta la defensa especial si los PS están por debajo de 25%",
}, },
"SALAC": { "SALAC": {
name: "Salac Berry", name: "Baya Aslac",
effect: "Raises Speed if HP is below 25%", effect: "Aumenta la velocidad si los PS están por debajo de 25%",
}, },
"LANSAT": { "LANSAT": {
name: "Lansat Berry", name: "Baya Zonlan",
effect: "Raises critical hit ratio if HP is below 25%", effect: "Aumenta el índice de golpe crítico si los PS están por debajo de 25%",
}, },
"STARF": { "STARF": {
name: "Starf Berry", name: "Baya Arabol",
effect: "Sharply raises a random stat if HP is below 25%", effect: "Aumenta mucho una estadística al azar si los PS están por debajo de 25%",
}, },
"LEPPA": { "LEPPA": {
name: "Leppa Berry", name: "Baya Zanama",
effect: "Restores 10 PP to a move if its PP reaches 0", effect: "Restaura 10 PP del primer movimiento cuyos PP bajen a 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": "Save and Quit", "SAVE_AND_QUIT": "Guardar y Salir",
"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": "PV", "HP": "PS",
"HPshortened": "PV", "HPshortened": "PS",
"ATK": "Ataque", "ATK": "Ataque",
"ATKshortened": "Ata", "ATKshortened": "Ata",
"DEF": "Defensa", "DEF": "Defensa",
@ -17,25 +17,25 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
}, },
Type: { Type: {
"UNKNOWN": "Unknown", "UNKNOWN": "Desconocido",
"NORMAL": "Normal", "NORMAL": "Normal",
"FIGHTING": "Fighting", "FIGHTING": "Lucha",
"FLYING": "Flying", "FLYING": "Volador",
"POISON": "Poison", "POISON": "Veneno",
"GROUND": "Ground", "GROUND": "Tierra",
"ROCK": "Rock", "ROCK": "Roca",
"BUG": "Bug", "BUG": "Bicho",
"GHOST": "Ghost", "GHOST": "Fantasma",
"STEEL": "Steel", "STEEL": "Acero",
"FIRE": "Fire", "FIRE": "Fuego",
"WATER": "Water", "WATER": "Agua",
"GRASS": "Grass", "GRASS": "Planta",
"ELECTRIC": "Electric", "ELECTRIC": "Eléctrico",
"PSYCHIC": "Psychic", "PSYCHIC": "Psíquico",
"ICE": "Ice", "ICE": "Hielo",
"DRAGON": "Dragon", "DRAGON": "Dragón",
"DARK": "Dark", "DARK": "Siniestro",
"FAIRY": "Fairy", "FAIRY": "Hada",
"STELLAR": "Stellar", "STELLAR": "Astral",
}, },
} as const; } as const;

View File

@ -2076,6 +2076,8 @@ 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));
@ -2090,6 +2092,42 @@ 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);
@ -2108,10 +2146,6 @@ 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()) {
@ -4093,38 +4127,6 @@ 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;