mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-04 07:22:19 +02:00
Added move condition to Transform + fixed up imposter target selection
This commit is contained in:
parent
5a1fbda44a
commit
d9d119aada
@ -75,7 +75,7 @@ import type {
|
||||
AbAttrString,
|
||||
AbAttrMap,
|
||||
} from "#app/@types/ability-types";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import type Move from "#app/data/moves/move";
|
||||
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||
import type { Constructor } from "#app/utils/common";
|
||||
@ -3870,60 +3870,39 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr {
|
||||
* Attribute used by {@linkcode AbilityId.IMPOSTER} to transform into a random opposing pokemon on entry.
|
||||
*/
|
||||
export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
||||
private targetIndex: BattlerIndex = BattlerIndex.ATTACKER;
|
||||
constructor() {
|
||||
super(true, false);
|
||||
}
|
||||
|
||||
private getTarget(targets: Pokemon[]): Pokemon {
|
||||
let target: Pokemon = targets[0];
|
||||
if (targets.length > 1) {
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
// in a double battle, if one of the opposing pokemon is fused the other one will be chosen
|
||||
// if both are fused, then Imposter will fail below
|
||||
if (targets[0].fusionSpecies) {
|
||||
target = targets[1];
|
||||
return;
|
||||
}
|
||||
if (targets[1].fusionSpecies) {
|
||||
target = targets[0];
|
||||
return;
|
||||
}
|
||||
target = randSeedItem(targets);
|
||||
}, globalScene.currentBattle.waveIndex);
|
||||
} else {
|
||||
target = targets[0];
|
||||
/**
|
||||
* Return the correct opponent for Imposter to copy, barring enemies with fusions, substitutes and illusions.
|
||||
* @param user - The {@linkcode Pokemon} with this ability.
|
||||
* @returns The {@linkcode Pokemon} to transform into, or `undefined` if none are eligible.
|
||||
* @remarks
|
||||
* This sets the private `targetIndex` field to the target's {@linkcode BattlerIndex} on success.
|
||||
*/
|
||||
private getTarget(user: Pokemon): Pokemon | undefined {
|
||||
// As opposed to the mainline behavior of "always copy the opposite slot",
|
||||
// PKR Imposter instead attempts to copy a random eligible opposing Pokemon meeting Transform's criteria.
|
||||
// If none are eligible to copy, it will not activate.
|
||||
const targets = user.getOpponents().filter(opp => user.canTransformInto(opp));
|
||||
if (targets.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
target = target!;
|
||||
|
||||
return target;
|
||||
const mon = targets[user.randBattleSeedInt(targets.length)];
|
||||
this.targetIndex = mon.getBattlerIndex();
|
||||
return mon;
|
||||
}
|
||||
|
||||
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean {
|
||||
const targets = pokemon.getOpponents();
|
||||
const target = this.getTarget(targets);
|
||||
|
||||
if (target?.summonData?.illusion || pokemon?.isTransformed() || target?.isTransformed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (simulated || !targets.length) {
|
||||
return simulated;
|
||||
}
|
||||
|
||||
// transforming from or into fusion pokemon causes various problems (including crashes and save corruption)
|
||||
return !(this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies);
|
||||
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
||||
const target = this.getTarget(pokemon);
|
||||
return !!target;
|
||||
}
|
||||
|
||||
override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
||||
const target = this.getTarget(pokemon.getOpponents());
|
||||
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"PokemonTransformPhase",
|
||||
pokemon.getBattlerIndex(),
|
||||
target.getBattlerIndex(),
|
||||
true,
|
||||
);
|
||||
globalScene.phaseManager.unshiftNew("PokemonTransformPhase", pokemon.getBattlerIndex(), this.targetIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3997,7 +3976,7 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
|
||||
/**
|
||||
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander}.
|
||||
* When the source of an ability with this attribute detects a Dondozo as their active ally, the source "jumps
|
||||
* into the Dondozo's mouth," sharply boosting the Dondozo's stats, cancelling the source's moves, and
|
||||
* into the Dondozo's mouth"m sharply boosting the Dondozo's stats, cancelling the source's moves, and
|
||||
* causing attacks that target the source to always miss.
|
||||
*/
|
||||
export class CommanderAbAttr extends AbAttr {
|
||||
@ -8402,7 +8381,8 @@ export function initAbilities() {
|
||||
.bypassFaint(),
|
||||
new Ability(AbilityId.IMPOSTER, 5)
|
||||
.attr(PostSummonTransformAbAttr)
|
||||
.uncopiable(),
|
||||
.uncopiable()
|
||||
.edgeCase(), // Should copy rage fist hit count
|
||||
new Ability(AbilityId.INFILTRATOR, 5)
|
||||
.attr(InfiltratorAbAttr)
|
||||
.partial(), // does not bypass Mist
|
||||
|
@ -7606,19 +7606,23 @@ export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by Transform
|
||||
* Attribute used to transform into the target on move use.
|
||||
*
|
||||
* Used for {@linkcode MoveId.TRANSFORM}.
|
||||
*/
|
||||
export class TransformAttr extends MoveEffectAttr {
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args) || (target.isTransformed() || user.isTransformed())) {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battle:attackFailed"));
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.phaseManager.unshiftNew("PokemonTransformPhase", user.getBattlerIndex(), target.getBattlerIndex());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
return (user, target) => user.canTransformInto(target)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -8840,12 +8844,12 @@ export function initMoves() {
|
||||
.makesContact(false),
|
||||
new StatusMove(MoveId.TRANSFORM, PokemonType.NORMAL, -1, 10, -1, 0, 1)
|
||||
.attr(TransformAttr)
|
||||
.condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE))
|
||||
.condition((user, target, move) => !target.summonData.illusion && !user.summonData.illusion)
|
||||
// transforming from or into fusion pokemon causes various problems (such as crashes)
|
||||
.condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE) && !user.fusionSpecies && !target.fusionSpecies)
|
||||
.ignoresProtect()
|
||||
// Transforming should copy the target's rage fist hit count
|
||||
/* Transform:
|
||||
* Does not copy the target's rage fist hit count
|
||||
* Does not copy the target's volatile status conditions (ie BattlerTags)
|
||||
* Renders user typeless when copying typeless opponent (should revert to original typing)
|
||||
*/
|
||||
.edgeCase(),
|
||||
new AttackMove(MoveId.BUBBLE, PokemonType.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
|
||||
|
@ -1066,6 +1066,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return this.summonData.speciesForm !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this Pokemon can transform into an opposing Pokemon.
|
||||
* @param target - The {@linkcode Pokemon} being transformed into.
|
||||
* @returns Whether this Pokemon can transform into `target`.
|
||||
*/
|
||||
canTransformInto(target: Pokemon): boolean {
|
||||
return !(
|
||||
// Neither pokemon can be already transformed
|
||||
(
|
||||
this.isTransformed() ||
|
||||
target.isTransformed() ||
|
||||
// Neither pokemon can be behind an illusion
|
||||
target.summonData.illusion ||
|
||||
this.summonData.illusion ||
|
||||
// The target cannot be behind a substitute
|
||||
target.getTag(BattlerTagType.SUBSTITUTE) ||
|
||||
// Transforming to/from fusion pokemon causes various problems (crashes, etc.)
|
||||
// TODO: Consider lifting restriction once bug is fixed
|
||||
this.isFusion() ||
|
||||
target.isFusion()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the fusionSpeciesForm of the illusion or not.
|
||||
*/
|
||||
@ -1280,39 +1304,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
/**
|
||||
* Retrieves the entire set of stats of this {@linkcode Pokemon}.
|
||||
* @param bypassSummonData - whether to use actual stats or in-battle overriden stats from Transform; default `true`
|
||||
* @returns the numeric values of this {@linkcode Pokemon}'s stats
|
||||
* @param bypassSummonData - Whether to prefer actual stats (`true`) or in-battle overridden stats (`false`); default `true`
|
||||
* @returns The numeric values of this {@linkcode Pokemon}'s stats as an array.
|
||||
*/
|
||||
getStats(bypassSummonData = true): number[] {
|
||||
if (!bypassSummonData && this.summonData.stats) {
|
||||
return this.summonData.stats;
|
||||
if (!bypassSummonData) {
|
||||
// Only grab summon data stats if nonzero
|
||||
return this.summonData.stats.map((s, i) => s || this.stats[i]);
|
||||
}
|
||||
return this.stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the corresponding {@linkcode PermanentStat} of the {@linkcode Pokemon}.
|
||||
* @param stat the desired {@linkcode PermanentStat}
|
||||
* @param bypassSummonData prefer actual stats (`true` by default) or in-battle overridden stats (`false`)
|
||||
* @returns the numeric value of the desired {@linkcode Stat}
|
||||
* @param stat - The desired {@linkcode PermanentStat}.
|
||||
* @param bypassSummonData - Whether to prefer actual stats (`true`) or in-battle overridden stats (`false`); default `true`
|
||||
* @returns The numeric value of the desired {@linkcode Stat}.
|
||||
*/
|
||||
getStat(stat: PermanentStat, bypassSummonData = true): number {
|
||||
if (!bypassSummonData && this.summonData.stats[stat] !== 0) {
|
||||
return this.summonData.stats[stat];
|
||||
if (!bypassSummonData) {
|
||||
// 0 = no override
|
||||
return this.summonData.stats[stat] || this.stats[stat];
|
||||
}
|
||||
return this.stats[stat];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value to the corrseponding {@linkcode PermanentStat} of the {@linkcode Pokemon}.
|
||||
*
|
||||
* Note that this does nothing if {@linkcode value} is less than 0.
|
||||
* @param stat the desired {@linkcode PermanentStat} to be overwritten
|
||||
* @param value the desired numeric value
|
||||
* @param bypassSummonData write to actual stats (`true` by default) or in-battle overridden stats (`false`)
|
||||
* Change one of this {@linkcode Pokemon}'s {@linkcode PermanentStat}s to the specified value.
|
||||
* @param stat - The {@linkcode PermanentStat} to be overwritten.
|
||||
* @param value - The stat value to set. Ignored if `<=0`
|
||||
* @param bypassSummonData - Whether to write to actual stats (`true`) or in-battle overridden stats (`false`); default `true`
|
||||
*/
|
||||
setStat(stat: PermanentStat, value: number, bypassSummonData = true): void {
|
||||
if (value < 0) {
|
||||
if (value <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1328,31 +1352,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @returns the numeric values of the {@linkcode Pokemon}'s in-battle stat stages if available, a fresh stat stage array otherwise
|
||||
*/
|
||||
getStatStages(): number[] {
|
||||
return this.summonData ? this.summonData.statStages : [0, 0, 0, 0, 0, 0, 0];
|
||||
return this.summonData.statStages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the in-battle stage of the specified {@linkcode BattleStat}.
|
||||
* @param stat the {@linkcode BattleStat} whose stage is desired
|
||||
* @returns the stage of the desired {@linkcode BattleStat} if available, 0 otherwise
|
||||
* Retrieve the value of the given stat stage for this {@linkcode Pokemon}.
|
||||
* @param stat - The {@linkcode BattleStat} to retrieve the stat stage for.
|
||||
* @returns The value of the desired stat stage as a number within the range `[-6, +6]`.
|
||||
*/
|
||||
getStatStage(stat: BattleStat): number {
|
||||
return this.summonData ? this.summonData.statStages[stat - 1] : 0;
|
||||
return this.summonData.statStages[stat - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value to the in-battle stage of the corresponding {@linkcode BattleStat} of the {@linkcode Pokemon}.
|
||||
*
|
||||
* Note that, if the value is not within a range of [-6, 6], it will be forced to the closest range bound.
|
||||
* @param stat the {@linkcode BattleStat} whose stage is to be overwritten
|
||||
* @param value the desired numeric value
|
||||
* Sets this {@linkcode Pokemon}'s in-battle stat stage to the corresponding value.
|
||||
* @param stat - The {@linkcode BattleStat} whose stage is to be overwritten.
|
||||
* @param value - The value of the stat stage to set, forcibly clamped within the range `[-6, +6]`.
|
||||
*/
|
||||
setStatStage(stat: BattleStat, value: number): void {
|
||||
if (value >= -6) {
|
||||
this.summonData.statStages[stat - 1] = Math.min(value, 6);
|
||||
} else {
|
||||
this.summonData.statStages[stat - 1] = Math.max(value, -6);
|
||||
}
|
||||
this.summonData.statStages[stat - 1] = Phaser.Math.Clamp(value, -6, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3296,7 +3314,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param onField - whether to also check if the pokemon is currently on the field (defaults to true)
|
||||
*/
|
||||
getOpponents(onField = true): Pokemon[] {
|
||||
return ((this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField()) as Pokemon[]).filter(p =>
|
||||
return (this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField()).filter(p =>
|
||||
p.isActive(onField),
|
||||
);
|
||||
}
|
||||
|
@ -21,12 +21,13 @@ export class PokemonTransformPhase extends PokemonPhase {
|
||||
super(userIndex);
|
||||
|
||||
this.targetIndex = targetIndex;
|
||||
|
||||
this.playSound = playSound;
|
||||
}
|
||||
|
||||
public override start(): void {
|
||||
const user = this.getPokemon();
|
||||
const target = globalScene.getField(true).find(p => p.getBattlerIndex() === this.targetIndex);
|
||||
const target = globalScene.getField()[this.targetIndex];
|
||||
|
||||
if (!target) {
|
||||
this.end();
|
||||
@ -58,6 +59,8 @@ export class PokemonTransformPhase extends PokemonPhase {
|
||||
console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`);
|
||||
return new PokemonMove(MoveId.NONE);
|
||||
});
|
||||
|
||||
// TODO: This should fallback to the target's original typing if none are left (from Burn Up, etc.)
|
||||
user.summonData.types = target.getTypes();
|
||||
|
||||
const promises = [user.updateInfo()];
|
||||
|
Loading…
Reference in New Issue
Block a user