mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-20 07:12:32 +02:00
Merge locales
This commit is contained in:
commit
653e328f4d
17
.github/CODEOWNERS
vendored
17
.github/CODEOWNERS
vendored
@ -4,4 +4,19 @@
|
||||
* @pagefaultgames/junior-dev-team
|
||||
|
||||
# github actions/templates etc. - Dev Leads
|
||||
/.github @pagefaultgames/dev-leads
|
||||
/.github @pagefaultgames/senior-dev-team
|
||||
|
||||
# Art Team
|
||||
/public/**/*.png @pagefaultgames/art-team
|
||||
/public/**/*.json @pagefaultgames/art-team
|
||||
/public/images @pagefaultgames/art-team
|
||||
/public/battle-anims @pagefaultgames/art-team
|
||||
|
||||
# Audio files
|
||||
*.mp3 @pagefaultgames/composer-team
|
||||
*.wav @pagefaultgames/composer-team
|
||||
*.ogg @pagefaultgames/composer-team
|
||||
/public/audio @pagefaultgames/composer-team
|
||||
|
||||
# Balance Files; contain actual code logic and must also be owned by dev team
|
||||
/src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team
|
@ -133,7 +133,7 @@
|
||||
<span class="apad-label">V</span>
|
||||
</div>
|
||||
<!-- buttons to display battle-specific information -->
|
||||
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="V">
|
||||
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="CYCLE_TERA">
|
||||
<span class="apad-label">V</span>
|
||||
</div>
|
||||
<div id="apadStats" class="apad-button apad-rectangle apad-small" data-key="STATS">
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pokemon-rogue-battle",
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pokemon-rogue-battle",
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.6",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pokemon-rogue-battle",
|
||||
"private": true,
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
@ -5054,7 +5054,7 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr {
|
||||
const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
|
||||
const isCommandFight = turnCommand?.command === Command.FIGHT;
|
||||
const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null;
|
||||
if (this.condition(pokemon, move!) && isCommandFight) {
|
||||
if (isCommandFight && this.condition(pokemon, move!)) {
|
||||
bypassSpeed.value = false;
|
||||
canCheckHeldItems.value = false;
|
||||
return false;
|
||||
|
@ -1,7 +1,20 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move";
|
||||
import {
|
||||
AttackMove,
|
||||
BeakBlastHeaderAttr,
|
||||
DelayedAttackAttr,
|
||||
MoveFlags,
|
||||
SelfStatusMove,
|
||||
allMoves,
|
||||
} from "./move";
|
||||
import type Pokemon from "../field/pokemon";
|
||||
import * as Utils from "../utils";
|
||||
import {
|
||||
type nil,
|
||||
getFrameMs,
|
||||
getEnumKeys,
|
||||
getEnumValues,
|
||||
animationFileName,
|
||||
} from "../utils";
|
||||
import type { BattlerIndex } from "../battle";
|
||||
import type { Element } from "json-stable-stringify";
|
||||
import { Moves } from "#enums/moves";
|
||||
@ -401,7 +414,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
|
||||
if (Object.keys(tweenProps).length) {
|
||||
globalScene.tweens.add(Object.assign({
|
||||
targets: moveAnim.bgSprite,
|
||||
duration: Utils.getFrameMs(this.duration * 3)
|
||||
duration: getFrameMs(this.duration * 3)
|
||||
}, tweenProps));
|
||||
}
|
||||
return this.duration * 2;
|
||||
@ -437,7 +450,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
|
||||
|
||||
globalScene.tweens.add({
|
||||
targets: moveAnim.bgSprite,
|
||||
duration: Utils.getFrameMs(this.duration * 3)
|
||||
duration: getFrameMs(this.duration * 3)
|
||||
});
|
||||
|
||||
return this.duration * 2;
|
||||
@ -455,8 +468,8 @@ export const encounterAnims = new Map<EncounterAnim, AnimConfig>();
|
||||
|
||||
export function initCommonAnims(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const commonAnimNames = Utils.getEnumKeys(CommonAnim);
|
||||
const commonAnimIds = Utils.getEnumValues(CommonAnim);
|
||||
const commonAnimNames = getEnumKeys(CommonAnim);
|
||||
const commonAnimIds = getEnumValues(CommonAnim);
|
||||
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
|
||||
for (let ca = 0; ca < commonAnimIds.length; ca++) {
|
||||
const commonAnimId = commonAnimIds[ca];
|
||||
@ -493,7 +506,7 @@ export function initMoveAnim(move: Moves): Promise<void> {
|
||||
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
|
||||
|
||||
const fetchAnimAndResolve = (move: Moves) => {
|
||||
globalScene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
|
||||
globalScene.cachedFetch(`./battle-anims/${animationFileName(move)}.json`)
|
||||
.then(response => {
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!response.ok || contentType?.indexOf("application/json") === -1) {
|
||||
@ -550,7 +563,7 @@ function useDefaultAnim(move: Moves, defaultMoveAnim: Moves) {
|
||||
* @remarks use {@linkcode useDefaultAnim} to use a default animation
|
||||
*/
|
||||
function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
|
||||
const moveName = Utils.animationFileName(move);
|
||||
const moveName = animationFileName(move);
|
||||
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
|
||||
}
|
||||
|
||||
@ -560,7 +573,7 @@ function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
|
||||
*/
|
||||
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
||||
const anims = Array.isArray(encounterAnim) ? encounterAnim : [ encounterAnim ];
|
||||
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
|
||||
const encounterAnimNames = getEnumKeys(EncounterAnim);
|
||||
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
|
||||
for (const anim of anims) {
|
||||
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
|
||||
@ -922,7 +935,7 @@ export abstract class BattleAnim {
|
||||
let f = 0;
|
||||
|
||||
globalScene.tweens.addCounter({
|
||||
duration: Utils.getFrameMs(3),
|
||||
duration: getFrameMs(3),
|
||||
repeat: anim?.frames.length ?? 0,
|
||||
onRepeat: () => {
|
||||
if (!f) {
|
||||
@ -994,47 +1007,39 @@ export abstract class BattleAnim {
|
||||
const moveSprite = sprites[graphicIndex];
|
||||
if (spritePriorities[graphicIndex] !== frame.priority) {
|
||||
spritePriorities[graphicIndex] = frame.priority;
|
||||
/** Move the position that the moveSprite is rendered in based on the priority.
|
||||
* @param priority The priority level to draw the sprite.
|
||||
* - 0: Draw the sprite in front of the pokemon on the field.
|
||||
* - 1: Draw the sprite in front of the user pokemon.
|
||||
* - 2: Draw the sprite in front of its `bgSprite` (if it has one), or its
|
||||
* `AnimFocus` (if that is user/target), otherwise behind everything.
|
||||
* - 3: Draw the sprite behind its `AnimFocus` (if that is user/target), otherwise in front of everything.
|
||||
*/
|
||||
const setSpritePriority = (priority: number) => {
|
||||
switch (priority) {
|
||||
case 0:
|
||||
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false)!); // TODO: is this bang correct?
|
||||
break;
|
||||
case 1:
|
||||
globalScene.field.moveTo(moveSprite, globalScene.field.getAll().length - 1);
|
||||
break;
|
||||
case 2:
|
||||
switch (frame.focus) {
|
||||
case AnimFocus.USER:
|
||||
if (this.bgSprite) {
|
||||
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
|
||||
} else {
|
||||
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
|
||||
}
|
||||
break;
|
||||
case AnimFocus.TARGET:
|
||||
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
|
||||
break;
|
||||
default:
|
||||
setSpritePriority(1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
switch (frame.focus) {
|
||||
case AnimFocus.USER:
|
||||
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
|
||||
break;
|
||||
case AnimFocus.TARGET:
|
||||
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
|
||||
break;
|
||||
default:
|
||||
setSpritePriority(1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setSpritePriority(1);
|
||||
/** The sprite we are moving the moveSprite in relation to */
|
||||
let targetSprite: Phaser.GameObjects.GameObject | nil;
|
||||
/** The method that is being used to move the sprite.*/
|
||||
let moveFunc: ((sprite: Phaser.GameObjects.GameObject, target: Phaser.GameObjects.GameObject) => void) |
|
||||
((sprite: Phaser.GameObjects.GameObject) => void) = globalScene.field.bringToTop;
|
||||
|
||||
if (priority === 0) { // Place the sprite in front of the pokemon on the field.
|
||||
targetSprite = globalScene.getEnemyField().find(p => p) ?? globalScene.getPlayerField().find(p => p);
|
||||
console.log(typeof targetSprite);
|
||||
moveFunc = globalScene.field.moveBelow;
|
||||
} else if (priority === 2 && this.bgSprite) {
|
||||
moveFunc = globalScene.field.moveAbove;
|
||||
targetSprite = this.bgSprite;
|
||||
} else if (priority === 2 || priority === 3) {
|
||||
moveFunc = priority === 2 ? globalScene.field.moveBelow : globalScene.field.moveAbove;
|
||||
if (frame.focus === AnimFocus.USER) {
|
||||
targetSprite = this.user;
|
||||
} else if (frame.focus === AnimFocus.TARGET) {
|
||||
targetSprite = this.target;
|
||||
}
|
||||
}
|
||||
// If target sprite is not undefined and exists in the field container, then move the sprite using the moveFunc.
|
||||
// Otherwise, default to just bringing it to the top.
|
||||
targetSprite && globalScene.field.exists(targetSprite) ? moveFunc.bind(globalScene.field)(moveSprite as Phaser.GameObjects.GameObject, targetSprite) : globalScene.field.bringToTop(moveSprite as Phaser.GameObjects.GameObject);
|
||||
};
|
||||
setSpritePriority(frame.priority);
|
||||
}
|
||||
@ -1052,11 +1057,13 @@ export abstract class BattleAnim {
|
||||
}
|
||||
}
|
||||
if (anim?.frameTimedEvents.has(f)) {
|
||||
for (const event of anim.frameTimedEvents.get(f)!) { // TODO: is this bang correct?
|
||||
r = Math.max((anim.frames.length - f) + event.execute(this), r);
|
||||
const base = anim.frames.length - f;
|
||||
// Bang is correct due to `has` check above, which cannot return true for an undefined / null `f`
|
||||
for (const event of anim.frameTimedEvents.get(f)!) {
|
||||
r = Math.max(base + event.execute(this), r);
|
||||
}
|
||||
}
|
||||
const targets = Utils.getEnumValues(AnimFrameTarget);
|
||||
const targets = getEnumValues(AnimFrameTarget);
|
||||
for (const i of targets) {
|
||||
const count = i === AnimFrameTarget.GRAPHIC ? g : i === AnimFrameTarget.USER ? u : t;
|
||||
if (count < spriteCache[i].length) {
|
||||
@ -1084,7 +1091,7 @@ export abstract class BattleAnim {
|
||||
}
|
||||
if (r) {
|
||||
globalScene.tweens.addCounter({
|
||||
duration: Utils.getFrameMs(r),
|
||||
duration: getFrameMs(r),
|
||||
onComplete: () => cleanUpAndComplete()
|
||||
});
|
||||
} else {
|
||||
@ -1166,7 +1173,7 @@ export abstract class BattleAnim {
|
||||
let existingFieldSprites = globalScene.field.getAll().slice(0);
|
||||
|
||||
globalScene.tweens.addCounter({
|
||||
duration: Utils.getFrameMs(3) * frameTimeMult,
|
||||
duration: getFrameMs(3) * frameTimeMult,
|
||||
repeat: anim!.frames.length,
|
||||
onRepeat: () => {
|
||||
existingFieldSprites = globalScene.field.getAll().slice(0);
|
||||
@ -1215,11 +1222,12 @@ export abstract class BattleAnim {
|
||||
}
|
||||
}
|
||||
if (anim?.frameTimedEvents.get(frameCount)) {
|
||||
const base = anim.frames.length - frameCount;
|
||||
for (const event of anim.frameTimedEvents.get(frameCount)!) {
|
||||
totalFrames = Math.max((anim.frames.length - frameCount) + event.execute(this, frameTimedEventPriority), totalFrames);
|
||||
totalFrames = Math.max(base + event.execute(this, frameTimedEventPriority), totalFrames);
|
||||
}
|
||||
}
|
||||
const targets = Utils.getEnumValues(AnimFrameTarget);
|
||||
const targets = getEnumValues(AnimFrameTarget);
|
||||
for (const i of targets) {
|
||||
const count = graphicFrameCount;
|
||||
if (count < spriteCache[i].length) {
|
||||
@ -1244,7 +1252,7 @@ export abstract class BattleAnim {
|
||||
}
|
||||
if (totalFrames) {
|
||||
globalScene.tweens.addCounter({
|
||||
duration: Utils.getFrameMs(totalFrames),
|
||||
duration: getFrameMs(totalFrames),
|
||||
onComplete: () => cleanUpAndComplete()
|
||||
});
|
||||
} else {
|
||||
@ -1342,15 +1350,15 @@ export class EncounterBattleAnim extends BattleAnim {
|
||||
}
|
||||
|
||||
export async function populateAnims() {
|
||||
const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase());
|
||||
const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase());
|
||||
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/\_/g, ""));
|
||||
const commonAnimIds = Utils.getEnumValues(CommonAnim) as CommonAnim[];
|
||||
const chargeAnimNames = Utils.getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
|
||||
const commonAnimIds = getEnumValues(CommonAnim) as CommonAnim[];
|
||||
const chargeAnimNames = getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
|
||||
const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/\_/g, " "));
|
||||
const chargeAnimIds = Utils.getEnumValues(ChargeAnim) as ChargeAnim[];
|
||||
const chargeAnimIds = getEnumValues(ChargeAnim) as ChargeAnim[];
|
||||
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
|
||||
const moveNameToId = {};
|
||||
for (const move of Utils.getEnumValues(Moves).slice(1)) {
|
||||
for (const move of getEnumValues(Moves).slice(1)) {
|
||||
const moveName = Moves[move].toUpperCase().replace(/\_/g, "");
|
||||
moveNameToId[moveName] = move;
|
||||
}
|
||||
|
@ -5235,7 +5235,7 @@ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
const combinedPledgeMove = user.turnData.combiningPledge;
|
||||
const combinedPledgeMove = user?.turnData?.combiningPledge;
|
||||
if (!combinedPledgeMove) {
|
||||
return false;
|
||||
}
|
||||
@ -9383,7 +9383,7 @@ export function initMoves() {
|
||||
.attr(BypassBurnDamageReductionAttr),
|
||||
new AttackMove(Moves.FOCUS_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3)
|
||||
.attr(MessageHeaderAttr, (user, move) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) }))
|
||||
.attr(PreUseInterruptAttr, i18next.t("moveTriggers:lostFocus"), user => !!user.turnData.attacksReceived.find(r => r.damage))
|
||||
.attr(PreUseInterruptAttr, (user, target, move) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => !!user.turnData.attacksReceived.find(r => r.damage))
|
||||
.punchingMove(),
|
||||
new AttackMove(Moves.SMELLING_SALTS, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1)
|
||||
|
@ -607,7 +607,7 @@ export class TrainerConfig {
|
||||
const shedinjaCanTera = !this.hasSpecialtyType() || this.specialtyType === Type.BUG; // Better to check one time than 6
|
||||
const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i)
|
||||
.filter(i => shedinjaCanTera || party[i].species.speciesId !== Species.SHEDINJA); // Shedinja can only Tera on Bug specialty type (or no specialty type)
|
||||
const setPartySlot = !Utils.isNullOrUndefined(slot) ? Phaser.Math.Wrap(slot, 0, party.length - 1) : -1; // If we have a tera slot defined, wrap it to party size.
|
||||
const setPartySlot = !Utils.isNullOrUndefined(slot) ? Phaser.Math.Wrap(slot, 0, party.length) : -1; // If we have a tera slot defined, wrap it to party size.
|
||||
for (let t = 0; t < Math.min(count(), party.length); t++) {
|
||||
const randomIndex = partyMemberIndexes.indexOf(setPartySlot) > -1 ? setPartySlot : Utils.randSeedItem(partyMemberIndexes);
|
||||
partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
|
||||
@ -2776,11 +2776,6 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
p.generateName();
|
||||
p.pokeball = PokeballType.ULTRA_BALL;
|
||||
}))
|
||||
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.ZAMAZENTA ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
p.pokeball = PokeballType.MASTER_BALL;
|
||||
}))
|
||||
.setInstantTera(0), // Tera Fairy Sylveon
|
||||
[TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer(true)
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLAYDOL ], TrainerSlot.TRAINER, true, p => {
|
||||
|
@ -1226,10 +1226,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* Checks if the {@linkcode Pokemon} has is the specified {@linkcode Species} or is fused with it.
|
||||
* @param species the pokemon {@linkcode Species} to check
|
||||
* @param formKey If provided, requires the species to be in that form
|
||||
* @returns `true` if the pokemon is the species or is fused with it, `false` otherwise
|
||||
*/
|
||||
hasSpecies(species: Species): boolean {
|
||||
return this.species.speciesId === species || this.fusionSpecies?.speciesId === species;
|
||||
hasSpecies(species: Species, formKey?: string): boolean {
|
||||
if (Utils.isNullOrUndefined(formKey)) {
|
||||
return this.species.speciesId === species || this.fusionSpecies?.speciesId === species;
|
||||
}
|
||||
|
||||
return (this.species.speciesId === species && this.getFormKey() === formKey) || (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey);
|
||||
}
|
||||
|
||||
abstract isBoss(): boolean;
|
||||
@ -3210,6 +3215,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!));
|
||||
}
|
||||
|
||||
isMega(): boolean {
|
||||
const megaForms = [ SpeciesFormKey.MEGA, SpeciesFormKey.MEGA_X, SpeciesFormKey.MEGA_Y, SpeciesFormKey.PRIMAL ] as string[];
|
||||
return megaForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && megaForms.includes(this.getFusionFormKey()!));
|
||||
}
|
||||
|
||||
canAddTag(tagType: BattlerTagType): boolean {
|
||||
if (this.getTag(tagType)) {
|
||||
return false;
|
||||
|
@ -1645,11 +1645,19 @@ export class GameData {
|
||||
} else if (formIndex === 3) {
|
||||
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(1);
|
||||
}
|
||||
}
|
||||
const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : [];
|
||||
const toCurrentFormChanges = allFormChanges.filter(f => (f.formKey === formKey));
|
||||
if (toCurrentFormChanges.length > 0) {
|
||||
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(0);
|
||||
} else if (pokemon.species.speciesId === Species.ZYGARDE) {
|
||||
if (formIndex === 4) {
|
||||
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(2);
|
||||
} else if (formIndex === 5) {
|
||||
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(3);
|
||||
}
|
||||
} else {
|
||||
const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : [];
|
||||
const toCurrentFormChanges = allFormChanges.filter(f => (f.formKey === formKey));
|
||||
if (toCurrentFormChanges.length > 0) {
|
||||
// Needs to do this or Castform can unlock the wrong form, etc.
|
||||
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
||||
itemIcon.setScale(3 * this.scale);
|
||||
this.optionSelectIcons.push(itemIcon);
|
||||
|
||||
this.optionSelectContainer.add(itemIcon);
|
||||
this.optionSelectTextContainer.add(itemIcon);
|
||||
|
||||
itemIcon.setPositionRelative(this.optionSelectText, 36 * this.scale, 7 + i * (114 * this.scale - 3));
|
||||
|
||||
@ -156,7 +156,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
||||
itemOverlayIcon.setScale(3 * this.scale);
|
||||
this.optionSelectIcons.push(itemOverlayIcon);
|
||||
|
||||
this.optionSelectContainer.add(itemOverlayIcon);
|
||||
this.optionSelectTextContainer.add(itemOverlayIcon);
|
||||
|
||||
itemOverlayIcon.setPositionRelative(this.optionSelectText, 36 * this.scale, 7 + i * (114 * this.scale - 3));
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { globalScene } from "#app/global-scene";
|
||||
import { TerastallizeAccessModifier } from "#app/modifier/modifier";
|
||||
import { Type } from "#app/enums/type";
|
||||
import { getTypeRgb } from "#app/data/type";
|
||||
import { Species } from "#enums/species";
|
||||
|
||||
export enum Command {
|
||||
FIGHT = 0,
|
||||
@ -180,9 +181,11 @@ export default class CommandUiHandler extends UiHandler {
|
||||
|
||||
canTera(): boolean {
|
||||
const hasTeraMod = !!globalScene.getModifiers(TerastallizeAccessModifier).length;
|
||||
const activePokemon = globalScene.getField()[this.fieldIndex];
|
||||
const isBlockedForm = activePokemon.isMega() || activePokemon.isMax() || activePokemon.hasSpecies(Species.NECROZMA, "ultra");
|
||||
const currentTeras = globalScene.arena.playerTerasUsed;
|
||||
const plannedTera = globalScene.currentBattle.preTurnCommands[0]?.command === Command.TERA && this.fieldIndex > 0 ? 1 : 0;
|
||||
return hasTeraMod && (currentTeras + plannedTera) < 1;
|
||||
return hasTeraMod && !isBlockedForm && (currentTeras + plannedTera) < 1;
|
||||
}
|
||||
|
||||
toggleTeraButton() {
|
||||
|
@ -422,7 +422,10 @@ export default class PartyUiHandler extends MessageUiHandler {
|
||||
if (option === PartyOption.TRANSFER) {
|
||||
if (this.transferCursor !== this.cursor) {
|
||||
if (this.transferAll) {
|
||||
getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach((_, i) => (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, i, this.transferQuantitiesMax[i], this.cursor));
|
||||
getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach((_, i, array) => {
|
||||
const invertedIndex = array.length - 1 - i;
|
||||
(this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, invertedIndex, this.transferQuantitiesMax[invertedIndex], this.cursor);
|
||||
});
|
||||
} else {
|
||||
(this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], this.cursor);
|
||||
}
|
||||
@ -1187,7 +1190,6 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
public slotHpText: Phaser.GameObjects.Text;
|
||||
public slotDescriptionLabel: Phaser.GameObjects.Text; // this is used to show text instead of the HP bar i.e. for showing "Able"/"Not Able" for TMs when you try to learn them
|
||||
|
||||
|
||||
private pokemonIcon: Phaser.GameObjects.Container;
|
||||
private iconAnimHandler: PokemonIconAnimHandler;
|
||||
|
||||
@ -1208,6 +1210,10 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
setup(partyUiMode: PartyUiMode, tmMoveId: Moves) {
|
||||
|
||||
const currentLanguage = i18next.resolvedLanguage ?? "en";
|
||||
const offsetJa = currentLanguage === "ja";
|
||||
|
||||
const battlerCount = globalScene.currentBattle.getBattlerCount();
|
||||
|
||||
const slotKey = `party_slot${this.slotIndex >= battlerCount ? "" : "_main"}`;
|
||||
@ -1246,15 +1252,15 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
nameSizeTest.destroy();
|
||||
|
||||
this.slotName = addTextObject(0, 0, displayName, TextStyle.PARTY);
|
||||
this.slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 2 : 10);
|
||||
this.slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, (this.slotIndex >= battlerCount ? 2 : 10) + (offsetJa ? 2 : 0));
|
||||
this.slotName.setOrigin(0, 0);
|
||||
|
||||
const slotLevelLabel = globalScene.add.image(0, 0, "party_slot_overlay_lv");
|
||||
slotLevelLabel.setPositionRelative(this.slotName, 8, 12);
|
||||
slotLevelLabel.setPositionRelative(slotBg, (this.slotIndex >= battlerCount ? 21 : 24) + 8, (this.slotIndex >= battlerCount ? 2 : 10) + 12);
|
||||
slotLevelLabel.setOrigin(0, 0);
|
||||
|
||||
const slotLevelText = addTextObject(0, 0, this.pokemon.level.toString(), this.pokemon.level < globalScene.getMaxExpLevel() ? TextStyle.PARTY : TextStyle.PARTY_RED);
|
||||
slotLevelText.setPositionRelative(slotLevelLabel, 9, 0);
|
||||
slotLevelText.setPositionRelative(slotLevelLabel, 9, offsetJa ? 1.5 : 0);
|
||||
slotLevelText.setOrigin(0, 0.25);
|
||||
|
||||
slotInfoContainer.add([ this.slotName, slotLevelLabel, slotLevelText ]);
|
||||
@ -1331,7 +1337,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
this.slotHpOverlay.setVisible(false);
|
||||
|
||||
this.slotHpText = addTextObject(0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY);
|
||||
this.slotHpText.setPositionRelative(this.slotHpBar, this.slotHpBar.width - 3, this.slotHpBar.height - 2);
|
||||
this.slotHpText.setPositionRelative(this.slotHpBar, this.slotHpBar.width - 3, this.slotHpBar.height - 2 + (offsetJa ? 2 : 0));
|
||||
this.slotHpText.setOrigin(1, 0);
|
||||
this.slotHpText.setVisible(false);
|
||||
|
||||
|
@ -250,6 +250,8 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
private availableVariants: number;
|
||||
private unlockedVariants: boolean[];
|
||||
|
||||
private canUseCandies: boolean;
|
||||
|
||||
constructor() {
|
||||
super(Mode.POKEDEX_PAGE);
|
||||
}
|
||||
@ -556,6 +558,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
|
||||
show(args: any[]): boolean {
|
||||
|
||||
// Allow the use of candies if we are in one of the whitelisted phases
|
||||
this.canUseCandies = [ "TitlePhase", "SelectStarterPhase", "CommandPhase" ].includes(globalScene.getCurrentPhase()?.constructor.name ?? "");
|
||||
|
||||
if (args.length >= 1 && args[0] === "refresh") {
|
||||
return false;
|
||||
} else {
|
||||
@ -597,6 +602,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.battleForms = [];
|
||||
|
||||
const species = this.species;
|
||||
|
||||
let formKey = this.species?.forms.length > 0 ? this.species.forms[this.formIndex].formKey : "";
|
||||
this.isFormGender = formKey === "male" || formKey === "female";
|
||||
if (this.isFormGender && ((this.savedStarterAttributes.female === true && formKey === "male") || (this.savedStarterAttributes.female === false && formKey === "female"))) {
|
||||
this.formIndex = (this.formIndex + 1) % 2;
|
||||
formKey = this.species.forms[this.formIndex].formKey;
|
||||
}
|
||||
|
||||
const formIndex = this.formIndex ?? 0;
|
||||
|
||||
this.starterId = this.getStarterSpeciesId(this.species.speciesId);
|
||||
@ -630,12 +643,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.eggMoves = speciesEggMoves[this.starterId] ?? [];
|
||||
this.hasEggMoves = Array.from({ length: 4 }, (_, em) => (globalScene.gameData.starterData[this.starterId].eggMoves & (1 << em)) !== 0);
|
||||
|
||||
const formKey = this.species?.forms.length > 0 ? this.species.forms[this.formIndex].formKey : "";
|
||||
this.tmMoves = speciesTmMoves[species.speciesId]?.filter(m => Array.isArray(m) ? (m[0] === formKey ? true : false ) : true)
|
||||
.map(m => Array.isArray(m) ? m[1] : m).sort((a, b) => allMoves[a].name > allMoves[b].name ? 1 : -1) ?? [];
|
||||
|
||||
this.isFormGender = formKey === "male" || formKey === "female";
|
||||
|
||||
const passiveId = starterPassiveAbilities.hasOwnProperty(species.speciesId) ? species.speciesId :
|
||||
starterPassiveAbilities.hasOwnProperty(this.starterId) ? this.starterId : pokemonPrevolutions[this.starterId];
|
||||
const passives = starterPassiveAbilities[passiveId];
|
||||
@ -779,6 +789,10 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
const formIndex = otherFormIndex !== undefined ? otherFormIndex : this.formIndex;
|
||||
const caughtAttr = this.isCaught(species);
|
||||
|
||||
if (caughtAttr && (!species.forms.length || species.forms.length === 1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isFormCaught = (caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n;
|
||||
return isFormCaught;
|
||||
}
|
||||
@ -1570,15 +1584,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
|
||||
starterAttributes.variant = newVariant; // store the selected variant
|
||||
this.savedStarterAttributes.variant = starterAttributes.variant;
|
||||
if (newVariant > props.variant) {
|
||||
this.setSpeciesDetails(this.species, { variant: newVariant as Variant });
|
||||
success = true;
|
||||
} else {
|
||||
if ((this.isCaught() & DexAttr.NON_SHINY) && (newVariant <= props.variant)) {
|
||||
this.setSpeciesDetails(this.species, { shiny: false, variant: 0 });
|
||||
success = true;
|
||||
|
||||
starterAttributes.shiny = false;
|
||||
this.savedStarterAttributes.shiny = starterAttributes.shiny;
|
||||
} else {
|
||||
this.setSpeciesDetails(this.species, { variant: newVariant as Variant });
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1626,7 +1639,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
}
|
||||
break;
|
||||
case Button.STATS:
|
||||
if (!isCaught || !isFormCaught) {
|
||||
if (!isCaught || !isFormCaught || !this.canUseCandies) {
|
||||
error = true;
|
||||
} else {
|
||||
const ui = this.getUi();
|
||||
@ -1888,7 +1901,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
|
||||
if (this.isCaught()) {
|
||||
if (isFormCaught) {
|
||||
this.updateButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.candyUpgradeIconElement, this.candyUpgradeLabel);
|
||||
if (this.canUseCandies) {
|
||||
this.updateButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.candyUpgradeIconElement, this.candyUpgradeLabel);
|
||||
}
|
||||
if (this.canCycleShiny) {
|
||||
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel);
|
||||
}
|
||||
@ -2189,7 +2204,8 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY);
|
||||
const isShinyCaught = !!(caughtAttr & DexAttr.SHINY);
|
||||
|
||||
this.canCycleShiny = isNonShinyCaught && isShinyCaught;
|
||||
const caughtVariants = [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => caughtAttr & v);
|
||||
this.canCycleShiny = (isNonShinyCaught && isShinyCaught) || (isShinyCaught && caughtVariants.length > 1);
|
||||
|
||||
const isMaleCaught = !!(caughtAttr & DexAttr.MALE);
|
||||
const isFemaleCaught = !!(caughtAttr & DexAttr.FEMALE);
|
||||
|
@ -986,7 +986,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
this.updateScroll();
|
||||
const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1);
|
||||
const targetCol = Math.min(8, proportion < 0.5 ? Math.floor(proportion * 8) : Math.ceil(proportion * 8));
|
||||
this.setCursor(Math.min(targetCol, numberOfStarters));
|
||||
this.setCursor(Math.min(targetCol, numberOfStarters - 1));
|
||||
success = true;
|
||||
}
|
||||
break;
|
||||
@ -1108,7 +1108,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
}
|
||||
break;
|
||||
case Button.DOWN:
|
||||
if (currentRow < numOfRows - 1) { // not last row
|
||||
if ((currentRow < numOfRows - 1) && (this.cursor + 9 < this.filteredPokemonData.length)) { // not last row
|
||||
if (currentRow - this.scrollCursor === 8) { // last row of visible pokemon
|
||||
this.scrollCursor++;
|
||||
this.updateScroll();
|
||||
@ -1577,6 +1577,37 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
container.icon.setTint(0);
|
||||
}
|
||||
|
||||
if (data.eggMove1) {
|
||||
container.eggMove1Icon.setVisible(true);
|
||||
} else {
|
||||
container.eggMove1Icon.setVisible(false);
|
||||
}
|
||||
if (data.eggMove2) {
|
||||
container.eggMove2Icon.setVisible(true);
|
||||
} else {
|
||||
container.eggMove2Icon.setVisible(false);
|
||||
}
|
||||
if (data.tmMove1) {
|
||||
container.tmMove1Icon.setVisible(true);
|
||||
} else {
|
||||
container.tmMove1Icon.setVisible(false);
|
||||
}
|
||||
if (data.tmMove2) {
|
||||
container.tmMove2Icon.setVisible(true);
|
||||
} else {
|
||||
container.tmMove2Icon.setVisible(false);
|
||||
}
|
||||
if (data.passive1) {
|
||||
container.passive1Icon.setVisible(true);
|
||||
} else {
|
||||
container.passive1Icon.setVisible(false);
|
||||
}
|
||||
if (data.passive2) {
|
||||
container.passive2Icon.setVisible(true);
|
||||
} else {
|
||||
container.passive2Icon.setVisible(false);
|
||||
}
|
||||
|
||||
if (this.showDecorations) {
|
||||
|
||||
if (this.pokerusSpecies.includes(data.species)) {
|
||||
|
@ -8,6 +8,7 @@ import i18next from "i18next";
|
||||
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
|
||||
import { starterColors } from "#app/battle-scene";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { Ability } from "#app/data/ability";
|
||||
import { allAbilities } from "#app/data/ability";
|
||||
import { speciesEggMoves } from "#app/data/balance/egg-moves";
|
||||
import { GrowthRate, getGrowthRateColor } from "#app/data/exp";
|
||||
@ -2067,20 +2068,20 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
} while (newVariant !== props.variant);
|
||||
starterAttributes.variant = newVariant; // store the selected variant
|
||||
// If going to a higher variant, display that
|
||||
if (newVariant > props.variant) {
|
||||
if ((this.speciesStarterDexEntry!.caughtAttr & DexAttr.NON_SHINY) && (newVariant <= props.variant)) {
|
||||
// If we have run out of variants, go back to non shiny
|
||||
this.setSpeciesDetails(this.lastSpecies, { shiny: false, variant: 0 });
|
||||
this.pokemonShinyIcon.setVisible(false);
|
||||
success = true;
|
||||
starterAttributes.shiny = false;
|
||||
} else {
|
||||
// If going to a higher variant, or only shiny forms are caught, go to next variant
|
||||
this.setSpeciesDetails(this.lastSpecies, { variant: newVariant as Variant });
|
||||
// Cycle tint based on current sprite tint
|
||||
const tint = getVariantTint(newVariant as Variant);
|
||||
this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant));
|
||||
this.pokemonShinyIcon.setTint(tint);
|
||||
success = true;
|
||||
// If we have run out of variants, go back to non shiny
|
||||
} else {
|
||||
this.setSpeciesDetails(this.lastSpecies, { shiny: false, variant: 0 });
|
||||
this.pokemonShinyIcon.setVisible(false);
|
||||
success = true;
|
||||
starterAttributes.shiny = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3327,7 +3328,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY);
|
||||
const isShinyCaught = !!(caughtAttr & DexAttr.SHINY);
|
||||
|
||||
this.canCycleShiny = isNonShinyCaught && isShinyCaught;
|
||||
const caughtVariants = [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => caughtAttr & v);
|
||||
this.canCycleShiny = (isNonShinyCaught && isShinyCaught) || (isShinyCaught && caughtVariants.length > 1);
|
||||
|
||||
const isMaleCaught = !!(caughtAttr & DexAttr.MALE);
|
||||
const isFemaleCaught = !!(caughtAttr & DexAttr.FEMALE);
|
||||
@ -3351,7 +3353,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.canCycleForm = species.forms.filter(f => f.isStarterSelectable || !pokemonFormChanges[species.speciesId]?.find(fc => fc.formKey))
|
||||
.map((_, f) => dexEntry.caughtAttr & globalScene.gameData.getFormAttr(f)).filter(f => f).length > 1;
|
||||
this.canCycleNature = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1;
|
||||
this.canCycleTera = globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2);
|
||||
this.canCycleTera = !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2);
|
||||
}
|
||||
|
||||
if (dexEntry.caughtAttr && species.malePercent !== null) {
|
||||
@ -3364,7 +3366,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
|
||||
if (dexEntry.caughtAttr) {
|
||||
const ability = allAbilities[this.lastSpecies.getAbility(abilityIndex!)]; // TODO: is this bang correct?
|
||||
let ability: Ability;
|
||||
if (this.lastSpecies.forms?.length > 1) {
|
||||
ability = allAbilities[this.lastSpecies.forms[formIndex ?? 0].getAbility(abilityIndex!)];
|
||||
} else {
|
||||
ability = allAbilities[this.lastSpecies.getAbility(abilityIndex!)]; // TODO: is this bang correct?
|
||||
}
|
||||
this.pokemonAbilityText.setText(ability.name);
|
||||
|
||||
const isHidden = abilityIndex === (this.lastSpecies.ability2 ? 2 : 1);
|
||||
@ -3851,12 +3858,20 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.showStats();
|
||||
this.statsMode = true;
|
||||
this.pokemonSprite.setVisible(false);
|
||||
this.teraIcon.setVisible(false);
|
||||
this.canCycleTera = false;
|
||||
this.updateInstructions();
|
||||
} else {
|
||||
this.statsMode = false;
|
||||
this.statsContainer.setVisible(false);
|
||||
this.pokemonSprite.setVisible(!!this.speciesStarterDexEntry?.caughtAttr);
|
||||
//@ts-ignore
|
||||
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!?
|
||||
this.teraIcon.setVisible(globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id));
|
||||
const props = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId));
|
||||
const formIndex = props.formIndex;
|
||||
this.canCycleTera = !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2);
|
||||
this.updateInstructions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,6 +140,6 @@ describe("Moves - Focus Punch", () => {
|
||||
await game.phaseInterceptor.to("MessagePhase", false);
|
||||
const consoleSpy = vi.spyOn(console, "log");
|
||||
await game.phaseInterceptor.to("MoveEndPhase", true);
|
||||
expect(consoleSpy).nthCalledWith(1, i18next.t("moveTriggers:lostFocus"));
|
||||
expect(consoleSpy).nthCalledWith(1, i18next.t("moveTriggers:lostFocus", { pokemonName: "Charizard" }));
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user