diff --git a/src/data/ability.ts b/src/data/ability.ts index c44357cc3c6..c236aa85805 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2282,6 +2282,34 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { } } + +/** + * Attribute used for abilities (Bad Dreams) that damages the opponents for being asleep + */ +export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { + + /** + * Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) + * @param {Pokemon} pokemon Pokemon that has this ability + * @param {boolean} passive N/A + * @param {any[]} args N/A + * @returns {boolean} true if any opponents are sleeping + */ + applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise { + let hadEffect: boolean = false; + for(let opp of pokemon.getOpponents()) { + if(opp.status !== undefined && opp.status.effect === StatusEffect.SLEEP) { + opp.damageAndUpdate(Math.floor(Math.max(1, opp.getMaxHp() / 8)), HitResult.OTHER); + pokemon.scene.queueMessage(i18next.t('abilityTriggers:badDreams', {pokemonName: `${getPokemonPrefix(opp)}${opp.name}`})); + hadEffect = true; + } + + } + return hadEffect; + } +} + + /** * Grabs the last failed Pokeball used * @extends PostTurnAbAttr @@ -3327,7 +3355,7 @@ export function initAbilities() { .ignorable() .partial(), new Ability(Abilities.BAD_DREAMS, 4) - .unimplemented(), + .attr(PostTurnHurtIfSleepingAbAttr), new Ability(Abilities.PICKPOCKET, 5) .attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT)), new Ability(Abilities.SHEER_FORCE, 5) diff --git a/src/data/pokemon-evolutions.ts b/src/data/pokemon-evolutions.ts index 7511b0e4162..de80df37260 100644 --- a/src/data/pokemon-evolutions.ts +++ b/src/data/pokemon-evolutions.ts @@ -1608,8 +1608,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.FROSMOTH, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.GIMMIGHOUL]: [ - new SpeciesEvolution(Species.GHOLDENGO, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.VERY_LONG) - ] + new SpeciesEvolution(Species.GHOLDENGO, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.VERY_LONG) ] }; interface PokemonPrevolutions { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 99cd56fbf04..abce327a645 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from '#app/data/variant'; import { variantData } from '#app/data/variant'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info'; import { Moves } from "../data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from '../data/pokemon-species'; import * as Utils from '../utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; @@ -1127,7 +1127,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let shinyThreshold = new Utils.IntegerHolder(32); if (thresholdOverride === undefined) { if (!this.hasTrainer()) { - if (new Date() < new Date(2024, 4, 21, 20)) + if (new Date() < new Date(Date.UTC(2024, 4, 22, 0))) shinyThreshold.value *= 3; this.scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); } @@ -1282,11 +1282,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isBoss()) // Bosses never get self ko moves movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttr).length); + movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttrOnHit).length); if (this.hasTrainer()) { // Trainers never get OHKO moves movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(OneHitKOAttr).length); // Half the weight of self KO moves movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(SacrificialAttr).length ? 0.5 : 1)]); + movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(SacrificialAttrOnHit).length ? 0.5 : 1)]); // Trainers get a weight bump to stat buffing moves movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => (a as StatChangeAttr).levels > 1 && (a as StatChangeAttr).selfTarget) ? 1.25 : 1)]); // Trainers get a weight decrease to multiturn moves diff --git a/src/locales/de/ability-trigger.ts b/src/locales/de/ability-trigger.ts index 27d2053b621..eb5022996d4 100644 --- a/src/locales/de/ability-trigger.ts +++ b/src/locales/de/ability-trigger.ts @@ -2,4 +2,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { 'blockRecoilDamage' : `{{pokemonName}} wurde durch {{abilityName}}\nvor Rückstoß geschützt!`, + 'badDreams': `{{pokemonName}} ist in einem Alptraum gefangen!`, } as const; diff --git a/src/locales/en/ability-trigger.ts b/src/locales/en/ability-trigger.ts index 88900741218..49505217126 100644 --- a/src/locales/en/ability-trigger.ts +++ b/src/locales/en/ability-trigger.ts @@ -2,4 +2,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { 'blockRecoilDamage' : `{{pokemonName}}'s {{abilityName}}\nprotected it from recoil!`, + 'badDreams': `{{pokemonName}} is tormented!`, } as const; \ No newline at end of file diff --git a/src/locales/es/ability-trigger.ts b/src/locales/es/ability-trigger.ts index 88900741218..cd6def4c628 100644 --- a/src/locales/es/ability-trigger.ts +++ b/src/locales/es/ability-trigger.ts @@ -2,4 +2,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { 'blockRecoilDamage' : `{{pokemonName}}'s {{abilityName}}\nprotected it from recoil!`, -} as const; \ No newline at end of file + 'badDreams': `{{pokemonName}} Está atormentado!` +} as const; diff --git a/src/locales/fr/ability-trigger.ts b/src/locales/fr/ability-trigger.ts index f668ee5e8ab..b1bbaa5e353 100644 --- a/src/locales/fr/ability-trigger.ts +++ b/src/locales/fr/ability-trigger.ts @@ -2,4 +2,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { 'blockRecoilDamage' : `{{abilityName}}\nde {{pokemonName}} le protège du contrecoup !`, + 'badDreams': `{{pokemonName}} a le sommeil agité !` } as const; diff --git a/src/locales/it/ability-trigger.ts b/src/locales/it/ability-trigger.ts index de41e087236..3f7d09c9221 100644 --- a/src/locales/it/ability-trigger.ts +++ b/src/locales/it/ability-trigger.ts @@ -2,4 +2,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { 'blockRecoilDamage' : `{{abilityName}} di {{pokemonName}}\nl'ha protetto dal contraccolpo!`, + 'badDreams': `{{pokemonName}} è tormentato!`, } as const; \ No newline at end of file diff --git a/src/locales/zh_CN/ability-trigger.ts b/src/locales/zh_CN/ability-trigger.ts index 85152b1bccc..d40fad7a10a 100644 --- a/src/locales/zh_CN/ability-trigger.ts +++ b/src/locales/zh_CN/ability-trigger.ts @@ -2,4 +2,5 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { 'blockRecoilDamage' : `{{pokemonName}} 的 {{abilityName}}\n抵消了反作用力!`, + 'badDreams': `{{pokemonName}} 被折磨着!` } as const; \ No newline at end of file diff --git a/src/phases.ts b/src/phases.ts index de40e1abe6b..b861c82f0e8 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -366,10 +366,14 @@ export class TitlePhase extends Phase { this.scene.pushPhase(new SummonPhase(this.scene, 0, true, true)); if (this.scene.currentBattle.double && availablePartyMembers > 1) this.scene.pushPhase(new SummonPhase(this.scene, 1, true, true)); - if (this.scene.currentBattle.waveIndex > 1 && this.scene.currentBattle.battleType !== BattleType.TRAINER) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + + if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { + const minPartySize = this.scene.currentBattle.double ? 2 : 1; + if (availablePartyMembers > minPartySize) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); + if (this.scene.currentBattle.double) + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + } } } @@ -955,10 +959,13 @@ export class EncounterPhase extends BattlePhase { this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, false)); } - if (this.scene.currentBattle.waveIndex > startingWave && this.scene.currentBattle.battleType !== BattleType.TRAINER) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double && availablePartyMembers.length > 1) - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { + const minPartySize = this.scene.currentBattle.double ? 2 : 1; + if (availablePartyMembers.length > minPartySize) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); + if (this.scene.currentBattle.double) + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + } } } @@ -4416,6 +4423,7 @@ export class AttemptCapturePhase extends PokemonPhase { if (this.scene.getParty().length === 6) { const promptRelease = () => { this.scene.ui.showText(`Your party is full.\nRelease a Pokémon to make room for ${pokemon.name}?`, null, () => { + this.scene.pokemonInfoContainer.makeRoomForConfirmUi(); this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { this.scene.ui.setMode(Mode.MESSAGE).then(() => { diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 29e28f60f39..7bb3535ba4a 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -93,8 +93,9 @@ export function initI18n(): void { i18next.use(LanguageDetector).init({ lng: lang, + nonExplicitSupportedLngs: true, fallbackLng: 'en', - supportedLngs: ['en', 'es', 'fr', 'it', 'de', 'zh_CN','pt_BR'], + supportedLngs: ['en', 'es', 'fr', 'it', 'de', 'zh','pt'], debug: true, interpolation: { escapeValue: false, diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index bac980db99e..a9b959a9950 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -5,6 +5,9 @@ import i18next from "i18next"; import {Button} from "../enums/buttons"; export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { + + public static readonly windowWidth: integer = 48; + private switchCheck: boolean; private switchCheckCursor: integer; @@ -13,7 +16,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { } getWindowWidth(): integer { - return 48; + return ConfirmUiHandler.windowWidth; } show(args: any[]): boolean { diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 572a28f10c8..14d7ec35d1b 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -9,8 +9,11 @@ import { getNatureName } from "../data/nature"; import * as Utils from "../utils"; import { Type } from "../data/type"; import { getVariantTint } from "#app/data/variant"; +import ConfirmUiHandler from "./confirm-ui-handler"; export default class PokemonInfoContainer extends Phaser.GameObjects.Container { + private readonly infoWindowWidth = 104; + private pokemonGenderLabelText: Phaser.GameObjects.Text; private pokemonGenderText: Phaser.GameObjects.Text; private pokemonAbilityLabelText: Phaser.GameObjects.Text; @@ -37,7 +40,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { } setup(): void { - const infoBg = addWindow(this.scene, 0, 0, 104, 132); + const infoBg = addWindow(this.scene, 0, 0, this.infoWindowWidth, 132); infoBg.setOrigin(0.5, 0.5); this.pokemonMovesContainer = this.scene.add.container(6, 14); @@ -172,7 +175,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { targets: this, duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)), ease: 'Cubic.easeInOut', - x: this.initialX - 104, + x: this.initialX - this.infoWindowWidth, onComplete: () => { resolve(); } @@ -201,6 +204,20 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { }); } + makeRoomForConfirmUi(speedMultiplier: number = 1): Promise { + return new Promise(resolve => { + this.scene.tweens.add({ + targets: this, + duration: Utils.fixedInt(Math.floor(150 / speedMultiplier)), + ease: 'Cubic.easeInOut', + x: this.initialX - this.infoWindowWidth - ConfirmUiHandler.windowWidth, + onComplete: () => { + resolve(); + } + }); + }); + } + hide(speedMultiplier: number = 1): Promise { return new Promise(resolve => { if (!this.shown) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 090a0692424..4a040792643 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -70,12 +70,12 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoTextSize: '54px', instructionTextSize: '42px', }, - "zh_CN":{ + "zh":{ starterInfoTextSize: '40px', instructionTextSize: '42px', starterInfoYOffset: 2 }, - "pt_BR":{ + "pt":{ starterInfoTextSize: '47px', instructionTextSize: '38px', starterInfoXPos: 32, @@ -217,7 +217,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); const currentLanguage = i18next.language; - const textSettings = languageSettings[currentLanguage]; + const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)); + const textSettings = languageSettings[langSettingKey]; this.starterSelectContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); this.starterSelectContainer.setVisible(false); @@ -280,7 +281,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { let starterInfoYOffset = textSettings?.starterInfoYOffset || 0; // The font size should be set per language - let starterInfoTextSize = textSettings.starterInfoTextSize; + let starterInfoTextSize = textSettings?.starterInfoTextSize || 56; this.pokemonAbilityLabelText = addTextObject(this.scene, 6, 127 + starterInfoYOffset, i18next.t("starterSelectUiHandler:ability"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonAbilityLabelText.setOrigin(0, 0); diff --git a/src/ui/text.ts b/src/ui/text.ts index 8be46b1b238..31c76c72956 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -49,11 +49,11 @@ const languageSettings: { [key: string]: LanguageSetting } = { } export function addTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): Phaser.GameObjects.Text { - const [ styleOptions, shadowColor, shadowSize ] = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); + const [ styleOptions, shadowColor, shadowXpos, shadowYpos ] = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); const ret = scene.add.text(x, y, content, styleOptions); ret.setScale(0.1666666667); - ret.setShadow(shadowSize, shadowSize, shadowColor); + ret.setShadow(shadowXpos, shadowYpos, shadowColor); if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) ret.setLineSpacing(5); @@ -61,12 +61,12 @@ export function addTextObject(scene: Phaser.Scene, x: number, y: number, content } export function addBBCodeTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): BBCodeText { - const [ styleOptions, shadowColor, shadowSize ] = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); + const [ styleOptions, shadowColor, shadowXpos, shadowYpos ] = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); const ret = new BBCodeText(scene, x, y, content, styleOptions as BBCodeText.TextStyle); scene.add.existing(ret); ret.setScale(0.1666666667); - ret.setShadow(shadowSize, shadowSize, shadowColor); + ret.setShadow(shadowXpos, shadowYpos, shadowColor); if (!(styleOptions as BBCodeText.TextStyle).lineSpacing) ret.setLineSpacing(10); @@ -86,7 +86,8 @@ export function addTextInputObject(scene: Phaser.Scene, x: number, y: number, wi function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): [ Phaser.Types.GameObjects.Text.TextStyle | InputText.IConfig, string, integer ] { const lang = i18next.language; let shadowColor: string; - let shadowSize = 6; + let shadowXpos = 4; + let shadowYpos = 5; let styleOptions: Phaser.Types.GameObjects.Text.TextStyle = { fontFamily: 'emerald', @@ -117,7 +118,8 @@ function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptio case TextStyle.MONEY: case TextStyle.TOOLTIP_TITLE: styleOptions.fontSize = languageSettings[lang]?.battleInfoFontSize || '72px'; - shadowSize = 4.5; + shadowXpos = 3.5; + shadowYpos = 3.5; break; case TextStyle.PARTY: case TextStyle.PARTY_RED: @@ -126,11 +128,13 @@ function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptio break; case TextStyle.TOOLTIP_CONTENT: styleOptions.fontSize = languageSettings[lang]?.tooltipContentFontSize || '64px'; - shadowSize = 4; + shadowXpos = 3; + shadowYpos = 3; break; case TextStyle.MOVE_INFO_CONTENT: styleOptions.fontSize = languageSettings[lang]?.moveInfoFontSize || '56px'; - shadowSize = 3; + shadowXpos = 3; + shadowYpos = 3; break; } @@ -139,12 +143,12 @@ function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptio if (extraStyleOptions) { if (extraStyleOptions.fontSize) { const sizeRatio = parseInt(extraStyleOptions.fontSize.toString().slice(0, -2)) / parseInt(styleOptions.fontSize.toString().slice(0, -2)); - shadowSize *= sizeRatio; + shadowXpos *= sizeRatio; } styleOptions = Object.assign(styleOptions, extraStyleOptions); } - return [ styleOptions, shadowColor, shadowSize ]; + return [ styleOptions, shadowColor, shadowXpos, shadowYpos ]; } export function getBBCodeFrag(content: string, textStyle: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {