Compare commits

...

36 Commits

Author SHA1 Message Date
Lugiad
f473df4249
Update src/locales/ko/menu.json
Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com>
2024-09-06 17:55:29 +02:00
Lugiad
abeb78b414
Update src/locales/ko/ability-trigger.json
Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com>
2024-09-06 17:55:21 +02:00
Lugiad
8d818b8471
Update src/locales/ko/menu.json
Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com>
2024-09-06 17:55:11 +02:00
Lugiad
ba929f8d44
Update src/locales/ko/ability-trigger.json
Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com>
2024-09-06 17:55:03 +02:00
Lugiad
0d43313a5a
Update src/locales/de/menu.json
Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
2024-09-06 17:48:43 +02:00
Lugiad
4f7f45a27d
Update src/locales/de/ability-trigger.json
Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
2024-09-06 17:48:36 +02:00
Lugiad
bdfd189bee
Update src/locales/de/ability-trigger.json
Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
2024-09-06 17:48:27 +02:00
Lugiad
a4fe9e9b55
Update menu.json 2024-09-06 16:52:41 +02:00
Lugiad
a85ac01047
Update menu.json 2024-09-06 16:51:50 +02:00
Lugiad
02b69f0290
Update menu.json 2024-09-06 16:45:25 +02:00
Lugiad
2ee22d70b5
Update menu.json 2024-09-06 16:45:08 +02:00
Lugiad
f0abacc1ee
Update menu.json 2024-09-06 16:44:52 +02:00
Lugiad
9d0fe1f016
Update menu.json 2024-09-06 16:44:33 +02:00
Lugiad
461971b60e
Update menu.json 2024-09-06 16:44:13 +02:00
Lugiad
88d86861b0
Update menu.json 2024-09-06 16:43:52 +02:00
Lugiad
669ab8672c
Update ability-trigger.json 2024-09-06 16:43:23 +02:00
Lugiad
977db2363c
Update ability-trigger.json 2024-09-06 16:42:33 +02:00
Lugiad
064565e4e5
Update ability-trigger.json 2024-09-06 16:42:07 +02:00
Lugiad
b4b1ae8f2c
Update ability-trigger.json 2024-09-06 16:41:02 +02:00
Lugiad
b41336a110
Update ability-trigger.json 2024-09-06 16:40:44 +02:00
Lugiad
999c320c03
Update ability-trigger.json 2024-09-06 16:40:16 +02:00
Lugiad
e7d2fbe149
Update ability-trigger.json 2024-09-06 16:39:24 +02:00
Lugiad
929c3fd89c
Update ability-trigger.json 2024-09-06 16:37:44 +02:00
Lugiad
9790a1d6f2
Update menu.json 2024-09-06 16:33:56 +02:00
Lugiad
aaa87c3c0c
Merge branch 'beta' into move-disabling-effects-locales 2024-09-06 16:32:00 +02:00
Lugiad
ca2150f950
Update src/locales/it/battler-tags.json
Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com>
2024-09-06 16:31:46 +02:00
Lugiad
99ab40f4e9
Update src/locales/it/battle.json
Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com>
2024-09-06 16:31:37 +02:00
Adrian T.
d58f035287
[Misc] Migrate REROLL_TARGET to SHOP_CURSOR_TARGET (#4016)
* migrate reroll target to shop cursor target

* delete key after migrating
2024-09-06 09:41:48 +00:00
innerthunder
7b97657756
[Bug] Fix Aura Break applying without Dark/Fairy Aura present (#4057)
* Fix Aura Break ignoring active Dark/Fairy Aura condition

* Add conditional post-summon message

---------

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
2024-09-06 07:59:22 +00:00
chaosgrimmon
f1650d2515
[Sprite] Fix Tangrowth Variants (#4059)
* [Sprite] Front Tangrowth male variants

* [Sprite] Front Tangrowth female variants

* [Sprite] Back Tangrowth female variants

* [Sprite] Back Tangrowth male epic palette

* Delete public/images/pokemon/variant/465_2.png

* Delete public/images/pokemon/variant/back/465_3.png

* Delete public/images/pokemon/variant/465_3.png

* Delete public/images/pokemon/variant/465_2.json

* Delete public/images/pokemon/variant/465_3.json

* Delete public/images/pokemon/variant/back/465_3.json

* [Sprite] Update Tangrowth variants with females
2024-09-06 02:32:13 +00:00
flx-sta
f3bdaa12ca
[Bug][Refactor] fix username-finder issues + code & visual improvements (#4055)
* fix username-finder issues & refactor `login-form-ui-handler`

- reduce redundancy
- add hover effect for interactable game objects
- add error handler for "No save files found!"
- Make user finder errors support i18n

* add `disableInteractive` to mockContainer
2024-09-06 00:07:24 +00:00
Adrian T.
39f3572c1b
[Test] Add Dragon Cheer tests (#4013)
* add dragon cheer tests

* Update src/test/moves/dragon_cheer.test.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* update tests

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2024-09-05 23:51:47 +00:00
NightKev
57a3efd9e2
[Bug] Fix off-by-one errors in some random number calls (#3665)
* Fix off-by-one error in some random number calls

* Fix mock RNG function used by tests

Also remove unnecessary extra RNG mock from Glaive Rush test

* Just some github UI manipulation don't mind me

* Update Glaive Rush test

* Remove unnecessary `Math.floor()`

* Remove resolved comment

* Add tsdocs to various functions

* Remove `src/rng.md` file

* Update tsdoc
2024-09-05 23:51:05 +00:00
Adrian T.
deac3141a1
remove partial from tera blast (#4044) 2024-09-05 20:05:09 +00:00
innerthunder
6e26db27b8
Protection moves now fail when used last (#4045) 2024-09-05 19:07:04 +00:00
damocleas
6d312a2909
Update egg.ts Same-Species Egg Shiny Rate (#4052) 2024-09-05 18:50:28 +00:00
49 changed files with 607 additions and 12485 deletions

View File

@ -0,0 +1,22 @@
{
"1": {
"529cc5": "8153c7",
"d65a94": "5ad662",
"3a73ad": "6b3aad",
"bd216b": "21bd69",
"5a193a": "195a2a",
"193a63": "391963",
"295a84": "472984"
},
"2": {
"529cc5": "ffedb6",
"d65a94": "e67d2f",
"3a73ad": "ebc582",
"bd216b": "b35131",
"31313a": "3d1519",
"5a193a": "752e2e",
"193a63": "705040",
"295a84": "ad875a",
"4a4a52": "57211a"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@ -1691,8 +1691,8 @@
], ],
"465": [ "465": [
0, 0,
2, 1,
2 1
], ],
"466": [ "466": [
1, 1,
@ -3980,6 +3980,11 @@
1, 1,
1 1
], ],
"465": [
0,
1,
1
],
"592": [ "592": [
1, 1,
1, 1,
@ -5690,7 +5695,7 @@
"465": [ "465": [
0, 0,
1, 1,
2 1
], ],
"466": [ "466": [
2, 2,
@ -8008,6 +8013,11 @@
1, 1,
1 1
], ],
"465": [
0,
1,
1
],
"592": [ "592": [
1, 1,
1, 1,

View File

@ -8,5 +8,14 @@
"bd216b": "21bd69", "bd216b": "21bd69",
"31313a": "31313a", "31313a": "31313a",
"d65a94": "5ad662" "d65a94": "5ad662"
},
"2": {
"5a193a": "752e2e",
"31313a": "3d1519",
"d65a94": "e67d2f",
"3a73ad": "ebc582",
"295a84": "ad875a",
"bd216b": "b35131",
"193a63": "705040"
} }
} }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,21 @@
{
"1": {
"193a63": "391963",
"295a84": "472984",
"3a73ad": "6b3aad",
"000000": "000000",
"5a193a": "195a2a",
"bd216b": "21bd69",
"31313a": "31313a",
"d65a94": "5ad662"
},
"2": {
"5a193a": "752e2e",
"31313a": "3d1519",
"d65a94": "e67d2f",
"3a73ad": "ebc582",
"295a84": "ad875a",
"bd216b": "b35131",
"193a63": "705040"
}
}

View File

@ -0,0 +1,22 @@
{
"1": {
"529cc5": "8153c7",
"d65a94": "5ad662",
"3a73ad": "6b3aad",
"bd216b": "21bd69",
"5a193a": "195a2a",
"193a63": "391963",
"295a84": "472984"
},
"2": {
"529cc5": "ffedb6",
"d65a94": "e67d2f",
"3a73ad": "ebc582",
"bd216b": "b35131",
"31313a": "3d1519",
"5a193a": "752e2e",
"193a63": "705040",
"295a84": "ad875a",
"4a4a52": "57211a"
}
}

View File

@ -855,7 +855,7 @@ export default class BattleScene extends SceneBase {
overrideModifiers(this, false); overrideModifiers(this, false);
overrideHeldItems(this, pokemon, false); overrideHeldItems(this, pokemon, false);
if (boss && !dataSource) { if (boss && !dataSource) {
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295)); const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296));
for (let s = 0; s < pokemon.ivs.length; s++) { for (let s = 0; s < pokemon.ivs.length; s++) {
pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75)); pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75));
@ -961,6 +961,16 @@ export default class BattleScene extends SceneBase {
this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym(); this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym();
} }
/**
* Generates a random number using the current battle's seed
*
* This calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`
*
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randBattleSeedInt(range: integer, min: integer = 0): integer { randBattleSeedInt(range: integer, min: integer = 0): integer {
return this.currentBattle?.randSeedInt(this, range, min); return this.currentBattle?.randSeedInt(this, range, min);
} }
@ -1112,7 +1122,8 @@ export default class BattleScene extends SceneBase {
doubleTrainer = false; doubleTrainer = false;
} }
} }
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT); const variant = doubleTrainer ? TrainerVariant.DOUBLE : (Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, variant);
this.field.add(newTrainer); this.field.add(newTrainer);
} }
} }
@ -2620,7 +2631,7 @@ export default class BattleScene extends SceneBase {
if (mods.length < 1) { if (mods.length < 1) {
return mods; return mods;
} }
const rand = Math.floor(Utils.randSeedInt(mods.length)); const rand = Utils.randSeedInt(mods.length);
return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))]; return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))];
}; };
modifiers = shuffleModifiers(modifiers); modifiers = shuffleModifiers(modifiers);

View File

@ -354,6 +354,12 @@ export default class Battle {
return null; return null;
} }
/**
* Generates a random number using the current battle's seed. Calls {@linkcode Utils.randSeedInt}
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randSeedInt(scene: BattleScene, range: number, min: number = 0): number { randSeedInt(scene: BattleScene, range: number, min: number = 0): number {
if (range <= 1) { if (range <= 1) {
return min; return min;

View File

@ -2642,7 +2642,7 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
if (simulated) { if (simulated) {
return defender.canAddTag(BattlerTagType.CONFUSED); return defender.canAddTag(BattlerTagType.CONFUSED);
} else { } else {
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3, 2), move.id, defender.id); return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedIntRange(2, 5), move.id, defender.id);
} }
} }
return false; return false;
@ -5333,8 +5333,10 @@ export function initAbilities() {
.attr(FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 4 / 3), .attr(FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 4 / 3),
new Ability(Abilities.AURA_BREAK, 6) new Ability(Abilities.AURA_BREAK, 6)
.ignorable() .ignorable()
.conditionalAttr(target => target.hasAbility(Abilities.DARK_AURA), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16) .conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16)
.conditionalAttr(target => target.hasAbility(Abilities.FAIRY_AURA), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16), .conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16)
.conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA) || p.hasAbility(Abilities.FAIRY_AURA)),
PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAuraBreak", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })),
new Ability(Abilities.PRIMORDIAL_SEA, 6) new Ability(Abilities.PRIMORDIAL_SEA, 6)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN) .attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)

View File

@ -486,7 +486,7 @@ export class ConfusedTag extends BattlerTag {
if (pokemon.randSeedInt(3) === 0) { if (pokemon.randSeedInt(3) === 0) {
const atk = pokemon.getEffectiveStat(Stat.ATK); const atk = pokemon.getEffectiveStat(Stat.ATK);
const def = pokemon.getEffectiveStat(Stat.DEF); const def = pokemon.getEffectiveStat(Stat.DEF);
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100)); const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
pokemon.damageAndUpdate(damage); pokemon.damageAndUpdate(damage);
pokemon.battleData.hitCount++; pokemon.battleData.hitCount++;

View File

@ -15,7 +15,7 @@ export const EGG_SEED = 1073741824;
// Rates for specific random properties in 1/x // Rates for specific random properties in 1/x
const DEFAULT_SHINY_RATE = 128; const DEFAULT_SHINY_RATE = 128;
const GACHA_SHINY_UP_SHINY_RATE = 64; const GACHA_SHINY_UP_SHINY_RATE = 64;
const SAME_SPECIES_EGG_SHINY_RATE = 24; const SAME_SPECIES_EGG_SHINY_RATE = 12;
const SAME_SPECIES_EGG_HA_RATE = 8; const SAME_SPECIES_EGG_HA_RATE = 8;
const MANAPHY_EGG_MANAPHY_RATE = 8; const MANAPHY_EGG_MANAPHY_RATE = 8;
const GACHA_EGG_HA_RATE = 192; const GACHA_EGG_HA_RATE = 192;

View File

@ -757,7 +757,10 @@ export default class Move implements Localizable {
const fieldAuras = new Set( const fieldAuras = new Set(
source.scene.getField(true) source.scene.getField(true)
.map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[]) .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr).filter(attr => {
const condition = attr.getCondition();
return (!condition || condition(p));
}) as FieldMoveTypePowerBoostAbAttr[])
.flat(), .flat(),
); );
for (const aura of fieldAuras) { for (const aura of fieldAuras) {
@ -4400,7 +4403,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id); return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedIntRange(this.turnCountMin, this.turnCountMax), move.id, user.id);
} }
return false; return false;
@ -6234,6 +6237,8 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target:
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE); const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE);
const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => user.scene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined;
export type MoveAttrFilter = (attr: MoveAttr) => boolean; export type MoveAttrFilter = (attr: MoveAttr) => boolean;
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> { function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
@ -6972,7 +6977,8 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.FREEZE)
.target(MoveTarget.ALL_NEAR_ENEMIES), .target(MoveTarget.ALL_NEAR_ENEMIES),
new SelfStatusMove(Moves.PROTECT, Type.NORMAL, -1, 10, -1, 4, 2) new SelfStatusMove(Moves.PROTECT, Type.NORMAL, -1, 10, -1, 4, 2)
.attr(ProtectAttr), .attr(ProtectAttr)
.condition(failIfLastCondition),
new AttackMove(Moves.MACH_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2) new AttackMove(Moves.MACH_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2)
.punchingMove(), .punchingMove(),
new StatusMove(Moves.SCARY_FACE, Type.NORMAL, 100, 10, -1, 0, 2) new StatusMove(Moves.SCARY_FACE, Type.NORMAL, 100, 10, -1, 0, 2)
@ -7023,7 +7029,8 @@ export function initMoves() {
.windMove() .windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES), .target(MoveTarget.ALL_NEAR_ENEMIES),
new SelfStatusMove(Moves.DETECT, Type.FIGHTING, -1, 5, -1, 4, 2) new SelfStatusMove(Moves.DETECT, Type.FIGHTING, -1, 5, -1, 4, 2)
.attr(ProtectAttr), .attr(ProtectAttr)
.condition(failIfLastCondition),
new AttackMove(Moves.BONE_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 2) new AttackMove(Moves.BONE_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 2)
.attr(MultiHitAttr) .attr(MultiHitAttr)
.makesContact(false), .makesContact(false),
@ -7041,7 +7048,8 @@ export function initMoves() {
.attr(HitHealAttr) .attr(HitHealAttr)
.triageMove(), .triageMove(),
new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2) new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2)
.attr(ProtectAttr, BattlerTagType.ENDURING), .attr(ProtectAttr, BattlerTagType.ENDURING)
.condition(failIfLastCondition),
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2) new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
.attr(StatStageChangeAttr, [ Stat.ATK ], -2), .attr(StatStageChangeAttr, [ Stat.ATK ], -2),
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2) new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
@ -7788,7 +7796,8 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true), .attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true),
new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5) new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5)
.target(MoveTarget.USER_SIDE) .target(MoveTarget.USER_SIDE)
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true), .attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true)
.condition(failIfLastCondition),
new StatusMove(Moves.GUARD_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5) new StatusMove(Moves.GUARD_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
.attr(AverageStatsAttr, [ Stat.DEF, Stat.SPDEF ], "moveTriggers:sharedGuard"), .attr(AverageStatsAttr, [ Stat.DEF, Stat.SPDEF ], "moveTriggers:sharedGuard"),
new StatusMove(Moves.POWER_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5) new StatusMove(Moves.POWER_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
@ -7876,7 +7885,8 @@ export function initMoves() {
.attr(PositiveStatStagePowerAttr), .attr(PositiveStatStagePowerAttr),
new StatusMove(Moves.QUICK_GUARD, Type.FIGHTING, -1, 15, -1, 3, 5) new StatusMove(Moves.QUICK_GUARD, Type.FIGHTING, -1, 15, -1, 3, 5)
.target(MoveTarget.USER_SIDE) .target(MoveTarget.USER_SIDE)
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true), .attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true)
.condition(failIfLastCondition),
new SelfStatusMove(Moves.ALLY_SWITCH, Type.PSYCHIC, -1, 15, -1, 2, 5) new SelfStatusMove(Moves.ALLY_SWITCH, Type.PSYCHIC, -1, 15, -1, 2, 5)
.ignoresProtect() .ignoresProtect()
.unimplemented(), .unimplemented(),
@ -8047,7 +8057,8 @@ export function initMoves() {
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6) new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
.target(MoveTarget.USER_SIDE) .target(MoveTarget.USER_SIDE)
.attr(AddArenaTagAttr, ArenaTagType.MAT_BLOCK, 1, true, true) .attr(AddArenaTagAttr, ArenaTagType.MAT_BLOCK, 1, true, true)
.condition(new FirstMoveCondition()), .condition(new FirstMoveCondition())
.condition(failIfLastCondition),
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6) new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
.condition((user, target, move) => user.battleData.berriesEaten.length > 0), .condition((user, target, move) => user.battleData.berriesEaten.length > 0),
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6) new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6)
@ -8105,7 +8116,8 @@ export function initMoves() {
.triageMove(), .triageMove(),
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6) new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
.target(MoveTarget.USER_SIDE) .target(MoveTarget.USER_SIDE)
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true), .attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true)
.condition(failIfLastCondition),
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6) new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
.target(MoveTarget.ALL) .target(MoveTarget.ALL)
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)), .attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
@ -8130,7 +8142,8 @@ export function initMoves() {
.target(MoveTarget.BOTH_SIDES) .target(MoveTarget.BOTH_SIDES)
.unimplemented(), .unimplemented(),
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6) new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD), .attr(ProtectAttr, BattlerTagType.KINGS_SHIELD)
.condition(failIfLastCondition),
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6) new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
.attr(StatStageChangeAttr, [ Stat.ATK ], -1), .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6) new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
@ -8153,7 +8166,8 @@ export function initMoves() {
new AttackMove(Moves.MYSTICAL_FIRE, Type.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6) new AttackMove(Moves.MYSTICAL_FIRE, Type.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6)
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1), .attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
new SelfStatusMove(Moves.SPIKY_SHIELD, Type.GRASS, -1, 10, -1, 4, 6) new SelfStatusMove(Moves.SPIKY_SHIELD, Type.GRASS, -1, 10, -1, 4, 6)
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD), .attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD)
.condition(failIfLastCondition),
new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6) new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6)
.attr(StatStageChangeAttr, [ Stat.SPDEF ], 1) .attr(StatStageChangeAttr, [ Stat.SPDEF ], 1)
.target(MoveTarget.NEAR_ALLY), .target(MoveTarget.NEAR_ALLY),
@ -8349,7 +8363,8 @@ export function initMoves() {
new AttackMove(Moves.FIRST_IMPRESSION, Type.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7) new AttackMove(Moves.FIRST_IMPRESSION, Type.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7)
.condition(new FirstMoveCondition()), .condition(new FirstMoveCondition()),
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7) new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER), .attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER)
.condition(failIfLastCondition),
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7) new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
.makesContact(false), .makesContact(false),
@ -8592,6 +8607,7 @@ export function initMoves() {
/* Unused */ /* Unused */
new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8) new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8)
.attr(ProtectAttr) .attr(ProtectAttr)
.condition(failIfLastCondition)
.ignoresVirtual(), .ignoresVirtual(),
/* End Unused */ /* End Unused */
new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
@ -8770,7 +8786,8 @@ export function initMoves() {
.target(MoveTarget.USER_AND_ALLIES) .target(MoveTarget.USER_AND_ALLIES)
.ignoresProtect(), .ignoresProtect(),
new SelfStatusMove(Moves.OBSTRUCT, Type.DARK, 100, 10, -1, 4, 8) new SelfStatusMove(Moves.OBSTRUCT, Type.DARK, 100, 10, -1, 4, 8)
.attr(ProtectAttr, BattlerTagType.OBSTRUCT), .attr(ProtectAttr, BattlerTagType.OBSTRUCT)
.condition(failIfLastCondition),
new AttackMove(Moves.FALSE_SURRENDER, Type.DARK, MoveCategory.PHYSICAL, 80, -1, 10, -1, 0, 8), new AttackMove(Moves.FALSE_SURRENDER, Type.DARK, MoveCategory.PHYSICAL, 80, -1, 10, -1, 0, 8),
new AttackMove(Moves.METEOR_ASSAULT, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, 0, 8) new AttackMove(Moves.METEOR_ASSAULT, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, 0, 8)
.attr(RechargeAttr) .attr(RechargeAttr)
@ -9058,10 +9075,10 @@ export function initMoves() {
.attr(TeraBlastCategoryAttr) .attr(TeraBlastCategoryAttr)
.attr(TeraBlastTypeAttr) .attr(TeraBlastTypeAttr)
.attr(TeraBlastPowerAttr) .attr(TeraBlastPowerAttr)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR)) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR)),
.partial(),
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9) new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.SILK_TRAP), .attr(ProtectAttr, BattlerTagType.SILK_TRAP)
.condition(failIfLastCondition),
new AttackMove(Moves.AXE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 90, 10, 30, 0, 9) new AttackMove(Moves.AXE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 90, 10, 30, 0, 9)
.attr(MissEffectAttr, crashDamageFunc) .attr(MissEffectAttr, crashDamageFunc)
.attr(NoEffectAttr, crashDamageFunc) .attr(NoEffectAttr, crashDamageFunc)
@ -9253,7 +9270,8 @@ export function initMoves() {
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc) .attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
.attr(DoublePowerChanceAttr), .attr(DoublePowerChanceAttr),
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9) new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK), .attr(ProtectAttr, BattlerTagType.BURNING_BULWARK)
.condition(failIfLastCondition),
new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9) new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9)
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct? .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct?
new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9) new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9)

View File

@ -1720,7 +1720,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}; };
this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true); this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true);
this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? this.fusionSpecies.ability2 ? 2 : 1 : this.fusionSpecies.ability2 ? randAbilityIndex : 0); this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? 2 : this.fusionSpecies.ability2 !== this.fusionSpecies.ability1 ? randAbilityIndex : 0);
this.fusionShiny = this.shiny; this.fusionShiny = this.shiny;
this.fusionVariant = this.variant; this.fusionVariant = this.variant;
@ -2278,7 +2278,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!isTypeImmune) { if (!isTypeImmune) {
const levelMultiplier = (2 * source.level / 5 + 2); const levelMultiplier = (2 * source.level / 5 + 2);
const randomMultiplier = ((this.scene.randBattleSeedInt(16) + 85) / 100); const randomMultiplier = (this.randSeedIntRange(85, 100) / 100);
damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2) damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2)
* stabMultiplier.value * stabMultiplier.value
* typeMultiplier * typeMultiplier
@ -3448,12 +3448,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
fusionCanvas.remove(); fusionCanvas.remove();
} }
/**
* Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy
* <!-- @import "../battle".Battle -->
* This calls either {@linkcode BattleScene.randBattleSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle-scene.ts`
* which calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`,
* or it directly calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` if there is no current battle
*
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randSeedInt(range: integer, min: integer = 0): integer { randSeedInt(range: integer, min: integer = 0): integer {
return this.scene.currentBattle return this.scene.currentBattle
? this.scene.randBattleSeedInt(range, min) ? this.scene.randBattleSeedInt(range, min)
: Utils.randSeedInt(range, min); : Utils.randSeedInt(range, min);
} }
/**
* Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy
* @param min The minimum integer to generate
* @param max The maximum integer to generate
* @returns a random integer between {@linkcode min} and {@linkcode max} inclusive
*/
randSeedIntRange(min: integer, max: integer): integer { randSeedIntRange(min: integer, max: integer): integer {
return this.randSeedInt((max - min) + 1, min); return this.randSeedInt((max - min) + 1, min);
} }

View File

@ -12,6 +12,7 @@
"typeImmunityHeal": "{{abilityName}} von {{pokemonNameWithAffix}} füllte einige KP auf!", "typeImmunityHeal": "{{abilityName}} von {{pokemonNameWithAffix}} füllte einige KP auf!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} vermeidet Schaden mit {{abilityName}}!", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} vermeidet Schaden mit {{abilityName}}!",
"disguiseAvoidedDamage": "Die Tarnung von {{pokemonNameWithAffix}} ist aufgeflogen!!", "disguiseAvoidedDamage": "Die Tarnung von {{pokemonNameWithAffix}} ist aufgeflogen!!",
"fullHpResistType": "Der Panzer von {{pokemonNameWithAffix}} funkelt und verzerrt die Wechselwirkungen zwischen den Typen!",
"moveImmunity": "Es hat keine Wirkung auf {{pokemonNameWithAffix}}...", "moveImmunity": "Es hat keine Wirkung auf {{pokemonNameWithAffix}}...",
"reverseDrain": "{{pokemonNameWithAffix}} saugt Kloakensoße auf!", "reverseDrain": "{{pokemonNameWithAffix}} saugt Kloakensoße auf!",
"postDefendTypeChange": "{{abilityName}} von {{pokemonNameWithAffix}} macht es zu einem {{typeName}}-Typ!", "postDefendTypeChange": "{{abilityName}} von {{pokemonNameWithAffix}} macht es zu einem {{typeName}}-Typ!",
@ -51,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}} strahlt eine knisternde Aura aus!", "postSummonTeravolt": "{{pokemonNameWithAffix}} strahlt eine knisternde Aura aus!",
"postSummonDarkAura": "{{pokemonNameWithAffix}} strahlt eine dunkle Aura aus!", "postSummonDarkAura": "{{pokemonNameWithAffix}} strahlt eine dunkle Aura aus!",
"postSummonFairyAura": "{{pokemonNameWithAffix}} strahlt eine Feenaura aus!", "postSummonFairyAura": "{{pokemonNameWithAffix}} strahlt eine Feenaura aus!",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} kehrt die Wirkung aller Aura-Fähigkeiten um!",
"postSummonNeutralizingGas": "Reaktionsgas von {{pokemonNameWithAffix}} hat sich in der Umgebung ausgebreitet!", "postSummonNeutralizingGas": "Reaktionsgas von {{pokemonNameWithAffix}} hat sich in der Umgebung ausgebreitet!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} verfügt über zwei Fähigkeiten!", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} verfügt über zwei Fähigkeiten!",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} verfügt über zwei Fähigkeiten!", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} verfügt über zwei Fähigkeiten!",
@ -59,4 +61,4 @@
"postSummonTabletsOfRuin": "Unheilstafeln von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!", "postSummonTabletsOfRuin": "Unheilstafeln von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!",
"postSummonBeadsOfRuin": "Unheilsjuwelen von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!", "postSummonBeadsOfRuin": "Unheilsjuwelen von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!",
"preventBerryUse": "{{pokemonNameWithAffix}} kriegt vor Anspannung keine Beeren mehr runter!" "preventBerryUse": "{{pokemonNameWithAffix}} kriegt vor Anspannung keine Beeren mehr runter!"
} }

View File

@ -51,5 +51,8 @@
"renamePokemon": "Pokémon umbennenen", "renamePokemon": "Pokémon umbennenen",
"rename": "Umbenennen", "rename": "Umbenennen",
"nickname": "Spitzname", "nickname": "Spitzname",
"errorServerDown": "Ups! Es gab einen Fehler beim Versuch\nden Server zu kontaktieren\nLasse dieses Fenster offen\nDu wirst automatisch neu verbunden." "errorServerDown": "Ups! Es gab einen Fehler beim Versuch\nden Server zu kontaktieren\nLasse dieses Fenster offen\nDu wirst automatisch neu verbunden.",
} "noSaves": "Du hast keine gespeicherten Dateien!",
"tooManySaves": "Du hast zu viele gespeicherte Dateien!"
}

View File

@ -52,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!", "postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!",
"postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!", "postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!",
"postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!", "postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
"postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!", "postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!",

View File

@ -51,5 +51,7 @@
"renamePokemon": "Rename Pokémon", "renamePokemon": "Rename Pokémon",
"rename": "Rename", "rename": "Rename",
"nickname": "Nickname", "nickname": "Nickname",
"errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect." "errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect.",
"noSaves": "You don't have any save files on record!",
"tooManySaves": "You have too many save files on record!"
} }

View File

@ -12,6 +12,7 @@
"blockItemTheft": "¡{{pokemonNameWithAffix}} evitó el robo gracias a {{abilityName}}!", "blockItemTheft": "¡{{pokemonNameWithAffix}} evitó el robo gracias a {{abilityName}}!",
"typeImmunityHeal": "¡{{pokemonNameWithAffix}} restauró algunos de sus PS gracias a {{abilityName}}!", "typeImmunityHeal": "¡{{pokemonNameWithAffix}} restauró algunos de sus PS gracias a {{abilityName}}!",
"nonSuperEffectiveImmunity": "¡{{pokemonNameWithAffix}} evitó el daño gracias a {{abilityName}}!", "nonSuperEffectiveImmunity": "¡{{pokemonNameWithAffix}} evitó el daño gracias a {{abilityName}}!",
"fullHpResistType": "{{pokemonNameWithAffix}} made its shell gleam!\nIt's distorting type matchups!",
"moveImmunity": "¡No afecta a {{pokemonNameWithAffix}}!", "moveImmunity": "¡No afecta a {{pokemonNameWithAffix}}!",
"reverseDrain": "¡{{pokemonNameWithAffix}} absorbió lodo líquido!", "reverseDrain": "¡{{pokemonNameWithAffix}} absorbió lodo líquido!",
"postDefendTypeChange": "¡{{abilityName}} de {{pokemonNameWithAffix}} cambió a tipo {{typeName}}!", "postDefendTypeChange": "¡{{abilityName}} de {{pokemonNameWithAffix}} cambió a tipo {{typeName}}!",
@ -51,6 +52,7 @@
"postSummonTeravolt": "¡{{pokemonNameWithAffix}} irradia un aura chisporroteante!", "postSummonTeravolt": "¡{{pokemonNameWithAffix}} irradia un aura chisporroteante!",
"postSummonDarkAura": "¡{{pokemonNameWithAffix}} irradia un aura oscura!", "postSummonDarkAura": "¡{{pokemonNameWithAffix}} irradia un aura oscura!",
"postSummonFairyAura": "¡{{pokemonNameWithAffix}} irradia un aura feérica!", "postSummonFairyAura": "¡{{pokemonNameWithAffix}} irradia un aura feérica!",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
"postSummonNeutralizingGas": "¡El Gas Reactivo de {{pokemonNameWithAffix}} se propaga por toda la zona!", "postSummonNeutralizingGas": "¡El Gas Reactivo de {{pokemonNameWithAffix}} se propaga por toda la zona!",
"postSummonAsOneGlastrier": "¡{{pokemonNameWithAffix}} tiene dos Habilidades!", "postSummonAsOneGlastrier": "¡{{pokemonNameWithAffix}} tiene dos Habilidades!",
"postSummonAsOneSpectrier": "¡{{pokemonNameWithAffix}} tiene dos Habilidades!", "postSummonAsOneSpectrier": "¡{{pokemonNameWithAffix}} tiene dos Habilidades!",

View File

@ -51,5 +51,7 @@
"renamePokemon": "Renombrar Pokémon.", "renamePokemon": "Renombrar Pokémon.",
"rename": "Renombrar", "rename": "Renombrar",
"nickname": "Apodo", "nickname": "Apodo",
"errorServerDown": "¡Ups! Ha habido un problema al contactar con el servidor.\n\nPuedes mantener esta ventana abierta, el juego se reconectará automáticamente." "errorServerDown": "¡Ups! Ha habido un problema al contactar con el servidor.\n\nPuedes mantener esta ventana abierta, el juego se reconectará automáticamente.",
"noSaves": "You don't have any save files on record!",
"tooManySaves": "You have too many save files on record!"
} }

View File

@ -11,6 +11,7 @@
"blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nempêche son objet dêtre volé !", "blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nempêche son objet dêtre volé !",
"typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaure un peu ses PV !", "typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaure un peu ses PV !",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} évite\nles dégâts avec {{abilityName}} !", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} évite\nles dégâts avec {{abilityName}} !",
"fullHpResistType": "{{pokemonNameWithAffix}} fait briller sa carapace\net fausse les affinités de type !",
"disguiseAvoidedDamage": "Le déguisement de {{pokemonNameWithAffix}}\ntombe !", "disguiseAvoidedDamage": "Le déguisement de {{pokemonNameWithAffix}}\ntombe !",
"moveImmunity": "Ça naffecte pas {{pokemonNameWithAffix}}…", "moveImmunity": "Ça naffecte pas {{pokemonNameWithAffix}}…",
"reverseDrain": "{{pokemonNameWithAffix}} aspire\nle suintement !", "reverseDrain": "{{pokemonNameWithAffix}} aspire\nle suintement !",
@ -51,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}} dégage\nune aura électrique instable !", "postSummonTeravolt": "{{pokemonNameWithAffix}} dégage\nune aura électrique instable !",
"postSummonDarkAura": "{{pokemonNameWithAffix}} dégage\nune aura ténébreuse !", "postSummonDarkAura": "{{pokemonNameWithAffix}} dégage\nune aura ténébreuse !",
"postSummonFairyAura": "{{pokemonNameWithAffix}} dégage\nune aura enchanteresse !", "postSummonFairyAura": "{{pokemonNameWithAffix}} dégage\nune aura enchanteresse !",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
"postSummonNeutralizingGas": "Le gaz inhibiteur {{pokemonNameWithAffix}}\nenvahit les lieux !", "postSummonNeutralizingGas": "Le gaz inhibiteur {{pokemonNameWithAffix}}\nenvahit les lieux !",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}}\na deux talents !", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}}\na deux talents !",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}}\na deux talents !", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}}\na deux talents !",

View File

@ -51,5 +51,7 @@
"renamePokemon": "Renommer le Pokémon", "renamePokemon": "Renommer le Pokémon",
"rename": "Renommer", "rename": "Renommer",
"nickname": "Surnom", "nickname": "Surnom",
"errorServerDown": "Oupsi ! Un problème de connexion au serveur est survenu.\n\nVous pouvez garder cette fenêtre ouverte,\nle jeu se reconnectera automatiquement." "errorServerDown": "Oupsi ! Un problème de connexion au serveur est survenu.\n\nVous pouvez garder cette fenêtre ouverte,\nle jeu se reconnectera automatiquement.",
"noSaves": "Vous navez aucune sauvegarde enregistrée !",
"tooManySaves": "Vous avez trop de sauvegardes enregistrées !"
} }

View File

@ -11,6 +11,7 @@
"blockItemTheft": "{{abilityName}} di {{pokemonNameWithAffix}}\nlo rende immune ai furti!", "blockItemTheft": "{{abilityName}} di {{pokemonNameWithAffix}}\nlo rende immune ai furti!",
"typeImmunityHeal": "{{pokemonName}} recupera alcuni PS\ncon {{abilityName}}!", "typeImmunityHeal": "{{pokemonName}} recupera alcuni PS\ncon {{abilityName}}!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evita il colpo\ncon {{abilityName}}!", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evita il colpo\ncon {{abilityName}}!",
"fullHpResistType": "{{pokemonNameWithAffix}} made its shell gleam!\nIt's distorting type matchups!",
"disguiseAvoidedDamage": "{{pokemonNameWithAffix}} è stato smascherato!", "disguiseAvoidedDamage": "{{pokemonNameWithAffix}} è stato smascherato!",
"moveImmunity": "Non ha effetto su {{pokemonNameWithAffix}}!", "moveImmunity": "Non ha effetto su {{pokemonNameWithAffix}}!",
"reverseDrain": "{{pokemonNameWithAffix}} ha assorbito la melma!", "reverseDrain": "{{pokemonNameWithAffix}} ha assorbito la melma!",
@ -51,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}} emana unaura repulsiva!", "postSummonTeravolt": "{{pokemonNameWithAffix}} emana unaura repulsiva!",
"postSummonDarkAura": "Labilità Auratetra di {{pokemonNameWithAffix}} è attiva.", "postSummonDarkAura": "Labilità Auratetra di {{pokemonNameWithAffix}} è attiva.",
"postSummonFairyAura": "Labilità Aurafolletto di {{pokemonNameWithAffix}} è attiva.", "postSummonFairyAura": "Labilità Aurafolletto di {{pokemonNameWithAffix}} è attiva.",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
"postSummonNeutralizingGas": "Il Gas Reagente di {{pokemonNameWithAffix}}\nsi diffonde tuttintorno!", "postSummonNeutralizingGas": "Il Gas Reagente di {{pokemonNameWithAffix}}\nsi diffonde tuttintorno!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} ha due abilità!", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} ha due abilità!",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} ha due abilità!", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} ha due abilità!",
@ -59,4 +61,4 @@
"postSummonTabletsOfRuin": "La/l'{{statName}} dei Pokémon intorno si indebolisce a causa\ndell'abilità Amuleto Nefasto di {{pokemonNameWithAffix}}!", "postSummonTabletsOfRuin": "La/l'{{statName}} dei Pokémon intorno si indebolisce a causa\ndell'abilità Amuleto Nefasto di {{pokemonNameWithAffix}}!",
"postSummonBeadsOfRuin": "La/l'{{statName}} dei Pokémon intorno si indebolisce a causa\ndell'abilità Monile Nefasto di {{pokemonNameWithAffix}}!", "postSummonBeadsOfRuin": "La/l'{{statName}} dei Pokémon intorno si indebolisce a causa\ndell'abilità Monile Nefasto di {{pokemonNameWithAffix}}!",
"preventBerryUse": "{{pokemonNameWithAffix}} non riesce a\nmangiare le bacche per l'agitazione!" "preventBerryUse": "{{pokemonNameWithAffix}} non riesce a\nmangiare le bacche per l'agitazione!"
} }

View File

@ -44,7 +44,7 @@
"moveNotImplemented": "{{moveName}} non è ancora implementata e non può essere selezionata.", "moveNotImplemented": "{{moveName}} non è ancora implementata e non può essere selezionata.",
"moveNoPP": "Non ci sono PP rimanenti\nper questa mossa!", "moveNoPP": "Non ci sono PP rimanenti\nper questa mossa!",
"moveDisabled": "{{moveName}} è disabilitata!", "moveDisabled": "{{moveName}} è disabilitata!",
"disableInterruptedMove": "{{pokemonNameWithAffix}}'s {{moveName}}\nis disabled!", "disableInterruptedMove": "La mossa {{moveName}} di\n{{pokemonNameWithAffix}} è bloccata!",
"noPokeballForce": "Una forza misteriosa\nimpedisce l'uso delle Poké Ball.", "noPokeballForce": "Una forza misteriosa\nimpedisce l'uso delle Poké Ball.",
"noPokeballTrainer": "Non puoi catturare\nPokémon di altri allenatori!", "noPokeballTrainer": "Non puoi catturare\nPokémon di altri allenatori!",
"noPokeballMulti": "Puoi lanciare una Poké Ball\nsolo quando rimane un singolo Pokémon!", "noPokeballMulti": "Puoi lanciare una Poké Ball\nsolo quando rimane un singolo Pokémon!",

View File

@ -68,6 +68,6 @@
"cursedOnAdd": "{{pokemonNameWithAffix}} ha sacrificato metà dei suoi PS per\nlanciare una maledizione su {{pokemonName}}!", "cursedOnAdd": "{{pokemonNameWithAffix}} ha sacrificato metà dei suoi PS per\nlanciare una maledizione su {{pokemonName}}!",
"cursedLapse": "{{pokemonNameWithAffix}} subisce la maledizione!", "cursedLapse": "{{pokemonNameWithAffix}} subisce la maledizione!",
"stockpilingOnAdd": "{{pokemonNameWithAffix}} ha usato Accumulo per la\n{{stockpiledCount}}ª volta!", "stockpilingOnAdd": "{{pokemonNameWithAffix}} ha usato Accumulo per la\n{{stockpiledCount}}ª volta!",
"disabledOnAdd": "{{pokemonNameWithAffix}}'s {{moveName}}\nwas disabled!", "disabledOnAdd": "La mossa {{moveName}} di\n{{pokemonNameWithAffix}} è stata bloccata!",
"disabledLapse": "{{pokemonNameWithAffix}}'s {{moveName}}\nis no longer disabled." "disabledLapse": "La mossa {{moveName}} di\n{{pokemonNameWithAffix}} non è più bloccata!"
} }

View File

@ -51,5 +51,7 @@
"renamePokemon": "Rinomina un Pokémon", "renamePokemon": "Rinomina un Pokémon",
"rename": "Rinomina", "rename": "Rinomina",
"nickname": "Nickname", "nickname": "Nickname",
"errorServerDown": "Poffarbacco! C'è stato un errore nella comunicazione col server.\n\nPuoi lasciare questa finestra aperta,\nil gioco si riconnetterà automaticamente." "errorServerDown": "Poffarbacco! C'è stato un errore nella comunicazione col server.\n\nPuoi lasciare questa finestra aperta,\nil gioco si riconnetterà automaticamente.",
} "noSaves": "You don't have any save files on record!",
"tooManySaves": "You have too many save files on record!"
}

View File

@ -12,6 +12,7 @@
"blockItemTheft": "{{pokemonNameWithAffix}}の {{abilityName}}で\n道具を うばわれない", "blockItemTheft": "{{pokemonNameWithAffix}}の {{abilityName}}で\n道具を うばわれない",
"typeImmunityHeal": "{{pokemonNameWithAffix}}は {{abilityName}}で\n体力を 回復した", "typeImmunityHeal": "{{pokemonNameWithAffix}}は {{abilityName}}で\n体力を 回復した",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}は {{abilityName}}で\nダメージを 受けない。", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}は {{abilityName}}で\nダメージを 受けない。",
"fullHpResistType": "{{pokemonNameWithAffix}} made its shell gleam!\nIt's distorting type matchups!",
"moveImmunity": "{{pokemonNameWithAffix}}には\n効果が ないようだ…", "moveImmunity": "{{pokemonNameWithAffix}}には\n効果が ないようだ…",
"reverseDrain": "{{pokemonNameWithAffix}}は\nヘドロえきを 吸い取った", "reverseDrain": "{{pokemonNameWithAffix}}は\nヘドロえきを 吸い取った",
"postDefendTypeChange": "{{pokemonNameWithAffix}}は {{abilityName}}で\n{{typeName}}タイプに なった!", "postDefendTypeChange": "{{pokemonNameWithAffix}}は {{abilityName}}で\n{{typeName}}タイプに なった!",
@ -51,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}}は\n弾(はじ)ける オーラを 放っている!", "postSummonTeravolt": "{{pokemonNameWithAffix}}は\n弾(はじ)ける オーラを 放っている!",
"postSummonDarkAura": "{{pokemonNameWithAffix}}は\nダークオーラを 放っている", "postSummonDarkAura": "{{pokemonNameWithAffix}}は\nダークオーラを 放っている",
"postSummonFairyAura": "{{pokemonNameWithAffix}}は\nフェアリーオーラを 放っている", "postSummonFairyAura": "{{pokemonNameWithAffix}}は\nフェアリーオーラを 放っている",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
"postSummonNeutralizingGas": "あたりに かがくへんかガスが 充満した!", "postSummonNeutralizingGas": "あたりに かがくへんかガスが 充満した!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}}は\nふたつの 特性を あわせ持つ", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}}は\nふたつの 特性を あわせ持つ",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}}は\nふたつの 特性を あわせ持つ", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}}は\nふたつの 特性を あわせ持つ",

View File

@ -51,5 +51,7 @@
"renamePokemon": "ニックネームを変える", "renamePokemon": "ニックネームを変える",
"rename": "変える", "rename": "変える",
"nickname": "ニックネーム", "nickname": "ニックネーム",
"errorServerDown": "おや!\nサーバーとの 接続中に 問題が 発生しました。\nゲームは 自動的に 再接続されます から\nウィンドウは 開いたままに しておいても よろしいです。" "errorServerDown": "おや!\nサーバーとの 接続中に 問題が 発生しました。\nゲームは 自動的に 再接続されます から\nウィンドウは 開いたままに しておいても よろしいです。",
"noSaves": "You don't have any save files on record!",
"tooManySaves": "You have too many save files on record!"
} }

View File

@ -12,6 +12,7 @@
"blockItemTheft": "{{pokemonNameWithAffix}}의 {{abilityName}}에 의해\n도구를 빼앗기지 않는다!", "blockItemTheft": "{{pokemonNameWithAffix}}의 {{abilityName}}에 의해\n도구를 빼앗기지 않는다!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}[[는]]\n{{abilityName}}[[로]] 체력이 회복되었다!", "typeImmunityHeal": "{{pokemonNameWithAffix}}[[는]]\n{{abilityName}}[[로]] 체력이 회복되었다!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}[[는]] {{abilityName}} 때문에\n데미지를 입지 않는다!", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}[[는]] {{abilityName}} 때문에\n데미지를 입지 않는다!",
"fullHpResistType": "{{pokemonNameWithAffix}}[[는]] 등껍질을 빛나게 하여\n타입 상성을 왜곡시켰다!!",
"moveImmunity": "{{pokemonNameWithAffix}}에게는\n효과가 없는 것 같다...", "moveImmunity": "{{pokemonNameWithAffix}}에게는\n효과가 없는 것 같다...",
"reverseDrain": "{{pokemonNameWithAffix}}[[는]]\n해감액을 흡수했다!", "reverseDrain": "{{pokemonNameWithAffix}}[[는]]\n해감액을 흡수했다!",
"postDefendTypeChange": "{{pokemonNameWithAffix}}[[는]] {{abilityName}}[[로]] 인해\n{{typeName}}타입이 됐다!", "postDefendTypeChange": "{{pokemonNameWithAffix}}[[는]] {{abilityName}}[[로]] 인해\n{{typeName}}타입이 됐다!",
@ -51,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}}[[는]]\n세차게 튀는 오라를 발산하고 있다!", "postSummonTeravolt": "{{pokemonNameWithAffix}}[[는]]\n세차게 튀는 오라를 발산하고 있다!",
"postSummonDarkAura": "{{pokemonNameWithAffix}}[[는]]\n다크오라를 발산하고 있다!", "postSummonDarkAura": "{{pokemonNameWithAffix}}[[는]]\n다크오라를 발산하고 있다!",
"postSummonFairyAura": "{{pokemonNameWithAffix}}[[는]]\n페어리오라를 발산하고 있다!", "postSummonFairyAura": "{{pokemonNameWithAffix}}[[는]]\n페어리오라를 발산하고 있다!",
"postSummonAuraBreak": "{{pokemonNameWithAffix}}[[는]]\n모든 오라를 제압한다!",
"postSummonNeutralizingGas": "주위가 화학변화가스로 가득 찼다!", "postSummonNeutralizingGas": "주위가 화학변화가스로 가득 찼다!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}}[[는]]\n두 가지 특성을 겸비한다!", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}}[[는]]\n두 가지 특성을 겸비한다!",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}}[[는]]\n두 가지 특성을 겸비한다!", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}}[[는]]\n두 가지 특성을 겸비한다!",
@ -59,4 +61,4 @@
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}의 재앙의목간에 의해\n주위의 {{statName}}[[가]] 약해졌다!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}의 재앙의목간에 의해\n주위의 {{statName}}[[가]] 약해졌다!",
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}의 재앙의구슬에 의해\n주위의 {{statName}}[[가]] 약해졌다!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}의 재앙의구슬에 의해\n주위의 {{statName}}[[가]] 약해졌다!",
"preventBerryUse": "{{pokemonNameWithAffix}}[[는]] 긴장해서\n나무열매를 먹을 수 없게 되었다!" "preventBerryUse": "{{pokemonNameWithAffix}}[[는]] 긴장해서\n나무열매를 먹을 수 없게 되었다!"
} }

View File

@ -51,5 +51,7 @@
"renamePokemon": "포켓몬의 닉네임은?", "renamePokemon": "포켓몬의 닉네임은?",
"rename": "닉네임 바꾸기", "rename": "닉네임 바꾸기",
"nickname": "닉네임", "nickname": "닉네임",
"errorServerDown": "서버 연결 중 문제가 발생했습니다.\n\n이 창을 종료하지 않고 두면,\n게임은 자동으로 재접속됩니다." "errorServerDown": "서버 연결 중 문제가 발생했습니다.\n\n이 창을 종료하지 않고 두면,\n게임은 자동으로 재접속됩니다.",
"noSaves": "기기에 세이브 파일이 없습니다!",
"tooManySaves": "기기에 세이브 파일이 너무 많습니다!"
} }

View File

@ -12,6 +12,7 @@
"blockItemTheft": "{{pokemonNameWithAffix}}的{{abilityName}}\n阻止了对方夺取道具", "blockItemTheft": "{{pokemonNameWithAffix}}的{{abilityName}}\n阻止了对方夺取道具",
"typeImmunityHeal": "{{pokemonNameWithAffix}}因{{abilityName}}\n回复了少许HP", "typeImmunityHeal": "{{pokemonNameWithAffix}}因{{abilityName}}\n回复了少许HP",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}因{{abilityName}}\n避免了伤害", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}因{{abilityName}}\n避免了伤害",
"fullHpResistType": "{{pokemonNameWithAffix}} made its shell gleam!\nIt's distorting type matchups!",
"moveImmunity": "对{{pokemonNameWithAffix}}没有效果!", "moveImmunity": "对{{pokemonNameWithAffix}}没有效果!",
"reverseDrain": "{{pokemonNameWithAffix}}\n吸到了污泥浆", "reverseDrain": "{{pokemonNameWithAffix}}\n吸到了污泥浆",
"postDefendTypeChange": "{{pokemonNameWithAffix}}因{{abilityName}}\n变成了{{typeName}}属性!", "postDefendTypeChange": "{{pokemonNameWithAffix}}因{{abilityName}}\n变成了{{typeName}}属性!",
@ -51,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}}\n正在释放溅射气场", "postSummonTeravolt": "{{pokemonNameWithAffix}}\n正在释放溅射气场",
"postSummonDarkAura": "{{pokemonNameWithAffix}}\n正在释放暗黑气场", "postSummonDarkAura": "{{pokemonNameWithAffix}}\n正在释放暗黑气场",
"postSummonFairyAura": "{{pokemonNameWithAffix}}\n正在释放妖精气场", "postSummonFairyAura": "{{pokemonNameWithAffix}}\n正在释放妖精气场",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
"postSummonNeutralizingGas": "周围充满了\n{{pokemonNameWithAffix}}的化学变化气体!", "postSummonNeutralizingGas": "周围充满了\n{{pokemonNameWithAffix}}的化学变化气体!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}}\n同时拥有了两种特性", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}}\n同时拥有了两种特性",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}}\n同时拥有了两种特性", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}}\n同时拥有了两种特性",
@ -59,4 +61,4 @@
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}的灾祸之简\n令周围的宝可梦的{{statName}}减弱了!", "postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}的灾祸之简\n令周围的宝可梦的{{statName}}减弱了!",
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}的灾祸之玉\n令周围的宝可梦的{{statName}}减弱了!", "postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}的灾祸之玉\n令周围的宝可梦的{{statName}}减弱了!",
"preventBerryUse": "{{pokemonNameWithAffix}}因太紧张\n而无法食用树果" "preventBerryUse": "{{pokemonNameWithAffix}}因太紧张\n而无法食用树果"
} }

View File

@ -51,5 +51,7 @@
"renamePokemon": "给宝可梦起名", "renamePokemon": "给宝可梦起名",
"rename": "起名", "rename": "起名",
"nickname": "昵称", "nickname": "昵称",
"errorServerDown": "糟糕!访问服务器时发生了错误。\n\n你可以保持页面开启\n游戏会自动重新连接。" "errorServerDown": "糟糕!访问服务器时发生了错误。\n\n你可以保持页面开启\n游戏会自动重新连接。",
} "noSaves": "You don't have any save files on record!",
"tooManySaves": "You have too many save files on record!"
}

View File

@ -377,16 +377,16 @@ export class MoveEffectPhase extends PokemonPhase {
return false; return false;
} }
const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user!, target); // TODO: is the bang correct here? const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user, target);
if (moveAccuracy === -1) { if (moveAccuracy === -1) {
return true; return true;
} }
const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove()); const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove());
const rand = user.randSeedInt(100, 1); const rand = user.randSeedInt(100);
return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct? return rand < (moveAccuracy * accuracyMultiplier);
} }
/** Returns the {@linkcode Pokemon} using this phase's invoked move */ /** Returns the {@linkcode Pokemon} using this phase's invoked move */

View File

@ -857,6 +857,14 @@ export class GameData {
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct? const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
// TODO: Remove this block after save migration is implemented
if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"];
delete settings["REROLL_TARGET"];
localStorage.setItem("settings", JSON.stringify(settings));
}
// End of block to remove
for (const setting of Object.keys(settings)) { for (const setting of Object.keys(settings)) {
setSetting(this.scene, setting, settings[setting]); setSetting(this.scene, setting, settings[setting]);
} }

View File

@ -1,5 +1,4 @@
import { allMoves } from "#app/data/move"; import { allMoves } from "#app/data/move";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -33,31 +32,45 @@ describe("Abilities - Aura Break", () => {
game.override.enemySpecies(Species.SHUCKLE); game.override.enemySpecies(Species.SHUCKLE);
}); });
it("reverses the effect of fairy aura", async () => { it("reverses the effect of Fairy Aura", async () => {
const moveToCheck = allMoves[Moves.MOONBLAST]; const moveToCheck = allMoves[Moves.MOONBLAST];
const basePower = moveToCheck.power; const basePower = moveToCheck.power;
game.override.ability(Abilities.FAIRY_AURA); game.override.ability(Abilities.FAIRY_AURA);
vi.spyOn(moveToCheck, "calculateBattlePower"); vi.spyOn(moveToCheck, "calculateBattlePower");
await game.startBattle([Species.PIKACHU]); await game.classicMode.startBattle([Species.PIKACHU]);
game.move.select(Moves.MOONBLAST); game.move.select(Moves.MOONBLAST);
await game.phaseInterceptor.to(MoveEffectPhase); await game.phaseInterceptor.to("MoveEffectPhase");
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier)); expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier));
}); });
it("reverses the effect of dark aura", async () => { it("reverses the effect of Dark Aura", async () => {
const moveToCheck = allMoves[Moves.DARK_PULSE]; const moveToCheck = allMoves[Moves.DARK_PULSE];
const basePower = moveToCheck.power; const basePower = moveToCheck.power;
game.override.ability(Abilities.DARK_AURA); game.override.ability(Abilities.DARK_AURA);
vi.spyOn(moveToCheck, "calculateBattlePower"); vi.spyOn(moveToCheck, "calculateBattlePower");
await game.startBattle([Species.PIKACHU]); await game.classicMode.startBattle([Species.PIKACHU]);
game.move.select(Moves.DARK_PULSE); game.move.select(Moves.DARK_PULSE);
await game.phaseInterceptor.to(MoveEffectPhase); await game.phaseInterceptor.to("MoveEffectPhase");
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier)); expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier));
}); });
it("has no effect if neither Fairy Aura nor Dark Aura are present", async () => {
const moveToCheck = allMoves[Moves.MOONBLAST];
const basePower = moveToCheck.power;
game.override.ability(Abilities.BALL_FETCH);
vi.spyOn(moveToCheck, "calculateBattlePower");
await game.classicMode.startBattle([Species.PIKACHU]);
game.move.select(Moves.MOONBLAST);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower);
});
}); });

View File

@ -0,0 +1,101 @@
import { BattlerIndex } from "#app/battle";
import { Type } from "#app/data/type";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species";
import { Abilities } from "#enums/abilities";
import GameManager from "#test/utils/gameManager";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Dragon Cheer", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const TIMEOUT = 20 * 1000;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("double")
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(SPLASH_ONLY)
.enemyLevel(20)
.moveset([Moves.DRAGON_CHEER, Moves.TACKLE, Moves.SPLASH]);
});
it("increases the user's allies' critical hit ratio by one stage", async () => {
await game.classicMode.startBattle([Species.DRAGONAIR, Species.MAGIKARP]);
const enemy = game.scene.getEnemyField()[0];
vi.spyOn(enemy, "getCritStage");
game.move.select(Moves.DRAGON_CHEER, 0);
game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
// After Tackle
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
}, TIMEOUT);
it("increases the user's Dragon-type allies' critical hit ratio by two stages", async () => {
await game.classicMode.startBattle([Species.MAGIKARP, Species.DRAGONAIR]);
const enemy = game.scene.getEnemyField()[0];
vi.spyOn(enemy, "getCritStage");
game.move.select(Moves.DRAGON_CHEER, 0);
game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
// After Tackle
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy.getCritStage).toHaveReturnedWith(2); // getCritStage is called on defender
}, TIMEOUT);
it("applies the effect based on the allies' type upon use of the move, and do not change if the allies' type changes later in battle", async () => {
await game.classicMode.startBattle([Species.DRAGONAIR, Species.MAGIKARP]);
const magikarp = game.scene.getPlayerField()[1];
const enemy = game.scene.getEnemyField()[0];
vi.spyOn(enemy, "getCritStage");
game.move.select(Moves.DRAGON_CHEER, 0);
game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
// After Tackle
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
await game.toNextTurn();
// Change Magikarp's type to Dragon
vi.spyOn(magikarp, "getTypes").mockReturnValue([Type.DRAGON]);
expect(magikarp.getTypes()).toEqual([Type.DRAGON]);
game.move.select(Moves.SPLASH, 0);
game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to("MoveEndPhase");
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
}, TIMEOUT);
});

View File

@ -1,13 +1,12 @@
import { allMoves } from "#app/data/move"; import { allMoves } from "#app/data/move";
import { Abilities } from "#app/enums/abilities"; import { Abilities } from "#app/enums/abilities";
import { DamagePhase } from "#app/phases/damage-phase"; import { Moves } from "#app/enums/moves";
import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Species } from "#app/enums/species";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
const TIMEOUT = 20 * 1000;
describe("Moves - Glaive Rush", () => { describe("Moves - Glaive Rush", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -25,131 +24,142 @@ describe("Moves - Glaive Rush", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleType("single"); game.override
game.override.disableCrits(); .battleType("single")
game.override.enemySpecies(Species.MAGIKARP); .disableCrits()
game.override.enemyAbility(Abilities.BALL_FETCH); .enemySpecies(Species.MAGIKARP)
game.override.enemyMoveset(Array(4).fill(Moves.GLAIVE_RUSH)); .enemyAbility(Abilities.BALL_FETCH)
game.override.starterSpecies(Species.KLINK); .enemyMoveset(Array(4).fill(Moves.GLAIVE_RUSH))
game.override.ability(Abilities.UNNERVE); .starterSpecies(Species.KLINK)
game.override.passiveAbility(Abilities.FUR_COAT); .ability(Abilities.BALL_FETCH)
game.override.moveset([Moves.SHADOW_SNEAK, Moves.AVALANCHE, Moves.SPLASH, Moves.GLAIVE_RUSH]); .moveset([Moves.SHADOW_SNEAK, Moves.AVALANCHE, Moves.SPLASH, Moves.GLAIVE_RUSH]);
}); });
it("takes double damage from attacks", async () => { it("takes double damage from attacks", async () => {
await game.startBattle(); await game.classicMode.startBattle();
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.hp = 1000; enemy.hp = 1000;
vi.spyOn(game.scene, "randBattleSeedInt").mockReturnValue(0);
game.move.select(Moves.SHADOW_SNEAK); game.move.select(Moves.SHADOW_SNEAK);
await game.phaseInterceptor.to(DamagePhase); await game.phaseInterceptor.to("DamagePhase");
const damageDealt = 1000 - enemy.hp; const damageDealt = 1000 - enemy.hp;
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.SHADOW_SNEAK); game.move.select(Moves.SHADOW_SNEAK);
await game.phaseInterceptor.to(DamagePhase); await game.phaseInterceptor.to("DamagePhase");
expect(enemy.hp).toBeLessThanOrEqual(1001 - (damageDealt * 3)); expect(enemy.hp).toBeLessThanOrEqual(1001 - (damageDealt * 3));
}, 5000); // TODO: revert back to 20s }, TIMEOUT);
it("always gets hit by attacks", async () => { it("always gets hit by attacks", async () => {
await game.startBattle(); await game.classicMode.startBattle();
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.hp = 1000; enemy.hp = 1000;
allMoves[Moves.AVALANCHE].accuracy = 0; allMoves[Moves.AVALANCHE].accuracy = 0;
game.move.select(Moves.AVALANCHE); game.move.select(Moves.AVALANCHE);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy.hp).toBeLessThan(1000); expect(enemy.hp).toBeLessThan(1000);
}, 20000); }, TIMEOUT);
it("interacts properly with multi-lens", async () => { it("interacts properly with multi-lens", async () => {
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]); game.override
game.override.enemyMoveset(Array(4).fill(Moves.AVALANCHE)); .startingHeldItems([{ name: "MULTI_LENS", count: 2 }])
await game.startBattle(); .enemyMoveset(Array(4).fill(Moves.AVALANCHE));
await game.classicMode.startBattle();
const player = game.scene.getPlayerPokemon()!; const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.hp = 1000; enemy.hp = 1000;
player.hp = 1000; player.hp = 1000;
allMoves[Moves.AVALANCHE].accuracy = 0; allMoves[Moves.AVALANCHE].accuracy = 0;
game.move.select(Moves.GLAIVE_RUSH); game.move.select(Moves.GLAIVE_RUSH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBeLessThan(1000); expect(player.hp).toBeLessThan(1000);
player.hp = 1000; player.hp = 1000;
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(1000); expect(player.hp).toBe(1000);
}, 20000); }, TIMEOUT);
it("secondary effects only last until next move", async () => { it("secondary effects only last until next move", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK)); game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK));
await game.startBattle(); await game.classicMode.startBattle();
const player = game.scene.getPlayerPokemon()!; const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.hp = 1000; enemy.hp = 1000;
player.hp = 1000; player.hp = 1000;
allMoves[Moves.SHADOW_SNEAK].accuracy = 0; allMoves[Moves.SHADOW_SNEAK].accuracy = 0;
game.move.select(Moves.GLAIVE_RUSH); game.move.select(Moves.GLAIVE_RUSH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(1000); expect(player.hp).toBe(1000);
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
const damagedHp = player.hp; const damagedHp = player.hp;
expect(player.hp).toBeLessThan(1000); expect(player.hp).toBeLessThan(1000);
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(damagedHp); expect(player.hp).toBe(damagedHp);
}, 20000); }, TIMEOUT);
it("secondary effects are removed upon switching", async () => { it("secondary effects are removed upon switching", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK)); game.override
game.override.starterSpecies(0); .enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK))
await game.startBattle([Species.KLINK, Species.FEEBAS]); .starterSpecies(0);
await game.classicMode.startBattle([Species.KLINK, Species.FEEBAS]);
const player = game.scene.getPlayerPokemon()!; const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.hp = 1000; enemy.hp = 1000;
allMoves[Moves.SHADOW_SNEAK].accuracy = 0; allMoves[Moves.SHADOW_SNEAK].accuracy = 0;
game.move.select(Moves.GLAIVE_RUSH); game.move.select(Moves.GLAIVE_RUSH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(player.getMaxHp()); expect(player.hp).toBe(player.getMaxHp());
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
expect(player.hp).toBe(player.getMaxHp()); expect(player.hp).toBe(player.getMaxHp());
}, 20000); }, TIMEOUT);
it("secondary effects don't activate if move fails", async () => { it("secondary effects don't activate if move fails", async () => {
game.override.moveset([Moves.SHADOW_SNEAK, Moves.PROTECT, Moves.SPLASH, Moves.GLAIVE_RUSH]); game.override.moveset([Moves.SHADOW_SNEAK, Moves.PROTECT, Moves.SPLASH, Moves.GLAIVE_RUSH]);
await game.startBattle(); await game.classicMode.startBattle();
const player = game.scene.getPlayerPokemon()!; const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.hp = 1000; enemy.hp = 1000;
player.hp = 1000; player.hp = 1000;
game.move.select(Moves.PROTECT); game.move.select(Moves.PROTECT);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.SHADOW_SNEAK); game.move.select(Moves.SHADOW_SNEAK);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
game.override.enemyMoveset(Array(4).fill(Moves.SPLASH)); game.override.enemyMoveset(Array(4).fill(Moves.SPLASH));
const damagedHP1 = 1000 - enemy.hp; const damagedHP1 = 1000 - enemy.hp;
enemy.hp = 1000; enemy.hp = 1000;
game.move.select(Moves.SHADOW_SNEAK); game.move.select(Moves.SHADOW_SNEAK);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
const damagedHP2 = 1000 - enemy.hp; const damagedHP2 = 1000 - enemy.hp;
expect(damagedHP2).toBeGreaterThanOrEqual((damagedHP1 * 2) - 1); expect(damagedHP2).toBeGreaterThanOrEqual((damagedHP1 * 2) - 1);
}, 20000); }, TIMEOUT);
}); });

View File

@ -7,7 +7,8 @@ import { Moves } from "#enums/moves";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { allMoves } from "#app/data/move"; import { allMoves } from "#app/data/move";
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
import { BerryPhase } from "#app/phases/berry-phase"; import { BattlerIndex } from "#app/battle";
import { MoveResult } from "#app/field/pokemon";
const TIMEOUT = 20 * 1000; const TIMEOUT = 20 * 1000;
@ -43,13 +44,13 @@ describe("Moves - Protect", () => {
test( test(
"should protect the user from attacks", "should protect the user from attacks",
async () => { async () => {
await game.startBattle([Species.CHARIZARD]); await game.classicMode.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.PROTECT); game.move.select(Moves.PROTECT);
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to("BerryPhase", false);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
}, TIMEOUT }, TIMEOUT
@ -61,13 +62,13 @@ describe("Moves - Protect", () => {
game.override.enemyMoveset(Array(4).fill(Moves.CEASELESS_EDGE)); game.override.enemyMoveset(Array(4).fill(Moves.CEASELESS_EDGE));
vi.spyOn(allMoves[Moves.CEASELESS_EDGE], "accuracy", "get").mockReturnValue(100); vi.spyOn(allMoves[Moves.CEASELESS_EDGE], "accuracy", "get").mockReturnValue(100);
await game.startBattle([Species.CHARIZARD]); await game.classicMode.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.PROTECT); game.move.select(Moves.PROTECT);
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to("BerryPhase", false);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeUndefined(); expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeUndefined();
@ -79,13 +80,13 @@ describe("Moves - Protect", () => {
async () => { async () => {
game.override.enemyMoveset(Array(4).fill(Moves.CHARM)); game.override.enemyMoveset(Array(4).fill(Moves.CHARM));
await game.startBattle([Species.CHARIZARD]); await game.classicMode.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.PROTECT); game.move.select(Moves.PROTECT);
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to("BerryPhase", false);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
}, TIMEOUT }, TIMEOUT
@ -96,18 +97,38 @@ describe("Moves - Protect", () => {
async () => { async () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACHYON_CUTTER)); game.override.enemyMoveset(Array(4).fill(Moves.TACHYON_CUTTER));
await game.startBattle([Species.CHARIZARD]); await game.classicMode.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
game.move.select(Moves.PROTECT); game.move.select(Moves.PROTECT);
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to("BerryPhase", false);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(enemyPokemon.turnData.hitCount).toBe(1); expect(enemyPokemon.turnData.hitCount).toBe(1);
}, TIMEOUT }, TIMEOUT
); );
test(
"should fail if the user is the last to move in the turn",
async () => {
game.override.enemyMoveset(Array(4).fill(Moves.PROTECT));
await game.classicMode.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
game.move.select(Moves.PROTECT);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
expect(leadPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
}, TIMEOUT
);
}); });

View File

@ -5,8 +5,8 @@ import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { BerryPhase } from "#app/phases/berry-phase"; import { BattlerIndex } from "#app/battle";
import { CommandPhase } from "#app/phases/command-phase"; import { MoveResult } from "#app/field/pokemon";
const TIMEOUT = 20 * 1000; const TIMEOUT = 20 * 1000;
@ -42,19 +42,16 @@ describe("Moves - Quick Guard", () => {
test( test(
"should protect the user and allies from priority moves", "should protect the user and allies from priority moves",
async () => { async () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerField(); const playerPokemon = game.scene.getPlayerField();
game.move.select(Moves.QUICK_GUARD); game.move.select(Moves.QUICK_GUARD);
await game.phaseInterceptor.to(CommandPhase);
game.move.select(Moves.SPLASH, 1); game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to("BerryPhase", false);
leadPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp())); playerPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
}, TIMEOUT }, TIMEOUT
); );
@ -64,19 +61,16 @@ describe("Moves - Quick Guard", () => {
game.override.enemyAbility(Abilities.PRANKSTER); game.override.enemyAbility(Abilities.PRANKSTER);
game.override.enemyMoveset(Array(4).fill(Moves.GROWL)); game.override.enemyMoveset(Array(4).fill(Moves.GROWL));
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerField(); const playerPokemon = game.scene.getPlayerField();
game.move.select(Moves.QUICK_GUARD); game.move.select(Moves.QUICK_GUARD);
await game.phaseInterceptor.to(CommandPhase);
game.move.select(Moves.SPLASH, 1); game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to("BerryPhase", false);
leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0)); playerPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
}, TIMEOUT }, TIMEOUT
); );
@ -85,21 +79,40 @@ describe("Moves - Quick Guard", () => {
async () => { async () => {
game.override.enemyMoveset(Array(4).fill(Moves.WATER_SHURIKEN)); game.override.enemyMoveset(Array(4).fill(Moves.WATER_SHURIKEN));
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerField(); const playerPokemon = game.scene.getPlayerField();
const enemyPokemon = game.scene.getEnemyField(); const enemyPokemon = game.scene.getEnemyField();
game.move.select(Moves.QUICK_GUARD); game.move.select(Moves.QUICK_GUARD);
await game.phaseInterceptor.to(CommandPhase);
game.move.select(Moves.FOLLOW_ME, 1); game.move.select(Moves.FOLLOW_ME, 1);
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to("BerryPhase", false);
leadPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp())); playerPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
enemyPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1)); enemyPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1));
} }
); );
test(
"should fail if the user is the last to move in the turn",
async () => {
game.override.battleType("single");
game.override.enemyMoveset(Array(4).fill(Moves.QUICK_GUARD));
await game.classicMode.startBattle([Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
game.move.select(Moves.QUICK_GUARD);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
}, TIMEOUT
);
}); });

View File

@ -76,7 +76,7 @@ export default class GameManager {
constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) { constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) {
localStorage.clear(); localStorage.clear();
ErrorInterceptor.getInstance().clear(); ErrorInterceptor.getInstance().clear();
BattleScene.prototype.randBattleSeedInt = (arg) => arg-1; BattleScene.prototype.randBattleSeedInt = (range, min: number = 0) => min + range - 1; // This simulates a max roll
this.gameWrapper = new GameWrapper(phaserGame, bypassLogin); this.gameWrapper = new GameWrapper(phaserGame, bypassLogin);
this.scene = new BattleScene(); this.scene = new BattleScene();
this.phaseInterceptor = new PhaseInterceptor(this.scene); this.phaseInterceptor = new PhaseInterceptor(this.scene);

View File

@ -208,4 +208,5 @@ export default class MockContainer implements MockGameObject {
return this.list; return this.list;
} }
disableInteractive = vi.fn();
} }

View File

@ -8,7 +8,21 @@ import { addTextObject, TextStyle } from "./text";
import { addWindow } from "./ui-theme"; import { addWindow } from "./ui-theme";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
interface BuildInteractableImageOpts {
scale?: number;
x?: number;
y?: number;
origin?: { x: number; y: number };
}
export default class LoginFormUiHandler extends FormModalUiHandler { export default class LoginFormUiHandler extends FormModalUiHandler {
private readonly ERR_USERNAME: string = "invalid username";
private readonly ERR_PASSWORD: string = "invalid password";
private readonly ERR_ACCOUNT_EXIST: string = "account doesn't exist";
private readonly ERR_PASSWORD_MATCH: string = "password doesn't match";
private readonly ERR_NO_SAVES: string = "No save files found";
private readonly ERR_TOO_MANY_SAVES: string = "Too many save files found";
private googleImage: Phaser.GameObjects.Image; private googleImage: Phaser.GameObjects.Image;
private discordImage: Phaser.GameObjects.Image; private discordImage: Phaser.GameObjects.Image;
private usernameInfoImage: Phaser.GameObjects.Image; private usernameInfoImage: Phaser.GameObjects.Image;
@ -21,8 +35,23 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
} }
setup(): void { setup(): void {
super.setup(); super.setup();
this.buildExternalPartyContainer();
this.infoContainer = this.scene.add.container(0, 0);
this.usernameInfoImage = this.buildInteractableImage("settings_icon", "username-info-icon", {
x: 20,
scale: 0.5
});
this.infoContainer.add(this.usernameInfoImage);
this.getUi().add(this.infoContainer);
this.infoContainer.setVisible(false);
this.infoContainer.disableInteractive();
}
private buildExternalPartyContainer() {
this.externalPartyContainer = this.scene.add.container(0, 0); this.externalPartyContainer = this.scene.add.container(0, 0);
this.externalPartyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains); this.externalPartyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains);
this.externalPartyTitle = addTextObject(this.scene, 0, 4, "", TextStyle.SETTINGS_LABEL); this.externalPartyTitle = addTextObject(this.scene, 0, 4, "", TextStyle.SETTINGS_LABEL);
@ -31,23 +60,8 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
this.externalPartyContainer.add(this.externalPartyBg); this.externalPartyContainer.add(this.externalPartyBg);
this.externalPartyContainer.add(this.externalPartyTitle); this.externalPartyContainer.add(this.externalPartyTitle);
this.infoContainer = this.scene.add.container(0, 0); this.googleImage = this.buildInteractableImage("google", "google-icon");
this.infoContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains); this.discordImage = this.buildInteractableImage("discord", "discord-icon");
const googleImage = this.scene.add.image(0, 0, "google");
googleImage.setOrigin(0, 0);
googleImage.setScale(0.07);
googleImage.setInteractive();
googleImage.setName("google-icon");
this.googleImage = googleImage;
const discordImage = this.scene.add.image(20, 0, "discord");
discordImage.setOrigin(0, 0);
discordImage.setScale(0.07);
discordImage.setInteractive();
discordImage.setName("discord-icon");
this.discordImage = discordImage;
this.externalPartyContainer.add(this.googleImage); this.externalPartyContainer.add(this.googleImage);
this.externalPartyContainer.add(this.discordImage); this.externalPartyContainer.add(this.discordImage);
@ -55,59 +69,52 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
this.externalPartyContainer.add(this.googleImage); this.externalPartyContainer.add(this.googleImage);
this.externalPartyContainer.add(this.discordImage); this.externalPartyContainer.add(this.discordImage);
this.externalPartyContainer.setVisible(false); this.externalPartyContainer.setVisible(false);
const usernameInfoImage = this.scene.add.image(20, 0, "settings_icon");
usernameInfoImage.setOrigin(0, 0);
usernameInfoImage.setScale(0.5);
usernameInfoImage.setInteractive();
usernameInfoImage.setName("username-info-icon");
this.usernameInfoImage = usernameInfoImage;
this.infoContainer.add(this.usernameInfoImage);
this.getUi().add(this.infoContainer);
this.infoContainer.setVisible(false);
} }
getModalTitle(config?: ModalConfig): string { override getModalTitle(_config?: ModalConfig): string {
return i18next.t("menu:login"); return i18next.t("menu:login");
} }
getFields(config?: ModalConfig): string[] { override getFields(_config?: ModalConfig): string[] {
return [ i18next.t("menu:username"), i18next.t("menu:password") ]; return [ i18next.t("menu:username"), i18next.t("menu:password") ];
} }
getWidth(config?: ModalConfig): number { override getWidth(_config?: ModalConfig): number {
return 160; return 160;
} }
getMargin(config?: ModalConfig): [number, number, number, number] { override getMargin(_config?: ModalConfig): [number, number, number, number] {
return [ 0, 0, 48, 0 ]; return [ 0, 0, 48, 0 ];
} }
getButtonLabels(config?: ModalConfig): string[] { override getButtonLabels(_config?: ModalConfig): string[] {
return [ i18next.t("menu:login"), i18next.t("menu:register")]; return [ i18next.t("menu:login"), i18next.t("menu:register")];
} }
getReadableErrorMessage(error: string): string { override getReadableErrorMessage(error: string): string {
const colonIndex = error?.indexOf(":"); const colonIndex = error?.indexOf(":");
if (colonIndex > 0) { if (colonIndex > 0) {
error = error.slice(0, colonIndex); error = error.slice(0, colonIndex);
} }
switch (error) { switch (error) {
case "invalid username": case this.ERR_USERNAME:
return i18next.t("menu:invalidLoginUsername"); return i18next.t("menu:invalidLoginUsername");
case "invalid password": case this.ERR_PASSWORD:
return i18next.t("menu:invalidLoginPassword"); return i18next.t("menu:invalidLoginPassword");
case "account doesn't exist": case this.ERR_ACCOUNT_EXIST:
return i18next.t("menu:accountNonExistent"); return i18next.t("menu:accountNonExistent");
case "password doesn't match": case this.ERR_PASSWORD_MATCH:
return i18next.t("menu:unmatchingPassword"); return i18next.t("menu:unmatchingPassword");
case this.ERR_NO_SAVES:
return i18next.t("menu:noSaves");
case this.ERR_TOO_MANY_SAVES:
return i18next.t("menu:tooManySaves");
} }
return super.getReadableErrorMessage(error); return super.getReadableErrorMessage(error);
} }
show(args: any[]): boolean { override show(args: any[]): boolean {
if (super.show(args)) { if (super.show(args)) {
const config = args[0] as ModalConfig; const config = args[0] as ModalConfig;
@ -148,17 +155,16 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
return false; return false;
} }
clear() { override clear() {
super.clear(); super.clear();
this.externalPartyContainer.setVisible(false); this.externalPartyContainer.setVisible(false);
this.infoContainer.setVisible(false); this.infoContainer.setVisible(false);
this.setMouseCursorStyle("default"); //reset cursor
this.discordImage.off("pointerdown"); [this.discordImage, this.googleImage, this.usernameInfoImage].forEach((img) => img.off("pointerdown"));
this.googleImage.off("pointerdown");
this.usernameInfoImage.off("pointerdown");
} }
processExternalProvider(config: ModalConfig) : void { private processExternalProvider(config: ModalConfig) : void {
this.externalPartyTitle.setText(i18next.t("menu:orUse") ?? ""); this.externalPartyTitle.setText(i18next.t("menu:orUse") ?? "");
this.externalPartyTitle.setX(20+this.externalPartyTitle.text.length); this.externalPartyTitle.setX(20+this.externalPartyTitle.text.length);
this.externalPartyTitle.setVisible(true); this.externalPartyTitle.setVisible(true);
@ -205,6 +211,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
label: dataKeys[i].replace(keyToFind, ""), label: dataKeys[i].replace(keyToFind, ""),
handler: () => { handler: () => {
this.scene.ui.revertMode(); this.scene.ui.revertMode();
this.infoContainer.disableInteractive();
return true; return true;
} }
}); });
@ -213,8 +220,13 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
options: options, options: options,
delay: 1000 delay: 1000
}); });
this.infoContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width, this.scene.game.canvas.height), Phaser.Geom.Rectangle.Contains);
} else { } else {
return onFail("You have too many save files to use this"); if (dataKeys.length > 2) {
return onFail(this.ERR_TOO_MANY_SAVES);
} else {
return onFail(this.ERR_NO_SAVES);
}
} }
}); });
@ -236,4 +248,21 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
alpha: 1 alpha: 1
}); });
} }
private buildInteractableImage(texture: string, name: string, opts: BuildInteractableImageOpts = {}) {
const {
scale = 0.07,
x = 0,
y = 0,
origin = { x: 0, y: 0 }
} = opts;
const img = this.scene.add.image(x, y, texture);
img.setName(name);
img.setOrigin(origin.x, origin.y);
img.setScale(scale);
img.setInteractive();
this.addInteractionHoverEffect(img);
return img;
}
} }

View File

@ -57,29 +57,35 @@ export abstract class ModalUiHandler extends UiHandler {
const buttonLabels = this.getButtonLabels(); const buttonLabels = this.getButtonLabels();
const buttonTopMargin = this.getButtonTopMargin();
for (const label of buttonLabels) { for (const label of buttonLabels) {
const buttonLabel = addTextObject(this.scene, 0, 8, label, TextStyle.TOOLTIP_CONTENT); this.addButton(label);
buttonLabel.setOrigin(0.5, 0.5);
const buttonBg = addWindow(this.scene, 0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN);
buttonBg.setOrigin(0.5, 0);
buttonBg.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonBg.width, buttonBg.height), Phaser.Geom.Rectangle.Contains);
const buttonContainer = this.scene.add.container(0, buttonTopMargin);
this.buttonBgs.push(buttonBg);
this.buttonContainers.push(buttonContainer);
buttonContainer.add(buttonBg);
buttonContainer.add(buttonLabel);
this.modalContainer.add(buttonContainer);
} }
this.modalContainer.setVisible(false); this.modalContainer.setVisible(false);
} }
private addButton(label: string) {
const buttonTopMargin = this.getButtonTopMargin();
const buttonLabel = addTextObject(this.scene, 0, 8, label, TextStyle.TOOLTIP_CONTENT);
buttonLabel.setOrigin(0.5, 0.5);
const buttonBg = addWindow(this.scene, 0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN);
buttonBg.setOrigin(0.5, 0);
buttonBg.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonBg.width, buttonBg.height), Phaser.Geom.Rectangle.Contains);
const buttonContainer = this.scene.add.container(0, buttonTopMargin);
this.buttonBgs.push(buttonBg);
this.buttonContainers.push(buttonContainer);
buttonContainer.add(buttonBg);
buttonContainer.add(buttonLabel);
this.addInteractionHoverEffect(buttonBg);
this.modalContainer.add(buttonContainer);
}
show(args: any[]): boolean { show(args: any[]): boolean {
if (args.length >= 1 && "buttonActions" in args[0]) { if (args.length >= 1 && "buttonActions" in args[0]) {
super.show(args); super.show(args);
@ -135,4 +141,20 @@ export abstract class ModalUiHandler extends UiHandler {
this.buttonBgs.map(bg => bg.off("pointerdown")); this.buttonBgs.map(bg => bg.off("pointerdown"));
} }
/**
* Adds a hover effect to a game object which changes the cursor to a `pointer` and tints it slighly
* @param gameObject the game object to add hover events/effects to
*/
protected addInteractionHoverEffect(gameObject: Phaser.GameObjects.Image | Phaser.GameObjects.NineSlice | Phaser.GameObjects.Sprite) {
gameObject.on("pointerover", () => {
this.setMouseCursorStyle("pointer");
gameObject.setTint(0xbbbbbb);
});
gameObject.on("pointerout", () => {
this.setMouseCursorStyle("default");
gameObject.clearTint();
});
}
} }

View File

@ -52,6 +52,15 @@ export default abstract class UiHandler {
return changed; return changed;
} }
/**
* Changes the style of the mouse cursor.
* @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor}
* @param cursorStyle cursor style to apply
*/
protected setMouseCursorStyle(cursorStyle: "pointer" | "default") {
this.scene.input.manager.canvas.style.cursor = cursorStyle;
}
clear() { clear() {
this.active = false; this.active = false;
} }

View File

@ -1,5 +1,5 @@
import i18next from "i18next";
import { MoneyFormat } from "#enums/money-format"; import { MoneyFormat } from "#enums/money-format";
import i18next from "i18next";
export const MissingTextureKey = "__MISSING"; export const MissingTextureKey = "__MISSING";
@ -82,6 +82,12 @@ export function randInt(range: integer, min: integer = 0): integer {
return Math.floor(Math.random() * range) + min; return Math.floor(Math.random() * range) + min;
} }
/**
* Generates a random number using the global seed, or the current battle's seed if called via `Battle.randSeedInt`
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
export function randSeedInt(range: integer, min: integer = 0): integer { export function randSeedInt(range: integer, min: integer = 0): integer {
if (range <= 1) { if (range <= 1) {
return min; return min;