mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-22 00:02:20 +02:00
Make moveset gen consider multi hit moves fairly
This commit is contained in:
parent
39f2fdf4ff
commit
a669d5813d
115
src/data/move.ts
115
src/data/move.ts
@ -24,7 +24,42 @@ import * as Utils from "../utils";
|
|||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import type { ArenaTrapTag } from "./arena-tag";
|
import type { ArenaTrapTag } from "./arena-tag";
|
||||||
import { ArenaTagSide, WeakenMoveTypeTag } from "./arena-tag";
|
import { ArenaTagSide, WeakenMoveTypeTag } from "./arena-tag";
|
||||||
import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPostItemLostAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ChangeMovePriorityAbAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAbAttr, PostItemLostAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability";
|
import {
|
||||||
|
allAbilities,
|
||||||
|
AllyMoveCategoryPowerBoostAbAttr,
|
||||||
|
applyAbAttrs,
|
||||||
|
applyPostAttackAbAttrs,
|
||||||
|
applyPostItemLostAbAttrs,
|
||||||
|
applyPreAttackAbAttrs,
|
||||||
|
applyPreDefendAbAttrs,
|
||||||
|
BlockItemTheftAbAttr,
|
||||||
|
BlockNonDirectDamageAbAttr,
|
||||||
|
BlockOneHitKOAbAttr,
|
||||||
|
BlockRecoilDamageAttr,
|
||||||
|
ChangeMovePriorityAbAttr,
|
||||||
|
ConfusionOnStatusEffectAbAttr,
|
||||||
|
FieldMoveTypePowerBoostAbAttr,
|
||||||
|
FieldPreventExplosiveMovesAbAttr,
|
||||||
|
ForceSwitchOutImmunityAbAttr,
|
||||||
|
HealFromBerryUseAbAttr,
|
||||||
|
IgnoreContactAbAttr,
|
||||||
|
IgnoreMoveEffectsAbAttr,
|
||||||
|
IgnoreProtectOnContactAbAttr,
|
||||||
|
InfiltratorAbAttr,
|
||||||
|
MaxMultiHitAbAttr,
|
||||||
|
MoveAbilityBypassAbAttr,
|
||||||
|
MoveEffectChanceMultiplierAbAttr,
|
||||||
|
MoveTypeChangeAbAttr,
|
||||||
|
PostDamageForceSwitchAbAttr,
|
||||||
|
PostItemLostAbAttr,
|
||||||
|
ReverseDrainAbAttr,
|
||||||
|
UncopiableAbilityAbAttr,
|
||||||
|
UnsuppressableAbilityAbAttr,
|
||||||
|
UnswappableAbilityAbAttr,
|
||||||
|
UserFieldMoveTypePowerBoostAbAttr,
|
||||||
|
VariableMovePowerAbAttr,
|
||||||
|
WonderSkinAbAttr,
|
||||||
|
} from "./ability";
|
||||||
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
|
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
|
||||||
import type { BattlerIndex } from "../battle";
|
import type { BattlerIndex } from "../battle";
|
||||||
import { BattleType } from "../battle";
|
import { BattleType } from "../battle";
|
||||||
@ -861,6 +896,46 @@ export default class Move implements Localizable {
|
|||||||
return priority.value;
|
return priority.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the [Expected Power](https://en.wikipedia.org/wiki/Expected_value) per turn
|
||||||
|
* of this move, taking into account multi hit moves, accuracy, and the number of turns it
|
||||||
|
* takes to execute.
|
||||||
|
*
|
||||||
|
* Does not (yet) consider the current field effects or the user's abilities.
|
||||||
|
*/
|
||||||
|
calculateEffectivePower(): number {
|
||||||
|
let effectivePower: number;
|
||||||
|
// Triple axel and triple kick are easier to special case.
|
||||||
|
if (this.id === Moves.TRIPLE_AXEL) {
|
||||||
|
effectivePower = 94.14;
|
||||||
|
} else if (this.id === Moves.TRIPLE_KICK) {
|
||||||
|
effectivePower = 47.07;
|
||||||
|
} else {
|
||||||
|
const multiHitAttr = this.getAttrs(MultiHitAttr)[0];
|
||||||
|
if (multiHitAttr) {
|
||||||
|
effectivePower = multiHitAttr.calculateExpectedHitCount(this) * this.power;
|
||||||
|
} else {
|
||||||
|
effectivePower = this.power * this.accuracy === -1 ? 1 : this.accuracy / 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** The number of turns the user must commit to for this move's damage */
|
||||||
|
let numTurns = 1;
|
||||||
|
|
||||||
|
// These are intentionally not else-if statements even though there are no
|
||||||
|
// pokemon moves that have more than one of these attributes. This allows
|
||||||
|
// the function to future proof new moves / custom move behaviors.
|
||||||
|
if (this.hasAttr(DelayedAttackAttr)) {
|
||||||
|
numTurns += 2;
|
||||||
|
}
|
||||||
|
if (this.hasAttr(RechargeAttr)) {
|
||||||
|
numTurns += 1;
|
||||||
|
}
|
||||||
|
if (this.isChargingMove()) {
|
||||||
|
numTurns += 1;
|
||||||
|
}
|
||||||
|
return effectivePower / numTurns;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `true` if this move can be given additional strikes
|
* Returns `true` if this move can be given additional strikes
|
||||||
* by enhancing effects.
|
* by enhancing effects.
|
||||||
@ -2280,6 +2355,44 @@ export class MultiHitAttr extends MoveAttr {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the expected number of hits given this attribute's {@linkcode MultiHitType},
|
||||||
|
* the move's accuracy, and a number of situational parameters.
|
||||||
|
*
|
||||||
|
* @param move - The move that this attribtue is applied to
|
||||||
|
* @param partySize - The size of the user's party, used for {@linkcode Moves.BEAT_UP | Beat Up} (default: `1`)
|
||||||
|
* @param maxMultiHit - Whether the move should always hit the maximum number of times, e.g. due to {@linkcode Abilities.SKILL_LINK | Skill Link} (default: `false`)
|
||||||
|
* @param ignoreAcc - `true` if the move should ignore accuracy checks, e.g. due to {@linkcode Abilities.NO_GUARD | No Guard} (default: `false`)
|
||||||
|
*/
|
||||||
|
calculateExpectedHitCount(move: Move, { ignoreAcc = false, maxMultiHit = false, partySize = 1 }: {ignoreAcc?: boolean, maxMultiHit?: boolean, partySize?: number} = {}): number {
|
||||||
|
let expectedHits: number;
|
||||||
|
switch (this.multiHitType) {
|
||||||
|
case MultiHitType._2_TO_5:
|
||||||
|
expectedHits = maxMultiHit ? 5 : 3.1;
|
||||||
|
break;
|
||||||
|
case MultiHitType._2:
|
||||||
|
expectedHits = 2;
|
||||||
|
break;
|
||||||
|
case MultiHitType._3:
|
||||||
|
expectedHits = 3;
|
||||||
|
break;
|
||||||
|
case MultiHitType._10:
|
||||||
|
expectedHits = 10;
|
||||||
|
break;
|
||||||
|
case MultiHitType.BEAT_UP:
|
||||||
|
// Estimate that half of the party can contribute to beat up.
|
||||||
|
expectedHits = Math.max(1, partySize / 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ignoreAcc || move.accuracy === -1) {
|
||||||
|
return expectedHits;
|
||||||
|
}
|
||||||
|
if (move.hasFlag(MoveFlags.CHECK_ALL_HITS) && !maxMultiHit) {
|
||||||
|
return Math.pow(move.accuracy, expectedHits);
|
||||||
|
}
|
||||||
|
return expectedHits *= move.accuracy / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChangeMultiHitTypeAttr extends MoveAttr {
|
export class ChangeMultiHitTypeAttr extends MoveAttr {
|
||||||
|
@ -2344,8 +2344,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
// Weight towards higher power moves, by reducing the power of moves below the highest power.
|
// Weight towards higher power moves, by reducing the power of moves below the highest power.
|
||||||
// Caps max power at 90 to avoid something like hyper beam ruining the stats.
|
// Caps max power at 90 to avoid something like hyper beam ruining the stats.
|
||||||
// This is a pretty soft weighting factor, although it is scaled with the weight multiplier.
|
// This is a pretty soft weighting factor, although it is scaled with the weight multiplier.
|
||||||
const maxPower = Math.min(movePool.reduce((v, m) => Math.max(allMoves[m[0]].power, v), 40), 90);
|
const maxPower = Math.min(movePool.reduce((v, m) => Math.max(allMoves[m[0]].calculateEffectivePower(), v), 40), 90);
|
||||||
movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === MoveCategory.STATUS ? 1 : Math.max(Math.min(allMoves[m[0]].power / maxPower, 1), 0.5)) ]);
|
movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === MoveCategory.STATUS ? 1 : Math.max(Math.min(allMoves[m[0]].calculateEffectivePower() / maxPower, 1), 0.5)) ]);
|
||||||
|
|
||||||
// Weight damaging moves against the lower stat. This uses a non-linear relationship.
|
// Weight damaging moves against the lower stat. This uses a non-linear relationship.
|
||||||
// If the higher stat is 1 - 1.09x higher, no change. At higher stat ~1.38x lower stat, off-stat moves have half weight.
|
// If the higher stat is 1 - 1.09x higher, no change. At higher stat ~1.38x lower stat, off-stat moves have half weight.
|
||||||
|
Loading…
Reference in New Issue
Block a user