Merge branch 'pagefaultgames:main' into flingImplementation

This commit is contained in:
AyushBarik 2024-06-16 22:28:38 +05:30 committed by GitHub
commit cb3da4cc4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 2140 additions and 528 deletions

384
.dependency-cruiser.cjs Normal file
View File

@ -0,0 +1,384 @@
/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
forbidden: [
{
name: 'no-circular-at-runtime',
severity: 'warn',
comment:
'This dependency is part of a circular relationship. You might want to revise ' +
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
from: {},
to: {
circular: true,
viaOnly: {
dependencyTypesNot: [
'type-only'
]
}
}
},
{
name: 'no-orphans',
comment:
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
"add an exception for it in your dependency-cruiser configuration. By default " +
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
severity: 'warn',
from: {
orphan: true,
pathNot: [
'(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files
'[.]d[.]ts$', // TypeScript declaration files
'(^|/)tsconfig[.]json$', // TypeScript config
'(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs
]
},
to: {},
},
{
name: 'no-deprecated-core',
comment:
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
"bound to exist - node doesn't deprecate lightly.",
severity: 'warn',
from: {},
to: {
dependencyTypes: [
'core'
],
path: [
'^v8/tools/codemap$',
'^v8/tools/consarray$',
'^v8/tools/csvparser$',
'^v8/tools/logreader$',
'^v8/tools/profile_view$',
'^v8/tools/profile$',
'^v8/tools/SourceMap$',
'^v8/tools/splaytree$',
'^v8/tools/tickprocessor-driver$',
'^v8/tools/tickprocessor$',
'^node-inspect/lib/_inspect$',
'^node-inspect/lib/internal/inspect_client$',
'^node-inspect/lib/internal/inspect_repl$',
'^async_hooks$',
'^punycode$',
'^domain$',
'^constants$',
'^sys$',
'^_linklist$',
'^_stream_wrap$'
],
}
},
{
name: 'not-to-deprecated',
comment:
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
'version of that module, or find an alternative. Deprecated modules are a security risk.',
severity: 'warn',
from: {},
to: {
dependencyTypes: [
'deprecated'
]
}
},
{
name: 'no-non-package-json',
severity: 'error',
comment:
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
"in your package.json.",
from: {},
to: {
dependencyTypes: [
'npm-no-pkg',
'npm-unknown'
]
}
},
{
name: 'not-to-unresolvable',
comment:
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
'module: add it to your package.json. In all other cases you likely already know what to do.',
severity: 'error',
from: {},
to: {
couldNotResolve: true
}
},
{
name: 'no-duplicate-dep-types',
comment:
"Likely this module depends on an external ('npm') package that occurs more than once " +
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
"maintenance problems later on.",
severity: 'warn',
from: {},
to: {
moreThanOneDependencyType: true,
// as it's pretty common to have a type import be a type only import
// _and_ (e.g.) a devDependency - don't consider type-only dependency
// types for this rule
dependencyTypesNot: ["type-only"]
}
},
/* rules you might want to tweak for your specific situation: */
{
name: 'not-to-spec',
comment:
'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' +
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.',
severity: 'error',
from: {},
to: {
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
}
},
{
name: 'not-to-dev-dep',
severity: 'error',
comment:
"This module depends on an npm package from the 'devDependencies' section of your " +
'package.json. It looks like something that ships to production, though. To prevent problems ' +
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
'section of your package.json. If this module is development only - add it to the ' +
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
from: {
path: '^(src)',
pathNot: [
'[.](?:spec|test|setup|script)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$',
'src/test'
]
},
to: {
dependencyTypes: [
'npm-dev',
],
// type only dependencies are not a problem as they don't end up in the
// production code or are ignored by the runtime.
dependencyTypesNot: [
'type-only'
],
pathNot: [
'node_modules/@types/'
]
}
},
{
name: 'optional-deps-used',
severity: 'info',
comment:
"This module depends on an npm package that is declared as an optional dependency " +
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
"If you're using an optional dependency here by design - add an exception to your" +
"dependency-cruiser configuration.",
from: {},
to: {
dependencyTypes: [
'npm-optional'
]
}
},
{
name: 'peer-deps-used',
comment:
"This module depends on an npm package that is declared as a peer dependency " +
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
"add an exception to your dependency-cruiser configuration.",
severity: 'warn',
from: {},
to: {
dependencyTypes: [
'npm-peer'
]
}
}
],
options: {
/* Which modules not to follow further when encountered */
doNotFollow: {
/* path: an array of regular expressions in strings to match against */
path: ['node_modules']
},
/* Which modules to exclude */
// exclude : {
// /* path: an array of regular expressions in strings to match against */
// path: '',
// },
/* Which modules to exclusively include (array of regular expressions in strings)
dependency-cruiser will skip everything not matching this pattern
*/
// includeOnly : [''],
/* List of module systems to cruise.
When left out dependency-cruiser will fall back to the list of _all_
module systems it knows of. It's the default because it's the safe option
It might come at a performance penalty, though.
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
are widely used, you can limit the moduleSystems to those.
*/
// moduleSystems: ['cjs', 'es6'],
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
to open it on your online repo or `vscode://file/${process.cwd()}/` to
open it in visual studio code),
*/
// prefix: `vscode://file/${process.cwd()}/`,
/* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
true: also detect dependencies that only exist before typescript-to-javascript compilation
"specify": for each dependency identify whether it only exists before compilation or also after
*/
// tsPreCompilationDeps: false,
/* list of extensions to scan that aren't javascript or compile-to-javascript.
Empty by default. Only put extensions in here that you want to take into
account that are _not_ parsable.
*/
// extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
/* if true combines the package.jsons found from the module up to the base
folder the cruise is initiated from. Useful for how (some) mono-repos
manage dependencies & dependency definitions.
*/
// combinedDependencies: false,
/* if true leave symlinks untouched, otherwise use the realpath */
// preserveSymlinks: false,
/* TypeScript project file ('tsconfig.json') to use for
(1) compilation and
(2) resolution (e.g. with the paths property)
The (optional) fileName attribute specifies which file to take (relative to
dependency-cruiser's current working directory). When not provided
defaults to './tsconfig.json'.
*/
tsConfig: {
fileName: 'tsconfig.json'
},
/* Webpack configuration to use to get resolve options from.
The (optional) fileName attribute specifies which file to take (relative
to dependency-cruiser's current working directory. When not provided defaults
to './webpack.conf.js'.
The (optional) `env` and `arguments` attributes contain the parameters
to be passed if your webpack config is a function and takes them (see
webpack documentation for details)
*/
// webpackConfig: {
// fileName: 'webpack.config.js',
// env: {},
// arguments: {}
// },
/* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
for compilation
*/
// babelConfig: {
// fileName: '.babelrc',
// },
/* List of strings you have in use in addition to cjs/ es6 requires
& imports to declare module dependencies. Use this e.g. if you've
re-declared require, use a require-wrapper or use window.require as
a hack.
*/
// exoticRequireStrings: [],
/* options to pass on to enhanced-resolve, the package dependency-cruiser
uses to resolve module references to disk. The values below should be
suitable for most situations
If you use webpack: you can also set these in webpack.conf.js. The set
there will override the ones specified here.
*/
enhancedResolveOptions: {
/* What to consider as an 'exports' field in package.jsons */
exportsFields: ["exports"],
/* List of conditions to check for in the exports field.
Only works when the 'exportsFields' array is non-empty.
*/
conditionNames: ["import", "require", "node", "default", "types"],
/*
The extensions, by default are the same as the ones dependency-cruiser
can access (run `npx depcruise --info` to see which ones that are in
_your_ environment). If that list is larger than you need you can pass
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
up module resolution, which is the most expensive step.
*/
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
/* What to consider a 'main' field in package.json */
mainFields: ["module", "main", "types", "typings"],
/*
A list of alias fields in package.jsons
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
documentation
Defaults to an empty array (= don't use alias fields).
*/
// aliasFields: ["browser"],
},
reporterOptions: {
dot: {
/* pattern of modules that can be consolidated in the detailed
graphical dependency graph. The default pattern in this configuration
collapses everything in node_modules to one folder deep so you see
the external modules, but their innards.
*/
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)',
/* Options to tweak the appearance of your graph.See
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
for details and some examples. If you don't specify a theme
dependency-cruiser falls back to a built-in one.
*/
// theme: {
// graph: {
// /* splines: "ortho" gives straight lines, but is slow on big graphs
// splines: "true" gives bezier curves (fast, not as nice as ortho)
// */
// splines: "true"
// },
// }
},
archi: {
/* pattern of modules that can be consolidated in the high level
graphical dependency graph. If you use the high level graphical
dependency graph reporter (`archi`) you probably want to tweak
this collapsePattern to your situation.
*/
collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)',
/* Options to tweak the appearance of your graph. If you don't specify a
theme for 'archi' dependency-cruiser will use the one specified in the
dot section above and otherwise use the default one.
*/
// theme: { },
},
"text": {
"highlightFocused": true
},
}
}
};
// generated: dependency-cruiser@16.3.3 on 2024-06-13T23:26:36.169Z

1
.gitignore vendored
View File

@ -39,3 +39,4 @@ coverage
# Local Documentation # Local Documentation
/typedoc /typedoc
/dependency-graph.svg

13
dependency-graph.js Normal file
View File

@ -0,0 +1,13 @@
import { Graphviz } from "@hpcc-js/wasm/graphviz";
const graphviz = await Graphviz.load();
const inputFile = [];
for await (const chunk of process.stdin) {
inputFile.push(chunk);
}
const file = Buffer.concat(inputFile).toString("utf-8");
const svg = graphviz.dot(file, "svg");
process.stdout.write(svg);

717
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,9 @@
"test:silent": "vitest run --silent", "test:silent": "vitest run --silent",
"eslint": "eslint --fix .", "eslint": "eslint --fix .",
"eslint-ci": "eslint .", "eslint-ci": "eslint .",
"docs": "typedoc" "docs": "typedoc",
"depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^9.3.0",
@ -41,8 +43,11 @@
"vitest-canvas-mock": "^0.3.3" "vitest-canvas-mock": "^0.3.3"
}, },
"dependencies": { "dependencies": {
"@hpcc-js/wasm": "^2.16.2",
"@material/material-color-utilities": "^0.2.7", "@material/material-color-utilities": "^0.2.7",
"@types/jsdom": "^21.1.7",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dependency-cruiser": "^16.3.3",
"i18next": "^23.11.1", "i18next": "^23.11.1",
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^7.2.1",
"i18next-korean-postposition-processor": "^1.0.0", "i18next-korean-postposition-processor": "^1.0.0",

View File

@ -3413,6 +3413,9 @@ export class SuppressFieldAbilitiesAbAttr extends AbAttr {
export class AlwaysHitAbAttr extends AbAttr { } export class AlwaysHitAbAttr extends AbAttr { }
/** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */
export class IgnoreProtectOnContactAbAttr extends AbAttr { }
export class UncopiableAbilityAbAttr extends AbAttr { export class UncopiableAbilityAbAttr extends AbAttr {
constructor() { constructor() {
super(false); super(false);
@ -4672,7 +4675,7 @@ export function initAbilities() {
new Ability(Abilities.QUICK_DRAW, 8) new Ability(Abilities.QUICK_DRAW, 8)
.unimplemented(), .unimplemented(),
new Ability(Abilities.UNSEEN_FIST, 8) new Ability(Abilities.UNSEEN_FIST, 8)
.unimplemented(), .attr(IgnoreProtectOnContactAbAttr),
new Ability(Abilities.CURIOUS_MEDICINE, 8) new Ability(Abilities.CURIOUS_MEDICINE, 8)
.attr(PostSummonClearAllyStatsAbAttr), .attr(PostSummonClearAllyStatsAbAttr),
new Ability(Abilities.TRANSISTOR, 8) new Ability(Abilities.TRANSISTOR, 8)

View File

@ -1,3 +1,5 @@
import i18next from "i18next";
export enum BattleStat { export enum BattleStat {
ATK, ATK,
DEF, DEF,
@ -12,19 +14,19 @@ export enum BattleStat {
export function getBattleStatName(stat: BattleStat) { export function getBattleStatName(stat: BattleStat) {
switch (stat) { switch (stat) {
case BattleStat.ATK: case BattleStat.ATK:
return "Attack"; return i18next.t("pokemonInfo:Stat.ATK");
case BattleStat.DEF: case BattleStat.DEF:
return "Defense"; return i18next.t("pokemonInfo:Stat.DEF");
case BattleStat.SPATK: case BattleStat.SPATK:
return "Sp. Atk"; return i18next.t("pokemonInfo:Stat.SPATK");
case BattleStat.SPDEF: case BattleStat.SPDEF:
return "Sp. Def"; return i18next.t("pokemonInfo:Stat.SPDEF");
case BattleStat.SPD: case BattleStat.SPD:
return "Speed"; return i18next.t("pokemonInfo:Stat.SPD");
case BattleStat.ACC: case BattleStat.ACC:
return "Accuracy"; return i18next.t("pokemonInfo:Stat.ACC");
case BattleStat.EVA: case BattleStat.EVA:
return "Evasiveness"; return i18next.t("pokemonInfo:Stat.EVA");
default: default:
return "???"; return "???";
} }
@ -34,30 +36,30 @@ export function getBattleStatLevelChangeDescription(levels: integer, up: boolean
if (up) { if (up) {
switch (levels) { switch (levels) {
case 1: case 1:
return "rose"; return i18next.t("battle:statRose");
case 2: case 2:
return "sharply rose"; return i18next.t("battle:statSharplyRose");
case 3: case 3:
case 4: case 4:
case 5: case 5:
case 6: case 6:
return "rose drastically"; return i18next.t("battle:statRoseDrastically");
default: default:
return "won't go any higher"; return i18next.t("battle:statWontGoAnyHigher");
} }
} else { } else {
switch (levels) { switch (levels) {
case 1: case 1:
return "fell"; return i18next.t("battle:statFell");
case 2: case 2:
return "harshly fell"; return i18next.t("battle:statHarshlyFell");
case 3: case 3:
case 4: case 4:
case 5: case 5:
case 6: case 6:
return "severely fell"; return i18next.t("battle:statSeverelyFell");
default: default:
return "won't go any lower"; return i18next.t("battle:statWontGoAnyLower");
} }
} }
} }

View File

@ -1,12 +1,12 @@
import { Type } from "./type"; import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import beautify from "json-beautify";
import {pokemonEvolutions, SpeciesFormEvolution} from "./pokemon-evolutions"; import {pokemonEvolutions, SpeciesFormEvolution} from "./pokemon-evolutions";
import i18next from "i18next"; import i18next from "i18next";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
// import beautify from "json-beautify";
export function getBiomeName(biome: Biome | -1) { export function getBiomeName(biome: Biome | -1) {
if (biome === -1) { if (biome === -1) {
@ -7808,57 +7808,56 @@ export function initBiomes() {
// used in a commented code // used in a commented code
// eslint-disable-next-line @typescript-eslint/no-unused-vars // function outputPools() {
function outputPools() { // const pokemonOutput = {};
const pokemonOutput = {}; // const trainerOutput = {};
const trainerOutput = {};
for (const b of Object.keys(biomePokemonPools)) { // for (const b of Object.keys(biomePokemonPools)) {
const biome = Biome[b]; // const biome = Biome[b];
pokemonOutput[biome] = {}; // pokemonOutput[biome] = {};
trainerOutput[biome] = {}; // trainerOutput[biome] = {};
for (const t of Object.keys(biomePokemonPools[b])) { // for (const t of Object.keys(biomePokemonPools[b])) {
const tier = BiomePoolTier[t]; // const tier = BiomePoolTier[t];
pokemonOutput[biome][tier] = {}; // pokemonOutput[biome][tier] = {};
for (const tod of Object.keys(biomePokemonPools[b][t])) { // for (const tod of Object.keys(biomePokemonPools[b][t])) {
const timeOfDay = TimeOfDay[tod]; // const timeOfDay = TimeOfDay[tod];
pokemonOutput[biome][tier][timeOfDay] = []; // pokemonOutput[biome][tier][timeOfDay] = [];
for (const f of biomePokemonPools[b][t][tod]) { // for (const f of biomePokemonPools[b][t][tod]) {
if (typeof f === "number") { // if (typeof f === "number") {
pokemonOutput[biome][tier][timeOfDay].push(Species[f]); // pokemonOutput[biome][tier][timeOfDay].push(Species[f]);
} else { // } else {
const tree = {}; // const tree = {};
for (const l of Object.keys(f)) { // for (const l of Object.keys(f)) {
tree[l] = f[l].map(s => Species[s]); // tree[l] = f[l].map(s => Species[s]);
} // }
pokemonOutput[biome][tier][timeOfDay].push(tree); // pokemonOutput[biome][tier][timeOfDay].push(tree);
} // }
} // }
} // }
} // }
for (const t of Object.keys(biomeTrainerPools[b])) { // for (const t of Object.keys(biomeTrainerPools[b])) {
const tier = BiomePoolTier[t]; // const tier = BiomePoolTier[t];
trainerOutput[biome][tier] = []; // trainerOutput[biome][tier] = [];
for (const f of biomeTrainerPools[b][t]) { // for (const f of biomeTrainerPools[b][t]) {
trainerOutput[biome][tier].push(TrainerType[f]); // trainerOutput[biome][tier].push(TrainerType[f]);
} // }
} // }
} // }
console.log(beautify(pokemonOutput, null, 2, 180).replace(/( | (?:\{ "\d+": \[ )?| "(?:.*?)": \[ |(?:,|\[) (?:"\w+": \[ |(?:\{ )?"\d+": \[ )?)"(\w+)"(?= |,|\n)/g, "$1Species.$2").replace(/"(\d+)": /g, "$1: ").replace(/((?: )|(?:(?!\n) "(?:.*?)": \{) |\[(?: .*? )?\], )"(\w+)"/g, "$1[TimeOfDay.$2]").replace(/( )"(.*?)"/g, "$1[BiomePoolTier.$2]").replace(/( )"(.*?)"/g, "$1[Biome.$2]")); // console.log(beautify(pokemonOutput, null, 2, 180).replace(/( | (?:\{ "\d+": \[ )?| "(?:.*?)": \[ |(?:,|\[) (?:"\w+": \[ |(?:\{ )?"\d+": \[ )?)"(\w+)"(?= |,|\n)/g, "$1Species.$2").replace(/"(\d+)": /g, "$1: ").replace(/((?: )|(?:(?!\n) "(?:.*?)": \{) |\[(?: .*? )?\], )"(\w+)"/g, "$1[TimeOfDay.$2]").replace(/( )"(.*?)"/g, "$1[BiomePoolTier.$2]").replace(/( )"(.*?)"/g, "$1[Biome.$2]"));
console.log(beautify(trainerOutput, null, 2, 120).replace(/( | (?:\{ "\d+": \[ )?| "(?:.*?)": \[ |, (?:(?:\{ )?"\d+": \[ )?)"(.*?)"/g, "$1TrainerType.$2").replace(/"(\d+)": /g, "$1: ").replace(/( )"(.*?)"/g, "$1[BiomePoolTier.$2]").replace(/( )"(.*?)"/g, "$1[Biome.$2]")); // console.log(beautify(trainerOutput, null, 2, 120).replace(/( | (?:\{ "\d+": \[ )?| "(?:.*?)": \[ |, (?:(?:\{ )?"\d+": \[ )?)"(.*?)"/g, "$1TrainerType.$2").replace(/"(\d+)": /g, "$1: ").replace(/( )"(.*?)"/g, "$1[BiomePoolTier.$2]").replace(/( )"(.*?)"/g, "$1[Biome.$2]"));
} // }
/*for (let pokemon of allSpecies) { /*for (let pokemon of allSpecies) {
if (pokemon.speciesId >= Species.XERNEAS) if (pokemon.speciesId >= Species.XERNEAS)

View File

@ -9,7 +9,7 @@ import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr } from "./ability"; import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr } from "./ability";
import { allAbilities } from "./ability"; import { allAbilities } from "./ability";
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier"; import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
@ -560,6 +560,11 @@ export default class Move implements Localizable {
return true; return true;
} }
} }
case MoveFlags.IGNORE_PROTECT:
if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) &&
this.checkFlag(MoveFlags.MAKES_CONTACT, user, target)) {
return true;
}
} }
return !!(this.flags & flag); return !!(this.flags & flag);
@ -812,7 +817,8 @@ export class MoveEffectAttr extends MoveAttr {
*/ */
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) { canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp) return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp)
&& (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) || move.hasFlag(MoveFlags.IGNORE_PROTECT)); && (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) ||
move.checkFlag(MoveFlags.IGNORE_PROTECT, user, target));
} }
/** Applies move effects so long as they are able based on {@linkcode canApply} */ /** Applies move effects so long as they are able based on {@linkcode canApply} */
@ -1734,7 +1740,11 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(user.status?.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0; return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(user.status?.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0;
} }
} }
/**
* The following needs to be implemented for Thief
* "If the user faints due to the target's Ability (Rough Skin or Iron Barbs) or held Rocky Helmet, it cannot remove the target's held item."
* "If Knock Off causes a Pokémon with the Sticky Hold Ability to faint, it can now remove that Pokémon's held item."
*/
export class StealHeldItemChanceAttr extends MoveEffectAttr { export class StealHeldItemChanceAttr extends MoveEffectAttr {
private chance: number; private chance: number;
@ -1784,37 +1794,65 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
} }
} }
/**
* Removes a random held item (or berry) from target.
* Used for Incinerate and Knock Off.
* Not Implemented Cases: (Same applies for Thief)
* "If the user faints due to the target's Ability (Rough Skin or Iron Barbs) or held Rocky Helmet, it cannot remove the target's held item."
* "If Knock Off causes a Pokémon with the Sticky Hold Ability to faint, it can now remove that Pokémon's held item."
*/
export class RemoveHeldItemAttr extends MoveEffectAttr { export class RemoveHeldItemAttr extends MoveEffectAttr {
private chance: number;
constructor(chance: number) { /** Optional restriction for item pool to berries only i.e. Differentiating Incinerate and Knock Off */
private berriesOnly: boolean;
constructor(berriesOnly: boolean) {
super(false, MoveEffectTrigger.HIT); super(false, MoveEffectTrigger.HIT);
this.chance = chance; this.berriesOnly = berriesOnly;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { /**
return new Promise<boolean>(resolve => { *
const rand = Phaser.Math.RND.realInRange(0, 1); * @param user {@linkcode Pokemon} that used the move
if (rand >= this.chance) { * @param target Target {@linkcode Pokemon} that the moves applies to
return resolve(false); * @param move {@linkcode Move} that is used
* @param args N/A
* @returns {boolean} True if an item was removed
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia)
return false;
} }
const heldItems = this.getTargetHeldItems(target).filter(i => i.getTransferrable(false));
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft
if (cancelled.value === true) {
return false;
}
// Considers entire transferrable item pool by default (Knock Off). Otherwise berries only if specified (Incinerate).
let heldItems = this.getTargetHeldItems(target).filter(i => i.getTransferrable(false));
if (this.berriesOnly) {
heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer());
}
if (heldItems.length) { if (heldItems.length) {
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; const removedItem = heldItems[user.randSeedInt(heldItems.length)];
const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier, highestTier), 0);
const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); // Decrease item amount and update icon
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)]; !--removedItem.stackCount;
user.scene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => { target.scene.updateModifiers(target.isPlayer());
if (success) {
user.scene.queueMessage(getPokemonMessage(user, ` knocked off\n${target.name}'s ${stolenItem.type.name}!`)); if (this.berriesOnly) {
user.scene.queueMessage(getPokemonMessage(user, ` incinerated\n${target.name}'s ${removedItem.type.name}!`));
} else {
user.scene.queueMessage(getPokemonMessage(user, ` knocked off\n${target.name}'s ${removedItem.type.name}!`));
} }
resolve(success);
});
return;
} }
resolve(false); return true;
});
} }
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
@ -3221,17 +3259,6 @@ export class PresentPowerAttr extends VariablePowerAttr {
} }
} }
export class KnockOffPowerAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (target.getHeldItems().length > 0) {
(args[0] as Utils.NumberHolder).value *= 1.5;
return true;
}
return false;
}
}
export class WaterShurikenPowerAttr extends VariablePowerAttr { export class WaterShurikenPowerAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (user.species.speciesId === Species.GRENINJA && user.hasAbility(Abilities.BATTLE_BOND) && user.formIndex === 2) { if (user.species.speciesId === Species.GRENINJA && user.hasAbility(Abilities.BATTLE_BOND) && user.formIndex === 2) {
@ -5419,6 +5446,32 @@ export class LastResortAttr extends MoveAttr {
} }
} }
/**
* The move only works if the target has a transferable held item
* @extends MoveAttr
* @see {@linkcode getCondition}
*/
export class AttackedByItemAttr extends MoveAttr {
/**
* @returns the {@linkcode MoveConditionFunc} for this {@linkcode Move}
*/
getCondition(): MoveConditionFunc {
return (user: Pokemon, target: Pokemon, move: Move) => {
const heldItems = target.getHeldItems().filter(i => i.getTransferrable(true));
if (heldItems.length === 0) {
return false;
}
const itemName = heldItems[0]?.type?.name ?? "item";
const attackedByItemString = ` is about to be attacked by its ${itemName}!`;
target.scene.queueMessage(getPokemonMessage(target, attackedByItemString));
return true;
};
}
}
export class VariableTargetAttr extends MoveAttr { export class VariableTargetAttr extends MoveAttr {
private targetChangeFunc: (user: Pokemon, target: Pokemon, move: Move) => number; private targetChangeFunc: (user: Pokemon, target: Pokemon, move: Move) => number;
@ -6363,8 +6416,8 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true) .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true)
.condition((user, target, move) => !target.status), .condition((user, target, move) => !target.status),
new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3) new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
.attr(KnockOffPowerAttr) .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.getTransferrable(false)).length > 0 ? 1.5 : 1)
.partial(), .attr(RemoveHeldItemAttr, false),
new AttackMove(Moves.ENDEAVOR, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 3) new AttackMove(Moves.ENDEAVOR, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 3)
.attr(MatchHpAttr) .attr(MatchHpAttr)
.condition(failOnBossCondition), .condition(failOnBossCondition),
@ -6994,7 +7047,7 @@ export function initMoves() {
.attr(ForceSwitchOutAttr), .attr(ForceSwitchOutAttr),
new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5) new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
.target(MoveTarget.ALL_NEAR_ENEMIES) .target(MoveTarget.ALL_NEAR_ENEMIES)
.partial(), .attr(RemoveHeldItemAttr, true),
new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5) new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5)
.unimplemented(), .unimplemented(),
new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5) new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5)
@ -7893,8 +7946,8 @@ export function initMoves() {
new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8) new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8)
.partial(), .partial(),
new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8) new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8)
.makesContact(false) .attr(AttackedByItemAttr)
.partial(), .makesContact(false),
new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8) new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8)
.target(MoveTarget.ALL_NEAR_OTHERS) .target(MoveTarget.ALL_NEAR_OTHERS)
.unimplemented(), .unimplemented(),

View File

@ -1,4 +1,5 @@
import { BattleStat, getBattleStatName } from "./battle-stat"; import { BattleStat, getBattleStatName } from "./battle-stat";
import i18next from "i18next";
export enum TempBattleStat { export enum TempBattleStat {
ATK, ATK,
@ -12,7 +13,7 @@ export enum TempBattleStat {
export function getTempBattleStatName(tempBattleStat: TempBattleStat) { export function getTempBattleStatName(tempBattleStat: TempBattleStat) {
if (tempBattleStat === TempBattleStat.CRIT) { if (tempBattleStat === TempBattleStat.CRIT) {
return "critical-hit ratio"; return i18next.t("modifierType:TempBattleStatBoosterStatName.CRIT");
} }
return getBattleStatName(tempBattleStat as integer as BattleStat); return getBattleStatName(tempBattleStat as integer as BattleStat);
} }

View File

@ -262,6 +262,9 @@ export class EggHatchPhase extends Phase {
if (!this.canSkip || this.skipped) { if (!this.canSkip || this.skipped) {
return false; return false;
} }
if (this.eggCounterContainer.eggCountText?.data === undefined) {
return false;
}
this.skipped = true; this.skipped = true;
if (!this.hatched) { if (!this.hatched) {
this.doHatch(); this.doHatch();

View File

@ -1729,7 +1729,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
// Apply arena tags for conditional protection // Apply arena tags for conditional protection
if (!move.hasFlag(MoveFlags.IGNORE_PROTECT) && !move.isAllyTarget()) { if (!move.checkFlag(MoveFlags.IGNORE_PROTECT, source, this) && !move.isAllyTarget()) {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
this.scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority); this.scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority);
this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget); this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget);

View File

@ -61,5 +61,13 @@ export const battle: SimpleTranslationEntries = {
"useMove": "{{pokemonNameWithAffix}} setzt {{moveName}} ein!", "useMove": "{{pokemonNameWithAffix}} setzt {{moveName}} ein!",
"drainMessage": "{{pokemonName}} wurde Energie abgesaugt", "drainMessage": "{{pokemonName}} wurde Energie abgesaugt",
"regainHealth": "KP von {{pokemonName}} wurden wieder aufgefrischt!", "regainHealth": "KP von {{pokemonName}} wurden wieder aufgefrischt!",
"fainted": "{{pokemonNameWithAffix}} wurde besiegt!" "fainted": "{{pokemonNameWithAffix}} wurde besiegt!",
"statRose": "rose",
"statSharplyRose": "sharply rose",
"statRoseDrastically": "rose drastically",
"statWontGoAnyHigher": "won't go any higher",
"statFell": "fell",
"statHarshlyFell": "harshly fell",
"statSeverelyFell": "severely fell",
"statWontGoAnyLower": "won't go any lower",
} as const; } as const;

View File

@ -249,6 +249,20 @@ export const modifierType: ModifierTypeTranslationEntries = {
"x_accuracy": "X-Treffer", "x_accuracy": "X-Treffer",
"dire_hit": "X-Volltreffer", "dire_hit": "X-Volltreffer",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Angriff",
"DEF": "Verteidigung",
"SPATK": "Sp. Ang",
"SPDEF": "Sp. Vert",
"SPD": "Initiative",
"ACC": "Genauigkeit",
"CRIT": "Volltrefferquote",
"EVA": "Fluchtwert",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
"silk_scarf": "Seidenschal", "silk_scarf": "Seidenschal",
"black_belt": "Schwarzgurt", "black_belt": "Schwarzgurt",

View File

@ -14,6 +14,8 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEFshortened": "SpVert", "SPDEFshortened": "SpVert",
"SPD": "Initiative", "SPD": "Initiative",
"SPDshortened": "Init", "SPDshortened": "Init",
"ACC": "Accuracy",
"EVA": "Evasiveness"
}, },
Type: { Type: {

View File

@ -61,5 +61,13 @@ export const battle: SimpleTranslationEntries = {
"useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!",
"drainMessage": "{{pokemonName}} had its\nenergy drained!", "drainMessage": "{{pokemonName}} had its\nenergy drained!",
"regainHealth": "{{pokemonName}} regained\nhealth!", "regainHealth": "{{pokemonName}} regained\nhealth!",
"fainted": "{{pokemonNameWithAffix}} fainted!" "fainted": "{{pokemonNameWithAffix}} fainted!",
"statRose": "rose",
"statSharplyRose": "sharply rose",
"statRoseDrastically": "rose drastically",
"statWontGoAnyHigher": "won't go any higher",
"statFell": "fell",
"statHarshlyFell": "harshly fell",
"statSeverelyFell": "severely fell",
"statWontGoAnyLower": "won't go any lower",
} as const; } as const;

View File

@ -248,6 +248,19 @@ export const modifierType: ModifierTypeTranslationEntries = {
"x_accuracy": "X Accuracy", "x_accuracy": "X Accuracy",
"dire_hit": "Dire Hit", "dire_hit": "Dire Hit",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Attack",
"DEF": "Defense",
"SPATK": "Sp. Atk",
"SPDEF": "Sp. Def",
"SPD": "Speed",
"ACC": "Accuracy",
"CRIT": "Critical Hit Ratio",
"EVA": "Evasiveness",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
"silk_scarf": "Silk Scarf", "silk_scarf": "Silk Scarf",
"black_belt": "Black Belt", "black_belt": "Black Belt",

View File

@ -13,7 +13,9 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEF": "Sp. Def", "SPDEF": "Sp. Def",
"SPDEFshortened": "SpDef", "SPDEFshortened": "SpDef",
"SPD": "Speed", "SPD": "Speed",
"SPDshortened": "Spd" "SPDshortened": "Spd",
"ACC": "Accuracy",
"EVA": "Evasiveness"
}, },
Type: { Type: {

View File

@ -61,5 +61,13 @@ export const battle: SimpleTranslationEntries = {
"useMove": "¡{{pokemonNameWithAffix}} usó {{moveName}}!", "useMove": "¡{{pokemonNameWithAffix}} usó {{moveName}}!",
"drainMessage": "¡{{pokemonName}} tuvo su\nenergía absorbida!", "drainMessage": "¡{{pokemonName}} tuvo su\nenergía absorbida!",
"regainHealth": "¡{{pokemonName}} recuperó\nPS!", "regainHealth": "¡{{pokemonName}} recuperó\nPS!",
"fainted": "¡{{pokemonNameWithAffix}} se debilitó!" "fainted": "¡{{pokemonNameWithAffix}} se debilitó!",
"statRose": "rose",
"statSharplyRose": "sharply rose",
"statRoseDrastically": "rose drastically",
"statWontGoAnyHigher": "won't go any higher",
"statFell": "fell",
"statHarshlyFell": "harshly fell",
"statSeverelyFell": "severely fell",
"statWontGoAnyLower": "won't go any lower",
} as const; } as const;

View File

@ -248,6 +248,18 @@ export const modifierType: ModifierTypeTranslationEntries = {
"x_accuracy": "Precisión X", "x_accuracy": "Precisión X",
"dire_hit": "Crítico X", "dire_hit": "Crítico X",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Attack",
"DEF": "Defense",
"SPATK": "Sp. Atk",
"SPDEF": "Sp. Def",
"SPD": "Speed",
"ACC": "Accuracy",
"CRIT": "Critical Hit Ratio",
"EVA": "Evasiveness",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
"silk_scarf": "Pañuelo Seda", "silk_scarf": "Pañuelo Seda",
"black_belt": "Cinturón Negro", "black_belt": "Cinturón Negro",

View File

@ -13,7 +13,9 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEF": "Def. Esp.", "SPDEF": "Def. Esp.",
"SPDEFshortened": "DefEsp", "SPDEFshortened": "DefEsp",
"SPD": "Velocidad", "SPD": "Velocidad",
"SPDshortened": "Veloc." "SPDshortened": "Veloc.",
"ACC": "Accuracy",
"EVA": "Evasiveness"
}, },
Type: { Type: {

View File

@ -61,5 +61,13 @@ export const battle: SimpleTranslationEntries = {
"useMove": "{{pokemonNameWithAffix}} utilise\n{{moveName}} !", "useMove": "{{pokemonNameWithAffix}} utilise\n{{moveName}} !",
"drainMessage": "Lénergie de {{pokemonName}}\nest drainée !", "drainMessage": "Lénergie de {{pokemonName}}\nest drainée !",
"regainHealth": "{{pokemonName}} récupère\ndes PV !", "regainHealth": "{{pokemonName}} récupère\ndes PV !",
"fainted": "{{pokemonNameWithAffix}}\nest K.O. !" "fainted": "{{pokemonNameWithAffix}}\nest K.O. !",
"statRose": "rose",
"statSharplyRose": "sharply rose",
"statRoseDrastically": "rose drastically",
"statWontGoAnyHigher": "won't go any higher",
"statFell": "fell",
"statHarshlyFell": "harshly fell",
"statSeverelyFell": "severely fell",
"statWontGoAnyLower": "won't go any lower",
} as const; } as const;

View File

@ -248,6 +248,19 @@ export const modifierType: ModifierTypeTranslationEntries = {
"x_accuracy": "Précision +", "x_accuracy": "Précision +",
"dire_hit": "Muscle +", "dire_hit": "Muscle +",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Attaque",
"DEF": "Défense",
"SPATK": "Atq. Spé.",
"SPDEF": "Déf. Spé.",
"SPD": "Vitesse",
"ACC": "Précision",
"CRIT": "Taux de critique",
"EVA": "Esquive",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
"silk_scarf": "Mouchoir Soie", "silk_scarf": "Mouchoir Soie",
"black_belt": "Ceinture Noire", "black_belt": "Ceinture Noire",

View File

@ -13,7 +13,9 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEF": "Déf. Spé.", "SPDEF": "Déf. Spé.",
"SPDEFshortened": "DéfSp", "SPDEFshortened": "DéfSp",
"SPD": "Vitesse", "SPD": "Vitesse",
"SPDshortened": "Vit" "SPDshortened": "Vit",
"ACC": "Accuracy",
"EVA": "Evasiveness"
}, },
Type: { Type: {

View File

@ -61,5 +61,13 @@ export const battle: SimpleTranslationEntries = {
"useMove": "{{pokemonNameWithAffix}} usa {{moveName}}!", "useMove": "{{pokemonNameWithAffix}} usa {{moveName}}!",
"drainMessage": "Viene prelevata energia\n da{{pokemonName}}!", "drainMessage": "Viene prelevata energia\n da{{pokemonName}}!",
"regainHealth": "{{pokemonName}} ha rigenerato\npunti salute!", "regainHealth": "{{pokemonName}} ha rigenerato\npunti salute!",
"fainted": "{{pokemonNameWithAffix}} non è più in\ngrado di combattere!" "fainted": "{{pokemonNameWithAffix}} non è più in\ngrado di combattere!",
"statRose": "rose",
"statSharplyRose": "sharply rose",
"statRoseDrastically": "rose drastically",
"statWontGoAnyHigher": "won't go any higher",
"statFell": "fell",
"statHarshlyFell": "harshly fell",
"statSeverelyFell": "severely fell",
"statWontGoAnyLower": "won't go any lower",
} as const; } as const;

View File

@ -248,6 +248,19 @@ export const modifierType: ModifierTypeTranslationEntries = {
"x_accuracy": "Precisione X", "x_accuracy": "Precisione X",
"dire_hit": "Supercolpo", "dire_hit": "Supercolpo",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Attack",
"DEF": "Defense",
"SPATK": "Sp. Atk",
"SPDEF": "Sp. Def",
"SPD": "Speed",
"ACC": "Accuracy",
"CRIT": "Critical Hit Ratio",
"EVA": "Evasiveness",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
"silk_scarf": "Sciarpa seta", "silk_scarf": "Sciarpa seta",
"black_belt": "Cinturanera", "black_belt": "Cinturanera",

View File

@ -1,10 +1,10 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n"; import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const partyUiHandler: SimpleTranslationEntries = { export const partyUiHandler: SimpleTranslationEntries = {
"SEND_OUT": "Send Out", "SEND_OUT": "Manda in campo",
"SUMMARY": "Summary", "SUMMARY": "Sommario",
"CANCEL": "Cancel", "CANCEL": "Annulla",
"RELEASE": "Release", "RELEASE": "Rilascia",
"APPLY": "Apply", "APPLY": "Applica",
"TEACH": "Teach" "TEACH": "Insegna"
} as const; } as const;

View File

@ -6,5 +6,5 @@ export const pokeball: SimpleTranslationEntries = {
"ultraBall": "Ultra Ball", "ultraBall": "Ultra Ball",
"rogueBall": "Rogue Ball", "rogueBall": "Rogue Ball",
"masterBall": "Master Ball", "masterBall": "Master Ball",
"luxuryBall": "Chich Ball", "luxuryBall": "Chic Ball",
} as const; } as const;

View File

@ -1,11 +1,11 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n"; import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const pokemonInfoContainer: SimpleTranslationEntries = { export const pokemonInfoContainer: SimpleTranslationEntries = {
"moveset": "Moveset", "moveset": "Set di mosse",
"gender": "Gender:", "gender": "Genere:",
"ability": "Ability:", "ability": "Abilità:",
"nature": "Nature:", "nature": "Natura:",
"epic": "Epic", "epic": "Epico",
"rare": "Rare", "rare": "Raro",
"common": "Common" "common": "Comune"
} as const; } as const;

View File

@ -13,7 +13,9 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEF": "Dif. Sp.", "SPDEF": "Dif. Sp.",
"SPDEFshortened": "DifSp", "SPDEFshortened": "DifSp",
"SPD": "Velocità", "SPD": "Velocità",
"SPDshortened": "Vel" "SPDshortened": "Vel",
"ACC": "Precisione",
"EVA": "Elusione"
}, },
Type: { Type: {

View File

@ -1,9 +1,9 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n"; import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const saveSlotSelectUiHandler: SimpleTranslationEntries = { export const saveSlotSelectUiHandler: SimpleTranslationEntries = {
"overwriteData": "Overwrite the data in the selected slot?", "overwriteData": "Sovrascrivere i dati nello slot selezionato?",
"loading": "Loading...", "loading": "Caricamento...",
"wave": "Wave", "wave": "Onda",
"lv": "Lv", "lv": "Lv",
"empty": "Vuoto", "empty": "Vuoto",
} as const; } as const;

View File

@ -5,10 +5,10 @@ export const splashMessages: SimpleTranslationEntries = {
"joinTheDiscord": "Entra nel Discord!", "joinTheDiscord": "Entra nel Discord!",
"infiniteLevels": "Livelli Infiniti!", "infiniteLevels": "Livelli Infiniti!",
"everythingStacks": "Tutto si impila!", "everythingStacks": "Tutto si impila!",
"optionalSaveScumming": "Salvataggio Facoltativo!", "optionalSaveScumming": "Salvataggi Facoltativi!",
"biomes": "35 Biomi!", "biomes": "35 Biomi!",
"openSource": "Open Source!", "openSource": "Open Source!",
"playWithSpeed": "Gioca con il 5x di Velocità!", "playWithSpeed": "Gioca con la velocità aumentata di 5 volte!",
"liveBugTesting": "Test dei Bug in Tempo Reale!", "liveBugTesting": "Test dei Bug in Tempo Reale!",
"heavyInfluence": "Influenzato da RoR2!", "heavyInfluence": "Influenzato da RoR2!",
"pokemonRiskAndPokemonRain": "Pokémon Risk e Pokémon Rain!", "pokemonRiskAndPokemonRain": "Pokémon Risk e Pokémon Rain!",

View File

@ -7,38 +7,38 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n";
*/ */
export const starterSelectUiHandler: SimpleTranslationEntries = { export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam":"Vuoi iniziare con questi Pokémon?", "confirmStartTeam":"Vuoi iniziare con questi Pokémon?",
"gen1": "I", "gen1": "",
"gen2": "II", "gen2": "",
"gen3": "III", "gen3": "",
"gen4": "IV", "gen4": "",
"gen5": "V", "gen5": "",
"gen6": "VI", "gen6": "",
"gen7": "VII", "gen7": "",
"gen8": "VIII", "gen8": "",
"gen9": "IX", "gen9": "",
"growthRate": "Vel. Crescita:", "growthRate": "Vel. Crescita:",
"ability": "Abilità:", "ability": "Abilità:",
"passive": "Passiva:", "passive": "Passiva:",
"nature": "Natura:", "nature": "Natura:",
"eggMoves": "Mosse delle uova", "eggMoves": "Mosse da uova",
"start": "Inizia", "start": "Inizia",
"addToParty": "Aggiungi al Gruppo", "addToParty": "Aggiungi al gruppo",
"toggleIVs": "Vedi/Nascondi IV", "toggleIVs": "Vedi/Nascondi IV",
"manageMoves": "Gestisci Mosse", "manageMoves": "Gestisci mosse",
"useCandies": "Usa Caramelle", "useCandies": "Usa caramelle",
"selectMoveSwapOut": "Seleziona una mossa da scambiare.", "selectMoveSwapOut": "Seleziona una mossa da scambiare.",
"selectMoveSwapWith": "Seleziona una mossa da scambiare con", "selectMoveSwapWith": "Seleziona una mossa da scambiare con",
"unlockPassive": "Sblocca Passiva", "unlockPassive": "Sblocca passiva",
"reduceCost": "Riduci Costo", "reduceCost": "Riduci costo",
"cycleShiny": ": Shiny", "cycleShiny": ": Shiny",
"cycleForm": ": Forma", "cycleForm": ": Forma",
"cycleGender": ": Sesso", "cycleGender": ": Genere",
"cycleAbility": ": Abilità", "cycleAbility": ": Abilità",
"cycleNature": ": Natura", "cycleNature": ": Natura",
"cycleVariant": ": Variante", "cycleVariant": ": Variante",
"enablePassive": "Attiva Passiva", "enablePassive": "Attiva passiva",
"disablePassive": "Disattiva Passiva", "disablePassive": "Disattiva passiva",
"locked": "Bloccato", "locked": "Bloccato",
"disabled": "Disabilitato", "disabled": "Disabilitato",
"uncaught": "Non Catturato" "uncaught": "Non catturato"
}; };

View File

@ -6,258 +6,258 @@ export const titles: SimpleTranslationEntries = {
"elite_four_female": "Superquattro", "elite_four_female": "Superquattro",
"gym_leader": "Capopalestra", "gym_leader": "Capopalestra",
"gym_leader_female": "Capopalestra", "gym_leader_female": "Capopalestra",
"gym_leader_double": "Gym Leader Duo", "gym_leader_double": "Duo Capopalestra",
"champion": "Campione", "champion": "Campione",
"champion_female": "Champion", "champion_female": "Campionessa",
"champion_double": "Champion Duo", "champion_double": "Duo Campioni",
"rival": "Rivale", "rival": "Rivale",
"professor": "Professore", "professor": "Professore",
"frontier_brain": "Asso Lotta", "frontier_brain": "Asso lotta",
// Maybe if we add the evil teams we can add "Team Rocket" and "Team Aqua" etc. here as well as "Team Rocket Boss" and "Team Aqua Admin" etc. // Maybe if we add the evil teams we can add "Team Rocket" and "Team Aqua" etc. here as well as "Team Rocket Boss" and "Team Aqua Admin" etc.
} as const; } as const;
// Titles of trainers like "Youngster" or "Lass" // Titles of trainers like "Youngster" or "Lass"
export const trainerClasses: SimpleTranslationEntries = { export const trainerClasses: SimpleTranslationEntries = {
"ace_trainer": "Ace Trainer", "ace_trainer": "Fantallenatore",
"ace_trainer_female": "Ace Trainer", "ace_trainer_female": "Fantallenatrice",
"ace_duo": "Ace Duo", "ace_duo": "Fantallenatori",
"artist": "Artist", "artist": "Artista",
"artist_female": "Artist", "artist_female": "Artista",
"backers": "Backers", "backers": "Fan",
"backpacker": "Backpacker", "backpacker": "Giramondo",
"backpacker_female": "Backpacker", "backpacker_female": "Giramondo",
"backpackers": "Backpackers", "backpackers": "Giramondo",
"baker": "Baker", "baker": "Panettiera",
"battle_girl": "Battle Girl", "battle_girl": "Combat Girl",
"beauty": "Beauty", "beauty": "Bellezza",
"beginners": "Beginners", "beginners": "Novellini",
"biker": "Biker", "biker": "Centauro",
"black_belt": "Black Belt", "black_belt": "Cinturanera",
"breeder": "Breeder", "breeder": "Allevapokémon",
"breeder_female": "Breeder", "breeder_female": "Allevapokémon",
"breeders": "Breeders", "breeders": "Allevapokémon",
"clerk": "Clerk", "clerk": "Affarista",
"clerk_female": "Clerk", "clerk_female": "Donna in carriera",
"colleagues": "Colleagues", "colleagues": "Soci in affari",
"crush_kin": "Crush Kin", "crush_kin": "Duo Lotta",
"cyclist": "Cyclist", "cyclist": "Ciclista",
"cyclist_female": "Cyclist", "cyclist_female": "Ciclista",
"cyclists": "Cyclists", "cyclists": "Ciclisti",
"dancer": "Dancer", "dancer": "Ballerino",
"dancer_female": "Dancer", "dancer_female": "Ballerina",
"depot_agent": "Depot Agent", "depot_agent": "Ferroviario",
"doctor": "Doctor", "doctor": "Medico",
"doctor_female": "Doctor", "doctor_female": "Medica",
"firebreather": "Firebreather", "firebreather": "Mangiafuoco",
"fisherman": "Fisherman", "fisherman": "Pescatore",
"fisherman_female": "Fisherman", "fisherman_female": "Pescatrice",
"gentleman": "Gentleman", "gentleman": "Gentiluomo",
"guitarist": "Guitarist", "guitarist": "Chitarrista",
"guitarist_female": "Guitarist", "guitarist_female": "Chitarrista",
"harlequin": "Harlequin", "harlequin": "Buffone",
"hiker": "Hiker", "hiker": "Montanaro",
"hooligans": "Hooligans", "hooligans": "Teppisti",
"hoopster": "Hoopster", "hoopster": "Cestista",
"infielder": "Infielder", "infielder": "Battitore",
"janitor": "Janitor", "janitor": "Netturbino",
"lady": "Lady", "lady": "Lady",
"lass": "Lass", "lass": "Pupa",
"linebacker": "Linebacker", "linebacker": "Quarterback",
"maid": "Maid", "maid": "Domestica",
"madame": "Madame", "madame": "Madame",
"medical_team": "Medical Team", "medical_team": "Équipe medica",
"musician": "Musician", "musician": "Musicista",
"hex_maniac": "Hex Maniac", "hex_maniac": "Streghetta",
"nurse": "Nurse", "nurse": "Infermiera",
"nursery_aide": "Nursery Aide", "nursery_aide": "Maestrina",
"officer": "Officer", "officer": "Guardia",
"parasol_lady": "Parasol Lady", "parasol_lady": "Ombrellina",
"pilot": "Pilot", "pilot": "Pilota",
"pokéfan": "PokéFan", "pokéfan": "PokéFan",
"pokéfan_female": "PokéFan", "pokéfan_female": "PokéFan",
"pokéfan_family": "Poké Fan Family", "pokéfan_family": "Famiglia PokéFan",
"preschooler": "Preschooler", "preschooler": "Bimbo",
"preschooler_female": "Preschooler", "preschooler_female": "Bimba",
"preschoolers": "Preschoolers", "preschoolers": "Bimbi",
"psychic": "Psychic", "psychic": "Sensitivo",
"psychic_female": "Psychic", "psychic_female": "Sensitiva",
"psychics": "Psychics", "psychics": "Sensitivi",
"pokémon_ranger": "Pokémon Ranger", "pokémon_ranger": "Pokémon Ranger",
"pokémon_ranger_female": "Pokémon Ranger", "pokémon_ranger_female": "Pokémon Ranger",
"pokémon_rangers": "Pokémon Ranger", "pokémon_rangers": "Duo Ranger",
"ranger": "Ranger", "ranger": "Ranger",
"restaurant_staff": "Restaurant Staff", "restaurant_staff": "Personale del ristorante",
"rich": "Rich", "rich": "Ricco",
"rich_female": "Rich", "rich_female": "Ricca",
"rich_boy": "Rich Boy", "rich_boy": "Elegantone",
"rich_couple": "Rich Couple", "rich_couple": "Ricchi",
"rich_kid": "Rich Kid", "rich_kid": "Bimbo ricco",
"rich_kid_female": "Rich Kid", "rich_kid_female": "Bimba ricca",
"rich_kids": "Rich Kids", "rich_kids": "Bimbi ricchi",
"roughneck": "Roughneck", "roughneck": "Zuccapelata",
"sailor": "Sailor", "sailor": "Marinaio",
"scientist": "Scientist", "scientist": "Scienziato",
"scientist_female": "Scientist", "scientist_female": "Scienziata",
"scientists": "Scientists", "scientists": "Scienziati",
"smasher": "Smasher", "smasher": "Tennista",
"snow_worker": "Snow Worker", "snow_worker": "Lavoratore",
"snow_worker_female": "Snow Worker", "snow_worker_female": "Lavoratrice",
"striker": "Striker", "striker": "Calciatore",
"school_kid": "School Kid", "school_kid": "Alunno",
"school_kid_female": "School Kid", "school_kid_female": "Alunna",
"school_kids": "School Kids", "school_kids": "Alunni",
"swimmer": "Swimmer", "swimmer": "Nuotatore",
"swimmer_female": "Swimmer", "swimmer_female": "Nuotatrice",
"swimmers": "Swimmers", "swimmers": "Nuotatori",
"twins": "Twins", "twins": "Gemelli",
"veteran": "Veteran", "veteran": "Veterano",
"veteran_female": "Veteran", "veteran_female": "Veterana",
"veteran_duo": "Veteran Duo", "veteran_duo": "Veterani",
"waiter": "Waiter", "waiter": "Cameriere",
"waitress": "Waitress", "waitress": "Cameriera",
"worker": "Worker", "worker": "Operaio",
"worker_female": "Worker", "worker_female": "Lavoratrice",
"workers": "Workers", "workers": "Lavoratori",
"youngster": "Youngster" "youngster": "Bullo"
} as const; } as const;
// Names of special trainers like gym leaders, elite four, and the champion // Names of special trainers like gym leaders, elite four, and the champion
export const trainerNames: SimpleTranslationEntries = { export const trainerNames: SimpleTranslationEntries = {
"brock": "Brock", "brock": "Brock",
"misty": "Misty", "misty": "Misty",
"lt_surge": "Lt Surge", "lt_surge": "Lt. Surge",
"erika": "Erika", "erika": "Erika",
"janine": "Janine", "janine": "Nina",
"sabrina": "Sabrina", "sabrina": "Sabrina",
"blaine": "Blaine", "blaine": "Blaine",
"giovanni": "Giovanni", "giovanni": "Giovanni",
"falkner": "Falkner", "falkner": "Valerio",
"bugsy": "Bugsy", "bugsy": "Raffaello",
"whitney": "Whitney", "whitney": "Chiara",
"morty": "Morty", "morty": "Angelo",
"chuck": "Chuck", "chuck": "Furio",
"jasmine": "Jasmine", "jasmine": "Jasmine",
"pryce": "Pryce", "pryce": "Alfredo",
"clair": "Clair", "clair": "Sandra",
"roxanne": "Roxanne", "roxanne": "Petra",
"brawly": "Brawly", "brawly": "Rudi",
"wattson": "Wattson", "wattson": "Walter",
"flannery": "Flannery", "flannery": "Fiammetta",
"norman": "Norman", "norman": "Norman",
"winona": "Winona", "winona": "Alice",
"tate": "Tate", "tate": "Tell",
"liza": "Liza", "liza": "Pat",
"juan": "Juan", "juan": "Rodolfo",
"roark": "Roark", "roark": "Pedro",
"gardenia": "Gardenia", "gardenia": "Gardenia",
"maylene": "Maylene", "maylene": "Marzia",
"crasher_wake": "Crasher Wake", "crasher_wake": "Omar",
"fantina": "Fantina", "fantina": "Fannie",
"byron": "Byron", "byron": "Ferruccio",
"candice": "Candice", "candice": "Bianca",
"volkner": "Volkner", "volkner": "Corrado",
"cilan": "Cilan", "cilan": "Spighetto",
"chili": "Chili", "chili": "Chicco",
"cress": "Cress", "cress": "Maisello",
"cheren": "Cheren", "cheren": "Komor",
"lenora": "Lenora", "lenora": "Aloé",
"roxie": "Roxie", "roxie": "Velia",
"burgh": "Burgh", "burgh": "Artemisio",
"elesa": "Elesa", "elesa": "Camelia",
"clay": "Clay", "clay": "Rafan",
"skyla": "Skyla", "skyla": "Anemone",
"brycen": "Brycen", "brycen": "Silvestro",
"drayden": "Drayden", "drayden": "Aristide",
"marlon": "Marlon", "marlon": "Ciprian",
"viola": "Viola", "viola": "Violetta",
"grant": "Grant", "grant": "Lino",
"korrina": "Korrina", "korrina": "Ornella",
"ramos": "Ramos", "ramos": "Amur",
"clemont": "Clemont", "clemont": "Lem",
"valerie": "Valerie", "valerie": "Valérie",
"olympia": "Olympia", "olympia": "Astra",
"wulfric": "Wulfric", "wulfric": "Edel",
"milo": "Milo", "milo": "Yarrow",
"nessa": "Nessa", "nessa": "Azzurra",
"kabu": "Kabu", "kabu": "Kabu",
"bea": "Bea", "bea": "Fabia",
"allister": "Allister", "allister": "Onion",
"opal": "Opal", "opal": "Poppy",
"bede": "Bede", "bede": "Beet",
"gordie": "Gordie", "gordie": "Milo",
"melony": "Melony", "melony": "Melania",
"piers": "Piers", "piers": "Ginepro",
"marnie": "Marnie", "marnie": "Mary",
"raihan": "Raihan", "raihan": "Raihan",
"katy": "Katy", "katy": "Aceria",
"brassius": "Brassius", "brassius": "Brassius",
"iono": "Iono", "iono": "Kissara",
"kofu": "Kofu", "kofu": "Algaro",
"larry": "Larry", "larry": "Ubaldo",
"ryme": "Ryme", "ryme": "Ryme",
"tulip": "Tulip", "tulip": "Tulipa",
"grusha": "Grusha", "grusha": "Grusha",
"lorelei": "Lorelei", "lorelei": "Lorelei",
"bruno": "Bruno", "bruno": "Bruno",
"agatha": "Agatha", "agatha": "Agatha",
"lance": "Lance", "lance": "Lance",
"will": "Will", "will": "Pino",
"koga": "Koga", "koga": "Koga",
"karen": "Karen", "karen": "Karen",
"sidney": "Sidney", "sidney": "Fosco",
"phoebe": "Phoebe", "phoebe": "Ester",
"glacia": "Glacia", "glacia": "Frida",
"drake": "Drake", "drake": "Drake",
"aaron": "Aaron", "aaron": "Aaron",
"bertha": "Bertha", "bertha": "Terrie",
"flint": "Flint", "flint": "Vulcano",
"lucian": "Lucian", "lucian": "Luciano",
"shauntal": "Shauntal", "shauntal": "Antemia",
"marshal": "Marshal", "marshal": "Marzio",
"grimsley": "Grimsley", "grimsley": "Mirton",
"caitlin": "Caitlin", "caitlin": "Catlina",
"malva": "Malva", "malva": "Malva",
"siebold": "Siebold", "siebold": "Narciso",
"wikstrom": "Wikstrom", "wikstrom": "Timeos",
"drasna": "Drasna", "drasna": "Lila",
"hala": "Hala", "hala": "Hala",
"molayne": "Molayne", "molayne": "Tapso",
"olivia": "Olivia", "olivia": "Olive",
"acerola": "Acerola", "acerola": "Mapli",
"kahili": "Kahili", "kahili": "Kahili",
"rika": "Rika", "rika": "Rika",
"poppy": "Poppy", "poppy": "Poppy",
"hassel": "Hassel", "hassel": "Oranzio",
"crispin": "Crispin", "crispin": "Piros",
"amarys": "Amarys", "amarys": "Erin",
"lacey": "Lacey", "lacey": "Rupi",
"drayton": "Drayton", "drayton": "Aris",
"blue": "Blue", "blue": "Blu",
"red": "Red", "red": "Rosso",
"steven": "Steven", "steven": "Rocco",
"wallace": "Wallace", "wallace": "Adriano",
"cynthia": "Cynthia", "cynthia": "Camilla",
"alder": "Alder", "alder": "Nardo",
"iris": "Iris", "iris": "Iris",
"diantha": "Diantha", "diantha": "Diantha",
"hau": "Hau", "hau": "Hau",
"geeta": "Geeta", "geeta": "Alisma",
"nemona": "Nemona", "nemona": "Nemi",
"kieran": "Kieran", "kieran": "Riben",
"leon": "Leon", "leon": "Dandel",
"rival": "Finn", "rival": "Finn",
"rival_female": "Ivy", "rival_female": "Ivy",
// Double Names // Double Names
"blue_red_double": "Blue & Red", "blue_red_double": "Blu & Rosso",
"red_blue_double": "Red & Blue", "red_blue_double": "Rosso & Blu",
"tate_liza_double": "Tate & Liza", "tate_liza_double": "Tell & Pat",
"liza_tate_double": "Liza & Tate", "liza_tate_double": "Pat & Tell",
"steven_wallace_double": "Steven & Wallace", "steven_wallace_double": "Rocco & Adriano",
"wallace_steven_double": "Wallace & Steven", "wallace_steven_double": "Adriano & Rocco",
"alder_iris_double": "Alder & Iris", "alder_iris_double": "Nardo & Iris",
"iris_alder_double": "Iris & Alder", "iris_alder_double": "Iris & Nardo",
"marnie_piers_double": "Marnie & Piers", "marnie_piers_double": "Mary & Ginepro",
"piers_marnie_double": "Piers & Marnie", "piers_marnie_double": "Ginepro & Mary",
} as const; } as const;

View File

@ -2,10 +2,10 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const voucher: SimpleTranslationEntries = { export const voucher: SimpleTranslationEntries = {
"vouchers": "Vouchers", "vouchers": "Vouchers",
"eggVoucher": "Egg Voucher", "eggVoucher": "Voucher uovo",
"eggVoucherPlus": "Egg Voucher Plus", "eggVoucherPlus": "Voucher uovo plus",
"eggVoucherPremium": "Egg Voucher Premium", "eggVoucherPremium": "Voucher uovo premium",
"eggVoucherGold": "Egg Voucher Gold", "eggVoucherGold": "Voucher uovo dorato",
"locked": "Locked", "locked": "Bloccato",
"defeatTrainer": "Defeat {{trainerName}}" "defeatTrainer": "Sconfiggi {{trainerName}}"
} as const; } as const;

View File

@ -62,4 +62,12 @@ export const battle: SimpleTranslationEntries = {
"drainMessage": "{{pokemonName}}[[로]]부터\n체력을 흡수했다!", "drainMessage": "{{pokemonName}}[[로]]부터\n체력을 흡수했다!",
"regainHealth": "{{pokemonName}}[[는]]\n기력을 회복했다!", "regainHealth": "{{pokemonName}}[[는]]\n기력을 회복했다!",
"fainted": "{{pokemonNameWithAffix}}[[는]] 쓰러졌다!", "fainted": "{{pokemonNameWithAffix}}[[는]] 쓰러졌다!",
"statRose": "상승했다",
"statSharplyRose": "약간 상승했다",
"statRoseDrastically": "대폭 상승했다",
"statWontGoAnyHigher": "더 이상 상승할 수 없다",
"statFell": "떨어졌다",
"statHarshlyFell": "약간 떨어졌다",
"statSeverelyFell": "대폭 떨어졌다",
"statWontGoAnyLower": "더 이상 떨어질 수 없다",
} as const; } as const;

View File

@ -248,6 +248,19 @@ export const modifierType: ModifierTypeTranslationEntries = {
"x_accuracy": "잘-맞히기", "x_accuracy": "잘-맞히기",
"dire_hit": "크리티컬커터", "dire_hit": "크리티컬커터",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Attack",
"DEF": "Defense",
"SPATK": "Sp. Atk",
"SPDEF": "Sp. Def",
"SPD": "Speed",
"ACC": "Accuracy",
"CRIT": "Critical Hit Ratio",
"EVA": "Evasiveness",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
"silk_scarf": "실크스카프", "silk_scarf": "실크스카프",
"black_belt": "검은띠", "black_belt": "검은띠",

View File

@ -13,7 +13,9 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEF": "특수방어", "SPDEF": "특수방어",
"SPDEFshortened": "특방", "SPDEFshortened": "특방",
"SPD": "스피드", "SPD": "스피드",
"SPDshortened": "스피드" "SPDshortened": "스피드",
"ACC": "명중률",
"EVA": "회피율"
}, },
Type: { Type: {

View File

@ -62,4 +62,12 @@ export const battle: SimpleTranslationEntries = {
"drainMessage": "{{pokemonName}} teve sua\nenergia drenada!", "drainMessage": "{{pokemonName}} teve sua\nenergia drenada!",
"regainHealth": "{{pokemonName}} recuperou\npontos de saúde!", "regainHealth": "{{pokemonName}} recuperou\npontos de saúde!",
"fainted": "{{pokemonNameWithAffix}} desmaiou!", "fainted": "{{pokemonNameWithAffix}} desmaiou!",
"statRose": "aumentou",
"statSharplyRose": "aumentou bruscamente",
"statRoseDrastically": "aumentou drasticamente",
"statWontGoAnyHigher": "não vai mais aumentar",
"statFell": "diminuiu",
"statHarshlyFell": "diminuiu duramente",
"statSeverelyFell": "diminuiu severamente",
"statWontGoAnyLower": "não vai mais diminuir",
} as const; } as const;

View File

@ -27,7 +27,6 @@ import { menuUiHandler } from "./menu-ui-handler";
import { modifierType } from "./modifier-type"; import { modifierType } from "./modifier-type";
import { move } from "./move"; import { move } from "./move";
import { nature } from "./nature"; import { nature } from "./nature";
import { partyUiHandler } from "./party-ui-handler";
import { pokeball } from "./pokeball"; import { pokeball } from "./pokeball";
import { pokemon } from "./pokemon"; import { pokemon } from "./pokemon";
import { pokemonInfo } from "./pokemon-info"; import { pokemonInfo } from "./pokemon-info";
@ -39,6 +38,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers";
import { tutorial } from "./tutorial"; import { tutorial } from "./tutorial";
import { voucher } from "./voucher"; import { voucher } from "./voucher";
import { weather } from "./weather"; import { weather } from "./weather";
import { partyUiHandler } from "./party-ui-handler";
export const ptBrConfig = { export const ptBrConfig = {
ability: ability, ability: ability,
@ -82,5 +82,5 @@ export const ptBrConfig = {
trainerNames: trainerNames, trainerNames: trainerNames,
tutorial: tutorial, tutorial: tutorial,
voucher: voucher, voucher: voucher,
weather: weather, weather: weather
}; };

View File

@ -248,6 +248,19 @@ export const modifierType: ModifierTypeTranslationEntries = {
"x_accuracy": "Precisão X", "x_accuracy": "Precisão X",
"dire_hit": "Direto", "dire_hit": "Direto",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Ataque",
"DEF": "Defesa",
"SPATK": "Ataque Esp.",
"SPDEF": "Defesa Esp.",
"SPD": "Velocidade",
"ACC": "Precisão",
"CRIT": "Chance de Acerto Crítico",
"EVA": "Evasão",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
"silk_scarf": "Lenço de Seda", "silk_scarf": "Lenço de Seda",
"black_belt": "Faixa Preta", "black_belt": "Faixa Preta",

View File

@ -13,7 +13,9 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEF": "Def. Esp.", "SPDEF": "Def. Esp.",
"SPDEFshortened": "DefEsp", "SPDEFshortened": "DefEsp",
"SPD": "Veloc.", "SPD": "Veloc.",
"SPDshortened": "Veloc." "SPDshortened": "Veloc.",
"ACC": "Precisão",
"EVA": "Evasão",
}, },
Type: { Type: {

View File

@ -61,5 +61,13 @@ export const battle: SimpleTranslationEntries = {
"useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!",
"drainMessage": "{{pokemonName}} had its\nenergy drained!", "drainMessage": "{{pokemonName}} had its\nenergy drained!",
"regainHealth": "{{pokemonName}} regained\nhealth!", "regainHealth": "{{pokemonName}} regained\nhealth!",
"fainted": "{{pokemonNameWithAffix}} fainted!" "fainted": "{{pokemonNameWithAffix}} fainted!",
"statRose": "rose",
"statSharplyRose": "sharply rose",
"statRoseDrastically": "rose drastically",
"statWontGoAnyHigher": "won't go any higher",
"statFell": "fell",
"statHarshlyFell": "harshly fell",
"statSeverelyFell": "severely fell",
"statWontGoAnyLower": "won't go any lower",
} as const; } as const;

View File

@ -248,6 +248,19 @@ export const modifierType: ModifierTypeTranslationEntries = {
"x_accuracy": "命中强化", "x_accuracy": "命中强化",
"dire_hit": "要害攻击", "dire_hit": "要害攻击",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Attack",
"DEF": "Defense",
"SPATK": "Sp. Atk",
"SPDEF": "Sp. Def",
"SPD": "Speed",
"ACC": "Accuracy",
"CRIT": "Critical Hit Ratio",
"EVA": "Evasiveness",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
"silk_scarf": "丝绸围巾", "silk_scarf": "丝绸围巾",
"black_belt": "黑带", "black_belt": "黑带",

View File

@ -13,7 +13,9 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEF": "特防", "SPDEF": "特防",
"SPDEFshortened": "特防", "SPDEFshortened": "特防",
"SPD": "速度", "SPD": "速度",
"SPDshortened": "速度" "SPDshortened": "速度",
"ACC": "Accuracy",
"EVA": "Evasiveness"
}, },
Type: { Type: {

View File

@ -58,5 +58,13 @@ export const battle: SimpleTranslationEntries = {
"useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!",
"drainMessage": "{{pokemonName}} had its\nenergy drained!", "drainMessage": "{{pokemonName}} had its\nenergy drained!",
"regainHealth": "{{pokemonName}} regained\nhealth!", "regainHealth": "{{pokemonName}} regained\nhealth!",
"fainted": "{{pokemonNameWithAffix}} fainted!" "fainted": "{{pokemonNameWithAffix}} fainted!",
"statRose": "rose",
"statSharplyRose": "sharply rose",
"statRoseDrastically": "rose drastically",
"statWontGoAnyHigher": "won't go any higher",
"statFell": "fell",
"statHarshlyFell": "harshly fell",
"statSeverelyFell": "severely fell",
"statWontGoAnyLower": "won't go any lower",
} as const; } as const;

View File

@ -306,6 +306,19 @@ export const modifierType: ModifierTypeTranslationEntries = {
x_accuracy: "命中強化", x_accuracy: "命中強化",
dire_hit: "要害攻擊", dire_hit: "要害攻擊",
}, },
TempBattleStatBoosterStatName: {
"ATK": "Attack",
"DEF": "Defense",
"SPATK": "Sp. Atk",
"SPDEF": "Sp. Def",
"SPD": "Speed",
"ACC": "Accuracy",
"CRIT": "Critical Hit Ratio",
"EVA": "Evasiveness",
"DEFAULT": "???",
},
AttackTypeBoosterItem: { AttackTypeBoosterItem: {
silk_scarf: "絲綢圍巾", silk_scarf: "絲綢圍巾",
black_belt: "黑帶", black_belt: "黑帶",

View File

@ -13,7 +13,9 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDEF": "特殊防禦", "SPDEF": "特殊防禦",
"SPDEFshortened": "特防", "SPDEFshortened": "特防",
"SPD": "速度", "SPD": "速度",
"SPDshortened": "速度" "SPDshortened": "速度",
"ACC": "Accuracy",
"EVA": "Evasiveness"
}, },
Type: { Type: {

View File

@ -35,7 +35,7 @@ import { TrainerSlot, trainerConfigs } from "./data/trainer-config";
import { EggHatchPhase } from "./egg-hatch-phase"; import { EggHatchPhase } from "./egg-hatch-phase";
import { Egg } from "./data/egg"; import { Egg } from "./data/egg";
import { vouchers } from "./system/voucher"; import { vouchers } from "./system/voucher";
import { loggedInUser, updateUserInfo } from "./account"; import { clientSessionId, loggedInUser, updateUserInfo } from "./account";
import { SessionSaveData } from "./system/game-data"; import { SessionSaveData } from "./system/game-data";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims";
import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms";
@ -2889,7 +2889,7 @@ export class MoveEffectPhase extends PokemonPhase {
continue; continue;
} }
const isProtected = !move.hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)); const isProtected = !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS; const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS;
@ -4198,8 +4198,7 @@ export class GameOverPhase extends BattlePhase {
If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */ If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */
if (this.victory) { if (this.victory) {
if (!Utils.isLocal) { if (!Utils.isLocal) {
Utils.apiFetch(`savedata/newclear?slot=${this.scene.sessionSlotId}`, true) Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true) .then(response => response.json())
.then(response => response.json())
.then(newClear => doGameOver(newClear)); .then(newClear => doGameOver(newClear));
} else { } else {
this.scene.gameData.offlineNewClear(this.scene).then(result => { this.scene.gameData.offlineNewClear(this.scene).then(result => {

View File

@ -44,6 +44,7 @@ export interface ModifierTypeTranslationEntries {
ModifierType: { [key: string]: ModifierTypeTranslationEntry }, ModifierType: { [key: string]: ModifierTypeTranslationEntry },
AttackTypeBoosterItem: SimpleTranslationEntries, AttackTypeBoosterItem: SimpleTranslationEntries,
TempBattleStatBoosterItem: SimpleTranslationEntries, TempBattleStatBoosterItem: SimpleTranslationEntries,
TempBattleStatBoosterStatName: SimpleTranslationEntries,
BaseStatBoosterItem: SimpleTranslationEntries, BaseStatBoosterItem: SimpleTranslationEntries,
EvolutionItem: SimpleTranslationEntries, EvolutionItem: SimpleTranslationEntries,
FormChangeItem: SimpleTranslationEntries, FormChangeItem: SimpleTranslationEntries,
@ -96,10 +97,15 @@ const fonts = [
), ),
]; ];
function initFonts() { async function initFonts() {
fonts.forEach((fontFace: FontFace) => { const results = await Promise.allSettled(fonts.map(font => font.load()));
fontFace.load().then(f => document.fonts.add(f)).catch(e => console.error(e)); for (const result of results) {
}); if (result.status === "fulfilled") {
document.fonts?.add(result.value);
} else {
console.error(result.reason);
}
}
} }
export async function initI18n(): Promise<void> { export async function initI18n(): Promise<void> {
@ -109,8 +115,6 @@ export async function initI18n(): Promise<void> {
} }
isInitialized = true; isInitialized = true;
initFonts();
/** /**
* i18next is a localization library for maintaining and using translation resources. * i18next is a localization library for maintaining and using translation resources.
* *
@ -172,6 +176,8 @@ export async function initI18n(): Promise<void> {
}, },
postProcess: ["korean-postposition"], postProcess: ["korean-postposition"],
}); });
await initFonts();
} }
// Module declared to make referencing keys in the localization files type-safe. // Module declared to make referencing keys in the localization files type-safe.
@ -222,6 +228,7 @@ declare module "i18next" {
tutorial: SimpleTranslationEntries; tutorial: SimpleTranslationEntries;
voucher: SimpleTranslationEntries; voucher: SimpleTranslationEntries;
weather: SimpleTranslationEntries; weather: SimpleTranslationEntries;
battleStat: SimpleTranslationEntries;
}; };
} }
} }

View File

@ -313,7 +313,7 @@ export class GameData {
localStorage.setItem(`data_${loggedInUser.username}`, encrypt(systemData, bypassLogin)); localStorage.setItem(`data_${loggedInUser.username}`, encrypt(systemData, bypassLogin));
if (!bypassLogin) { if (!bypassLogin) {
Utils.apiPost(`savedata/update?datatype=${GameDataType.SYSTEM}&clientSessionId=${clientSessionId}`, systemData, undefined, true) Utils.apiPost(`savedata/system/update?clientSessionId=${clientSessionId}`, systemData, undefined, true)
.then(response => response.text()) .then(response => response.text())
.then(error => { .then(error => {
this.scene.ui.savingIcon.hide(); this.scene.ui.savingIcon.hide();
@ -347,7 +347,7 @@ export class GameData {
} }
if (!bypassLogin) { if (!bypassLogin) {
Utils.apiFetch(`savedata/system?clientSessionId=${clientSessionId}`, true) Utils.apiFetch(`savedata/system/get?clientSessionId=${clientSessionId}`, true)
.then(response => response.text()) .then(response => response.text())
.then(response => { .then(response => {
if (!response.length || response[0] !== "{") { if (!response.length || response[0] !== "{") {
@ -545,7 +545,7 @@ export class GameData {
return true; return true;
} }
const response = await Utils.apiPost("savedata/system/verify", JSON.stringify({ clientSessionId: clientSessionId }), undefined, true) const response = await Utils.apiFetch(`savedata/system/verify?clientSessionId=${clientSessionId}`, true)
.then(response => response.json()); .then(response => response.json());
if (!response.valid) { if (!response.valid) {
@ -815,7 +815,7 @@ export class GameData {
}; };
if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`)) { if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`)) {
Utils.apiFetch(`savedata/session?slot=${slotId}&clientSessionId=${clientSessionId}`, true) Utils.apiFetch(`savedata/session/get?slot=${slotId}&clientSessionId=${clientSessionId}`, true)
.then(response => response.text()) .then(response => response.text())
.then(async response => { .then(async response => {
if (!response.length || response[0] !== "{") { if (!response.length || response[0] !== "{") {
@ -963,7 +963,7 @@ export class GameData {
if (success !== null && !success) { if (success !== null && !success) {
return resolve(false); return resolve(false);
} }
Utils.apiFetch(`savedata/delete?datatype=${GameDataType.SESSION}&slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => { Utils.apiFetch(`savedata/session/delete?slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => {
if (response.ok) { if (response.ok) {
loggedInUser.lastSessionSlot = -1; loggedInUser.lastSessionSlot = -1;
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`); localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`);
@ -1027,7 +1027,7 @@ export class GameData {
return resolve([false, false]); return resolve([false, false]);
} }
const sessionData = this.getSessionSaveData(scene); const sessionData = this.getSessionSaveData(scene);
Utils.apiPost(`savedata/clear?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, JSON.stringify(sessionData), undefined, true).then(response => { Utils.apiPost(`savedata/session/clear?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, JSON.stringify(sessionData), undefined, true).then(response => {
if (response.ok) { if (response.ok) {
loggedInUser.lastSessionSlot = -1; loggedInUser.lastSessionSlot = -1;
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`); localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`);
@ -1184,7 +1184,7 @@ export class GameData {
link.remove(); link.remove();
}; };
if (!bypassLogin && dataType < GameDataType.SETTINGS) { if (!bypassLogin && dataType < GameDataType.SETTINGS) {
Utils.apiFetch(`savedata/${dataType === GameDataType.SYSTEM ? "system" : "session"}?clientSessionId=${clientSessionId}${dataType === GameDataType.SESSION ? `&slot=${slotId}` : ""}`, true) Utils.apiFetch(`savedata/${dataType === GameDataType.SYSTEM ? "system" : "session"}/get?clientSessionId=${clientSessionId}${dataType === GameDataType.SESSION ? `&slot=${slotId}` : ""}`, true)
.then(response => response.text()) .then(response => response.text())
.then(response => { .then(response => {
if (!response.length || response[0] !== "{") { if (!response.length || response[0] !== "{") {
@ -1264,7 +1264,13 @@ export class GameData {
if (!success) { if (!success) {
return displayError(`Could not contact the server. Your ${dataName} data could not be imported.`); return displayError(`Could not contact the server. Your ${dataName} data could not be imported.`);
} }
Utils.apiPost(`savedata/update?datatype=${dataType}${dataType === GameDataType.SESSION ? `&slot=${slotId}` : ""}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, dataStr, undefined, true) let url: string;
if (dataType === GameDataType.SESSION) {
url = `savedata/session/update?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`;
} else {
url = `savedata/system/update?trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`;
}
Utils.apiPost(url, dataStr, undefined, true)
.then(response => response.text()) .then(response => response.text())
.then(error => { .then(error => {
if (error) { if (error) {

View File

@ -0,0 +1,91 @@
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "../utils/gameManager";
import * as Overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { getMovePosition } from "../utils/gameManagerUtils";
import { TurnEndPhase } from "#app/phases.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - Unseen Fist", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.URSHIFU);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.PROTECT, Moves.PROTECT, Moves.PROTECT, Moves.PROTECT]);
vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
});
test(
"ability causes a contact move to ignore Protect",
() => testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, true),
TIMEOUT
);
test(
"ability does not cause a non-contact move to ignore Protect",
() => testUnseenFistHitResult(game, Moves.ABSORB, Moves.PROTECT, false),
TIMEOUT
);
test(
"ability does not apply if the source has Long Reach",
() => {
vi.spyOn(Overrides, "PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.LONG_REACH);
testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, false);
}, TIMEOUT
);
test(
"ability causes a contact move to ignore Wide Guard",
() => testUnseenFistHitResult(game, Moves.BREAKING_SWIPE, Moves.WIDE_GUARD, true),
TIMEOUT
);
test(
"ability does not cause a non-contact move to ignore Wide Guard",
() => testUnseenFistHitResult(game, Moves.BULLDOZE, Moves.WIDE_GUARD, false),
TIMEOUT
);
});
async function testUnseenFistHitResult(game: GameManager, attackMove: Moves, protectMove: Moves, shouldSucceed: boolean = true): Promise<void> {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([attackMove]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([protectMove, protectMove, protectMove, protectMove]);
await game.startBattle();
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, attackMove));
await game.phaseInterceptor.to(TurnEndPhase);
if (shouldSucceed) {
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp);
} else {
expect(enemyPokemon.hp).toBe(enemyStartingHp);
}
}

View File

@ -0,0 +1,205 @@
import {beforeAll, describe, expect, it} from "vitest";
import { getBattleStatName, getBattleStatLevelChangeDescription } from "#app/data/battle-stat.js";
import { BattleStat } from "#app/data/battle-stat.js";
import { pokemonInfo as enPokemonInfo } from "#app/locales/en/pokemon-info.js";
import { battle as enBattleStat } from "#app/locales/en/battle.js";
import { pokemonInfo as dePokemonInfo } from "#app/locales/de/pokemon-info.js";
import { battle as deBattleStat } from "#app/locales/de/battle.js";
import { pokemonInfo as esPokemonInfo } from "#app/locales/es/pokemon-info.js";
import { battle as esBattleStat } from "#app/locales/es/battle.js";
import { pokemonInfo as frPokemonInfo } from "#app/locales/fr/pokemon-info.js";
import { battle as frBattleStat } from "#app/locales/fr/battle.js";
import { pokemonInfo as itPokemonInfo } from "#app/locales/it/pokemon-info.js";
import { battle as itBattleStat } from "#app/locales/it/battle.js";
import { pokemonInfo as koPokemonInfo } from "#app/locales/ko/pokemon-info.js";
import { battle as koBattleStat } from "#app/locales/ko/battle.js";
import { pokemonInfo as ptBrPokemonInfo } from "#app/locales/pt_BR/pokemon-info.js";
import { battle as ptBrBattleStat } from "#app/locales/pt_BR/battle.js";
import { pokemonInfo as zhCnPokemonInfo } from "#app/locales/zh_CN/pokemon-info.js";
import { battle as zhCnBattleStat } from "#app/locales/zh_CN/battle.js";
import { pokemonInfo as zhTwPokemonInfo } from "#app/locales/zh_TW/pokemon-info.js";
import { battle as zhTwBattleStat } from "#app/locales/zh_TW/battle.js";
import i18next, {initI18n} from "#app/plugins/i18n";
interface BattleStatTestUnit {
stat: BattleStat,
key: string
}
interface BattleStatLevelTestUnit {
levels: integer,
up: boolean,
key: string
}
function testBattleStatName(stat: BattleStat, expectMessage: string) {
const message = getBattleStatName(stat);
console.log(`message ${message}, expected ${expectMessage}`);
expect(message).toBe(expectMessage);
}
function testBattleStatLevelChangeDescription(levels: integer, up: boolean, expectMessage: string) {
const message = getBattleStatLevelChangeDescription(levels, up);
console.log(`message ${message}, expected ${expectMessage}`);
expect(message).toBe(expectMessage);
}
describe("Test for BattleStat Localization", () => {
const battleStatUnits: BattleStatTestUnit[] = [];
const battleStatLevelUnits: BattleStatLevelTestUnit[] = [];
beforeAll(() => {
initI18n();
battleStatUnits.push({ stat: BattleStat.ATK, key: "Stat.ATK" });
battleStatUnits.push({ stat: BattleStat.DEF, key: "Stat.DEF" });
battleStatUnits.push({ stat: BattleStat.SPATK, key: "Stat.SPATK" });
battleStatUnits.push({ stat: BattleStat.SPDEF, key: "Stat.SPDEF" });
battleStatUnits.push({ stat: BattleStat.SPD, key: "Stat.SPD" });
battleStatUnits.push({ stat: BattleStat.ACC, key: "Stat.ACC" });
battleStatUnits.push({ stat: BattleStat.EVA, key: "Stat.EVA" });
battleStatLevelUnits.push({ levels: 1, up: true, key: "statRose" });
battleStatLevelUnits.push({ levels: 2, up: true, key: "statSharplyRose" });
battleStatLevelUnits.push({ levels: 3, up: true, key: "statRoseDrastically" });
battleStatLevelUnits.push({ levels: 4, up: true, key: "statRoseDrastically" });
battleStatLevelUnits.push({ levels: 5, up: true, key: "statRoseDrastically" });
battleStatLevelUnits.push({ levels: 6, up: true, key: "statRoseDrastically" });
battleStatLevelUnits.push({ levels: 7, up: true, key: "statWontGoAnyHigher" });
battleStatLevelUnits.push({ levels: 1, up: false, key: "statFell" });
battleStatLevelUnits.push({ levels: 2, up: false, key: "statHarshlyFell" });
battleStatLevelUnits.push({ levels: 3, up: false, key: "statSeverelyFell" });
battleStatLevelUnits.push({ levels: 4, up: false, key: "statSeverelyFell" });
battleStatLevelUnits.push({ levels: 5, up: false, key: "statSeverelyFell" });
battleStatLevelUnits.push({ levels: 6, up: false, key: "statSeverelyFell" });
battleStatLevelUnits.push({ levels: 7, up: false, key: "statWontGoAnyLower" });
});
it("Test getBattleStatName() in English", async () => {
i18next.changeLanguage("en");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, enPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in English", async () => {
i18next.changeLanguage("en");
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, enBattleStat[unit.key]);
});
});
it("Test getBattleStatName() in Español", async () => {
i18next.changeLanguage("es");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, esPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in Español", async () => {
i18next.changeLanguage("es");
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, esBattleStat[unit.key]);
});
});
it("Test getBattleStatName() in Italiano", async () => {
i18next.changeLanguage("it");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, itPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in Italiano", async () => {
i18next.changeLanguage("it");
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, itBattleStat[unit.key]);
});
});
it("Test getBattleStatName() in Français", async () => {
i18next.changeLanguage("fr");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, frPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in Français", async () => {
i18next.changeLanguage("fr");
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, frBattleStat[unit.key]);
});
});
it("Test getBattleStatName() in Deutsch", async () => {
i18next.changeLanguage("de");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, dePokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in Deutsch", async () => {
i18next.changeLanguage("de");
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, deBattleStat[unit.key]);
});
});
it("Test getBattleStatName() in Português (BR)", async () => {
i18next.changeLanguage("pt-BR");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, ptBrPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in Português (BR)", async () => {
i18next.changeLanguage("pt-BR");
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, ptBrBattleStat[unit.key]);
});
});
it("Test getBattleStatName() in 简体中文", async () => {
i18next.changeLanguage("zh-CN");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, zhCnPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in 简体中文", async () => {
i18next.changeLanguage("zh-CN");
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, zhCnBattleStat[unit.key]);
});
});
it("Test getBattleStatName() in 繁體中文", async () => {
i18next.changeLanguage("zh-TW");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, zhTwPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in 繁體中文", async () => {
i18next.changeLanguage("zh-TW");
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, zhTwBattleStat[unit.key]);
});
});
it("Test getBattleStatName() in 한국어", async () => {
await i18next.changeLanguage("ko");
battleStatUnits.forEach(unit => {
testBattleStatName(unit.stat, koPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
});
});
it("Test getBattleStatLevelChangeDescription() in 한국어", async () => {
i18next.changeLanguage("ko", () => {
battleStatLevelUnits.forEach(unit => {
testBattleStatLevelChangeDescription(unit.levels, unit.up, koBattleStat[unit.key]);
});
});
});
});

View File

@ -1,16 +1,19 @@
import "vitest-canvas-mock";
import "#app/test/fontFace.setup"; import "#app/test/fontFace.setup";
import {initStatsKeys} from "#app/ui/game-stats-ui-handler"; import "vitest-canvas-mock";
import {initPokemonPrevolutions} from "#app/data/pokemon-evolutions";
import { initLoggedInUser } from "#app/account";
import { initAbilities } from "#app/data/ability";
import { initBiomes } from "#app/data/biomes"; import { initBiomes } from "#app/data/biomes";
import { initEggMoves } from "#app/data/egg-moves"; import { initEggMoves } from "#app/data/egg-moves";
import { initMoves } from "#app/data/move";
import { initPokemonPrevolutions } from "#app/data/pokemon-evolutions";
import { initPokemonForms } from "#app/data/pokemon-forms"; import { initPokemonForms } from "#app/data/pokemon-forms";
import { initSpecies } from "#app/data/pokemon-species"; import { initSpecies } from "#app/data/pokemon-species";
import {initMoves} from "#app/data/move";
import {initAbilities} from "#app/data/ability";
import { initAchievements } from "#app/system/achv.js"; import { initAchievements } from "#app/system/achv.js";
import { initVouchers } from "#app/system/voucher.js"; import { initVouchers } from "#app/system/voucher.js";
import {initLoggedInUser} from "#app/account"; import { initStatsKeys } from "#app/ui/game-stats-ui-handler";
import { beforeAll } from "vitest";
initVouchers(); initVouchers();
initAchievements(); initAchievements();
@ -25,3 +28,12 @@ initAbilities();
initLoggedInUser(); initLoggedInUser();
global.testFailed = false; global.testFailed = false;
beforeAll(() => {
Object.defineProperty(document, "fonts", {
writable: true,
value: {
add: () => {},
}
});
});

View File

@ -17,7 +17,7 @@ export default class EggCounterContainer extends Phaser.GameObjects.Container {
private battleScene: BattleScene; private battleScene: BattleScene;
private eggCount: integer; private eggCount: integer;
private eggCountWindow: Phaser.GameObjects.NineSlice; private eggCountWindow: Phaser.GameObjects.NineSlice;
private eggCountText: Phaser.GameObjects.Text; public eggCountText: Phaser.GameObjects.Text;
/** /**
* @param {BattleScene} scene - The scene to which this container belongs. * @param {BattleScene} scene - The scene to which this container belongs.
@ -49,6 +49,7 @@ export default class EggCounterContainer extends Phaser.GameObjects.Container {
eggSprite.setScale(0.32); eggSprite.setScale(0.32);
this.eggCountText = addTextObject(this.battleScene, 28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" }); this.eggCountText = addTextObject(this.battleScene, 28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" });
this.eggCountText.setName("text-egg-count");
this.add(eggSprite); this.add(eggSprite);
this.add(this.eggCountText); this.add(this.eggCountText);
@ -69,7 +70,7 @@ export default class EggCounterContainer extends Phaser.GameObjects.Container {
*/ */
private onEggCountChanged(event: Event): void { private onEggCountChanged(event: Event): void {
const eggCountChangedEvent = event as EggCountChangedEvent; const eggCountChangedEvent = event as EggCountChangedEvent;
if (!eggCountChangedEvent) { if (!eggCountChangedEvent || !this.eggCountText?.data) {
return; return;
} }

View File

@ -787,6 +787,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterSelectContainer.setVisible(true); this.starterSelectContainer.setVisible(true);
this.setCursor(0);
this.setGenMode(false); this.setGenMode(false);
this.setCursor(0); this.setCursor(0);
this.setGenMode(true); this.setGenMode(true);

View File

@ -23,5 +23,11 @@
"entryPoints": ["src/"], "entryPoints": ["src/"],
"entryPointStrategy": "expand", "entryPointStrategy": "expand",
"out": "typedoc", "out": "typedoc",
} },
"exclude": [
"node_modules",
"dist",
"vite.config.ts",
"vitest.config.ts"
]
} }

View File

@ -1,34 +0,0 @@
import { resolve } from 'path';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
// import fs from 'vite-plugin-fs';
export default defineConfig(({ mode }) => {
return {
plugins: [tsconfigPaths()],
server: { host: '0.0.0.0', port: 8000 },
clearScreen: false,
build: {
minify: 'esbuild',
sourcemap: false,
},
rollupOptions: {
onwarn(warning, warn) {
// Suppress "Module level directives cause errors when bundled" warnings
if (warning.code === "MODULE_LEVEL_DIRECTIVE") {
return;
}
warn(warning);
},
},
resolve: {
alias: {
"#enums": resolve('./src/enums')
}
},
esbuild: {
pure: mode === 'production' ? [ 'console.log' ] : [],
keepNames: true,
},
}
})

30
vite.config.ts Normal file
View File

@ -0,0 +1,30 @@
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export const defaultConfig = {
plugins: [tsconfigPaths() as any],
server: { host: '0.0.0.0', port: 8000 },
clearScreen: false,
build: {
minify: 'esbuild' as const,
sourcemap: false,
},
rollupOptions: {
onwarn(warning, warn) {
// Suppress "Module level directives cause errors when bundled" warnings
if (warning.code === "MODULE_LEVEL_DIRECTIVE") {
return;
}
warn(warning);
},
}
};
export default defineConfig(({mode}) => ({
...defaultConfig,
esbuild: {
pure: mode === 'production' ? [ 'console.log' ] : [],
keepNames: true,
},
}));

View File

@ -1,48 +0,0 @@
import { defineConfig } from 'vite';
import path from 'path';
// import fs from 'vite-plugin-fs';
export default defineConfig(({ mode }) => {
return {
test: {
setupFiles: ['./src/test/vitest.setup.ts'],
environment: 'jsdom',
deps: {
optimizer: {
web: {
include: ['vitest-canvas-mock'],
}
}
},
threads: false,
trace: true,
restoreMocks: true,
environmentOptions: {
jsdom: {
resources: 'usable',
},
},
coverage: {
provider: 'istanbul',
reportsDirectory: 'coverage',
reporters: ['text-summary', 'html'],
},
},
plugins: [/*fs()*/],
server: { host: '0.0.0.0', port: 8000 },
clearScreen: false,
build: {
minify: 'esbuild',
sourcemap: true
},
resolve: {
alias: {
"#enums": path.resolve('./src/enums')
}
},
esbuild: {
pure: mode === 'production' ? [ 'console.log' ] : [],
keepNames: true,
},
}
})

38
vitest.config.ts Normal file
View File

@ -0,0 +1,38 @@
import { defineConfig } from 'vitest/config';
import { defaultConfig } from './vite.config';
export default defineConfig(({mode}) => ({
...defaultConfig,
test: {
setupFiles: ['./src/test/vitest.setup.ts'],
server: {
deps: {
inline: ['vitest-canvas-mock'],
optimizer: {
web: {
include: ['vitest-canvas-mock'],
}
}
}
},
environment: 'jsdom' as const,
environmentOptions: {
jsdom: {
resources: 'usable',
},
},
threads: false,
trace: true,
restoreMocks: true,
watch: false,
coverage: {
provider: 'istanbul' as const,
reportsDirectory: 'coverage' as const,
reporters: ['text-summary', 'html'],
},
},
esbuild: {
pure: mode === 'production' ? [ 'console.log' ] : [],
keepNames: true,
},
}))