Sacrifical Moves (that dont require a target like explosion or self destruct) now also work if the target is flying, diving etc.

There is also a new catagorie of moves. "SacrificalMovesOnHit" for all moves that need to hit for them to be sacrifical like MEMENTO
This commit is contained in:
Jannik Tappert 2024-05-09 23:23:18 +02:00
parent d5681a6e03
commit 84d3350715
2 changed files with 87 additions and 7 deletions

View File

@ -737,12 +737,43 @@ export class RecoilAttr extends MoveEffectAttr {
} }
} }
/**
* Attribute used for moves which self KO the user regardless if the move hits a target
* */
export class SacrificialAttr extends MoveEffectAttr { export class SacrificialAttr extends MoveEffectAttr {
constructor() { constructor() {
super(true, MoveEffectTrigger.PRE_APPLY); super(true, MoveEffectTrigger.PRE_APPLY);
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
user.damageAndUpdate(user.hp, HitResult.OTHER, false, true, true);
user.turnData.damageTaken += user.hp;
return true;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
if (user.isBoss())
return -20;
return Math.ceil(((1 - user.getHpRatio()) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type, user) - 0.5));
}
}
/**
* Attribute used for moves which self KO the user but only if the move hits a target
*/
export class SacrificialAttrOnHit extends MoveEffectAttr {
constructor() {
super(true, MoveEffectTrigger.PRE_APPLY);
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(user, target, move, args)) if (!super.apply(user, target, move, args))
return false; return false;
@ -4771,7 +4802,7 @@ export function initMoves() {
new StatusMove(Moves.WILL_O_WISP, Type.FIRE, 85, 15, -1, 0, 3) new StatusMove(Moves.WILL_O_WISP, Type.FIRE, 85, 15, -1, 0, 3)
.attr(StatusEffectAttr, StatusEffect.BURN), .attr(StatusEffectAttr, StatusEffect.BURN),
new StatusMove(Moves.MEMENTO, Type.DARK, 100, 10, -1, 0, 3) new StatusMove(Moves.MEMENTO, Type.DARK, 100, 10, -1, 0, 3)
.attr(SacrificialAttr) .attr(SacrificialAttrOnHit)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -2), .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -2),
new AttackMove(Moves.FACADE, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 3) new AttackMove(Moves.FACADE, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 3)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.status .attr(MovePowerMultiplierAttr, (user, target, move) => user.status
@ -5305,7 +5336,7 @@ export function initMoves() {
new AttackMove(Moves.SPACIAL_REND, Type.DRAGON, MoveCategory.SPECIAL, 100, 95, 5, -1, 0, 4) new AttackMove(Moves.SPACIAL_REND, Type.DRAGON, MoveCategory.SPECIAL, 100, 95, 5, -1, 0, 4)
.attr(HighCritAttr), .attr(HighCritAttr),
new SelfStatusMove(Moves.LUNAR_DANCE, Type.PSYCHIC, -1, 10, -1, 0, 4) new SelfStatusMove(Moves.LUNAR_DANCE, Type.PSYCHIC, -1, 10, -1, 0, 4)
.attr(SacrificialAttr) .attr(SacrificialAttrOnHit)
.danceMove() .danceMove()
.triageMove() .triageMove()
.unimplemented(), .unimplemented(),
@ -5454,7 +5485,7 @@ export function initMoves() {
.partial(), .partial(),
new AttackMove(Moves.FINAL_GAMBIT, Type.FIGHTING, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 5) new AttackMove(Moves.FINAL_GAMBIT, Type.FIGHTING, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 5)
.attr(UserHpDamageAttr) .attr(UserHpDamageAttr)
.attr(SacrificialAttr), .attr(SacrificialAttrOnHit),
new StatusMove(Moves.BESTOW, Type.NORMAL, -1, 15, -1, 0, 5) new StatusMove(Moves.BESTOW, Type.NORMAL, -1, 15, -1, 0, 5)
.ignoresProtect() .ignoresProtect()
.unimplemented(), .unimplemented(),

View File

@ -2,7 +2,41 @@ import BattleScene, { AnySound, bypassLogin, startingWave } from "./battle-scene
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
import * as Utils from './utils'; import * as Utils from './utils';
import { Moves } from "./data/enums/moves"; import { Moves } from "./data/enums/moves";
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, DelayedAttackAttr, RechargeAttr, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, NoEffectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, OneHitKOAccuracyAttr, ForceSwitchOutAttr, VariableTargetAttr } from "./data/move"; import {
allMoves,
applyMoveAttrs,
BypassSleepAttr,
ChargeAttr,
applyFilteredMoveAttrs,
HitsTagAttr,
MissEffectAttr,
MoveAttr,
MoveEffectAttr,
MoveFlags,
MultiHitAttr,
OverrideMoveEffectAttr,
VariableAccuracyAttr,
MoveTarget,
OneHitKOAttr,
getMoveTargets,
MoveTargetSet,
MoveEffectTrigger,
CopyMoveAttr,
AttackMove,
SelfStatusMove,
DelayedAttackAttr,
RechargeAttr,
PreMoveMessageAttr,
HealStatusEffectAttr,
IgnoreOpponentStatChangesAttr,
NoEffectAttr,
FixedDamageAttr,
PostVictoryStatChangeAttr,
OneHitKOAccuracyAttr,
ForceSwitchOutAttr,
VariableTargetAttr,
SacrificialAttr
} from "./data/move";
import { Mode } from './ui/ui'; import { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat"; import { Stat } from "./data/pokemon-stat";
@ -2271,13 +2305,20 @@ export class MovePhase extends BattlePhase {
if (this.move.moveId) if (this.move.moveId)
this.showMoveText(); this.showMoveText();
if ((moveQueue.length && moveQueue[0].move === Moves.NONE) || !targets.length) { if ((moveQueue.length && moveQueue[0].move === Moves.NONE) || (!targets.length && !this.move.getMove().getAttrs(SacrificialAttr).length)) {
moveQueue.shift(); moveQueue.shift();
this.cancel(); this.cancel();
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL });
return this.end(); return this.end();
} }
if (targets.length === 0 && this.move.getMove().getAttrs(SacrificialAttr).length) {
// Add the user as a target if the move is sacrificial
targets.push(this.pokemon);
}
if (!moveQueue.length || !moveQueue.shift().ignorePP) // using .shift here clears out two turn moves once they've been used if (!moveQueue.length || !moveQueue.shift().ignorePP) // using .shift here clears out two turn moves once they've been used
this.move.usePp(ppUsed); this.move.usePp(ppUsed);
@ -2441,8 +2482,16 @@ export class MoveEffectPhase extends PokemonPhase {
// Move animation only needs one target // Move animation only needs one target
new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => { new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => {
// Check if the user is in the targets list for sacrificial moves
if (this.move.getMove().getAttrs(SacrificialAttr).length && !targets.includes(user)) {
targets.push(user);
targetHitChecks[user.getBattlerIndex()] = true;
}
for (let target of targets) { for (let target of targets) {
if (!targetHitChecks[target.getBattlerIndex()]) { if (!targetHitChecks[target.getBattlerIndex()]) {
if (this.move.getMove().getAttrs(SacrificialAttr).length && target !== user) {
continue;
}
user.turnData.hitCount = 1; user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1; user.turnData.hitsLeft = 1;
this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!')); this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));