Compare commits

...

12 Commits

Author SHA1 Message Date
Matthew Olker
751a3d1e49 add names to battle info ui objects and solve key already exists warning for starter select 2024-05-24 17:23:49 -04:00
Jon Studders
d01cd98d06
Fixed champion ribbon boss segments. (#1334) 2024-05-24 16:07:57 -05:00
Douglas Marchione de Souza
0d6145263f
Acrobatics does not treat vitamins as held items (#718)
* Changed the move Acrobatics so it doesn't include
non-held items on it's damage calculations

* Changed exception so it includes within_party
items, and updated the move description

* Small typo e_e'

* Change Description of Move

---------

Co-authored-by: Benjamin Odom <bennybroseph@gmail.com>
2024-05-24 15:49:23 -05:00
LaukkaE
0309fde7e8
Fix Aftermath not working (#1209) 2024-05-24 15:35:45 -05:00
Jakub Hanko
a48ba9864d
Implement Destiny Bond move (#1104)
* Use getBattleStat instead of getStat in BattleStatRatioPowerAttr

* Change unnecessary let into const

* Refactor BattleStatRatioPowerAttr into two distinct classes

* Add TSDoc for the new classes

* Implementation of Destiny Bond

* Add TSDocs

* Make the move fail in boss battles

* Fix boss immunity and ally fainting

* Update docs

* Add doc of return value of tag lapse

* Fix ESLint
2024-05-24 15:23:23 -05:00
Greenlamp2
136fcbfda8
init dialog call in the loading-scene (#1331) 2024-05-24 15:08:18 -05:00
Jon Studders
3670574342
Added a champion ribbon on enemy pokemon if they have a classic win. (#881)
* Added a champion ribbon on enemy pokemon if they have a classic win.

* Refactored to check for other non-root starterDex entities.

* Check for caughtIcon, if false then move ribbon to the left.

* Fixed Merge.

* Bit of refactoring, added check for classic mode.

* Removed random newline and removed unused import.

* Removed overlapping ribbon.
2024-05-24 15:57:02 -04:00
Madmadness65
41751ab8df Update encounter levels for some Pokémon
When the wild evolution delays were updated, the biomes file wasn't updated to match, so that has been fixed.
Additionally, the previous refactor removed the commented-out outputPools function. It is where it is because it allows for easily filling out the biomes file for an update.
2024-05-24 14:48:24 -05:00
Greenlamp2
c1a7df913a
refactor executed code while importing and initializing all of these in loading-scene (#1125) 2024-05-24 13:46:30 -04:00
神龟
a9af2bd6ff
Update pokemon.ts (#1297) 2024-05-24 11:15:11 -05:00
神龟
34e9236874
Make zh_CN translation easier to understand (#1292)
* Update modifier-type.ts

* Update menu.ts

* Update tutorial.ts
2024-05-24 11:13:51 -05:00
GhostFlys
6168b77761
Update Chinese battle.ts and egg-list-ui-handler.ts (#1314)
* Update battle.ts

update some lines of translation
make some translation more official

* Update egg-list-ui-handler.ts

make text position more accurate
2024-05-24 10:25:57 -05:00
27 changed files with 433 additions and 192 deletions

View File

@ -2,7 +2,7 @@ import Phaser from "phaser";
import UI from "./ui/ui";
import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase } from "./phases";
import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies, initSpecies } from "./data/pokemon-species";
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
import * as Utils from "./utils";
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball";
@ -15,10 +15,9 @@ import { GameData, PlayerGender } from "./system/game-data";
import { TextStyle, addTextObject } from "./ui/text";
import { Moves } from "./data/enums/moves";
import { allMoves } from "./data/move";
import { initMoves } from "./data/move";
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue } from "./modifier/modifier-type";
import AbilityBar from "./ui/ability-bar";
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from "./data/ability";
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs } from "./data/ability";
import { allAbilities } from "./data/ability";
import Battle, { BattleType, FixedBattleConfig, fixedBattles } from "./battle";
import { GameMode, GameModes, gameModes } from "./game-mode";
@ -187,11 +186,6 @@ export default class BattleScene extends SceneBase {
constructor() {
super("battle");
initSpecies();
initMoves();
initAbilities();
this.phaseQueue = [];
this.phaseQueuePrepend = [];
this.phaseQueuePrependSpliceIndex = -1;

View File

@ -2641,7 +2641,7 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
const cancelled = new Utils.BooleanHolder(false);
pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
if (cancelled) {
if (cancelled.value) {
return false;
}
attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);

View File

@ -254,6 +254,51 @@ export class ConfusedTag extends BattlerTag {
}
}
/**
* Tag applied to the {@linkcode Move.DESTINY_BOND} user.
* @extends BattlerTag
* @see {@linkcode apply}
*/
export class DestinyBondTag extends BattlerTag {
constructor(sourceMove: Moves, sourceId: integer) {
super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId);
}
/**
* Lapses either before the user's move and does nothing
* or after receiving fatal damage. When the damage is fatal,
* the attacking Pokemon is taken down as well, unless it's a boss.
*
* @param {Pokemon} pokemon Pokemon that is attacking the Destiny Bond user.
* @param {BattlerTagLapseType} lapseType CUSTOM or PRE_MOVE
* @returns false if the tag source fainted or one turn has passed since the application
*/
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType !== BattlerTagLapseType.CUSTOM) {
return super.lapse(pokemon, lapseType);
}
const source = pokemon.scene.getPokemonById(this.sourceId);
if (!source.isFainted()) {
return true;
}
if (source.getAlly() === pokemon) {
return false;
}
const targetMessage = getPokemonMessage(pokemon, "");
if (pokemon.isBossImmune()) {
pokemon.scene.queueMessage(`${targetMessage} is unaffected\nby the effects of Destiny Bond.`);
return false;
}
pokemon.scene.queueMessage(`${getPokemonMessage(source, ` took\n${targetMessage} down with it!`)}`);
pokemon.damageAndUpdate(pokemon.hp, HitResult.ONE_HIT_KO, false, false, true);
return false;
}
}
export class InfatuatedTag extends BattlerTag {
constructor(sourceMove: integer, sourceId: integer) {
super(BattlerTagType.INFATUATED, BattlerTagLapseType.MOVE, 1, sourceMove, sourceId);
@ -1416,6 +1461,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
return new MagnetRisenTag(tagType, sourceMove);
case BattlerTagType.MINIMIZED:
return new MinimizeTag();
case BattlerTagType.DESTINY_BOND:
return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -5,7 +5,7 @@ import beautify from "json-beautify";
import { TrainerType } from "./enums/trainer-type";
import { TimeOfDay } from "./enums/time-of-day";
import { Biome } from "./enums/biome";
import { SpeciesFormEvolution } from "./pokemon-evolutions";
import {pokemonEvolutions, SpeciesFormEvolution} from "./pokemon-evolutions";
export function getBiomeName(biome: Biome | -1) {
if (biome === -1) {
@ -200,7 +200,7 @@ export const biomePokemonPools: BiomePokemonPools = {
[TimeOfDay.NIGHT]: [ { 1: [ Species.SHINX ], 15: [ Species.LUXIO ], 30: [ Species.LUXRAY ] } ],
[TimeOfDay.ALL]: [ { 1: [ Species.ABRA ], 16: [ Species.KADABRA ] }, { 1: [ Species.BUNEARY ], 20: [ Species.LOPUNNY ] }, { 1: [ Species.ROOKIDEE ], 18: [ Species.CORVISQUIRE ], 38: [ Species.CORVIKNIGHT ] } ]
},
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ Species.FARFETCHD, Species.LICKITUNG, Species.CHANSEY, Species.EEVEE, Species.SNORLAX, { 1: [ Species.DUNSPARCE ], 72: [ Species.DUDUNSPARCE ] } ] },
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ Species.FARFETCHD, Species.LICKITUNG, Species.CHANSEY, Species.EEVEE, Species.SNORLAX, { 1: [ Species.DUNSPARCE ], 62: [ Species.DUDUNSPARCE ] } ] },
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ Species.DITTO, Species.LATIAS, Species.LATIOS ] },
[BiomePoolTier.BOSS]: {
[TimeOfDay.DAWN]: [ Species.DODRIO, Species.FURRET, Species.GUMSHOOS, Species.GREEDENT ],
@ -268,7 +268,7 @@ export const biomePokemonPools: BiomePokemonPools = {
[TimeOfDay.DAY]: [],
[TimeOfDay.DUSK]: [],
[TimeOfDay.NIGHT]: [],
[TimeOfDay.ALL]: [ Species.PINSIR, { 1: [ Species.CHIKORITA ], 16: [ Species.BAYLEEF ], 32: [ Species.MEGANIUM ] }, { 1: [ Species.GIRAFARIG ], 72: [ Species.FARIGIRAF ] }, Species.ZANGOOSE, Species.KECLEON, Species.TROPIUS ]
[TimeOfDay.ALL]: [ Species.PINSIR, { 1: [ Species.CHIKORITA ], 16: [ Species.BAYLEEF ], 32: [ Species.MEGANIUM ] }, { 1: [ Species.GIRAFARIG ], 62: [ Species.FARIGIRAF ] }, Species.ZANGOOSE, Species.KECLEON, Species.TROPIUS ]
},
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ Species.SCYTHER, Species.SHEDINJA, Species.ROTOM ] },
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [] },
@ -474,8 +474,8 @@ export const biomePokemonPools: BiomePokemonPools = {
[TimeOfDay.ALL]: [ { 1: [ Species.TOTODILE ], 18: [ Species.CROCONAW ], 30: [ Species.FERALIGATR ] }, { 1: [ Species.MUDKIP ], 16: [ Species.MARSHTOMP ], 36: [ Species.SWAMPERT ] } ]
},
[BiomePoolTier.SUPER_RARE]: {
[TimeOfDay.DAWN]: [ { 1: [ Species.GALAR_SLOWPOKE ], 40: [ Species.GALAR_SLOWBRO ] }, { 1: [ Species.HISUI_SLIGGOO ], 90: [ Species.HISUI_GOODRA ] } ],
[TimeOfDay.DAY]: [ { 1: [ Species.GALAR_SLOWPOKE ], 40: [ Species.GALAR_SLOWBRO ] }, { 1: [ Species.HISUI_SLIGGOO ], 90: [ Species.HISUI_GOODRA ] } ],
[TimeOfDay.DAWN]: [ { 1: [ Species.GALAR_SLOWPOKE ], 40: [ Species.GALAR_SLOWBRO ] }, { 1: [ Species.HISUI_SLIGGOO ], 80: [ Species.HISUI_GOODRA ] } ],
[TimeOfDay.DAY]: [ { 1: [ Species.GALAR_SLOWPOKE ], 40: [ Species.GALAR_SLOWBRO ] }, { 1: [ Species.HISUI_SLIGGOO ], 80: [ Species.HISUI_GOODRA ] } ],
[TimeOfDay.DUSK]: [],
[TimeOfDay.NIGHT]: [],
[TimeOfDay.ALL]: [ Species.POLITOED, Species.GALAR_STUNFISK ]
@ -2012,7 +2012,7 @@ export const biomeTrainerPools: BiomeTrainerPools = {
}
};
{
export function initBiomes() {
const pokemonBiomes = [
[ Species.BULBASAUR, Type.GRASS, Type.POISON, [
[ Biome.GRASS, BiomePoolTier.RARE ]
@ -7677,130 +7677,127 @@ export const biomeTrainerPools: BiomeTrainerPools = {
traverseBiome(Biome.TOWN, 0);
biomeDepths[Biome.END] = [ Object.values(biomeDepths).map(d => d[0]).reduce((max: integer, value: integer) => Math.max(max, value), 0) + 1, 1 ];
import("./pokemon-evolutions").then(pe => {
const pokemonEvolutions = pe.pokemonEvolutions;
for (const biome of Utils.getEnumValues(Biome)) {
biomePokemonPools[biome] = {};
biomeTrainerPools[biome] = {};
for (const biome of Utils.getEnumValues(Biome)) {
biomePokemonPools[biome] = {};
biomeTrainerPools[biome] = {};
for (const tier of Utils.getEnumValues(BiomePoolTier)) {
biomePokemonPools[biome][tier] = {};
biomeTrainerPools[biome][tier] = [];
for (const tier of Utils.getEnumValues(BiomePoolTier)) {
biomePokemonPools[biome][tier] = {};
biomeTrainerPools[biome][tier] = [];
for (const tod of Utils.getEnumValues(TimeOfDay)) {
biomePokemonPools[biome][tier][tod] = [];
}
for (const tod of Utils.getEnumValues(TimeOfDay)) {
biomePokemonPools[biome][tier][tod] = [];
}
}
}
for (const pb of pokemonBiomes) {
const speciesId = pb[0] as Species;
const biomeEntries = pb[3] as (Biome | BiomePoolTier)[][];
for (const pb of pokemonBiomes) {
const speciesId = pb[0] as Species;
const biomeEntries = pb[3] as (Biome | BiomePoolTier)[][];
const speciesEvolutions: SpeciesFormEvolution[] = pokemonEvolutions.hasOwnProperty(speciesId)
? pokemonEvolutions[speciesId]
: [];
const speciesEvolutions: SpeciesFormEvolution[] = pokemonEvolutions.hasOwnProperty(speciesId)
? pokemonEvolutions[speciesId]
: [];
if (!biomeEntries.filter(b => b[0] !== Biome.END).length && !speciesEvolutions.filter(es => !!((pokemonBiomes.find(p => p[0] === es.speciesId))[3] as any[]).filter(b => b[0] !== Biome.END).length).length) {
uncatchableSpecies.push(speciesId);
}
for (const b of biomeEntries) {
const biome = b[0];
const tier = b[1];
const timesOfDay = b.length > 2
? Array.isArray(b[2])
? b[2]
: [ b[2] ]
: [ TimeOfDay.ALL ];
for (const tod of timesOfDay) {
if (!biomePokemonPools.hasOwnProperty(biome) || !biomePokemonPools[biome].hasOwnProperty(tier) || !biomePokemonPools[biome][tier].hasOwnProperty(tod)) {
continue;
}
const biomeTierPool = biomePokemonPools[biome][tier][tod];
let treeIndex = -1;
let arrayIndex = 0;
for (let t = 0; t < biomeTierPool.length; t++) {
const existingSpeciesIds = biomeTierPool[t] as unknown as Species[];
for (let es = 0; es < existingSpeciesIds.length; es++) {
const existingSpeciesId = existingSpeciesIds[es];
if (pokemonEvolutions.hasOwnProperty(existingSpeciesId) && (pokemonEvolutions[existingSpeciesId] as SpeciesFormEvolution[]).find(ese => ese.speciesId === speciesId)) {
treeIndex = t;
arrayIndex = es + 1;
break;
} else if (speciesEvolutions && speciesEvolutions.find(se => se.speciesId === existingSpeciesId)) {
treeIndex = t;
arrayIndex = es;
break;
}
}
if (treeIndex > -1) {
break;
}
}
if (treeIndex > -1) {
(biomeTierPool[treeIndex] as unknown as Species[]).splice(arrayIndex, 0, speciesId);
} else {
(biomeTierPool as unknown as Species[][]).push([ speciesId ]);
}
}
}
if (!biomeEntries.filter(b => b[0] !== Biome.END).length && !speciesEvolutions.filter(es => !!((pokemonBiomes.find(p => p[0] === es.speciesId))[3] as any[]).filter(b => b[0] !== Biome.END).length).length) {
uncatchableSpecies.push(speciesId);
}
for (const b of Object.keys(biomePokemonPools)) {
for (const t of Object.keys(biomePokemonPools[b])) {
const tier = parseInt(t) as BiomePoolTier;
for (const tod of Object.keys(biomePokemonPools[b][t])) {
const biomeTierTimePool = biomePokemonPools[b][t][tod];
for (let e = 0; e < biomeTierTimePool.length; e++) {
const entry = biomeTierTimePool[e];
if (entry.length === 1) {
biomeTierTimePool[e] = entry[0];
} else {
const newEntry = {
1: [ entry[0] ]
};
for (let s = 1; s < entry.length; s++) {
const speciesId = entry[s];
const prevolution = entry.map(s => pokemonEvolutions[s]).flat().find(e => e && e.speciesId === speciesId);
const level = prevolution.level - (prevolution.level === 1 ? 1 : 0) + (prevolution.wildDelay * 10) - (tier >= BiomePoolTier.BOSS ? 10 : 0);
if (!newEntry.hasOwnProperty(level)) {
newEntry[level] = [ speciesId ];
} else {
newEntry[level].push(speciesId);
}
}
biomeTierTimePool[e] = newEntry;
}
}
}
}
}
for (const b of biomeEntries) {
const biome = b[0];
const tier = b[1];
const timesOfDay = b.length > 2
? Array.isArray(b[2])
? b[2]
: [ b[2] ]
: [ TimeOfDay.ALL ];
for (const tb of trainerBiomes) {
const trainerType = tb[0] as TrainerType;
const biomeEntries = tb[1] as BiomePoolTier[][];
for (const b of biomeEntries) {
const biome = b[0];
const tier = b[1];
if (!biomeTrainerPools.hasOwnProperty(biome) || !biomeTrainerPools[biome].hasOwnProperty(tier)) {
for (const tod of timesOfDay) {
if (!biomePokemonPools.hasOwnProperty(biome) || !biomePokemonPools[biome].hasOwnProperty(tier) || !biomePokemonPools[biome][tier].hasOwnProperty(tod)) {
continue;
}
const biomeTierPool = biomeTrainerPools[biome][tier];
biomeTierPool.push(trainerType);
const biomeTierPool = biomePokemonPools[biome][tier][tod];
let treeIndex = -1;
let arrayIndex = 0;
for (let t = 0; t < biomeTierPool.length; t++) {
const existingSpeciesIds = biomeTierPool[t] as unknown as Species[];
for (let es = 0; es < existingSpeciesIds.length; es++) {
const existingSpeciesId = existingSpeciesIds[es];
if (pokemonEvolutions.hasOwnProperty(existingSpeciesId) && (pokemonEvolutions[existingSpeciesId] as SpeciesFormEvolution[]).find(ese => ese.speciesId === speciesId)) {
treeIndex = t;
arrayIndex = es + 1;
break;
} else if (speciesEvolutions && speciesEvolutions.find(se => se.speciesId === existingSpeciesId)) {
treeIndex = t;
arrayIndex = es;
break;
}
}
if (treeIndex > -1) {
break;
}
}
if (treeIndex > -1) {
(biomeTierPool[treeIndex] as unknown as Species[]).splice(arrayIndex, 0, speciesId);
} else {
(biomeTierPool as unknown as Species[][]).push([ speciesId ]);
}
}
}
}
for (const b of Object.keys(biomePokemonPools)) {
for (const t of Object.keys(biomePokemonPools[b])) {
const tier = parseInt(t) as BiomePoolTier;
for (const tod of Object.keys(biomePokemonPools[b][t])) {
const biomeTierTimePool = biomePokemonPools[b][t][tod];
for (let e = 0; e < biomeTierTimePool.length; e++) {
const entry = biomeTierTimePool[e];
if (entry.length === 1) {
biomeTierTimePool[e] = entry[0];
} else {
const newEntry = {
1: [ entry[0] ]
};
for (let s = 1; s < entry.length; s++) {
const speciesId = entry[s];
const prevolution = entry.map(s => pokemonEvolutions[s]).flat().find(e => e && e.speciesId === speciesId);
const level = prevolution.level - (prevolution.level === 1 ? 1 : 0) + (prevolution.wildDelay * 10) - (tier >= BiomePoolTier.BOSS ? 10 : 0);
if (!newEntry.hasOwnProperty(level)) {
newEntry[level] = [ speciesId ];
} else {
newEntry[level].push(speciesId);
}
}
biomeTierTimePool[e] = newEntry;
}
}
}
}
}
for (const tb of trainerBiomes) {
const trainerType = tb[0] as TrainerType;
const biomeEntries = tb[1] as BiomePoolTier[][];
for (const b of biomeEntries) {
const biome = b[0];
const tier = b[1];
if (!biomeTrainerPools.hasOwnProperty(biome) || !biomeTrainerPools[biome].hasOwnProperty(tier)) {
continue;
}
const biomeTierPool = biomeTrainerPools[biome][tier];
biomeTierPool.push(trainerType);
}
//outputPools();
});
}
// used in a commented code
// eslint-disable-next-line @typescript-eslint/no-unused-vars

View File

@ -608,9 +608,11 @@ function parseEggMoves(content: string): void {
console.log(output);
}
const eggMovesStr = "";
if (eggMovesStr) {
setTimeout(() => {
parseEggMoves(eggMovesStr);
}, 1000);
export function initEggMoves() {
const eggMovesStr = "";
if (eggMovesStr) {
setTimeout(() => {
parseEggMoves(eggMovesStr);
}, 1000);
}
}

View File

@ -56,5 +56,6 @@ export enum BattlerTagType {
CHARGED = "CHARGED",
GROUNDED = "GROUNDED",
MAGNET_RISEN = "MAGNET_RISEN",
MINIMIZED = "MINIMIZED"
MINIMIZED = "MINIMIZED",
DESTINY_BOND = "DESTINY_BOND"
}

View File

@ -4704,6 +4704,31 @@ export class MoneyAttr extends MoveEffectAttr {
}
}
/**
* Applies {@linkcode BattlerTagType.DESTINY_BOND} to the user.
*
* @extends MoveEffectAttr
*/
export class DestinyBondAttr extends MoveEffectAttr {
constructor() {
super(true, MoveEffectTrigger.PRE_APPLY);
}
/**
* Applies {@linkcode BattlerTagType.DESTINY_BOND} to the user.
* @param user {@linkcode Pokemon} that is having the tag applied to.
* @param target {@linkcode Pokemon} N/A
* @param move {@linkcode Move} {@linkcode Move.DESTINY_BOND}
* @param {any[]} args N/A
* @returns true
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
user.scene.queueMessage(`${getPokemonMessage(user, " is trying\nto take its foe down with it!")}`);
user.addTag(BattlerTagType.DESTINY_BOND, undefined, move.id, user.id);
return true;
}
}
export class LastResortAttr extends MoveAttr {
getCondition(): MoveConditionFunc {
return (user: Pokemon, target: Pokemon, move: Move) => {
@ -5405,8 +5430,7 @@ export function initMoves() {
.unimplemented(),
new SelfStatusMove(Moves.DESTINY_BOND, Type.GHOST, -1, 5, -1, 0, 2)
.ignoresProtect()
.condition(failOnBossCondition)
.unimplemented(),
.attr(DestinyBondAttr),
new StatusMove(Moves.PERISH_SONG, Type.NORMAL, -1, 5, -1, 0, 2)
.attr(FaintCountdownAttr)
.ignoresProtect()
@ -6292,7 +6316,7 @@ export function initMoves() {
new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5)
.unimplemented(),
new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5)
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().reduce((v, m) => v + m.stackCount, 0))),
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.getTransferrable(true)).reduce((v, m) => v + m.stackCount, 0))),
new StatusMove(Moves.REFLECT_TYPE, Type.NORMAL, -1, 15, -1, 0, 5)
.attr(CopyTypeAttr),
new AttackMove(Moves.RETALIATE, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 5, -1, 0, 5)

View File

@ -1619,7 +1619,7 @@ interface PokemonPrevolutions {
export const pokemonPrevolutions: PokemonPrevolutions = {};
{
export function initPokemonPrevolutions(): void {
const megaFormKeys = [ SpeciesFormKey.MEGA, "", SpeciesFormKey.MEGA_X, "", SpeciesFormKey.MEGA_Y ].map(sfk => sfk as string);
const prevolutionKeys = Object.keys(pokemonEvolutions);
prevolutionKeys.forEach(pk => {

View File

@ -729,7 +729,7 @@ export const pokemonFormChanges: PokemonFormChanges = {
]
};
{
export function initPokemonForms() {
const formChangeKeys = Object.keys(pokemonFormChanges);
formChangeKeys.forEach(pk => {
const formChanges = pokemonFormChanges[pk];

View File

@ -404,12 +404,14 @@ export abstract class PokemonSpeciesForm {
console.warn = () => {};
const frameNames = scene.anims.generateFrameNames(spriteKey, { zeroPad: 4, suffix: ".png", start: 1, end: 400 });
console.warn = originalWarn;
scene.anims.create({
key: this.getSpriteKey(female, formIndex, shiny, variant),
frames: frameNames,
frameRate: 12,
repeat: -1
});
if (!(scene.anims.exists(spriteKey))) {
scene.anims.create({
key: this.getSpriteKey(female, formIndex, shiny, variant),
frames: frameNames,
frameRate: 12,
repeat: -1
});
}
let spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
const useExpSprite = scene.experimentalSprites && scene.hasExpSprite(spriteKey);
if (useExpSprite) {

View File

@ -10,7 +10,6 @@ import PokemonSpecies, {PokemonSpeciesFilter, getPokemonSpecies} from "./pokemon
import {Species} from "./enums/species";
import {tmSpecies} from "./tms";
import {Type} from "./type";
import {initTrainerTypeDialogue} from "./dialogue";
import {PersistentModifier} from "../modifier/modifier";
import {TrainerVariant} from "../field/trainer";
import {PartyMemberStrength} from "./enums/party-member-strength";
@ -1050,7 +1049,3 @@ export const trainerConfigs: TrainerConfigs = {
return [ modifierTypes.TERA_SHARD().generateType(null, [ starter.species.type1 ]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ];
}),
};
(function () {
initTrainerTypeDialogue();
})();

View File

@ -1790,6 +1790,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
console.log("damage", damage.value, move.name, power.value, sourceAtk, targetDef);
// In case of fatal damage, this tag would have gotten cleared before we could lapse it.
const destinyTag = this.getTag(BattlerTagType.DESTINY_BOND);
const oneHitKo = result === HitResult.ONE_HIT_KO;
if (damage.value) {
if (this.getHpRatio() === 1) {
@ -1850,6 +1853,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (damage) {
this.scene.clearPhaseQueueSplice();
const attacker = this.scene.getPokemonById(source.id);
destinyTag?.lapse(attacker, BattlerTagLapseType.CUSTOM);
}
}
break;

View File

@ -9,6 +9,15 @@ import { WindowVariant, getWindowVariantSuffix } from "./ui/ui-theme";
import { isMobile } from "./touch-controls";
import * as Utils from "./utils";
import { initI18n } from "./plugins/i18n";
import {initStatsKeys} from "#app/ui/game-stats-ui-handler";
import {initPokemonPrevolutions} from "#app/data/pokemon-evolutions";
import {initBiomes} from "#app/data/biomes";
import {initEggMoves} from "#app/data/egg-moves";
import {initPokemonForms} from "#app/data/pokemon-forms";
import {initSpecies} from "#app/data/pokemon-species";
import {initMoves} from "#app/data/move";
import {initAbilities} from "#app/data/ability";
import {initTrainerTypeDialogue} from "#app/data/dialogue";
export class LoadingScene extends SceneBase {
constructor() {
@ -284,6 +293,16 @@ export class LoadingScene extends SceneBase {
this.load.plugin("rextexteditplugin", "https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rextexteditplugin.min.js", true);
this.loadLoadingScreen();
initStatsKeys();
initPokemonPrevolutions();
initBiomes();
initEggMoves();
initPokemonForms();
initTrainerTypeDialogue();
initSpecies();
initMoves();
initAbilities();
}
loadLoadingScreen() {

View File

@ -2047,7 +2047,7 @@ export const move: MoveTranslationEntries = {
},
"acrobatics": {
name: "Acrobatics",
effect: "The user nimbly strikes the target. If the user is not holding an item, this attack inflicts massive damage."
effect: "The user nimbly strikes the target. The fewer held items, the higher the damage it inflicts."
},
"reflectType": {
name: "Reflect Type",

View File

@ -4,20 +4,20 @@ export const battle: SimpleTranslationEntries = {
"bossAppeared": "{{bossName}} 出现了。",
"trainerAppeared": "{{trainerName}}\n想要和你对战",
"trainerAppearedDouble": "{{trainerName}}\n想要和你对战",
"trainerSendOut": "{{trainerName}} sent out\n{{pokemonName}}!",
"trainerSendOut": "{{trainerName}} 派出了\n{{pokemonName}}!",
"singleWildAppeared": "一只野生 {{pokemonName}} 出现了!",
"multiWildAppeared": "野生的 {{pokemonName1}}\n和 {{pokemonName2}} 出现了!",
"playerComeBack": "回来吧, {{pokemonName}}",
"trainerComeBack": "{{trainerName}} 收回了 {{pokemonName}}",
"playerGo": "去吧! {{pokemonName}}",
"trainerGo": "{{trainerName}} 派出了 {{pokemonName}}",
"trainerGo": "{{trainerName}} 派出了\n{{pokemonName}}",
"switchQuestion": "要更换\n{{pokemonName}}吗?",
"trainerDefeated": "你击败了\n{{trainerName}}",
"moneyWon": "You got\n₽{{moneyAmount}} for winning!",
"moneyWon": "你赢得了\n₽{{moneyAmount}}",
"pokemonCaught": "{{pokemonName}} 被抓住了!",
"partyFull": "Your party is full.\nRelease a Pokémon to make room for {{pokemonName}}?",
"partyFull": "你的队伍已满员.是否放生其他宝可梦\n为 {{pokemonName}} 腾出空间?",
"pokemon": "宝可梦",
"sendOutPokemon": "上吧! {{pokemonName}}",
"sendOutPokemon": "上吧!\n{{pokemonName}}",
"hitResultCriticalHit": "击中了要害!",
"hitResultSuperEffective": "效果拔群!",
"hitResultNotVeryEffective": "收效甚微…",
@ -26,7 +26,7 @@ export const battle: SimpleTranslationEntries = {
"attackFailed": "但是失败了!",
"attackHitsCount": "击中 {{count}} 次!",
"expGain": "{{pokemonName}} 获得了 {{exp}} 经验值!",
"levelUp": "{{pokemonName}} 升级到 Lv. {{level}}",
"levelUp": "{{pokemonName}} 升级到 Lv.{{level}}",
"learnMove": "{{pokemonName}} 学会了 {{moveName}}",
"learnMovePrompt": "{{pokemonName}} 想要学习 {{moveName}}。",
"learnMoveLimitReached": "但是,{{pokemonName}} 已经学会了\n四个技能",
@ -35,7 +35,7 @@ export const battle: SimpleTranslationEntries = {
"learnMoveNotLearned": "{{pokemonName}} 没有学会 {{moveName}}。",
"learnMoveForgetQuestion": "要忘记哪个技能?",
"learnMoveForgetSuccess": "{{pokemonName}} 忘记了\n如何使用 {{moveName}}。",
"countdownPoof": "@d{32}1, @d{15}2, @d{15}和@d{15}… @d{15}… @d{15}… @d{15}@s{pb_bounce_1}噗",
"countdownPoof": "@d{32}1, @d{15}2 @d{15}… @d{15}… @d{15}@s{pb_bounce_1}空",
"learnMoveAnd": "然后...",
"levelCapUp": "等级上限提升到 {{levelCap}}",
"moveNotImplemented": "{{moveName}} 尚未实装,无法选择。",

View File

@ -18,13 +18,13 @@ export const menu: SimpleTranslationEntries = {
"login": "登录",
"register": "注册",
"emptyUsername": "用户名不能为空",
"invalidLoginUsername": "提供的用户名无效",
"invalidLoginUsername": "输入的用户名无效",
"invalidRegisterUsername": "用户名只能包含字母、数字或下划线",
"invalidLoginPassword": "提供的密码无效",
"invalidLoginPassword": "输入的密码无效",
"invalidRegisterPassword": "密码必须至少包含 6 个字符",
"usernameAlreadyUsed": "提供的用户名已被使用",
"accountNonExistent": "提供的用户不存在",
"unmatchingPassword": "提供的密码不匹配",
"usernameAlreadyUsed": "输入的用户名已被使用",
"accountNonExistent": "输入的用户不存在",
"unmatchingPassword": "输入的密码不匹配",
"passwordNotMatchingConfirmPassword": "密码必须与确认密码一致",
"confirmPassword": "确认密码",
"registrationAgeWarning": "注册即表示您确认您已年满 13 岁。",

View File

@ -17,7 +17,7 @@ export const modifierType: ModifierTypeTranslationEntries = {
}
},
"PokemonHpRestoreModifierType": {
description: "为一只宝可梦回复 {{restorePoints}} HP 或 {{restorePercent}}% HP大值",
description: "为一只宝可梦回复 {{restorePoints}} HP 或 {{restorePercent}}% HP大值",
extra: {
"fully": "为一只宝可梦回复全部HP",
"fullyWithStatus": "为一只宝可梦回复全部HP并消除所有负面\n状态",
@ -196,7 +196,7 @@ export const modifierType: ModifierTypeTranslationEntries = {
"MULTI_LENS": { name: "多重镜" },
"HEALING_CHARM": { name: "治愈护符", description: "HP回复量增加10% (含复活)" },
"CANDY_JAR": { name: "糖果罐", description: "神奇糖果提供的升级提升1级" },
"CANDY_JAR": { name: "糖果罐", description: "神奇糖果提供的升级额外增加1级" },
"BERRY_POUCH": { name: "树果袋", description: "使用树果时有33%的几率不会消耗树果" },

View File

@ -4,7 +4,7 @@ export const tutorial: SimpleTranslationEntries = {
"intro": `欢迎来到PokéRogue这是一款以战斗为核心的融合了roguelite元素的宝可梦同人游戏。
$本游戏未进行商业化\nPokémon或Pokémon使用的版
$权资产的所有权
$游戏仍在开发中\n告错误使 Discord
$游戏仍在开发中\n告错误 Discord
$如果游戏运行缓慢\n打开了`,
"accessMenu": "在等待输入时,按 M 或 Escape 键可访\n问菜单。菜单包含设置和各种功能。",

View File

@ -31,7 +31,7 @@ export const pokemon: SimpleTranslationEntries = {
"sandslash": "穿山王",
"nidoran_f": "尼多蘭",
"nidorina": "尼多娜",
"nidoqueen": "尼多",
"nidoqueen": "尼多",
"nidoran_m": "尼多朗",
"nidorino": "尼多力諾",
"nidoking": "尼多王",
@ -57,7 +57,7 @@ export const pokemon: SimpleTranslationEntries = {
"psyduck": "可達鴨",
"golduck": "哥達鴨",
"mankey": "猴怪",
"primeape": "火猴",
"primeape": "火猴",
"growlithe": "卡蒂狗",
"arcanine": "風速狗",
"poliwag": "蚊香蝌蚪",
@ -76,7 +76,7 @@ export const pokemon: SimpleTranslationEntries = {
"tentacruel": "毒刺水母",
"geodude": "小拳石",
"graveler": "隆隆石",
"golem": "隆隆",
"golem": "隆隆",
"ponyta": "小火馬",
"rapidash": "烈焰馬",
"slowpoke": "呆呆獸",
@ -95,7 +95,7 @@ export const pokemon: SimpleTranslationEntries = {
"gastly": "鬼斯",
"haunter": "鬼斯通",
"gengar": "耿鬼",
"onix": "大蛇",
"onix": "大蛇",
"drowzee": "催眠貘",
"hypno": "引夢貘人",
"krabby": "大鉗蟹",
@ -156,7 +156,7 @@ export const pokemon: SimpleTranslationEntries = {
"bayleef": "月桂葉",
"meganium": "大竺葵",
"cyndaquil": "火球鼠",
"quilava": "火鼠",
"quilava": "火鼠",
"typhlosion": "火暴獸",
"totodile": "小鋸鱷",
"croconaw": "藍鱷",
@ -185,7 +185,7 @@ export const pokemon: SimpleTranslationEntries = {
"bellossom": "美麗花",
"marill": "瑪力露",
"azumarill": "瑪力露麗",
"sudowoodo": "樹怪",
"sudowoodo": "樹怪",
"politoed": "蚊香蛙皇",
"hoppip": "毽子草",
"skiploom": "毽子花",
@ -233,7 +233,7 @@ export const pokemon: SimpleTranslationEntries = {
"kingdra": "刺龍王",
"phanpy": "小小象",
"donphan": "頓甲",
"porygon2": "多邊獸2型",
"porygon2": "多邊獸",
"stantler": "驚角鹿",
"smeargle": "圖圖犬",
"tyrogue": "無畏小子",
@ -338,7 +338,7 @@ export const pokemon: SimpleTranslationEntries = {
"zangoose": "貓鼬斬",
"seviper": "飯匙蛇",
"lunatone": "月石",
"solrock": "太陽",
"solrock": "太陽",
"barboach": "泥泥鰍",
"whiscash": "鯰魚王",
"corphish": "龍蝦小兵",
@ -438,11 +438,11 @@ export const pokemon: SimpleTranslationEntries = {
"skuntank": "坦克臭鼬",
"bronzor": "銅鏡怪",
"bronzong": "青銅鐘",
"bonsly": "盆怪",
"bonsly": "盆怪",
"mime_jr": "魔尼尼",
"happiny": "小福蛋",
"chatot": "聒噪鳥",
"spiritomb": "花怪",
"spiritomb": "花怪",
"gible": "圓陸鯊",
"gabite": "尖牙陸鯊",
"garchomp": "烈咬陸鯊",
@ -474,12 +474,12 @@ export const pokemon: SimpleTranslationEntries = {
"glaceon": "冰伊布",
"gliscor": "天蠍王",
"mamoswine": "象牙豬",
"porygon_z": "多邊獸乙型",
"porygon_z": "多邊獸",
"gallade": "艾路雷朵",
"probopass": "大朝北鼻",
"dusknoir": "黑夜魔靈",
"froslass": "雪妖女",
"rotom": "洛姆",
"rotom": "洛姆",
"uxie": "由克希",
"mesprit": "艾姆利多",
"azelf": "亞克諾姆",
@ -525,8 +525,8 @@ export const pokemon: SimpleTranslationEntries = {
"blitzle": "斑斑馬",
"zebstrika": "雷電斑馬",
"roggenrola": "石丸子",
"boldore": "地幔",
"gigalith": "龐怪",
"boldore": "地幔",
"gigalith": "龐怪",
"woobat": "滾滾蝙蝠",
"swoobat": "心蝙蝠",
"drilbur": "螺釘地鼠",
@ -544,7 +544,7 @@ export const pokemon: SimpleTranslationEntries = {
"swadloon": "寶包繭",
"leavanny": "保姆蟲",
"venipede": "百足蜈蚣",
"whirlipede": "車輪",
"whirlipede": "車輪",
"scolipede": "蜈蚣王",
"cottonee": "木棉球",
"whimsicott": "風妖精",
@ -558,12 +558,12 @@ export const pokemon: SimpleTranslationEntries = {
"darmanitan": "達摩狒狒",
"maractus": "沙鈴仙人掌",
"dwebble": "石居蟹",
"crustle": "殿居蟹",
"crustle": "殿居蟹",
"scraggy": "滑滑小子",
"scrafty": "頭巾混混",
"sigilyph": "象徵鳥",
"yamask": "哭哭面具",
"cofagrigus": "迭失棺",
"cofagrigus": "死神棺",
"tirtouga": "原蓋海龜",
"carracosta": "肋骨海龜",
"archen": "始祖小鳥",
@ -650,7 +650,7 @@ export const pokemon: SimpleTranslationEntries = {
"keldeo": "凱路迪歐",
"meloetta": "美洛耶塔",
"genesect": "蓋諾賽克特",
"chespin": "哈力",
"chespin": "哈力",
"quilladin": "胖胖哈力",
"chesnaught": "布里卡隆",
"fennekin": "火狐狸",
@ -744,8 +744,8 @@ export const pokemon: SimpleTranslationEntries = {
"oricorio": "花舞鳥",
"cutiefly": "萌虻",
"ribombee": "蝶結萌虻",
"rockruff": "狗狗",
"lycanroc": "鬃狼人",
"rockruff": "狗狗",
"lycanroc": "鬃狼人",
"wishiwashi": "弱丁魚",
"mareanie": "好壞星",
"toxapex": "超壞星",
@ -758,7 +758,7 @@ export const pokemon: SimpleTranslationEntries = {
"morelull": "睡睡菇",
"shiinotic": "燈罩夜菇",
"salandit": "夜盜火蜥",
"salazzle": "焰蜥",
"salazzle": "焰蜥",
"stufful": "童偶熊",
"bewear": "穿着熊",
"bounsweet": "甜竹竹",
@ -778,7 +778,7 @@ export const pokemon: SimpleTranslationEntries = {
"komala": "樹枕尾熊",
"turtonator": "爆焰龜獸",
"togedemaru": "託戈德瑪爾",
"mimikyu": "謎擬",
"mimikyu": "謎擬",
"bruxish": "磨牙彩皮魚",
"drampa": "老翁龍",
"dhelmise": "破破舵輪",
@ -827,8 +827,8 @@ export const pokemon: SimpleTranslationEntries = {
"blipbug": "索偵蟲",
"dottler": "天罩蟲",
"orbeetle": "以歐路普",
"nickit": "狡小狐",
"thievul": "猾大狐",
"nickit": "偷兒狐",
"thievul": "大盜",
"gossifleur": "幼棉棉",
"eldegoss": "白蓬蓬",
"wooloo": "毛辮羊",
@ -848,7 +848,7 @@ export const pokemon: SimpleTranslationEntries = {
"cramorant": "古月鳥",
"arrokuda": "刺梭魚",
"barraskewda": "戽斗尖梭",
"toxel": "嬰",
"toxel": "電嬰",
"toxtricity": "顫弦蠑螈",
"sizzlipede": "燒火蚣",
"centiskorch": "焚焰蚣",
@ -867,7 +867,7 @@ export const pokemon: SimpleTranslationEntries = {
"cursola": "魔靈珊瑚",
"sirfetchd": "蔥遊兵",
"mr_rime": "踏冰人偶",
"runerigus": "迭失板",
"runerigus": "死神板",
"milcery": "小仙奶",
"alcremie": "霜奶仙",
"falinks": "列陣兵",
@ -915,7 +915,7 @@ export const pokemon: SimpleTranslationEntries = {
"quaxly": "潤水鴨",
"quaxwell": "湧躍鴨",
"quaquaval": "狂歡浪舞鴨",
"lechonk": "愛豚",
"lechonk": "愛豚",
"oinkologne": "飄香豚",
"tarountula": "團珠蛛",
"spidops": "操陷蛛",
@ -1039,7 +1039,7 @@ export const pokemon: SimpleTranslationEntries = {
"alola_persian": "貓老大",
"alola_geodude": "小拳石",
"alola_graveler": "隆隆石",
"alola_golem": "隆隆",
"alola_golem": "隆隆",
"alola_grimer": "臭泥",
"alola_muk": "臭臭泥",
"alola_exeggutor": "椰蛋樹",

View File

@ -0,0 +1,9 @@
import {describe, expect, it} from "vitest";
import {MoneyAchv} from "#app/system/achv";
describe("check some Achievement related stuff", () => {
it ("should check Achievement creation", () => {
const ach = new MoneyAchv("Achievement", 1000, null, 100);
expect(ach.name).toBe("Achievement");
});
});

View File

@ -0,0 +1,35 @@
import { describe, expect, it} from "vitest";
import {initStatsKeys} from "#app/ui/game-stats-ui-handler";
async function importModule() {
try {
initStatsKeys();
const { PokemonMove } = await import("#app/field/pokemon");
const { Species } = await import("#app/data/enums/species");
return {
PokemonMove,
Species,
};
// Dynamically import the module
} catch (error) {
// Log the error stack trace
console.error("Error during import:", error.stack);
// Rethrow the error to ensure the test fails
throw error;
}
}
describe("tests to debug the import, with trace", () => {
it("import PokemonMove module", async () => {
const module = await importModule();
// Example assertion
expect(module.PokemonMove).toBeDefined();
});
it("import Species module", async () => {
const module = await importModule();
// Example assertion
expect(module.Species).toBeDefined();
});
});

57
src/test/pokemon.test.ts Normal file
View File

@ -0,0 +1,57 @@
import {describe, expect, it} from "vitest";
import {getPokemonSpecies} from "#app/data/pokemon-species";
import {PokemonMove} from "#app/field/pokemon";
import {Species} from "#app/data/enums/species";
import {Moves} from "#app/data/enums/moves";
import PokemonData from "#app/system/pokemon-data";
describe("some tests related to PokemonData and Species", () => {
it("should create a species", () => {
const species = getPokemonSpecies(Species.MEW);
expect(species).not.toBeNull();
});
it("should create a pokemon", () => {
const pokemon = new PokemonData({
species: Species.MEW,
level: 1,
});
expect(pokemon).not.toBeNull();
expect(pokemon.level).toEqual(1);
expect(pokemon.species).toEqual(Species.MEW);
});
it("should generate a moveset", () => {
const pokemon = new PokemonData({
species: Species.MEW,
level: 1,
});
expect(pokemon.moveset[0].moveId).toBe(Moves.TACKLE);
expect(pokemon.moveset[1].moveId).toBe(Moves.GROWL);
});
it("should create an ennemypokemon", () => {
const ennemyPokemon = new PokemonData({
species: Species.MEWTWO,
level: 100,
});
expect(ennemyPokemon).not.toBeNull();
expect(ennemyPokemon.level).toEqual(100);
expect(ennemyPokemon.species).toEqual(Species.MEWTWO);
});
it("should create an ennemypokemon with specified moveset", () => {
const ennemyPokemon = new PokemonData({
species: Species.MEWTWO,
level: 100,
moveset: [
new PokemonMove(Moves.ACID),
new PokemonMove(Moves.ACROBATICS),
new PokemonMove(Moves.FOCUS_ENERGY),
]
});
expect(ennemyPokemon.moveset[0].moveId).toBe(Moves.ACID);
expect(ennemyPokemon.moveset[1].moveId).toBe(Moves.ACROBATICS);
expect(ennemyPokemon.moveset[2].moveId).toBe(Moves.FOCUS_ENERGY);
});
});

View File

@ -1,2 +1,19 @@
import "vitest-canvas-mock";
import "#app/test/phaser.setup";
import {initStatsKeys} from "#app/ui/game-stats-ui-handler";
import {initPokemonPrevolutions} from "#app/data/pokemon-evolutions";
import {initBiomes} from "#app/data/biomes";
import {initEggMoves} from "#app/data/egg-moves";
import {initPokemonForms} from "#app/data/pokemon-forms";
import {initSpecies} from "#app/data/pokemon-species";
import {initMoves} from "#app/data/move";
import {initAbilities} from "#app/data/ability";
initStatsKeys();
initPokemonPrevolutions();
initBiomes();
initEggMoves();
initPokemonForms();
initSpecies();
initMoves();
initAbilities();

View File

@ -35,6 +35,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private nameText: Phaser.GameObjects.Text;
private genderText: Phaser.GameObjects.Text;
private ownedIcon: Phaser.GameObjects.Sprite;
private championRibbon: Phaser.GameObjects.Sprite;
private teraIcon: Phaser.GameObjects.Sprite;
private shinyIcon: Phaser.GameObjects.Sprite;
private fusionShinyIcon: Phaser.GameObjects.Sprite;
@ -78,27 +79,39 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.setVisible(false);
this.box = this.scene.add.sprite(0, 0, this.getTextureName());
this.box.setName("box");
this.box.setOrigin(1, 0.5);
this.add(this.box);
this.nameText = addTextObject(this.scene, player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO);
this.nameText.setName("text_name");
this.nameText.setOrigin(0, 0);
this.add(this.nameText);
this.genderText = addTextObject(this.scene, 0, 0, "", TextStyle.BATTLE_INFO);
this.genderText.setName("text_gender");
this.genderText.setOrigin(0, 0);
this.genderText.setPositionRelative(this.nameText, 0, 2);
this.add(this.genderText);
if (!this.player) {
this.ownedIcon = this.scene.add.sprite(0, 0, "icon_owned");
this.ownedIcon.setName("icon_owned");
this.ownedIcon.setVisible(false);
this.ownedIcon.setOrigin(0, 0);
this.ownedIcon.setPositionRelative(this.nameText, 0, 11.75);
this.add(this.ownedIcon);
this.championRibbon = this.scene.add.sprite(0, 0, "champion_ribbon");
this.championRibbon.setName("icon_champion_ribbon");
this.championRibbon.setVisible(false);
this.championRibbon.setOrigin(0, 0);
this.championRibbon.setPositionRelative(this.nameText, 11.75, 11.75);
this.add(this.championRibbon);
}
this.teraIcon = this.scene.add.sprite(0, 0, "icon_tera");
this.teraIcon.setName("icon_tera");
this.teraIcon.setVisible(false);
this.teraIcon.setOrigin(0, 0);
this.teraIcon.setScale(0.5);
@ -107,6 +120,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.add(this.teraIcon);
this.shinyIcon = this.scene.add.sprite(0, 0, "shiny_star");
this.shinyIcon.setName("icon_shiny");
this.shinyIcon.setVisible(false);
this.shinyIcon.setOrigin(0, 0);
this.shinyIcon.setScale(0.5);
@ -115,6 +129,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.add(this.shinyIcon);
this.fusionShinyIcon = this.scene.add.sprite(0, 0, "shiny_star_2");
this.fusionShinyIcon.setName("icon_fusion_shiny");
this.fusionShinyIcon.setVisible(false);
this.fusionShinyIcon.setOrigin(0, 0);
this.fusionShinyIcon.setScale(0.5);
@ -122,6 +137,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.add(this.fusionShinyIcon);
this.splicedIcon = this.scene.add.sprite(0, 0, "icon_spliced");
this.splicedIcon.setName("icon_spliced");
this.splicedIcon.setVisible(false);
this.splicedIcon.setOrigin(0, 0);
this.splicedIcon.setScale(0.5);
@ -130,31 +146,37 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.add(this.splicedIcon);
this.statusIndicator = this.scene.add.sprite(0, 0, "statuses");
this.statusIndicator.setName("icon_status");
this.statusIndicator.setVisible(false);
this.statusIndicator.setOrigin(0, 0);
this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5);
this.add(this.statusIndicator);
this.levelContainer = this.scene.add.container(player ? -41 : -50, player ? -10 : -5);
this.levelContainer.setName("container_level");
this.add(this.levelContainer);
const levelOverlay = this.scene.add.image(0, 0, "overlay_lv");
this.levelContainer.add(levelOverlay);
this.hpBar = this.scene.add.image(player ? -61 : -71, player ? -1 : 4.5, "overlay_hp");
this.hpBar.setName("hp_bar");
this.hpBar.setOrigin(0);
this.add(this.hpBar);
this.hpBarSegmentDividers = [];
this.levelNumbersContainer = this.scene.add.container(9.5, (this.scene as BattleScene).uiTheme ? 0 : -0.5);
this.levelNumbersContainer.setName("container_level");
this.levelContainer.add(this.levelNumbersContainer);
if (this.player) {
this.hpNumbersContainer = this.scene.add.container(-15, 10);
this.hpNumbersContainer.setName("container_hp");
this.add(this.hpNumbersContainer);
const expBar = this.scene.add.image(-98, 18, "overlay_exp");
expBar.setName("overlay_exp");
expBar.setOrigin(0);
this.add(expBar);
@ -173,10 +195,12 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
}
this.statsContainer = this.scene.add.container(0, 0);
this.statsContainer.setName("container_stats");
this.statsContainer.setAlpha(0);
this.add(this.statsContainer);
this.statsBox = this.scene.add.sprite(0, 0, `${this.getTextureName()}_stats`);
this.statsBox.setName("box_stats");
this.statsBox.setOrigin(1, 0.5);
this.statsContainer.add(this.statsBox);
@ -190,25 +214,30 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const statX = i > 1 ? this.statNumbers[i - 2].x + this.statNumbers[i - 2].width + 4 : -this.statsBox.width + 8;
const statY = -this.statsBox.height / 2 + 4 + (i < battleStatOrder.length - 1 ? (i % 2 ? 10 : 0) : 5);
const statLabel = this.scene.add.sprite(statX, statY, "pbinfo_stat", BattleStat[s]);
statLabel.setName("icon_stat_label_" + i.toString());
statLabel.setOrigin(0, 0);
statLabels.push(statLabel);
this.statValuesContainer.add(statLabel);
const statNumber = this.scene.add.sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", "3");
statNumber.setName("icon_stat_number_" + i.toString());
statNumber.setOrigin(0, 0);
this.statNumbers.push(statNumber);
this.statValuesContainer.add(statNumber);
});
this.type1Icon = this.scene.add.sprite(player ? -139 : -15, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type1`);
this.type1Icon.setName("icon_type_1");
this.type1Icon.setOrigin(0, 0);
this.add(this.type1Icon);
this.type2Icon = this.scene.add.sprite(player ? -139 : -15, player ? -1 : -2.5, `pbinfo_${player ? "player" : "enemy"}_type2`);
this.type2Icon.setName("icon_type_2");
this.type2Icon.setOrigin(0, 0);
this.add(this.type2Icon);
this.type3Icon = this.scene.add.sprite(player ? -154 : 0, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type`);
this.type3Icon.setName("icon_type_3");
this.type3Icon.setOrigin(0, 0);
this.add(this.type3Icon);
}
@ -266,6 +295,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const dexEntry = pokemon.scene.gameData.dexData[pokemon.species.speciesId];
this.ownedIcon.setVisible(!!dexEntry.caughtAttr);
const opponentPokemonDexAttr = pokemon.getDexAttr();
if (pokemon.scene.gameMode.isClassic) {
if (pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0) {
this.championRibbon.setVisible(true);
}
}
// Check if Player owns all genders and forms of the Pokemon
const missingDexAttrs = ((dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr);
@ -378,7 +412,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
if (boss !== this.boss) {
this.boss = boss;
[ this.nameText, this.genderText, this.teraIcon, this.splicedIcon, this.shinyIcon, this.ownedIcon, this.statusIndicator, this.levelContainer, this.statValuesContainer ].map(e => e.x += 48 * (boss ? -1 : 1));
[ this.nameText, this.genderText, this.teraIcon, this.splicedIcon, this.shinyIcon, this.ownedIcon, this.championRibbon, this.statusIndicator, this.levelContainer, this.statValuesContainer ].map(e => e.x += 48 * (boss ? -1 : 1));
this.hpBar.x += 38 * (boss ? -1 : 1);
this.hpBar.y += 2 * (this.boss ? -1 : 1);
this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`);
@ -402,6 +436,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width;
const divider = this.scene.add.rectangle(0, 0, 1, this.hpBar.height - (uiTheme ? 0 : 1), pokemon.bossSegmentIndex >= s ? 0xFFFFFF : 0x404040);
divider.setOrigin(0.5, 0);
divider.setName("hpBar_divider_" + s.toString());
this.add(divider);
this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer);

View File

@ -49,7 +49,7 @@ export default class EggListUiHandler extends MessageUiHandler {
this.iconAnimHandler = new PokemonIconAnimHandler();
this.iconAnimHandler.setup(this.scene);
this.eggNameText = addTextObject(this.scene, 8, 66, "", TextStyle.SUMMARY);
this.eggNameText = addTextObject(this.scene, 8, 68, "", TextStyle.SUMMARY);
this.eggNameText.setOrigin(0, 0);
this.eggListContainer.add(this.eggNameText);

View File

@ -230,7 +230,7 @@ export default class GameStatsUiHandler extends UiHandler {
}
}
(function () {
export function initStatsKeys() {
const statKeys = Object.keys(displayStats);
for (const key of statKeys) {
@ -256,4 +256,4 @@ export default class GameStatsUiHandler extends UiHandler {
(displayStats[key] as DisplayStat).label = Utils.toReadableString(`${splittableKey[0].toUpperCase()}${splittableKey.slice(1)}`);
}
}
})();
}

View File

@ -14,6 +14,7 @@ export default defineConfig(({ mode }) => {
}
},
threads: false,
trace: true,
environmentOptions: {
jsdom: {
resources: 'usable',