Compare commits

...

11 Commits

Author SHA1 Message Date
Dmitriy K
d052d444b6
[QoL] Add type inference to getAttrs methods and refactor accordingly (#1633)
* add type inference to getAttrs methods and refactor accordingly

* Tests/infer types for get attrs methods (#1)

* #1633: add spec/tests for coverage

* move ability/move tests into /src/tests and rename to *.test.ts to match common naming patterns

* use None in test cases to remove ambiguity

* revert back to before test cases were merged

---------

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
2024-05-31 19:50:30 -05:00
Dakurei
13c9888902
Trigger the github-pages workflow even during pull requests (#1655)
+ Allows upstream identification of problems such as those that occurred during merge of PR #1567
2024-06-01 01:30:55 +01:00
Benjamin Odom
19885e0558
Add Arena Events (#1627)
* Add Arena Events

* Update arena-events.ts
2024-05-31 17:52:16 -05:00
karl-police
f2a6fe5c6a
Autocomplete for modifiers in overrides (#1057)
* Autocomplete for modifiers

* Update overrides.ts

---------

Co-authored-by: Benjamin Odom <bennybroseph@gmail.com>
2024-05-31 17:41:09 -05:00
chaosgrimmon
218cf025a3
[Bug] Fix rare shiny Cleffa using default icon (#1642)
* [Bug] Fix rare shiny Cleffa using default icon

Rare shiny Cleffa's icon was showing up as the default colouration, or as shiny Lugia's icon.
This fixes that by adding it to the spritesheet pokemon_icons_2v and its .json, where the console log suggested it should belong.

* [Bug] Fix rare shiny Cleffa using default icon

Rare shiny Cleffa's icon was showing up as the default colouration, or as shiny Lugia's icon.
This fixes that by adding it to the spritesheet pokemon_icons_2v and its .json, where the console log suggested it should belong.
2024-05-31 17:15:41 -05:00
Jaime
7f1e94e8ca
[Bug] Fix status abilities not considering Comatose (fixes #1639) (#1641) 2024-05-31 16:50:03 -05:00
Tempoanon
cf9992f9b8
Add code (#1661) 2024-05-31 16:42:38 -05:00
innerthunder
2dff35c407
Fix Neutralizing Gas self-suppression (#1662) 2024-05-31 16:35:52 -05:00
Tempoanon
ff5b227a6b
[Documentation] Add documentation to egg-hatch-phase.ts (#1650)
* Add documentation

* Ben has made me take more sanity damage
2024-05-31 16:34:02 -05:00
Amani H
e0bc1d8781
[Item] Add Toxic Orb and Flame Orb (#1574)
* Add Toxic Orb and Flame Orb

* Change Weighting Logic & Functions

* Adjust Party Filter during Weighting

* Add Dynamic Weight Ceilings

* Refactor for Performance/Cleanliness

* Adjust Party Filter to Check Status Applicability

* Cover Same & Different Status Cases

* Adjust Full Heal & Restore Weighting

* Cover Unwanted Status Case

* Fix Wrong Status Effect on Reload
2024-05-31 14:31:11 -05:00
MadridPawmot
63a416a65b
[enhancement] Added Firebreather trainer class (#1409)
* Add files via upload

* Update trainer-type.ts

* Update biomes.ts

* Update trainer-config.ts

* Update trainer-names.ts

* Add Spanish localization

* Update trainers.ts

* Update trainers.ts

* Update trainers.ts

* Update trainers.ts

* Update trainers.ts

* Update trainers.ts

* Finished adding placeholder strings for each language

* Updated German translation

Thanks to CodeTappert

* Updated trainer.ts to resolve conflicts

* Update trainers.ts to resolve conflicts

* Update trainers.ts

* Fixed syntax error

* Update dialogue.ts

* Re-added dialogue back

* Added firebreather name string to Korean locale

* Added dialogue string to locale

* Added dialogue string to locale

* Update dialogue.ts

* Added dialogue string to locale

* Added dialogue string to locale

* Added dialogue string to locale

* Added dialogue string to locale

* Added dialogue string to locale

* Added requested changes

* Added requested changes

* Added requested changes

* Update dialogue.ts

* Update dialogue.ts

* Update dialogue.ts

* Update dialogue.ts

* Update dialogue.ts

* Finished adding requested changes to locales

* Added requested changes
2024-05-31 14:05:04 -05:00
48 changed files with 640 additions and 68 deletions

View File

@ -4,6 +4,9 @@ on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
pages:
@ -33,6 +36,7 @@ jobs:
node-version: 20
- name: Checkout repository for Github Pages
if: github.event_name == 'push'
uses: actions/checkout@v3
with:
path: pokerogue_gh
@ -52,6 +56,7 @@ jobs:
npx typedoc --out /tmp/docs --githubPages false --entryPoints ./src/
- name: Commit & Push docs
if: github.event_name == 'push'
run: |
cd pokerogue_gh
git config user.email "github-actions[bot]@users.noreply.github.com"

View File

@ -7400,6 +7400,48 @@
"w": 16,
"h": 16
}
},
{
"filename": "toxic_orb",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 32,
"h": 32
},
"spriteSourceSize": {
"x": 7,
"y": 7,
"w": 18,
"h": 18
},
"frame": {
"x": 379,
"y": 274,
"w": 18,
"h": 18
}
},
{
"filename": "flame_orb",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 32,
"h": 32
},
"spriteSourceSize": {
"x": 7,
"y": 7,
"w": 18,
"h": 18
},
"frame": {
"x": 379,
"y": 292,
"w": 18,
"h": 18
}
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

View File

@ -1332,6 +1332,27 @@
"h": 24
}
},
{
"filename": "173_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 11,
"y": 11,
"w": 18,
"h": 18
},
"frame": {
"x": 174,
"y": 253,
"w": 18,
"h": 18
}
},
{
"filename": "173_3",
"rotated": false,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "firebreather.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 72
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 72
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 72
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 72
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:f692676a166fc1915532cd94d5799af4:fb833f76fb6797474657726bb59a7eee:aeb55e30992938f494b6cd2420158dda$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -55,8 +55,22 @@ export class Ability implements Localizable {
this.description = this.id ? i18next.t(`ability:${i18nKey}.description`) as string : "";
}
getAttrs(attrType: { new(...args: any[]): AbAttr }): AbAttr[] {
return this.attrs.filter(a => a instanceof attrType);
/**
* Get all ability attributes that match `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends AbAttr>(attrType: new(...args: any[]) => T ): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
}
/**
* Check if an ability has an attribute that matches `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns true if the ability has attribute `attrType`
*/
hasAttr<T extends AbAttr>(attrType: new(...args: any[]) => T): boolean {
return this.attrs.some((attr) => attr instanceof attrType);
}
attr<T extends new (...args: any[]) => AbAttr>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
@ -74,10 +88,6 @@ export class Ability implements Localizable {
return this;
}
hasAttr(attrType: { new(...args: any[]): AbAttr }): boolean {
return !!this.getAttrs(attrType).length;
}
bypassFaint(): Ability {
this.isBypassFaint = true;
return this;
@ -341,7 +351,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => (attr as StatusMoveTypeImmunityAttr).immuneType === this.immuneType)) && move.getMove().type === this.immuneType) {
if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.getMove().type === this.immuneType) {
(args[0] as Utils.NumberHolder).value = 0;
return true;
}
@ -568,7 +578,7 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr {
export class ReverseDrainAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (!!move.getMove().getAttrs(HitHealAttr).length || !!move.getMove().getAttrs(StrengthSapHealAttr).length ) {
if (move.getMove().hasAttr(HitHealAttr) || move.getMove().hasAttr(StrengthSapHealAttr) ) {
pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!"));
return true;
}
@ -2194,7 +2204,7 @@ function getAnticipationCondition(): AbAttrCondition {
return true;
}
// move is a OHKO
if (move.getMove().findAttr(attr => attr instanceof OneHitKOAttr)) {
if (move.getMove().hasAttr(OneHitKOAttr)) {
return true;
}
// edge case for hidden power, type is computed
@ -2248,7 +2258,7 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
for (const move of opponent.moveset) {
if (move.getMove() instanceof StatusMove) {
movePower = 1;
} else if (move.getMove().findAttr(attr => attr instanceof OneHitKOAttr)) {
} else if (move.getMove().hasAttr(OneHitKOAttr)) {
movePower = 150;
} else if (move.getMove().id === Moves.COUNTER || move.getMove().id === Moves.MIRROR_COAT || move.getMove().id === Moves.METAL_BURST) {
movePower = 120;
@ -3325,7 +3335,7 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
}
const ability = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility());
const attrs = ability.getAttrs(attrType) as TAttr[];
const attrs = ability.getAttrs(attrType);
const clearSpliceQueueAndResolve = () => {
pokemon.scene.clearPhaseQueueSplice();
@ -3524,7 +3534,7 @@ export const allAbilities = [ new Ability(Abilities.NONE, 3) ];
export function initAbilities() {
allAbilities.push(
new Ability(Abilities.STENCH, 3)
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().findAttr(attr => attr instanceof FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED),
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED),
new Ability(Abilities.DRIZZLE, 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
@ -3716,9 +3726,9 @@ export function initAbilities() {
.conditionalAttr(pokemon => !Utils.randSeedInt(3), PostTurnResetStatusAbAttr),
new Ability(Abilities.GUTS, 3)
.attr(BypassBurnDamageReductionAbAttr)
.conditionalAttr(pokemon => !!pokemon.status, BattleStatMultiplierAbAttr, BattleStat.ATK, 1.5),
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), BattleStatMultiplierAbAttr, BattleStat.ATK, 1.5),
new Ability(Abilities.MARVEL_SCALE, 3)
.conditionalAttr(pokemon => !!pokemon.status, BattleStatMultiplierAbAttr, BattleStat.DEF, 1.5)
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), BattleStatMultiplierAbAttr, BattleStat.DEF, 1.5)
.ignorable(),
new Ability(Abilities.LIQUID_OOZE, 3)
.attr(ReverseDrainAbAttr),
@ -3813,7 +3823,7 @@ export function initAbilities() {
.condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)),
new Ability(Abilities.QUICK_FEET, 4)
.conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
.conditionalAttr(pokemon => !!pokemon.status, BattleStatMultiplierAbAttr, BattleStat.SPD, 1.5),
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), BattleStatMultiplierAbAttr, BattleStat.SPD, 1.5),
new Ability(Abilities.NORMALIZE, 4)
.attr(MoveTypeChangeAttr, Type.NORMAL, 1.2, (user, target, move) => move.id !== Moves.HIDDEN_POWER && move.id !== Moves.WEATHER_BALL &&
move.id !== Moves.NATURAL_GIFT && move.id !== Moves.JUDGMENT && move.id !== Moves.TECHNO_BLAST),

View File

@ -468,7 +468,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
const loadedCheckTimer = setInterval(() => {
if (moveAnims.get(move) !== null) {
const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr;
const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0];
if (chargeAttr && chargeAnims.get(chargeAttr.chargeAnim) === null) {
return;
}
@ -498,7 +498,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
populateMoveAnim(move, ba);
}
const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr;
const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0];
if (chargeAttr) {
initMoveChargeAnim(scene, chargeAttr.chargeAnim).then(() => resolve());
} else {
@ -569,7 +569,7 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
return new Promise(resolve => {
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
for (const moveId of moveIds) {
const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[moveId].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr;
const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] || allMoves[moveId].getAttrs(DelayedAttackAttr)[0];
if (chargeAttr) {
const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims[0]);

View File

@ -509,7 +509,7 @@ export class EncoreTag extends BattlerTag {
return false;
}
if (allMoves[repeatableMove.move].getAttrs(ChargeAttr).length && repeatableMove.result === MoveResult.OTHER) {
if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) {
return false;
}

View File

@ -1823,7 +1823,7 @@ export const biomeTrainerPools: BiomeTrainerPools = {
[BiomePoolTier.BOSS_ULTRA_RARE]: []
},
[Biome.VOLCANO]: {
[BiomePoolTier.COMMON]: [],
[BiomePoolTier.COMMON]: [ TrainerType.FIREBREATHER ],
[BiomePoolTier.UNCOMMON]: [],
[BiomePoolTier.RARE]: [],
[BiomePoolTier.SUPER_RARE]: [],
@ -7304,6 +7304,10 @@ export function initBiomes() {
[ Biome.GRAVEYARD, BiomePoolTier.UNCOMMON ]
]
],
[ TrainerType.FIREBREATHER, [
[ Biome.VOLCANO, BiomePoolTier.COMMON ]
]
],
[ TrainerType.BROCK, [
[ Biome.CAVE, BiomePoolTier.BOSS ]
]

View File

@ -421,6 +421,20 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
]
}
],
[TrainerType.FIREBREATHER]: [
{
encounter: [
"dialogue:firebreather.encounter.1",
"dialogue:firebreather.encounter.2",
"dialogue:firebreather.encounter.3",
],
victory: [
"dialogue:firebreather.victory.1",
"dialogue:firebreather.victory.2",
"dialogue:firebreather.victory.3",
]
}
],
[TrainerType.BROCK]: {
encounter: [
"dialogue:brock.encounter.1",

View File

@ -16,6 +16,7 @@ export enum TrainerType {
DANCER,
DEPOT_AGENT,
DOCTOR,
FIREBREATHER,
FISHERMAN,
GUITARIST,
HARLEQUIN,

View File

@ -148,8 +148,22 @@ export default class Move implements Localizable {
this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}` : "";
}
getAttrs(attrType: { new(...args: any[]): MoveAttr }): MoveAttr[] {
return this.attrs.filter(a => a instanceof attrType);
/**
* Get all move attributes that match `attrType`
* @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends MoveAttr>(attrType: new(...args: any[]) => T): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
}
/**
* Check if a move has an attribute that matches `attrType`
* @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns true if the move has attribute `attrType`
*/
hasAttr<T extends MoveAttr>(attrType: new(...args: any[]) => T): boolean {
return this.attrs.some((attr) => attr instanceof attrType);
}
findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr {
@ -3698,7 +3712,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
while (moveHistory.length) {
turnMove = moveHistory.shift();
if (!allMoves[turnMove.move].getAttrs(ProtectAttr).length || turnMove.result !== MoveResult.SUCCESS) {
if (!allMoves[turnMove.move].hasAttr(ProtectAttr) || turnMove.result !== MoveResult.SUCCESS) {
break;
}
timesUsed++;
@ -4480,7 +4494,7 @@ const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
return false;
}
if (allMoves[copiableMove].getAttrs(ChargeAttr).length) {
if (allMoves[copiableMove].hasAttr(ChargeAttr)) {
return false;
}
@ -4570,7 +4584,7 @@ const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
return false;
}
if (allMoves[copiableMove.move].getAttrs(ChargeAttr).length && copiableMove.result === MoveResult.OTHER) {
if (allMoves[copiableMove.move].hasAttr(ChargeAttr) && copiableMove.result === MoveResult.OTHER) {
return false;
}
@ -4987,7 +5001,7 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
const variableTarget = new Utils.NumberHolder(0);
user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget));
const moveTarget = allMoves[move].getAttrs(VariableTargetAttr).length ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
const moveTarget = allMoves[move].hasAttr(VariableTargetAttr) ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
const opponents = user.getOpponents();
let set: Pokemon[] = [];

View File

@ -56,7 +56,7 @@ export class Terrain {
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
switch (this.terrainType) {
case TerrainType.PSYCHIC:
if (!move.getAttrs(ProtectAttr).length) {
if (!move.hasAttr(ProtectAttr)) {
const priority = new Utils.IntegerHolder(move.priority);
applyAbAttrs(IncrementMovePriorityAbAttr, user, null, move, priority);
return priority.value > 0 && user.getOpponents().filter(o => targets.includes(o.getBattlerIndex())).length > 0;

View File

@ -989,6 +989,8 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.DEPOT_AGENT]: new TrainerConfig(++t).setMoneyMultiplier(1.45).setEncounterBgm(TrainerType.CLERK),
[TrainerType.DOCTOR]: new TrainerConfig(++t).setHasGenders("Nurse", "lass").setHasDouble("Medical Team").setMoneyMultiplier(3).setEncounterBgm(TrainerType.CLERK)
.setSpeciesFilter(s => !!s.getLevelMoves().find(plm => plm[1] === Moves.HEAL_PULSE)),
[TrainerType.FIREBREATHER]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.ROUGHNECK)
.setSpeciesFilter(s => !!s.getLevelMoves().find(plm => plm[1] === Moves.SMOG) || s.isOfType(Type.FIRE)),
[TrainerType.FISHERMAN]: new TrainerConfig(++t).setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.BACKPACKER).setSpecialtyTypes(Type.WATER)
.setPartyTemplates(trainerPartyTemplates.TWO_WEAK_SAME_ONE_AVG, trainerPartyTemplates.ONE_AVG, trainerPartyTemplates.THREE_WEAK_SAME, trainerPartyTemplates.ONE_STRONG, trainerPartyTemplates.SIX_WEAKER)
.setSpeciesPools({

View File

@ -36,6 +36,7 @@ const trainerNameConfigs: TrainerNameConfigs = {
[TrainerType.DANCER]: new TrainerNameConfig(TrainerType.DANCER),
[TrainerType.DEPOT_AGENT]: new TrainerNameConfig(TrainerType.DEPOT_AGENT),
[TrainerType.DOCTOR]: new TrainerNameConfig(TrainerType.DOCTOR).hasGenderVariant("Nurse"),
[TrainerType.FIREBREATHER]: new TrainerNameConfig(TrainerType.FIREBREATHER),
[TrainerType.FISHERMAN]: new TrainerNameConfig(TrainerType.FISHERMAN),
[TrainerType.GUITARIST]: new TrainerNameConfig(TrainerType.GUITARIST),
[TrainerType.HARLEQUIN]: new TrainerNameConfig(TrainerType.HARLEQUIN),
@ -87,6 +88,7 @@ export const trainerNamePools = {
[TrainerType.DANCER]: ["Brian","Davey","Dirk","Edmond","Mickey","Raymond","Cara","Julia","Maika","Mireille","Ronda","Zoe"],
[TrainerType.DEPOT_AGENT]: ["Josh","Hank","Vincent"],
[TrainerType.DOCTOR]: [["Hank","Jerry","Jules","Logan","Wayne","Braid","Derek","Heath","Julius","Kit","Graham"],["Kirsten","Sachiko","Shery","Carol","Dixie","Mariah"]],
[TrainerType.FIREBREATHER]: ["Bill","Burt","Cliff","Dick","Lyle","Ned","Otis","Ray","Richard","Walt"],
[TrainerType.FISHERMAN]: ["Andre","Arnold","Barney","Chris","Edgar","Henry","Jonah","Justin","Kyle","Martin","Marvin","Ralph","Raymond","Scott","Stephen","Wilton","Tully","Andrew","Barny","Carter","Claude","Dale","Elliot","Eugene","Ivan","Ned","Nolan","Roger","Ronald","Wade","Wayne","Darian","Kai","Chip","Hank","Kaden","Tommy","Tylor","Alec","Brett","Cameron","Cody","Cole","Cory","Erick","George","Joseph","Juan","Kenneth","Luc","Miguel","Travis","Walter","Zachary","Josh","Gideon","Kyler","Liam","Murphy","Bruce","Damon","Devon","Hubert","Jones","Lydon","Mick","Pete","Sean","Sid","Vince","Bucky","Dean","Eustace","Kenzo","Leroy","Mack","Ryder","Ewan","Finn","Murray","Seward","Shad","Wharton","Finley","Fisher","Fisk","River","Sheaffer","Timin","Carl","Ernest","Hal","Herbert","Hisato","Mike","Vernon","Harriet","Marina","Chase"],
[TrainerType.GUITARIST]: ["Anna","Beverly","January","Tina","Alicia","Claudia","Julia","Lidia","Mireia","Noelia","Sara","Sheila","Tatiana"],
[TrainerType.HARLEQUIN]: ["Charley","Ian","Jack","Kerry","Louis","Pat","Paul","Rick","Anders","Clarence","Gary"],

View File

@ -114,9 +114,9 @@ export class Weather {
const field = scene.getField(true);
for (const pokemon of field) {
let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0];
if (!suppressWeatherEffectAbAttr) {
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr : null;
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null;
}
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {
return true;

View File

@ -14,30 +14,51 @@ import { EggTier } from "./data/enums/egg-type";
import PokemonInfoContainer from "./ui/pokemon-info-container";
import EggsToHatchCountContainer from "./ui/eggs-to-hatch-count-container";
/**
* Class that represents egg hatching
*/
export class EggHatchPhase extends Phase {
/** The egg that is hatching */
private egg: Egg;
/** The number of eggs that are hatching */
private eggsToHatchCount: integer;
/** The container that lists how many eggs are hatching */
private eggsToHatchCountContainer: EggsToHatchCountContainer;
/** The scene handler for egg hatching */
private eggHatchHandler: EggHatchSceneHandler;
/** The phaser gameobject container that holds everything */
private eggHatchContainer: Phaser.GameObjects.Container;
/** The phaser image that is the background */
private eggHatchBg: Phaser.GameObjects.Image;
/** The phaser rectangle that overlays during the scene */
private eggHatchOverlay: Phaser.GameObjects.Rectangle;
/** The phaser container that holds the egg */
private eggContainer: Phaser.GameObjects.Container;
/** The phaser sprite of the egg */
private eggSprite: Phaser.GameObjects.Sprite;
/** The phaser sprite of the cracks in an egg */
private eggCrackSprite: Phaser.GameObjects.Sprite;
/** The phaser sprite that represents the overlaid light rays */
private eggLightraysOverlay: Phaser.GameObjects.Sprite;
/** The phaser sprite of the hatched Pokemon */
private pokemonSprite: Phaser.GameObjects.Sprite;
/** The phaser sprite for shiny sparkles */
private pokemonShinySparkle: Phaser.GameObjects.Sprite;
/** The {@link PokemonInfoContainer} of the newly hatched Pokemon */
private infoContainer: PokemonInfoContainer;
/** The newly hatched {@link PlayerPokemon} */
private pokemon: PlayerPokemon;
/** The index of which egg move is unlocked. 0-2 is common, 3 is rare */
private eggMoveIndex: integer;
/** Internal booleans representing if the egg is hatched, able to be skipped, or skipped */
private hatched: boolean;
private canSkip: boolean;
private skipped: boolean;
/** The sound effect being played when the egg is hatched */
private evolutionBgm: AnySound;
constructor(scene: BattleScene, egg: Egg, eggsToHatchCount: integer) {
@ -117,6 +138,7 @@ export class EggHatchPhase extends Phase {
this.eggHatchContainer.add(this.infoContainer);
// The game will try to unfuse any Pokemon even though eggs should not generate fused Pokemon in the first place
const pokemon = this.generatePokemon();
if (pokemon.fusionSpecies) {
pokemon.clearFusionSpecies();
@ -187,6 +209,13 @@ export class EggHatchPhase extends Phase {
super.end();
}
/**
* Function that animates egg shaking
* @param intensity of horizontal shaking. Doubled on the first call (where count is 0)
* @param repeatCount the number of times this function should be called (asynchronous recursion?!?)
* @param count the current number of times this function has been called.
* @returns nothing since it's a Promise<void>
*/
doEggShake(intensity: number, repeatCount?: integer, count?: integer): Promise<void> {
return new Promise(resolve => {
if (repeatCount === undefined) {
@ -226,6 +255,10 @@ export class EggHatchPhase extends Phase {
});
}
/**
* Tries to skip the hatching animation
* @returns false if cannot be skipped or already skipped. True otherwise
*/
trySkip(): boolean {
if (!this.canSkip || this.skipped) {
return false;
@ -239,6 +272,9 @@ export class EggHatchPhase extends Phase {
return true;
}
/**
* Plays the animation of an egg hatch
*/
doHatch(): void {
this.canSkip = false;
this.hatched = true;
@ -268,6 +304,9 @@ export class EggHatchPhase extends Phase {
});
}
/**
* Function to do the logic and animation of completing a hatch and revealing the Pokemon
*/
doReveal(): void {
// Update/reduce count of hatching eggs when revealed if count is at least 1
// If count is 0, hide eggsToHatchCountContainer instead
@ -333,10 +372,21 @@ export class EggHatchPhase extends Phase {
});
}
/**
* Helper function to generate sine. (Why is this not a Utils?!?)
* @param index random number from 0-7 being passed in to scale pi/128
* @param amplitude Scaling
* @returns a number
*/
sin(index: integer, amplitude: integer): number {
return amplitude * Math.sin(index * (Math.PI / 128));
}
/**
* Animates spraying
* @param intensity number of times this is repeated (this is a badly named variable)
* @param offsetY how much to offset the Y coordinates
*/
doSpray(intensity: integer, offsetY?: number) {
this.scene.tweens.addCounter({
repeat: intensity,
@ -347,6 +397,11 @@ export class EggHatchPhase extends Phase {
});
}
/**
* Animates a particle used in the spray animation
* @param trigIndex Used to modify the particle's vertical speed, is a random number from 0-7
* @param offsetY how much to offset the Y coordinate
*/
doSprayParticle(trigIndex: integer, offsetY: number) {
const initialX = this.eggHatchBg.displayWidth / 2;
const initialY = this.eggHatchBg.displayHeight / 2 + offsetY;
@ -387,12 +442,22 @@ export class EggHatchPhase extends Phase {
updateParticle();
}
/**
* Generates a Pokemon to be hatched by the egg
* @returns the hatched PlayerPokemon
*/
generatePokemon(): PlayerPokemon {
let ret: PlayerPokemon;
let speciesOverride: Species;
let speciesOverride: Species; // SpeciesOverride should probably be a passed in parameter for future species-eggs
this.scene.executeWithSeedOffset(() => {
/**
* Manaphy eggs have a 1/8 chance of being Manaphy and 7/8 chance of being Phione
* Legendary eggs pulled from the legendary gacha have a 50% of being converted into
* the species that was the legendary focus at the time
*/
if (this.egg.isManaphyEgg()) {
const rand = Utils.randSeedInt(8);
@ -437,6 +502,18 @@ export class EggHatchPhase extends Phase {
.map(s => parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
/**
* Pokemon that are cheaper in their tier get a weight boost. Regionals get a weight penalty
* 1 cost mons get 2x
* 2 cost mons get 1.5x
* 4, 6, 8 cost mons get 1.75x
* 3, 5, 7, 9 cost mons get 1x
* Alolan, Galarian, and Paldean mons get 0.5x
* Hisui mons get 0.125x
*
* The total weight is also being calculated EACH time there is an egg hatch instead of being generated once
* and being the same each time
*/
let totalWeight = 0;
const speciesWeights = [];
for (const speciesId of speciesPool) {
@ -464,6 +541,16 @@ export class EggHatchPhase extends Phase {
ret = this.scene.addPlayerPokemon(pokemonSpecies, 1, undefined, undefined, undefined, false);
}
/**
* Non Shiny gacha Pokemon have a 1/128 chance of being shiny
* Shiny gacha Pokemon have a 1/64 chance of being shiny
* IVs are rolled twice and the higher of each stat's IV is taken
* The egg move gacha doubles the rate of rare egg moves but the base rates are
* Common: 1/48
* Rare: 1/24
* Epic: 1/12
* Legendary: 1/6
*/
ret.trySetShiny(this.egg.gachaType === GachaType.SHINY ? 1024 : 512);
ret.variant = ret.shiny ? ret.generateVariant() : 0;

77
src/field/arena-events.ts Normal file
View File

@ -0,0 +1,77 @@
import { ArenaTagSide } from "#app/data/arena-tag.js";
import { ArenaTagType } from "#app/data/enums/arena-tag-type.js";
import { TerrainType } from "#app/data/terrain.js";
import { WeatherType } from "#app/data/weather.js";
/** Alias for all {@linkcode ArenaEvent} type strings */
export enum ArenaEventType {
/** Triggers when a {@linkcode WeatherType} is added, overlapped, or removed */
WEATHER_CHANGED = "onWeatherChanged",
/** Triggers when a {@linkcode TerrainType} is added, overlapped, or removed */
TERRAIN_CHANGED = "onTerrainChanged",
/** Triggers when a {@linkcode ArenaTagType} is added or removed */
TAG_CHANGED = "onTagChanged",
}
/**
* Base container class for all {@linkcode ArenaEventType} events
* @extends Event
*/
export class ArenaEvent extends Event {
/** The total duration of the {@linkcode ArenaEventType} */
public duration: number;
constructor(eventType: ArenaEventType, duration: number) {
super(eventType);
this.duration = duration;
}
}
/**
* Container class for {@linkcode ArenaEventType.WEATHER_CHANGED} events
* @extends ArenaEvent
*/
export class WeatherChangedEvent extends ArenaEvent {
/** The {@linkcode WeatherType} being overridden */
public oldWeatherType: WeatherType;
/** The {@linkcode WeatherType} being set */
public newWeatherType: WeatherType;
constructor(oldWeatherType: WeatherType, newWeatherType: WeatherType, duration: number) {
super(ArenaEventType.WEATHER_CHANGED, duration);
this.oldWeatherType = oldWeatherType;
this.newWeatherType = newWeatherType;
}
}
/**
* Container class for {@linkcode ArenaEventType.TERRAIN_CHANGED} events
* @extends ArenaEvent
*/
export class TerrainChangedEvent extends ArenaEvent {
/** The {@linkcode TerrainType} being overridden */
public oldTerrainType: TerrainType;
/** The {@linkcode TerrainType} being set */
public newTerrainType: TerrainType;
constructor(oldTerrainType: TerrainType, newTerrainType: TerrainType, duration: number) {
super(ArenaEventType.TERRAIN_CHANGED, duration);
this.oldTerrainType = oldTerrainType;
this.newTerrainType = newTerrainType;
}
}
/**
* Container class for {@linkcode ArenaEventType.TAG_CHANGED} events
* @extends ArenaEvent
*/
export class TagChangedEvent extends ArenaEvent {
/** The {@linkcode ArenaTagType} being set */
public arenaTagType: ArenaTagType;
/** The {@linkcode ArenaTagSide} the tag is being placed on */
public arenaTagSide: ArenaTagSide;
constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number) {
super(ArenaEventType.TAG_CHANGED, duration);
this.arenaTagType = arenaTagType;
this.arenaTagSide = arenaTagSide;
}
}

View File

@ -19,6 +19,7 @@ import { Terrain, TerrainType } from "../data/terrain";
import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability";
import Pokemon from "./pokemon";
import * as Overrides from "../overrides";
import { WeatherChangedEvent, TerrainChangedEvent, TagChangedEvent } from "./arena-events";
export class Arena {
public scene: BattleScene;
@ -34,6 +35,8 @@ export class Arena {
private pokemonPool: PokemonPools;
private trainerPool: BiomeTierTrainerPools;
public readonly eventTarget: EventTarget = new EventTarget();
constructor(scene: BattleScene, biome: Biome, bgm: string) {
this.scene = scene;
this.biomeType = biome;
@ -300,6 +303,7 @@ export class Arena {
const oldWeatherType = this.weather?.weatherType || WeatherType.NONE;
this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null;
this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType, this.weather?.turnsLeft));
if (this.weather) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
@ -324,6 +328,7 @@ export class Arena {
const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE;
this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null;
this.eventTarget.dispatchEvent(new TerrainChangedEvent(oldTerrainType,this.terrain?.terrainType, this.terrain?.turnsLeft));
if (this.terrain) {
if (!ignoreAnim) {
@ -545,6 +550,8 @@ export class Arena {
this.tags.push(newTag);
newTag.onAdd(this);
this.eventTarget.dispatchEvent(new TagChangedEvent(newTag.tagType, newTag.side, newTag.turnCount));
return true;
}

View File

@ -978,7 +978,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (this.isOnField() && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) {
const suppressed = new Utils.BooleanHolder(false);
this.scene.getField(true).map(p => {
this.scene.getField(true).filter(p => p !== this).map(p => {
if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) {
p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, suppressed, [ability]));
}
@ -1058,7 +1058,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier {
const typeless = !!move.getMove().getAttrs(TypelessAttr).length;
const typeless = move.getMove().hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source));
const cancelled = new Utils.BooleanHolder(false);
if (!typeless) {
@ -1424,19 +1424,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (this.isBoss()) { // Bosses never get self ko moves
movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttr).length);
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr));
}
movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttrOnHit).length);
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit));
if (this.hasTrainer()) {
// Trainers never get OHKO moves
movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(OneHitKOAttr).length);
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(OneHitKOAttr));
// Half the weight of self KO moves
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(SacrificialAttr).length ? 0.5 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(SacrificialAttrOnHit).length ? 0.5 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttrOnHit) ? 0.5 : 1)]);
// Trainers get a weight bump to stat buffing moves
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => (a as StatChangeAttr).levels > 1 && (a as StatChangeAttr).selfTarget) ? 1.25 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => a.levels > 1 && a.selfTarget) ? 1.25 : 1)]);
// Trainers get a weight decrease to multiturn moves
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].getAttrs(ChargeAttr).length || !!allMoves[m[0]].getAttrs(RechargeAttr).length ? 0.7 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(ChargeAttr) || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1)]);
}
// Weight towards higher power moves, by reducing the power of moves below the highest power.
@ -1627,8 +1627,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const types = this.getTypes(true, true);
const cancelled = new Utils.BooleanHolder(false);
const typeless = !!move.getAttrs(TypelessAttr).length;
const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes((attr as StatusMoveTypeImmunityAttr).immuneType)))
const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType)))
? this.getAttackTypeEffectiveness(type, source)
: 1);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
@ -1654,7 +1654,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const power = new Utils.NumberHolder(move.power);
const sourceTeraType = source.getTeraType();
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.getAttrs(MultiHitAttr).length && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60;
}
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power);
@ -1756,7 +1756,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!isTypeImmune) {
damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * ((this.scene.randBattleSeedInt(15) + 85) / 100) * criticalMultiplier.value);
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) {
if (!move.getAttrs(BypassBurnDamageReductionAttr).length) {
if (!move.hasAttr(BypassBurnDamageReductionAttr)) {
const burnDamageReductionCancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled);
if (!burnDamageReductionCancelled.value) {
@ -1772,8 +1772,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* The target has a {@link BattlerTagType} that this move interacts with
* AND
* The move doubles damage when used against that tag
* */
move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
*/
move.getAttrs(HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
if (this.getTag(hta.tagType)) {
damage.value *= 2;
}
@ -3454,7 +3454,7 @@ export class EnemyPokemon extends Pokemon {
if (!sortedBenefitScores.length) {
// Set target to BattlerIndex.ATTACKER when using a counter move
// This is the same as when the player does so
if (!!move.findAttr(attr => attr instanceof CounterDamageAttr)) {
if (move.hasAttr(CounterDamageAttr)) {
return [BattlerIndex.ATTACKER];
}

View File

@ -367,6 +367,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "Ich werde für das nächste Rennen tunen."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "Meine Expertise in Bezug auf Gesteins-Pokémon wird dich besiegen! Komm schon!",

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "Bahnangestellter",
"doctor": "Arzt",
"doctor_female": "Ärztin",
"firebreather": "Feuerspucker",
"fisherman": "Angler",
"fisherman_female": "Angler", // Seems to be the same in german but exists in other languages like italian
"gentleman": "Gentleman",

View File

@ -359,6 +359,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "I'll tune up for the next race."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = {
"LEFTOVERS": { name: "Leftovers", description: "Heals 1/16 of a Pokémon's maximum HP every turn" },
"SHELL_BELL": { name: "Shell Bell", description: "Heals 1/8 of a Pokémon's dealt damage" },
"TOXIC_ORB": { name: "Toxic Orb", description: "Badly poisons its holder at the end of the turn if they do not have a status condition already" },
"FLAME_ORB": { name: "Flame Orb", description: "Burns its holder at the end of the turn if they do not have a status condition already" },
"BATON": { name: "Baton", description: "Allows passing along effects when switching Pokémon, which also bypasses traps" },
"SHINY_CHARM": { name: "Shiny Charm", description: "Dramatically increases the chance of a wild Pokémon being Shiny" },

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "Depot Agent",
"doctor": "Doctor",
"doctor_female": "Doctor",
"firebreather": "Firebreather",
"fisherman": "Fisherman",
"fisherman_female": "Fisherman",
"gentleman": "Gentleman",

View File

@ -359,6 +359,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "I'll tune up for the next race."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "Ferroviario",
"doctor": "Enfermero",
"doctor_female": "Enfermera",
"firebreather": "Comefuegos",
"fisherman": "Pescador",
"fisherman_female": "Pescadora",
"gentleman": "Aristócrata",

View File

@ -359,6 +359,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "I'll tune up for the next race."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "Cheminot",
"doctor": "Docteur",
"doctor_female": "Docteure",
"firebreather": "Firebreather",
"fisherman": "Pêcheur",
"fisherman_female": "Pêcheuse",
"gentleman": "Gentleman",

View File

@ -359,6 +359,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "I'll tune up for the next race."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "Depot Agent",
"doctor": "Doctor",
"doctor_female": "Doctor",
"firebreather": "Firebreather",
"fisherman": "Fisherman",
"fisherman_female": "Fisherman",
"gentleman": "Gentleman",

View File

@ -359,6 +359,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "I'll tune up for the next race."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "역무원",
"doctor": "의사",
"doctor_female": "간호사", // doctor_f.png 파일이 간호사
"firebreather": "Firebreather",
"fisherman": "낚시꾼",
"fisherman_female": "낚시꾼",
"gentleman": "신사",

View File

@ -359,6 +359,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "I'll tune up for the next race."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "Ferroviário",
"doctor": "Doutor",
"doctor_female": "Doutora",
"firebreather": "Firebreather",
"fishermen": "Pescador",
"fishermen_female": "Pescadora",
"gentleman": "Cavalheiro",

View File

@ -359,6 +359,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "I'll tune up for the next race."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "铁路员工",
"doctor": "医生",
"doctor_female": "医生",
"firebreather": "Firebreather",
"fisherman": "垂钓者",
"fisherman_female": "垂钓者",
"gentleman": "绅士",

View File

@ -359,6 +359,18 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "I'll tune up for the next race."
},
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
},
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -48,6 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = {
"depot_agent": "鐵路員工",
"doctor": "醫生",
"doctor_female": "醫生",
"firebreather": "Firebreather",
"fisherman": "垂釣者",
"fisherman_female": "垂釣者",
"gentleman": "紳士",

View File

@ -1,6 +1,7 @@
import * as Modifiers from "./modifier";
import { AttackMove, allMoves } from "../data/move";
import { Moves } from "../data/enums/moves";
import { Abilities } from "../data/enums/abilities";
import { PokeballType, getPokeballCatchMultiplier, getPokeballName } from "../data/pokeball";
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "../field/pokemon";
import { EvolutionItem, pokemonEvolutions } from "../data/pokemon-evolutions";
@ -756,14 +757,23 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge
}
}
/**
* Class that represents form changing items
*/
export class FormChangeItemModifierType extends PokemonModifierType implements GeneratedPersistentModifierType {
public formChangeItem: FormChangeItem;
constructor(formChangeItem: FormChangeItem) {
super("", FormChangeItem[formChangeItem].toLowerCase(), (_type, args) => new Modifiers.PokemonFormChangeItemModifier(this, (args[0] as PlayerPokemon).id, formChangeItem, true),
(pokemon: PlayerPokemon) => {
if (pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) && !!pokemonFormChanges[pokemon.species.speciesId].find(fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger)
&& (fc.trigger as SpeciesFormChangeItemTrigger).item === this.formChangeItem)) {
// Make sure the Pokemon has alternate forms
if (pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId)
// Get all form changes for this species with an item trigger, including any compound triggers
&& pokemonFormChanges[pokemon.species.speciesId].filter(fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger))
// Returns true if any form changes match this item
.map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
.flat().flatMap(fc => fc.item).includes(this.formChangeItem)
) {
return null;
}
@ -1182,6 +1192,9 @@ export const modifierTypes = {
LEFTOVERS: () => new PokemonHeldItemModifierType("modifierType:ModifierType.LEFTOVERS", "leftovers", (type, args) => new Modifiers.TurnHealModifier(type, (args[0] as Pokemon).id)),
SHELL_BELL: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SHELL_BELL", "shell_bell", (type, args) => new Modifiers.HitHealModifier(type, (args[0] as Pokemon).id)),
TOXIC_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.TOXIC_ORB", "toxic_orb", (type, args) => new Modifiers.TurnStatusEffectModifier(type, (args[0] as Pokemon).id)),
FLAME_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.FLAME_ORB", "flame_orb", (type, args) => new Modifiers.TurnStatusEffectModifier(type, (args[0] as Pokemon).id)),
BATON: () => new PokemonHeldItemModifierType("modifierType:ModifierType.BATON", "stick", (type, args) => new Modifiers.SwitchEffectTransferModifier(type, (args[0] as Pokemon).id)),
SHINY_CHARM: () => new ModifierType("modifierType:ModifierType.SHINY_CHARM", "shiny_charm", (type, _args) => new Modifiers.ShinyRateBoosterModifier(type)),
@ -1247,7 +1260,12 @@ const modifierPool: ModifierPool = {
[ModifierTier.GREAT]: [
new WeightedModifierType(modifierTypes.GREAT_BALL, 6),
new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => {
const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status).length, 3);
const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => {
if (i instanceof Modifiers.TurnStatusEffectModifier) {
return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status.effect;
}
return false;
})).length, 3);
return statusEffectPartyMemberCount * 6;
}, 18),
new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => {
@ -1270,7 +1288,12 @@ const modifierPool: ModifierPool = {
return thresholdPartyMemberCount;
}, 3),
new WeightedModifierType(modifierTypes.FULL_RESTORE, (party: Pokemon[]) => {
const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status).length, 3);
const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => {
if (i instanceof Modifiers.TurnStatusEffectModifier) {
return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status.effect;
}
return false;
})).length, 3);
const thresholdPartyMemberCount = Math.floor((Math.min(party.filter(p => (p.getInverseHp() >= 150 || p.getHpRatio() <= 0.5) && !p.isFainted()).length, 3) + statusEffectPartyMemberCount) / 2);
return thresholdPartyMemberCount;
}, 3),
@ -1312,6 +1335,40 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.MINT, 4),
new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32),
new WeightedModifierType(modifierTypes.AMULET_COIN, 3),
new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => {
let weight = 0;
const filteredParty = party.filter(p => (p.status?.effect === StatusEffect.TOXIC || p.canSetStatus(StatusEffect.TOXIC, true, true))
&& !p.hasAbility(Abilities.FLARE_BOOST)
&& !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier));
if (filteredParty.some(p => p.hasAbility(Abilities.TOXIC_BOOST) || p.hasAbility(Abilities.POISON_HEAL))) {
weight = 4;
} else if (filteredParty.some(p => p.hasAbility(Abilities.GUTS) || p.hasAbility(Abilities.QUICK_FEET) || p.hasAbility(Abilities.MARVEL_SCALE))) {
weight = 2;
} else {
const moveList = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT];
if (filteredParty.some(p => p.getMoveset().some(m => moveList.includes(m.moveId)))) {
weight = 1;
}
}
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * weight, 8 * weight);
}, 32),
new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => {
let weight = 0;
const filteredParty = party.filter(p => (p.status?.effect === StatusEffect.BURN || p.canSetStatus(StatusEffect.BURN, true, true))
&& !p.hasAbility(Abilities.TOXIC_BOOST) && !p.hasAbility(Abilities.POISON_HEAL)
&& !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier));
if (filteredParty.some(p => p.hasAbility(Abilities.FLARE_BOOST))) {
weight = 4;
} else if (filteredParty.some(p => p.hasAbility(Abilities.GUTS) || p.hasAbility(Abilities.QUICK_FEET) || p.hasAbility(Abilities.MARVEL_SCALE))) {
weight = 2;
} else {
const moveList = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT];
if (filteredParty.some(p => p.getMoveset().some(m => moveList.includes(m.moveId)))) {
weight = 1;
}
}
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * weight, 8 * weight);
}, 32),
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
new WeightedModifierType(modifierTypes.CANDY_JAR, 5),
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10),

View File

@ -851,6 +851,66 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
}
}
/**
* Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a
* set {@linkcode StatusEffect} at the end of a turn.
* @extends PokemonHeldItemModifier
* @see {@linkcode apply}
*/
export class TurnStatusEffectModifier extends PokemonHeldItemModifier {
/** The status effect to be applied by the held item */
private effect: StatusEffect;
constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);
switch (type.id) {
case "TOXIC_ORB":
this.effect = StatusEffect.TOXIC;
break;
case "FLAME_ORB":
this.effect = StatusEffect.BURN;
break;
}
}
/**
* Checks if {@linkcode modifier} is an instance of this class,
* intentionally ignoring potentially different {@linkcode effect}s
* to prevent held item stockpiling since the item obtained first
* would be the only item able to {@linkcode apply} successfully.
* @override
* @param modifier {@linkcode Modifier} being type tested
* @return true if {@linkcode modifier} is an instance of
* TurnStatusEffectModifier, false otherwise
*/
matchType(modifier: Modifier): boolean {
return modifier instanceof TurnStatusEffectModifier;
}
clone() {
return new TurnStatusEffectModifier(this.type, this.pokemonId, this.stackCount);
}
/**
* Tries to inflicts the holder with the associated {@linkcode StatusEffect}.
* @param args [0] {@linkcode Pokemon} that holds the held item
* @returns true if the status effect was applied successfully, false if
* otherwise
*/
apply(args: any[]): boolean {
return (args[0] as Pokemon).trySetStatus(this.effect, true, undefined, undefined, this.type.name);
}
getMaxHeldItemCount(pokemon: Pokemon): integer {
return 1;
}
getStatusEffect(): StatusEffect {
return this.effect;
}
}
export class HitHealModifier extends PokemonHeldItemModifier {
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);

View File

@ -14,6 +14,7 @@ import { PokeballType } from "./data/pokeball";
import {TimeOfDay} from "#app/data/enums/time-of-day";
import { Gender } from "./data/gender";
import { StatusEffect } from "./data/status-effect";
import { modifierTypes } from "./modifier/modifier-type";
/**
* Overrides for testing different in game situations
@ -100,7 +101,7 @@ export const OPP_VARIANT_OVERRIDE: Variant = 0;
* - BerryType is for BERRY
*/
interface ModifierOverride {
name: string,
name: keyof typeof modifierTypes & string,
count?: integer
type?: TempBattleStat|Stat|Nature|Type|BerryType
}

View File

@ -6,7 +6,7 @@ import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMov
import { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat";
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, PokemonMoveAccuracyBoosterModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier } from "./modifier/modifier";
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, PokemonMoveAccuracyBoosterModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier } from "./modifier/modifier";
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims";
@ -1553,7 +1553,7 @@ export class SwitchSummonPhase extends SummonPhase {
const lastUsedMove = moveId ? allMoves[moveId] : undefined;
const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command;
const lastPokemonIsForceSwitchedAndNotFainted = !!lastUsedMove?.findAttr(attr => attr instanceof ForceSwitchOutAttr) && !this.lastPokemon.isFainted();
const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted();
// Compensate for turn spent summoning
// Or compensate for force switch move if switched out pokemon is not fainted
@ -2289,6 +2289,8 @@ export class TurnEndPhase extends FieldPhase {
applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);
this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon);
pokemon.battleSummonData.turnCount++;
@ -2457,9 +2459,9 @@ export class MovePhase extends BattlePhase {
const oldTarget = moveTarget.value;
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget));
//Check if this move is immune to being redirected, and restore its target to the intended target if it is.
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().getAttrs(BypassRedirectAttr).length)) {
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) {
//If an ability prevented this move from being redirected, display its ability pop up.
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().getAttrs(BypassRedirectAttr).length) && oldTarget !== moveTarget.value) {
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) {
this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr)));
}
moveTarget.value = oldTarget;
@ -2549,7 +2551,7 @@ export class MovePhase extends BattlePhase {
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
}
if (!allMoves[this.move.moveId].getAttrs(CopyMoveAttr).length) {
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
this.scene.currentBattle.lastMove = this.move.moveId;
}
@ -2633,7 +2635,7 @@ export class MovePhase extends BattlePhase {
}
showMoveText(): void {
if (this.move.getMove().getAttrs(ChargeAttr).length) {
if (this.move.getMove().hasAttr(ChargeAttr)) {
const lastMove = this.pokemon.getLastXMoves() as TurnMove[];
if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) {
this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500);
@ -2704,7 +2706,7 @@ export class MoveEffectPhase extends PokemonPhase {
const hitCount = new Utils.IntegerHolder(1);
// Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount);
if (this.move.getMove() instanceof AttackMove && !this.move.getMove().getAttrs(FixedDamageAttr).length) {
if (this.move.getMove() instanceof AttackMove && !this.move.getMove().hasAttr(FixedDamageAttr)) {
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));
}
user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value;
@ -2715,7 +2717,7 @@ export class MoveEffectPhase extends PokemonPhase {
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const activeTargets = targets.map(t => t.isActive(true));
if (!activeTargets.length || (!this.move.getMove().getAttrs(VariableTargetAttr).length && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) {
if (!activeTargets.length || (!this.move.getMove().hasAttr(VariableTargetAttr) && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) {
user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1;
if (activeTargets.length) {
@ -2759,7 +2761,7 @@ export class MoveEffectPhase extends PokemonPhase {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),
user, target, this.move.getMove()).then(() => {
if (hitResult !== HitResult.FAIL) {
const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => (ca as ChargeAttr).usedChargeEffect(user, this.getTarget(), this.move.getMove()));
const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), this.move.getMove()));
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => {
@ -2859,7 +2861,7 @@ export class MoveEffectPhase extends PokemonPhase {
}
const hiddenTag = target.getTag(HiddenTag);
if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) {
if (hiddenTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === hiddenTag.tagType)) {
return false;
}
@ -2871,7 +2873,7 @@ export class MoveEffectPhase extends PokemonPhase {
return true;
}
const isOhko = !!this.move.getMove().getAttrs(OneHitKOAccuracyAttr).length;
const isOhko = this.move.getMove().hasAttr(OneHitKOAccuracyAttr);
if (!isOhko) {
user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
@ -3532,7 +3534,7 @@ export class FaintPhase extends PokemonPhase {
if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr) as PostVictoryStatChangeAttr[];
const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr);
if (pvattrs.length) {
for (const pvattr of pvattrs) {
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove);

View File

@ -30,6 +30,7 @@ import { allMoves } from "../data/move";
import { TrainerVariant } from "../field/trainer";
import { OutdatedPhase, ReloadSessionPhase } from "#app/phases";
import { Variant, variantData } from "#app/data/variant";
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-events.js";
const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
@ -740,6 +741,10 @@ export class GameData {
});
scene.arena.weather = sessionData.arena.weather;
scene.arena.eventTarget.dispatchEvent(new WeatherChangedEvent(null, scene.arena.weather?.weatherType, scene.arena.weather?.turnsLeft));
scene.arena.terrain = sessionData.arena.terrain;
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(null, scene.arena.terrain?.terrainType, scene.arena.terrain?.turnsLeft));
// TODO
//scene.arena.tags = sessionData.arena.tags;