[Challenge] Inverse battle challenge (#3525)

* add inverse battle challenge. refactoring type.ts for inverse battle challenge

* update type integer -> number

* add inverse battle condition to thunder wave, conversion 2.

* add inverse_battle test code, add checking gameMode in runToSummon not to overwrite gameMode to CLASSIC always

* update startBattle with isClassicMode default = true

* add inverse achievement

* fix achv validation condition

* remove unnecessary new line

* update defaultWidth 160 -> 200

* update locales

* fix korean translation

* fix korean translation2

* Update src/locales/de/achv.ts

Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>

* Update src/locales/de/challenges.ts

Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>

* Update src/locales/de/challenges.ts

Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>

* resize challenge description 96 -> 84

* update challenge select UI size.

* revert font size to 84. update de translation

* Update src/locales/fr/challenges.ts

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>

* Update src/locales/fr/achv.ts

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>

* Update src/locales/es/challenges.ts

Co-authored-by: Asdar <asdargmng@gmail.com>

* Update src/locales/fr/challenges.ts

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>

* Update src/locales/fr/achv.ts

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>

* Update src/locales/es/achv.ts

Co-authored-by: Asdar <asdargmng@gmail.com>

* Update src/locales/fr/achv.ts

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>

* shrink de font size on achivement

* set middle align to achv title

* Update src/locales/zh_CN/achv.ts

Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com>

* Update src/locales/zh_TW/achv.ts

Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com>

* Update src/locales/zh_CN/challenges.ts

Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com>

* Update src/locales/zh_TW/challenges.ts

Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com>

* fix zh_TW ahiv.ts

* fix import code on inverse battle test for updated phase

* Update src/data/type.ts

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

* update requested changes

* Update src/locales/pt_BR/achv.ts

Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>

* Update src/locales/pt_BR/achv.ts

Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>

* Update src/locales/pt_BR/challenges.ts

Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>

* [draft] update inverse battle apply function

* change the way how to use applyChallenge for inverse type

* resolve confilct

* fix test codes

* remove unnecessary multiplier variable and break codes

* update getTypeDamageMultiplier argument type from `number` to `Type`

* Fix inverse types tests (#1)

* Fix Inverse Battle tests

* Add timeout parameter to tests

* update requested changes

* update requested changes

* update requested changes2

* update comments

* Update src/test/utils/helpers/challengeModeHelper.ts

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

* Update src/test/utils/helpers/challengeModeHelper.ts

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

* fix mis pasted code

* revert loadChallenge code for  FreshStartChallenge

* code refactoring

* restore challenge.json lost translations

* revert UI changes

* revert unreverted newlines

* Run History inclusion

* requested changes from torranx

* update WaterSuperEffectTypeMultiplierAttr for inverse battle matchup.

* fix test code. adding flying press test code

* update requested change from xavion3

* updated requested change from xavion 2

* update requested changes from xavion 3

* remove exception code which is not valid

* attach partial mark to Freeze dry. requested by xavion

* add missing game over phase code when we delete old phases.ts

* fix test codes

* merge conflict

* fix achv condition

* updated achv block condition. we don't want to change desc now

* resolve conflict

* Eternatus Moveset Tinkering

* Cleaning it up

---------

Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: Asdar <asdargmng@gmail.com>
Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
Co-authored-by: frutescens <info@laptop>
This commit is contained in:
Leo Kim 2024-08-30 03:59:33 +09:00 committed by GitHub
parent 1e95068f14
commit c112abbcd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 4005 additions and 3756 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -55,6 +55,11 @@ export enum ChallengeType {
* @see {@link Challenge.applyFixedBattle} * @see {@link Challenge.applyFixedBattle}
*/ */
FIXED_BATTLES, FIXED_BATTLES,
/**
* Modifies the effectiveness of Type matchups in battle
* @see {@linkcode Challenge.applyTypeEffectiveness}
*/
TYPE_EFFECTIVENESS,
/** /**
* Modifies what level the AI pokemon are. UNIMPLEMENTED. * Modifies what level the AI pokemon are. UNIMPLEMENTED.
*/ */
@ -327,6 +332,15 @@ export abstract class Challenge {
return false; return false;
} }
/**
* An apply function for TYPE_EFFECTIVENESS challenges. Derived classes should alter this.
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
* @returns Whether this function did anything.
*/
applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean {
return false;
}
/** /**
* An apply function for AI_LEVEL challenges. Derived classes should alter this. * An apply function for AI_LEVEL challenges. Derived classes should alter this.
* @param level {@link Utils.IntegerHolder} The generated level. * @param level {@link Utils.IntegerHolder} The generated level.
@ -651,10 +665,7 @@ export class FreshStartChallenge extends Challenge {
return true; return true;
} }
/** override getDifficulty(): number {
* @overrides
*/
getDifficulty(): number {
return 0; return 0;
} }
@ -666,6 +677,38 @@ export class FreshStartChallenge extends Challenge {
} }
} }
/**
* Implements an inverse battle challenge.
*/
export class InverseBattleChallenge extends Challenge {
constructor() {
super(Challenges.INVERSE_BATTLE, 1);
}
static loadChallenge(source: InverseBattleChallenge | any): InverseBattleChallenge {
const newChallenge = new InverseBattleChallenge();
newChallenge.value = source.value;
newChallenge.severity = source.severity;
return newChallenge;
}
override getDifficulty(): number {
return 0;
}
applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean {
if (effectiveness.value < 1) {
effectiveness.value = 2;
return true;
} else if (effectiveness.value > 1) {
effectiveness.value = 0.5;
return true;
}
return false;
}
}
/** /**
* Lowers the amount of starter points available. * Lowers the amount of starter points available.
*/ */
@ -785,6 +828,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FIXED_BATTLES, waveIndex: Number, battleConfig: FixedBattleConfig): boolean; export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FIXED_BATTLES, waveIndex: Number, battleConfig: FixedBattleConfig): boolean;
/**
* Apply all challenges that modify type effectiveness.
* @param gameMode {@linkcode GameMode} The current gameMode
* @param challengeType {@linkcode ChallengeType} ChallengeType.TYPE_EFFECTIVENESS
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: Utils.NumberHolder): boolean;
/** /**
* Apply all challenges that modify what level AI are. * Apply all challenges that modify what level AI are.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -866,6 +917,9 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
case ChallengeType.FIXED_BATTLES: case ChallengeType.FIXED_BATTLES:
ret ||= c.applyFixedBattle(args[0], args[1]); ret ||= c.applyFixedBattle(args[0], args[1]);
break; break;
case ChallengeType.TYPE_EFFECTIVENESS:
ret ||= c.applyTypeEffectiveness(args[0]);
break;
case ChallengeType.AI_LEVEL: case ChallengeType.AI_LEVEL:
ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]); ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]);
break; break;
@ -907,6 +961,8 @@ export function copyChallenge(source: Challenge | any): Challenge {
return LowerStarterPointsChallenge.loadChallenge(source); return LowerStarterPointsChallenge.loadChallenge(source);
case Challenges.FRESH_START: case Challenges.FRESH_START:
return FreshStartChallenge.loadChallenge(source); return FreshStartChallenge.loadChallenge(source);
case Challenges.INVERSE_BATTLE:
return InverseBattleChallenge.loadChallenge(source);
} }
throw new Error("Unknown challenge copied"); throw new Error("Unknown challenge copied");
} }
@ -918,5 +974,6 @@ export function initChallenges() {
new SingleGenerationChallenge(), new SingleGenerationChallenge(),
new SingleTypeChallenge(), new SingleTypeChallenge(),
new FreshStartChallenge(), new FreshStartChallenge(),
new InverseBattleChallenge(),
); );
} }

View File

@ -4,7 +4,7 @@ import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTr
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects } from "./status-effect"; import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects } from "./status-effect";
import { getTypeResistances, Type } from "./type"; import { getTypeDamageMultiplier, Type } from "./type";
import { Constructor } from "#app/utils"; import { Constructor } from "#app/utils";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
@ -37,6 +37,9 @@ import { StatChangePhase } from "#app/phases/stat-change-phase";
import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms";
import { NumberHolder } from "#app/utils";
import { GameMode } from "#app/game-mode";
import { applyChallenges, ChallengeType } from "./challenge";
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -4180,8 +4183,12 @@ export class WaterSuperEffectTypeMultiplierAttr extends VariableMoveTypeMultipli
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const multiplier = args[0] as Utils.NumberHolder; const multiplier = args[0] as Utils.NumberHolder;
if (target.isOfType(Type.WATER)) { if (target.isOfType(Type.WATER)) {
multiplier.value *= 4; // Increased twice because initial reduction against water const effectivenessAgainstWater = new Utils.NumberHolder(getTypeDamageMultiplier(move.type, Type.WATER));
return true; applyChallenges(user.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, effectivenessAgainstWater);
if (effectivenessAgainstWater.value !== 0) {
multiplier.value *= 2 / effectivenessAgainstWater.value;
return true;
}
} }
return false; return false;
@ -6203,7 +6210,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr {
return false; return false;
} }
const userTypes = user.getTypes(); const userTypes = user.getTypes();
const validTypes = getTypeResistances(moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types const validTypes = this.getTypeResistances(user.scene.gameMode, moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types
if (!validTypes.length) { if (!validTypes.length) {
return false; return false;
} }
@ -6215,6 +6222,25 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr {
return true; return true;
} }
/**
* Retrieve the types resisting a given type. Used by Conversion 2
* @returns An array populated with Types, or an empty array if no resistances exist (Unknown or Stellar type)
*/
getTypeResistances(gameMode: GameMode, type: number): Type[] {
const typeResistances: Type[] = [];
for (let i = 0; i < Object.keys(Type).length; i++) {
const multiplier = new NumberHolder(1);
multiplier.value = getTypeDamageMultiplier(type, i);
applyChallenges(gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier);
if (multiplier.value < 1) {
typeResistances.push(i);
}
}
return typeResistances;
}
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const moveHistory = target.getLastXMoves(); const moveHistory = target.getLastXMoves();
@ -7940,7 +7966,8 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_OTHERS), .target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.FREEZE_DRY, Type.ICE, MoveCategory.SPECIAL, 70, 100, 20, 10, 0, 6) new AttackMove(Moves.FREEZE_DRY, Type.ICE, MoveCategory.SPECIAL, 70, 100, 20, 10, 0, 6)
.attr(StatusEffectAttr, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.FREEZE)
.attr(WaterSuperEffectTypeMultiplierAttr), .attr(WaterSuperEffectTypeMultiplierAttr)
.partial(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell.
new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6) new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6)
.soundBased() .soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES), .target(MoveTarget.ALL_NEAR_ENEMIES),

View File

@ -23,7 +23,7 @@ export enum Type {
export type TypeDamageMultiplier = 0 | 0.125 | 0.25 | 0.5 | 1 | 2 | 4 | 8; export type TypeDamageMultiplier = 0 | 0.125 | 0.25 | 0.5 | 1 | 2 | 4 | 8;
export function getTypeDamageMultiplier(attackType: integer, defType: integer): TypeDamageMultiplier { export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDamageMultiplier {
if (attackType === Type.UNKNOWN || defType === Type.UNKNOWN) { if (attackType === Type.UNKNOWN || defType === Type.UNKNOWN) {
return 1; return 1;
} }
@ -33,26 +33,10 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
switch (attackType) { switch (attackType) {
case Type.FIGHTING: case Type.FIGHTING:
return 2; return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.POISON:
case Type.GROUND:
case Type.ROCK:
case Type.BUG:
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
case Type.PSYCHIC:
case Type.ICE:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.GHOST: case Type.GHOST:
default:
return 0; return 0;
default:
return 1;
} }
case Type.FIGHTING: case Type.FIGHTING:
switch (attackType) { switch (attackType) {
@ -60,25 +44,12 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.PSYCHIC: case Type.PSYCHIC:
case Type.FAIRY: case Type.FAIRY:
return 2; return 2;
case Type.NORMAL:
case Type.FIGHTING:
case Type.POISON:
case Type.GROUND:
case Type.GHOST:
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
case Type.ICE:
case Type.DRAGON:
return 1;
case Type.ROCK: case Type.ROCK:
case Type.BUG: case Type.BUG:
case Type.DARK: case Type.DARK:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.FLYING: case Type.FLYING:
switch (attackType) { switch (attackType) {
@ -86,43 +57,20 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.ELECTRIC: case Type.ELECTRIC:
case Type.ICE: case Type.ICE:
return 2; return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.POISON:
case Type.GHOST:
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.PSYCHIC:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.FIGHTING: case Type.FIGHTING:
case Type.BUG: case Type.BUG:
case Type.GRASS: case Type.GRASS:
return 0.5; return 0.5;
case Type.GROUND: case Type.GROUND:
default:
return 0; return 0;
default:
return 1;
} }
case Type.POISON: case Type.POISON:
switch (attackType) { switch (attackType) {
case Type.GROUND: case Type.GROUND:
case Type.PSYCHIC: case Type.PSYCHIC:
return 2; return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.ROCK:
case Type.GHOST:
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.ELECTRIC:
case Type.ICE:
case Type.DRAGON:
case Type.DARK:
return 1;
case Type.FIGHTING: case Type.FIGHTING:
case Type.POISON: case Type.POISON:
case Type.BUG: case Type.BUG:
@ -130,7 +78,7 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.FAIRY: case Type.FAIRY:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.GROUND: case Type.GROUND:
switch (attackType) { switch (attackType) {
@ -138,25 +86,13 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.GRASS: case Type.GRASS:
case Type.ICE: case Type.ICE:
return 2; return 2;
case Type.NORMAL:
case Type.FIGHTING:
case Type.FLYING:
case Type.GROUND:
case Type.BUG:
case Type.GHOST:
case Type.STEEL:
case Type.FIRE:
case Type.PSYCHIC:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.POISON: case Type.POISON:
case Type.ROCK: case Type.ROCK:
return 0.5; return 0.5;
case Type.ELECTRIC: case Type.ELECTRIC:
default:
return 0; return 0;
default:
return 1;
} }
case Type.ROCK: case Type.ROCK:
switch (attackType) { switch (attackType) {
@ -166,23 +102,13 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.WATER: case Type.WATER:
case Type.GRASS: case Type.GRASS:
return 2; return 2;
case Type.ROCK:
case Type.BUG:
case Type.GHOST:
case Type.ELECTRIC:
case Type.PSYCHIC:
case Type.ICE:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.NORMAL: case Type.NORMAL:
case Type.FLYING: case Type.FLYING:
case Type.POISON: case Type.POISON:
case Type.FIRE: case Type.FIRE:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.BUG: case Type.BUG:
switch (attackType) { switch (attackType) {
@ -190,51 +116,26 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.ROCK: case Type.ROCK:
case Type.FIRE: case Type.FIRE:
return 2; return 2;
case Type.NORMAL:
case Type.POISON:
case Type.BUG:
case Type.GHOST:
case Type.STEEL:
case Type.WATER:
case Type.ELECTRIC:
case Type.PSYCHIC:
case Type.ICE:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.FIGHTING: case Type.FIGHTING:
case Type.GROUND: case Type.GROUND:
case Type.GRASS: case Type.GRASS:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.GHOST: case Type.GHOST:
switch (attackType) { switch (attackType) {
case Type.GHOST: case Type.GHOST:
case Type.DARK: case Type.DARK:
return 2; return 2;
case Type.FLYING:
case Type.GROUND:
case Type.ROCK:
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
case Type.PSYCHIC:
case Type.ICE:
case Type.DRAGON:
case Type.FAIRY:
return 1;
case Type.POISON: case Type.POISON:
case Type.BUG: case Type.BUG:
return 0.5; return 0.5;
case Type.NORMAL: case Type.NORMAL:
case Type.FIGHTING: case Type.FIGHTING:
default:
return 0; return 0;
default:
return 1;
} }
case Type.STEEL: case Type.STEEL:
switch (attackType) { switch (attackType) {
@ -242,11 +143,6 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.GROUND: case Type.GROUND:
case Type.FIRE: case Type.FIRE:
return 2; return 2;
case Type.GHOST:
case Type.WATER:
case Type.ELECTRIC:
case Type.DARK:
return 1;
case Type.NORMAL: case Type.NORMAL:
case Type.FLYING: case Type.FLYING:
case Type.ROCK: case Type.ROCK:
@ -259,8 +155,9 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.FAIRY: case Type.FAIRY:
return 0.5; return 0.5;
case Type.POISON: case Type.POISON:
default:
return 0; return 0;
default:
return 1;
} }
case Type.FIRE: case Type.FIRE:
switch (attackType) { switch (attackType) {
@ -268,16 +165,6 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.ROCK: case Type.ROCK:
case Type.WATER: case Type.WATER:
return 2; return 2;
case Type.NORMAL:
case Type.FIGHTING:
case Type.FLYING:
case Type.POISON:
case Type.GHOST:
case Type.ELECTRIC:
case Type.PSYCHIC:
case Type.DRAGON:
case Type.DARK:
return 1;
case Type.BUG: case Type.BUG:
case Type.STEEL: case Type.STEEL:
case Type.FIRE: case Type.FIRE:
@ -286,33 +173,20 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.FAIRY: case Type.FAIRY:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.WATER: case Type.WATER:
switch (attackType) { switch (attackType) {
case Type.GRASS: case Type.GRASS:
case Type.ELECTRIC: case Type.ELECTRIC:
return 2; return 2;
case Type.NORMAL:
case Type.FIGHTING:
case Type.FLYING:
case Type.POISON:
case Type.GROUND:
case Type.ROCK:
case Type.BUG:
case Type.GHOST:
case Type.PSYCHIC:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.STEEL: case Type.STEEL:
case Type.FIRE: case Type.FIRE:
case Type.WATER: case Type.WATER:
case Type.ICE: case Type.ICE:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.GRASS: case Type.GRASS:
switch (attackType) { switch (attackType) {
@ -322,49 +196,24 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.FIRE: case Type.FIRE:
case Type.ICE: case Type.ICE:
return 2; return 2;
case Type.NORMAL:
case Type.FIGHTING:
case Type.ROCK:
case Type.GHOST:
case Type.STEEL:
case Type.PSYCHIC:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.GROUND: case Type.GROUND:
case Type.WATER: case Type.WATER:
case Type.GRASS: case Type.GRASS:
case Type.ELECTRIC: case Type.ELECTRIC:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.ELECTRIC: case Type.ELECTRIC:
switch (attackType) { switch (attackType) {
case Type.GROUND: case Type.GROUND:
return 2; return 2;
case Type.NORMAL:
case Type.FIGHTING:
case Type.POISON:
case Type.ROCK:
case Type.BUG:
case Type.GHOST:
case Type.FIRE:
case Type.WATER:
case Type.GRASS:
case Type.PSYCHIC:
case Type.ICE:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.FLYING: case Type.FLYING:
case Type.STEEL: case Type.STEEL:
case Type.ELECTRIC: case Type.ELECTRIC:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.PSYCHIC: case Type.PSYCHIC:
switch (attackType) { switch (attackType) {
@ -372,25 +221,11 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.GHOST: case Type.GHOST:
case Type.DARK: case Type.DARK:
return 2; return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.POISON:
case Type.GROUND:
case Type.ROCK:
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
case Type.ICE:
case Type.DRAGON:
case Type.FAIRY:
return 1;
case Type.FIGHTING: case Type.FIGHTING:
case Type.PSYCHIC: case Type.PSYCHIC:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.ICE: case Type.ICE:
switch (attackType) { switch (attackType) {
@ -399,24 +234,10 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.STEEL: case Type.STEEL:
case Type.FIRE: case Type.FIRE:
return 2; return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.POISON:
case Type.GROUND:
case Type.BUG:
case Type.GHOST:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
case Type.PSYCHIC:
case Type.DRAGON:
case Type.DARK:
case Type.FAIRY:
return 1;
case Type.ICE: case Type.ICE:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.DRAGON: case Type.DRAGON:
switch (attackType) { switch (attackType) {
@ -424,25 +245,13 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.DRAGON: case Type.DRAGON:
case Type.FAIRY: case Type.FAIRY:
return 2; return 2;
case Type.NORMAL:
case Type.FIGHTING:
case Type.FLYING:
case Type.POISON:
case Type.GROUND:
case Type.ROCK:
case Type.BUG:
case Type.GHOST:
case Type.STEEL:
case Type.PSYCHIC:
case Type.DARK:
return 1;
case Type.FIRE: case Type.FIRE:
case Type.WATER: case Type.WATER:
case Type.GRASS: case Type.GRASS:
case Type.ELECTRIC: case Type.ELECTRIC:
return 0.5; return 0.5;
default: default:
return 0; return 1;
} }
case Type.DARK: case Type.DARK:
switch (attackType) { switch (attackType) {
@ -450,106 +259,33 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.BUG: case Type.BUG:
case Type.FAIRY: case Type.FAIRY:
return 2; return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.POISON:
case Type.GROUND:
case Type.ROCK:
case Type.STEEL:
case Type.FIRE:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
case Type.ICE:
case Type.DRAGON:
return 1;
case Type.GHOST: case Type.GHOST:
case Type.DARK: case Type.DARK:
return 0.5; return 0.5;
case Type.PSYCHIC: case Type.PSYCHIC:
default:
return 0; return 0;
default:
return 1;
} }
case Type.FAIRY: case Type.FAIRY:
switch (attackType) { switch (attackType) {
case Type.POISON: case Type.POISON:
case Type.STEEL: case Type.STEEL:
return 2; return 2;
case Type.NORMAL:
case Type.FLYING:
case Type.GROUND:
case Type.ROCK:
case Type.GHOST:
case Type.FIRE:
case Type.WATER:
case Type.GRASS:
case Type.ELECTRIC:
case Type.PSYCHIC:
case Type.ICE:
case Type.FAIRY:
return 1;
case Type.FIGHTING: case Type.FIGHTING:
case Type.BUG: case Type.BUG:
case Type.DARK: case Type.DARK:
return 0.5; return 0.5;
case Type.DRAGON: case Type.DRAGON:
default:
return 0; return 0;
default:
return 1;
} }
case Type.STELLAR: case Type.STELLAR:
return 1; return 1;
} }
return 0; return 1;
}
/**
* Retrieve the types resisting a given type
* @returns An array populated with Types, or an empty array if no resistances exist (Unknown or Stellar type)
*/
export function getTypeResistances(type: number): Type[] {
switch (type) {
case Type.NORMAL:
return [Type.ROCK, Type.STEEL, Type.GHOST];
case Type.FIGHTING:
return [Type.FLYING, Type.POISON, Type.BUG, Type.PSYCHIC, Type.FAIRY, Type.GHOST];
case Type.FLYING:
return [Type.ROCK, Type.ELECTRIC, Type.STEEL];
case Type.POISON:
return [Type.POISON, Type.GROUND, Type.ROCK, Type.GHOST, Type.STEEL];
case Type.GROUND:
return [Type.BUG, Type.GRASS, Type.FLYING];
case Type.ROCK:
return [Type.FIGHTING, Type.GROUND, Type.STEEL];
case Type.BUG:
return [Type.FIGHTING, Type.FLYING, Type.POISON, Type.GHOST, Type.STEEL, Type.FIRE, Type.FAIRY];
case Type.GHOST:
return [Type.DARK, Type.NORMAL];
case Type.STEEL:
return [Type.STEEL, Type.FIRE, Type.WATER, Type.ELECTRIC];
case Type.FIRE:
return [Type.ROCK, Type.FIRE, Type.WATER, Type.DRAGON];
case Type.WATER:
return [Type.WATER, Type.GRASS, Type.DRAGON];
case Type.GRASS:
return [Type.FLYING, Type.POISON, Type.BUG, Type.STEEL, Type.FIRE, Type.GRASS, Type.DRAGON];
case Type.ELECTRIC:
return [Type.GRASS, Type.ELECTRIC, Type.DRAGON, Type.GROUND];
case Type.PSYCHIC:
return [Type.STEEL, Type.PSYCHIC];
case Type.ICE:
return [Type.STEEL, Type.FIRE, Type.WATER, Type.ICE];
case Type.DRAGON:
return [Type.STEEL, Type.FAIRY];
case Type.DARK:
return [Type.FIGHTING, Type.DARK, Type.FAIRY];
case Type.FAIRY:
return [Type.POISON, Type.STEEL, Type.FIRE];
case Type.UNKNOWN:
case Type.STELLAR:
default:
return [];
}
} }
/** /**

View File

@ -3,5 +3,6 @@ export enum Challenges {
SINGLE_TYPE, SINGLE_TYPE,
LOWER_MAX_STARTER_COST, LOWER_MAX_STARTER_COST,
LOWER_STARTER_POINTS, LOWER_STARTER_POINTS,
FRESH_START FRESH_START,
INVERSE_BATTLE,
} }

View File

@ -49,6 +49,7 @@ import { BerryType } from "#enums/berry-type";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { Challenges } from "#enums/challenges";
import { getPokemonNameWithAffix } from "#app/messages.js"; import { getPokemonNameWithAffix } from "#app/messages.js";
import { DamagePhase } from "#app/phases/damage-phase.js"; import { DamagePhase } from "#app/phases/damage-phase.js";
import { FaintPhase } from "#app/phases/faint-phase.js"; import { FaintPhase } from "#app/phases/faint-phase.js";
@ -1315,12 +1316,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return 1; return 1;
} }
} }
const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType));
return getTypeDamageMultiplier(moveType, defType); applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier);
return multiplier.value;
}).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; }).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier;
const typeMultiplierAgainstFlying = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, Type.FLYING));
applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, typeMultiplierAgainstFlying);
// Handle strong winds lowering effectiveness of types super effective against pure flying // Handle strong winds lowering effectiveness of types super effective against pure flying
if (!ignoreStrongWinds && arena.weather?.weatherType === WeatherType.STRONG_WINDS && !arena.weather.isEffectSuppressed(this.scene) && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { if (!ignoreStrongWinds && arena.weather?.weatherType === WeatherType.STRONG_WINDS && !arena.weather.isEffectSuppressed(this.scene) && this.isOfType(Type.FLYING) && typeMultiplierAgainstFlying.value === 2) {
multiplier /= 2; multiplier /= 2;
if (!simulated) { if (!simulated) {
this.scene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); this.scene.queueMessage(i18next.t("weather:strongWindsEffectMessage"));
@ -3852,6 +3856,9 @@ export class EnemyPokemon extends Pokemon {
new PokemonMove(Moves.FLAMETHROWER), new PokemonMove(Moves.FLAMETHROWER),
new PokemonMove(Moves.COSMIC_POWER) new PokemonMove(Moves.COSMIC_POWER)
]; ];
if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) {
this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT);
}
break; break;
default: default:
super.generateAndPopulateMoveset(); super.generateAndPopulateMoveset();

View File

@ -269,5 +269,9 @@
"FRESH_START": { "FRESH_START": {
"name": "Hussa, noch einmal von vorn!", "name": "Hussa, noch einmal von vorn!",
"description": "Schließe die 'Neuanfang' Herausforderung ab" "description": "Schließe die 'Neuanfang' Herausforderung ab"
},
"INVERSE_BATTLE": {
"name": "Spieglein, Spieglein an der Wand",
"description": "Schließe die 'Umkehrkampf' Herausforderung ab"
} }
} }

View File

@ -25,5 +25,12 @@
"desc": "Du kannst nur die ursprünglichen Starter verwenden, genau so, als hättest du gerade erst mit Pokérogue begonnen.", "desc": "Du kannst nur die ursprünglichen Starter verwenden, genau so, als hättest du gerade erst mit Pokérogue begonnen.",
"value.0": "Aus", "value.0": "Aus",
"value.1": "An" "value.1": "An"
},
"inverseBattle": {
"name": "Umkehrkampf",
"shortName": "Umkehrkampf",
"desc": "Die Typen-Effektivität wird umgekehrt, und kein Typ ist gegen einen anderen Typ immun.\nDeaktiviert die Erfolge anderer Herausforderungen.",
"value.0": "Aus",
"value.1": "An"
} }
} }

View File

@ -260,5 +260,9 @@
"FRESH_START": { "FRESH_START": {
"name": "First Try!", "name": "First Try!",
"description": "Complete the Fresh Start challenge." "description": "Complete the Fresh Start challenge."
},
"INVERSE_BATTLE": {
"name": "Mirror rorriM",
"description": "Complete the Inverse Battle challenge.\n.egnellahc elttaB esrevnI eht etelpmoC"
} }
} }

View File

@ -279,5 +279,9 @@
"FRESH_START": { "FRESH_START": {
"name": "First Try!", "name": "First Try!",
"description": "Complete the Fresh Start challenge." "description": "Complete the Fresh Start challenge."
},
"INVERSE_BATTLE": {
"name": "Mirror rorriM",
"description": "Complete the Inverse Battle challenge.\n.egnellahc elttaB esrevnI eht etelpmoC"
} }
} }

View File

@ -25,5 +25,12 @@
"desc": "You can only use the original starters, and only as if you had just started PokéRogue.", "desc": "You can only use the original starters, and only as if you had just started PokéRogue.",
"value.0": "Off", "value.0": "Off",
"value.1": "On" "value.1": "On"
},
"inverseBattle": {
"name": "Inverse Battle",
"shortName": "Inverse",
"desc": "Type matchups are reversed and no type is immune to any other type.\nDisables other challenges' achievements.",
"value.0": "Off",
"value.1": "On"
} }
} }

View File

@ -170,5 +170,9 @@
"CLASSIC_VICTORY": { "CLASSIC_VICTORY": {
"name": "Imbatible", "name": "Imbatible",
"description": "Completa el juego en modo clásico." "description": "Completa el juego en modo clásico."
},
"INVERSE_BATTLE": {
"name": "Espejo ojepsE",
"description": "Completa el reto de Combate Inverso.\n.osrevnI etabmoC ed oter le atelpmoC"
} }
} }

View File

@ -18,5 +18,12 @@
"name": "Monotipo", "name": "Monotipo",
"desc": "Solo puedes usar Pokémon with the {{type}} type.", "desc": "Solo puedes usar Pokémon with the {{type}} type.",
"desc_default": "Solo puedes usar Pokémon del tipo elegido." "desc_default": "Solo puedes usar Pokémon del tipo elegido."
},
"inverseBattle": {
"name": "Combate Inverso",
"shortName": "Inverso",
"desc": "La efectividad de los tipos es invertida. No hay inmunidades entre tipos.\nEste reto deshabilita logros de otros retos.",
"value.0": "Desactivado",
"value.1": "Activado"
} }
} }

View File

@ -274,5 +274,9 @@
"FRESH_START": { "FRESH_START": {
"name": "Du premier coup !", "name": "Du premier coup !",
"description": "Terminer un challenge « Nouveau départ »." "description": "Terminer un challenge « Nouveau départ »."
},
"INVERSE_BATTLE": {
"name": "La teuté à verlan",
"description": "Terminer un challenge en Combat Inversé.\nMineter un lenjcha en Ba-con Versin."
} }
} }

View File

@ -25,5 +25,12 @@
"desc": "Vous ne pouvez choisir que les starters de base du jeu, comme si vous le recommenciez.", "desc": "Vous ne pouvez choisir que les starters de base du jeu, comme si vous le recommenciez.",
"value.0": "Non", "value.0": "Non",
"value.1": "Oui" "value.1": "Oui"
},
"inverseBattle": {
"name": "Combat Inversé",
"shortName": "Inversé",
"desc": "Les affinités de la table des types sont inversées et plus aucun type na dimmunité.\nDésactive les succès des autres challenges.",
"value.0": "Non",
"value.1": "Oui"
} }
} }

View File

@ -22,6 +22,7 @@
}, },
"freshStart": { "freshStart": {
"name": "出直し", "name": "出直し",
"shortName": "出直し",
"desc": "ポケローグを 始めた ばかりの ような ままで ゲーム開始の 最初のパートナーしか 使えません", "desc": "ポケローグを 始めた ばかりの ような ままで ゲーム開始の 最初のパートナーしか 使えません",
"value.0": "オフ", "value.0": "オフ",
"value.1": "オン" "value.1": "オン"

View File

@ -260,5 +260,9 @@
"FRESH_START": { "FRESH_START": {
"name": "첫트!", "name": "첫트!",
"description": "새 출발 챌린지 모드 클리어." "description": "새 출발 챌린지 모드 클리어."
},
"INVERSE_BATTLE": {
"name": "상성 전문가(였던 것)",
"description": "거꾸로 배틀 챌린지 모드 클리어."
} }
} }

View File

@ -25,5 +25,12 @@
"desc": "포켓로그를 처음 시작했던 때처럼 강화가 전혀 되지 않은 오리지널 스타팅 포켓몬만 고를 수 있습니다.", "desc": "포켓로그를 처음 시작했던 때처럼 강화가 전혀 되지 않은 오리지널 스타팅 포켓몬만 고를 수 있습니다.",
"value.0": "해제", "value.0": "해제",
"value.1": "설정" "value.1": "설정"
},
"inverseBattle": {
"name": "거꾸로 배틀",
"shortName": "거꾸로",
"desc": "타입 상성이 반대로 바뀌고 면역 타입은 약점 타입이 됩니다.\n설정 시 다른 챌린지 업적은 달성할 수 없습니다.",
"value.0": "해제",
"value.1": "설정"
} }
} }

View File

@ -264,5 +264,9 @@
"FRESH_START": { "FRESH_START": {
"name": "De Primeira!", "name": "De Primeira!",
"description": "Complete o desafio de novo começo." "description": "Complete o desafio de novo começo."
},
"INVERSE_BATTLE": {
"name": "A torre da derrotA",
"description": "Complete o desafio da Batalha Inversa.\n.asrevnI ahlataB ad oifased o etelpmoC"
} }
} }

View File

@ -25,5 +25,12 @@
"desc": "Você só pode usar os iniciais originais, como se tivesse acabado de começar o PokéRogue.", "desc": "Você só pode usar os iniciais originais, como se tivesse acabado de começar o PokéRogue.",
"value.0": "Desligado", "value.0": "Desligado",
"value.1": "Ligado" "value.1": "Ligado"
},
"inverseBattle": {
"name": "Batalha Inversa",
"shortName": "Inversa",
"desc": "Fraquezas e resistências de tipos são invertidas e nenhum tipo é imune a outro tipo.\nDesativa as conquistas de outros desafios.",
"value.0": "Desligado",
"value.1": "Ligado"
} }
} }

View File

@ -268,5 +268,9 @@
"FRESH_START": { "FRESH_START": {
"name": "初次尝试!", "name": "初次尝试!",
"description": "完成初次尝试挑战" "description": "完成初次尝试挑战"
},
"INVERSE_BATTLE": {
"name": "镜子子镜",
"description": "完成逆转之战挑战\n战挑战之转逆成完"
} }
} }

View File

@ -25,5 +25,12 @@
"desc": "你只能使用御三家,就像是你第一次玩宝可梦肉鸽一样。", "desc": "你只能使用御三家,就像是你第一次玩宝可梦肉鸽一样。",
"value.0": "关闭", "value.0": "关闭",
"value.1": "开启" "value.1": "开启"
},
"inverseBattle": {
"name": "逆转之战",
"shortName": "逆转之战",
"desc": "属性相克关系被反转,且没有任何属性对其他属性免疫。\n禁用其他挑战的成就。",
"value.0": "关闭",
"value.1": "开启"
} }
} }

View File

@ -252,5 +252,9 @@
}, },
"MONO_FAIRY": { "MONO_FAIRY": {
"name": "林克,醒醒!" "name": "林克,醒醒!"
},
"INVERSE_BATTLE": {
"name": "鏡子子鏡",
"description": "完成逆轉之戰挑戰\n戰挑戰之轉逆成完"
} }
} }

View File

@ -19,5 +19,12 @@
"name": "單屬性", "name": "單屬性",
"desc": "你只能使用{{type}}\n屬性的寶可夢", "desc": "你只能使用{{type}}\n屬性的寶可夢",
"desc_default": "你只能使用所選\n屬性的寶可夢" "desc_default": "你只能使用所選\n屬性的寶可夢"
},
"inverseBattle": {
"name": "逆轉之戰",
"shortName": "逆轉之戰",
"desc": "屬性相克關系被反轉,且沒有任何屬性對其他屬性免疫。\n禁用其他挑戰的成就。",
"value.0": "關閉",
"value.1": "開啓"
} }
} }

View File

@ -5,8 +5,9 @@ import { pokemonEvolutions } from "#app/data/pokemon-evolutions";
import i18next from "i18next"; import i18next from "i18next";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js"; import { Challenge, FreshStartChallenge, InverseBattleChallenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge";
import { ConditionFn } from "#app/@types/common.js"; import { Challenges } from "#app/enums/challenges";
import { ConditionFn } from "#app/@types/common";
export enum AchvTier { export enum AchvTier {
COMMON, COMMON,
@ -137,8 +138,8 @@ export class ModifierAchv extends Achv {
} }
export class ChallengeAchv extends Achv { export class ChallengeAchv extends Achv {
constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, challengeFunc: (challenge: Challenge) => boolean) { constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, challengeFunc: (challenge: Challenge, scene: BattleScene) => boolean) {
super(localizationKey, name, description, iconImage, score, (_scene: BattleScene, args: any[]) => challengeFunc((args[0] as Challenge))); super(localizationKey, name, description, iconImage, score, (_scene: BattleScene, args: any[]) => challengeFunc(args[0] as Challenge, _scene));
} }
} }
@ -275,6 +276,8 @@ export function getAchievementDescription(localizationKey: string): string {
return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) }); return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) });
case "FRESH_START": case "FRESH_START":
return i18next.t("achv:FRESH_START.description", { context: genderStr }); return i18next.t("achv:FRESH_START.description", { context: genderStr });
case "INVERSE_BATTLE":
return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr });
default: default:
return ""; return "";
} }
@ -323,34 +326,35 @@ export const achvs = {
PERFECT_IVS: new Achv("PERFECT_IVS", "", "PERFECT_IVS.description", "blunder_policy", 100), PERFECT_IVS: new Achv("PERFECT_IVS", "", "PERFECT_IVS.description", "blunder_policy", 100),
CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY", "", "CLASSIC_VICTORY.description", "relic_crown", 150), CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY", "", "CLASSIC_VICTORY.description", "relic_crown", 150),
UNEVOLVED_CLASSIC_VICTORY: new Achv("UNEVOLVED_CLASSIC_VICTORY", "", "UNEVOLVED_CLASSIC_VICTORY.description", "eviolite", 175, c => c.getParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)), UNEVOLVED_CLASSIC_VICTORY: new Achv("UNEVOLVED_CLASSIC_VICTORY", "", "UNEVOLVED_CLASSIC_VICTORY.description", "eviolite", 175, c => c.getParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)),
MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, c => c instanceof SingleGenerationChallenge && c.value === 1), MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 1 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, c => c instanceof SingleGenerationChallenge && c.value === 2), MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 2 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, c => c instanceof SingleGenerationChallenge && c.value === 3), MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 3 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, c => c instanceof SingleGenerationChallenge && c.value === 4), MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 4 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, c => c instanceof SingleGenerationChallenge && c.value === 5), MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 5 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, c => c instanceof SingleGenerationChallenge && c.value === 6), MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 6 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, c => c instanceof SingleGenerationChallenge && c.value === 7), MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 7 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, c => c instanceof SingleGenerationChallenge && c.value === 8), MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 8 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, c => c instanceof SingleGenerationChallenge && c.value === 9), MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 9 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, c => c instanceof SingleTypeChallenge && c.value === 1), MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 1 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, c => c instanceof SingleTypeChallenge && c.value === 2), MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 2 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, c => c instanceof SingleTypeChallenge && c.value === 3), MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 3 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, c => c instanceof SingleTypeChallenge && c.value === 4), MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 4 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, c => c instanceof SingleTypeChallenge && c.value === 5), MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 5 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, c => c instanceof SingleTypeChallenge && c.value === 6), MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 6 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, c => c instanceof SingleTypeChallenge && c.value === 7), MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 7 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, c => c instanceof SingleTypeChallenge && c.value === 8), MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 8 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, c => c instanceof SingleTypeChallenge && c.value === 9), MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 9 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, c => c instanceof SingleTypeChallenge && c.value === 10), MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 10 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, c => c instanceof SingleTypeChallenge && c.value === 11), MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 11 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, c => c instanceof SingleTypeChallenge && c.value === 12), MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 12 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, c => c instanceof SingleTypeChallenge && c.value === 13), MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 13 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, c => c instanceof SingleTypeChallenge && c.value === 14), MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 14 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, c => c instanceof SingleTypeChallenge && c.value === 15), MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 15 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, c => c instanceof SingleTypeChallenge && c.value === 16), MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 16 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, c => c instanceof SingleTypeChallenge && c.value === 17), MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 17 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, c => c instanceof SingleTypeChallenge && c.value === 18), MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 18 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, c => c instanceof FreshStartChallenge && c.value === 1), FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c, scene) => c instanceof FreshStartChallenge && c.value > 0 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0),
}; };
export function initAchievements() { export function initAchievements() {

View File

@ -0,0 +1,203 @@
import { BattlerIndex } from "#app/battle";
import { allMoves } from "#app/data/move";
import { Type } from "#app/data/type";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { Challenges } from "#enums/challenges";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { StatusEffect } from "#enums/status-effect";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
const TIMEOUT = 20 * 1000;
describe("Inverse Battle", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1);
game.override
.battleType("single")
.starterSpecies(Species.FEEBAS)
.ability(Abilities.BALL_FETCH)
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH);
});
it("1. immune types are 2x effective - Thunderbolt against Ground Type", async () => {
game.override.enemySpecies(Species.SANDSHREW);
await game.challengeMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
expect(enemy.getMoveEffectiveness(player, allMoves[Moves.THUNDERBOLT])).toBe(2);
}, TIMEOUT);
it("2. 2x effective types are 0.5x effective - Thunderbolt against Flying Type", async () => {
game.override.enemySpecies(Species.PIDGEY);
await game.challengeMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
expect(enemy.getMoveEffectiveness(player, allMoves[Moves.THUNDERBOLT])).toBe(0.5);
}, TIMEOUT);
it("3. 0.5x effective types are 2x effective - Thunderbolt against Electric Type", async () => {
game.override.enemySpecies(Species.CHIKORITA);
await game.challengeMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
expect(enemy.getMoveEffectiveness(player, allMoves[Moves.THUNDERBOLT])).toBe(2);
}, TIMEOUT);
it("4. Stealth Rock follows the inverse matchups - Stealth Rock against Charizard deals 1/32 of max HP", async () => {
game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0);
game.override
.enemySpecies(Species.CHARIZARD)
.enemyLevel(100);
await game.challengeMode.startBattle();
const charizard = game.scene.getEnemyPokemon()!;
const maxHp = charizard.getMaxHp();
const damage_prediction = Math.max(Math.round(charizard.getMaxHp() / 32), 1);
console.log("Damage calcuation before round: " + charizard.getMaxHp() / 32);
const currentHp = charizard.hp;
const expectedHP = maxHp - damage_prediction;
console.log("Charizard's max HP: " + maxHp, "Damage: " + damage_prediction, "Current HP: " + currentHp, "Expected HP: " + expectedHP);
expect(currentHp).toBeGreaterThan(maxHp * 31 / 32 - 1);
}, TIMEOUT);
it("5. Freeze Dry is 2x effective against Water Type like other Ice type Move - Freeze Dry against Squirtle", async () => {
game.override.enemySpecies(Species.SQUIRTLE);
await game.challengeMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
expect(enemy.getMoveEffectiveness(player, allMoves[Moves.FREEZE_DRY])).toBe(2);
}, TIMEOUT);
it("6. Water Absorb should heal against water moves - Water Absorb against Water gun", async () => {
game.override
.moveset([Moves.WATER_GUN])
.enemyAbility(Abilities.WATER_ABSORB);
await game.challengeMode.startBattle();
const enemy = game.scene.getEnemyPokemon()!;
enemy.hp = enemy.getMaxHp() - 1;
game.move.select(Moves.WATER_GUN);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEndPhase);
expect(enemy.hp).toBe(enemy.getMaxHp());
}, TIMEOUT);
it("7. Fire type does not get burned - Will-O-Wisp against Charmander", async () => {
game.override
.moveset([Moves.WILL_O_WISP])
.enemySpecies(Species.CHARMANDER);
await game.challengeMode.startBattle();
const enemy = game.scene.getEnemyPokemon()!;
game.move.select(Moves.WILL_O_WISP);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.move.forceHit();
await game.phaseInterceptor.to(MoveEndPhase);
expect(enemy.status?.effect).not.toBe(StatusEffect.BURN);
}, TIMEOUT);
it("8. Electric type does not get paralyzed - Nuzzle against Pikachu", async () => {
game.override
.moveset([Moves.NUZZLE])
.enemySpecies(Species.PIKACHU)
.enemyLevel(50);
await game.challengeMode.startBattle();
const enemy = game.scene.getEnemyPokemon()!;
game.move.select(Moves.NUZZLE);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to(MoveEndPhase);
expect(enemy.status?.effect).not.toBe(StatusEffect.PARALYSIS);
}, TIMEOUT);
it("10. Anticipation should trigger on 2x effective moves - Anticipation against Thunderbolt", async () => {
game.override
.moveset([Moves.THUNDERBOLT])
.enemySpecies(Species.SANDSHREW)
.enemyAbility(Abilities.ANTICIPATION);
await game.challengeMode.startBattle();
expect(game.scene.getEnemyPokemon()?.summonData.abilitiesApplied[0]).toBe(Abilities.ANTICIPATION);
}, TIMEOUT);
it("11. Conversion 2 should change the type to the resistive type - Conversion 2 against Dragonite", async () => {
game.override
.moveset([Moves.CONVERSION_2])
.enemyMoveset([Moves.DRAGON_CLAW, Moves.DRAGON_CLAW, Moves.DRAGON_CLAW, Moves.DRAGON_CLAW]);
await game.challengeMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
game.move.select(Moves.CONVERSION_2);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to(TurnEndPhase);
expect(player.getTypes()[0]).toBe(Type.DRAGON);
}, TIMEOUT);
it("12. Flying Press should be 0.25x effective against Grass + Dark Type - Flying Press against Meowscarada", async () => {
game.override
.moveset([Moves.FLYING_PRESS])
.enemySpecies(Species.MEOWSCARADA);
await game.challengeMode.startBattle();
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
expect(enemy.getMoveEffectiveness(player, allMoves[Moves.FLYING_PRESS])).toBe(0.25);
}, TIMEOUT);
});

View File

@ -40,6 +40,7 @@ import fs from "fs";
import { vi } from "vitest"; import { vi } from "vitest";
import { ClassicModeHelper } from "./helpers/classicModeHelper"; import { ClassicModeHelper } from "./helpers/classicModeHelper";
import { DailyModeHelper } from "./helpers/dailyModeHelper"; import { DailyModeHelper } from "./helpers/dailyModeHelper";
import { ChallengeModeHelper } from "./helpers/challengeModeHelper";
import { MoveHelper } from "./helpers/moveHelper"; import { MoveHelper } from "./helpers/moveHelper";
import { OverridesHelper } from "./helpers/overridesHelper"; import { OverridesHelper } from "./helpers/overridesHelper";
import { SettingsHelper } from "./helpers/settingsHelper"; import { SettingsHelper } from "./helpers/settingsHelper";
@ -57,6 +58,7 @@ export default class GameManager {
public readonly move: MoveHelper; public readonly move: MoveHelper;
public readonly classicMode: ClassicModeHelper; public readonly classicMode: ClassicModeHelper;
public readonly dailyMode: DailyModeHelper; public readonly dailyMode: DailyModeHelper;
public readonly challengeMode: ChallengeModeHelper;
public readonly settings: SettingsHelper; public readonly settings: SettingsHelper;
/** /**
@ -77,6 +79,7 @@ export default class GameManager {
this.move = new MoveHelper(this); this.move = new MoveHelper(this);
this.classicMode = new ClassicModeHelper(this); this.classicMode = new ClassicModeHelper(this);
this.dailyMode = new DailyModeHelper(this); this.dailyMode = new DailyModeHelper(this);
this.challengeMode = new ChallengeModeHelper(this);
this.settings = new SettingsHelper(this); this.settings = new SettingsHelper(this);
} }

View File

@ -0,0 +1,78 @@
import { BattleStyle } from "#app/enums/battle-style";
import { Species } from "#app/enums/species";
import overrides from "#app/overrides";
import { EncounterPhase } from "#app/phases/encounter-phase";
import { SelectStarterPhase } from "#app/phases/select-starter-phase";
import { Mode } from "#app/ui/ui";
import { generateStarter } from "../gameManagerUtils";
import { GameManagerHelper } from "./gameManagerHelper";
import { Challenge } from "#app/data/challenge";
import { CommandPhase } from "#app/phases/command-phase";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { Challenges } from "#enums/challenges";
import { copyChallenge } from "data/challenge";
/**
* Helper to handle Challenge mode specifics
*/
export class ChallengeModeHelper extends GameManagerHelper {
challenges: Challenge[] = [];
/**
* Adds a challenge to the challenge mode helper.
* @param id - The challenge id.
* @param value - The challenge value.
* @param severity - The challenge severity.
*/
addChallenge(id: Challenges, value: number, severity: number) {
const challenge = copyChallenge({ id, value, severity });
this.challenges.push(challenge);
}
/**
* Runs the Challenge game to the summon phase.
* @param gameMode - Optional game mode to set.
* @returns A promise that resolves when the summon phase is reached.
*/
async runToSummon(species?: Species[]) {
await this.game.runToTitle();
this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
this.game.scene.gameMode.challenges = this.challenges;
const starters = generateStarter(this.game.scene, species);
const selectStarterPhase = new SelectStarterPhase(this.game.scene);
this.game.scene.pushPhase(new EncounterPhase(this.game.scene, false));
selectStarterPhase.initBattle(starters);
});
await this.game.phaseInterceptor.run(EncounterPhase);
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) {
this.game.removeEnemyHeldItems();
}
}
/**
* Transitions to the start of a battle.
* @param species - Optional array of species to start the battle with.
* @returns A promise that resolves when the battle is started.
*/
async startBattle(species?: Species[]) {
await this.runToSummon(species);
if (this.game.scene.battleStyle === BattleStyle.SWITCH) {
this.game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.game.setMode(Mode.MESSAGE);
this.game.endPhase();
}, () => this.game.isCurrentPhase(CommandPhase) || this.game.isCurrentPhase(TurnInitPhase));
this.game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.game.setMode(Mode.MESSAGE);
this.game.endPhase();
}, () => this.game.isCurrentPhase(CommandPhase) || this.game.isCurrentPhase(TurnInitPhase));
}
await this.game.phaseInterceptor.to(CommandPhase);
console.log("==================[New Turn]==================");
}
}

View File

@ -374,23 +374,14 @@ export default class RunInfoUiHandler extends UiHandler {
case GameModes.CHALLENGE: case GameModes.CHALLENGE:
modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); modeText.appendText(`${i18next.t("gameMode:challenge")}`, false);
modeText.appendText(`\t\t${i18next.t("runHistory:challengeRules")}: `); modeText.appendText(`\t\t${i18next.t("runHistory:challengeRules")}: `);
const runChallenges = this.runInfo.challenges; const rules: string[] = this.challengeParser();
const rules: string[] = [];
for (let i = 0; i < runChallenges.length; i++) {
if (runChallenges[i].id === Challenges.SINGLE_GENERATION && runChallenges[i].value !== 0) {
rules.push(i18next.t(`runHistory:challengeMonoGen${runChallenges[i].value}`));
} else if (runChallenges[i].id === Challenges.SINGLE_TYPE && runChallenges[i].value !== 0) {
rules.push(i18next.t(`pokemonInfo:Type.${Type[runChallenges[i].value-1]}` as const));
} else if (runChallenges[i].id === Challenges.FRESH_START && runChallenges[i].value !== 0) {
rules.push(i18next.t("challenges:freshStart.name"));
}
}
if (rules) { if (rules) {
for (let i = 0; i < rules.length; i++) { for (let i = 0; i < rules.length; i++) {
const newline = i > 0 && i%2 === 0;
if (i > 0) { if (i > 0) {
modeText.appendText(" + ", false); modeText.appendText(" + ", newline);
} }
modeText.appendText(rules[i], false); modeText.appendText(rules[i], newline);
} }
} }
break; break;
@ -466,6 +457,34 @@ export default class RunInfoUiHandler extends UiHandler {
this.runContainer.add(this.runInfoContainer); this.runContainer.add(this.runInfoContainer);
} }
/**
* This function parses the Challenges section of the Run Entry and returns a list of active challenge.
* @return string[] of active challenge names
*/
private challengeParser(): string[] {
const rules: string[] = [];
for (let i = 0; i < this.runInfo.challenges.length; i++) {
if (this.runInfo.challenges[i].value !== 0) {
switch (this.runInfo.challenges[i].id) {
case Challenges.SINGLE_GENERATION:
rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`));
break;
case Challenges.SINGLE_TYPE:
rules.push(i18next.t(`pokemonInfo:Type.${Type[this.runInfo.challenges[i].value-1]}` as const));
break;
case Challenges.FRESH_START:
rules.push(i18next.t("challenges:freshStart.name"));
break;
case Challenges.INVERSE_BATTLE:
//
rules.push(i18next.t("challenges:inverseBattle.shortName").split("").reverse().join(""));
break;
}
}
}
return rules;
}
/** /**
* Parses and displays the run's player party. * Parses and displays the run's player party.
* Default Information: Icon, Level, Nature, Ability, Passive, Shiny Status, Fusion Status, Stats, and Moves. * Default Information: Icon, Level, Nature, Ability, Passive, Shiny Status, Fusion Status, Stats, and Moves.