Implement Destiny Bond move (#1104)

* Use getBattleStat instead of getStat in BattleStatRatioPowerAttr

* Change unnecessary let into const

* Refactor BattleStatRatioPowerAttr into two distinct classes

* Add TSDoc for the new classes

* Implementation of Destiny Bond

* Add TSDocs

* Make the move fail in boss battles

* Fix boss immunity and ally fainting

* Update docs

* Add doc of return value of tag lapse

* Fix ESLint
This commit is contained in:
Jakub Hanko 2024-05-24 22:23:23 +02:00 committed by GitHub
parent 136fcbfda8
commit a48ba9864d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 3 deletions

View File

@ -254,6 +254,51 @@ export class ConfusedTag extends BattlerTag {
}
}
/**
* Tag applied to the {@linkcode Move.DESTINY_BOND} user.
* @extends BattlerTag
* @see {@linkcode apply}
*/
export class DestinyBondTag extends BattlerTag {
constructor(sourceMove: Moves, sourceId: integer) {
super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId);
}
/**
* Lapses either before the user's move and does nothing
* or after receiving fatal damage. When the damage is fatal,
* the attacking Pokemon is taken down as well, unless it's a boss.
*
* @param {Pokemon} pokemon Pokemon that is attacking the Destiny Bond user.
* @param {BattlerTagLapseType} lapseType CUSTOM or PRE_MOVE
* @returns false if the tag source fainted or one turn has passed since the application
*/
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType !== BattlerTagLapseType.CUSTOM) {
return super.lapse(pokemon, lapseType);
}
const source = pokemon.scene.getPokemonById(this.sourceId);
if (!source.isFainted()) {
return true;
}
if (source.getAlly() === pokemon) {
return false;
}
const targetMessage = getPokemonMessage(pokemon, "");
if (pokemon.isBossImmune()) {
pokemon.scene.queueMessage(`${targetMessage} is unaffected\nby the effects of Destiny Bond.`);
return false;
}
pokemon.scene.queueMessage(`${getPokemonMessage(source, ` took\n${targetMessage} down with it!`)}`);
pokemon.damageAndUpdate(pokemon.hp, HitResult.ONE_HIT_KO, false, false, true);
return false;
}
}
export class InfatuatedTag extends BattlerTag {
constructor(sourceMove: integer, sourceId: integer) {
super(BattlerTagType.INFATUATED, BattlerTagLapseType.MOVE, 1, sourceMove, sourceId);
@ -1416,6 +1461,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
return new MagnetRisenTag(tagType, sourceMove);
case BattlerTagType.MINIMIZED:
return new MinimizeTag();
case BattlerTagType.DESTINY_BOND:
return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -56,5 +56,6 @@ export enum BattlerTagType {
CHARGED = "CHARGED",
GROUNDED = "GROUNDED",
MAGNET_RISEN = "MAGNET_RISEN",
MINIMIZED = "MINIMIZED"
MINIMIZED = "MINIMIZED",
DESTINY_BOND = "DESTINY_BOND"
}

View File

@ -4704,6 +4704,31 @@ export class MoneyAttr extends MoveEffectAttr {
}
}
/**
* Applies {@linkcode BattlerTagType.DESTINY_BOND} to the user.
*
* @extends MoveEffectAttr
*/
export class DestinyBondAttr extends MoveEffectAttr {
constructor() {
super(true, MoveEffectTrigger.PRE_APPLY);
}
/**
* Applies {@linkcode BattlerTagType.DESTINY_BOND} to the user.
* @param user {@linkcode Pokemon} that is having the tag applied to.
* @param target {@linkcode Pokemon} N/A
* @param move {@linkcode Move} {@linkcode Move.DESTINY_BOND}
* @param {any[]} args N/A
* @returns true
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
user.scene.queueMessage(`${getPokemonMessage(user, " is trying\nto take its foe down with it!")}`);
user.addTag(BattlerTagType.DESTINY_BOND, undefined, move.id, user.id);
return true;
}
}
export class LastResortAttr extends MoveAttr {
getCondition(): MoveConditionFunc {
return (user: Pokemon, target: Pokemon, move: Move) => {
@ -5405,8 +5430,7 @@ export function initMoves() {
.unimplemented(),
new SelfStatusMove(Moves.DESTINY_BOND, Type.GHOST, -1, 5, -1, 0, 2)
.ignoresProtect()
.condition(failOnBossCondition)
.unimplemented(),
.attr(DestinyBondAttr),
new StatusMove(Moves.PERISH_SONG, Type.NORMAL, -1, 5, -1, 0, 2)
.attr(FaintCountdownAttr)
.ignoresProtect()

View File

@ -1790,6 +1790,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
console.log("damage", damage.value, move.name, power.value, sourceAtk, targetDef);
// In case of fatal damage, this tag would have gotten cleared before we could lapse it.
const destinyTag = this.getTag(BattlerTagType.DESTINY_BOND);
const oneHitKo = result === HitResult.ONE_HIT_KO;
if (damage.value) {
if (this.getHpRatio() === 1) {
@ -1850,6 +1853,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (damage) {
this.scene.clearPhaseQueueSplice();
const attacker = this.scene.getPokemonById(source.id);
destinyTag?.lapse(attacker, BattlerTagLapseType.CUSTOM);
}
}
break;