Compare commits

...

5 Commits

Author SHA1 Message Date
Leo Kim
b1d4037a57
[Bug] Fix some damage formulas processed with ceil instead of floor (#3557)
* fix damage calculations. add test code

* define toIntValue function to replace every repeatitive min floor function.

* revert unnecessary minimum boundary

* update function name `toIntValue` -> `toDmgValue`. update comments.

* add missing updates for changing function name

* Update src/utils.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* remove redundant comment

* update import code for test with phase

---------

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
2024-08-21 22:39:11 -07:00
Lugiad
61d659d8bb
[Localization] Localizable owned money symbol on battle UI (#3646)
Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Enoch <enoch.jwsong@gmail.com>
Co-authored-by: frutescens <info@laptop>
2024-08-21 20:57:46 -07:00
sodam
6ea0f3f18b
[Enhancement] Support for special characters(♪.★,♥,♣) in game font (issue#3180) (#3535)
* added  specialCharacters: "♪.★,♥,♣"

* did refactoring...maybe

* added note
2024-08-21 20:32:03 -07:00
chaosgrimmon
34bf932722
[Sprite] Trim exp Rellor palette (#3674)
* [Sprite] Trim exp Rellor palette

* [Sprite] Trim Rellor exp Palette

* [Sprite] Trim Rellor exp palette
2024-08-21 18:48:39 -07:00
innerthunder
051d38e0a2
[Enhancement] Add support for simulated calls to Abilities (#3529)
* Adds simulated tag support to all abilities

* Fix compiler errors in ability.ts

Most things are still on fire 😢

* Fix errors left over after merge

* Another pass through simulated ability call logic

* Fix leftover errors from merge resolution

* Another gh pages issue :pikamad:

* Simulated call fixes based on Kev's feedback

* RIP phases.ts

---------

Co-authored-by: Xavion3 <xavion333@gmail.com>
2024-08-21 18:29:21 -07:00
57 changed files with 927 additions and 598 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -2,79 +2,35 @@
"1": {
"5a4d37": "1c1e76",
"766348": "323aa5",
"9d8361": "4059bd",
"c5b4aa": "d3e6e6",
"37332b": "104139",
"9e8360": "4059bd",
"f18725": "2e8c19",
"f18625": "2e8c19",
"bb6f2a": "2f7410",
"0f0f0f": "0f0f0f",
"575243": "18734a",
"b8702d": "2f7410",
"777462": "199e46",
"585243": "18734a",
"4f4531": "b29c3e",
"4d4530": "b29c3e",
"aea56b": "f9fba2",
"afa667": "f9fba2",
"a28e85": "c1d8db",
"a28c8c": "c1d8db",
"b64c95": "dedb64",
"afa668": "f9fba2",
"a28c88": "c1d8db",
"f2f2f2": "e8eab5",
"b05f8f": "dedb64",
"776348": "323aa5",
"b45492": "dedb64",
"f28625": "2e8c19",
"bc6e28": "2f7410",
"bb6e27": "2f7410",
"4d4430": "b29c3e",
"b0a766": "f9fba2",
"a18f8d": "c1d8db",
"a28f86": "c1d8db",
"b74a94": "dedb64",
"9e8461": "4059bd",
"777362": "199e46",
"ba6d27": "2f7410",
"afa567": "f9fba2",
"a18f85": "c1d8db"
"4e4530": "b29c3e",
"ba6e29": "2f7410"
},
"2": {
"5a4d37": "333e5f",
"766348": "8c9fbf",
"9d8361": "dbedec",
"c5b4aa": "39cfbc",
"37332b": "261031",
"9e8360": "dbedec",
"f18725": "4baecd",
"f18625": "4baecd",
"bb6f2a": "4792bd",
"0f0f0f": "0f0f0f",
"575243": "5e2d72",
"b8702d": "4792bd",
"777462": "8358a1",
"585243": "5e2d72",
"4f4531": "534b8c",
"4d4530": "534b8c",
"aea56b": "c9dbac",
"afa667": "c9dbac",
"a28e85": "52b0b0",
"a28c8c": "52b0b0",
"b64c95": "b56c3e",
"afa668": "c9dbac",
"a28e88": "52b0b0",
"f2f2f2": "d9c951",
"b05f8f": "b56c3e",
"776348": "8c9fbf",
"b45492": "b56c3e",
"f28625": "4baecd",
"bc6e28": "4792bd",
"bb6e27": "4792bd",
"4d4430": "534b8c",
"b0a766": "c9dbac",
"a18f8d": "52b0b0",
"a28f86": "52b0b0",
"b74a94": "b56c3e",
"9e8461": "dbedec",
"777362": "8358a1",
"ba6d27": "4792bd",
"afa567": "c9dbac",
"a18f85": "52b0b0"
"4e4530": "534b8c",
"ba6e29": "4792bd"
}
}

View File

@ -1100,7 +1100,7 @@ export default class BattleScene extends SceneBase {
} else if (trainerConfigs[trainerType].hasDouble) {
const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8);
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance));
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance));
doubleTrainer = !Utils.randSeedInt(doubleChance.value);
// Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance
if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) {
@ -1116,7 +1116,7 @@ export default class BattleScene extends SceneBase {
if (newBattleType === BattleType.WILD && !this.gameMode.isWaveFinal(newWaveIndex)) {
const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8);
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance));
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance));
newDouble = !Utils.randSeedInt(doubleChance.value);
} else if (newBattleType === BattleType.TRAINER) {
newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
@ -1518,7 +1518,7 @@ export default class BattleScene extends SceneBase {
return;
}
const formattedMoney = Utils.formatMoney(this.moneyFormat, this.money);
this.moneyText.setText(`${formattedMoney}`);
this.moneyText.setText(i18next.t("battleScene:moneyOwned", { formattedMoney }));
this.fieldUI.moveAbove(this.moneyText, this.luckText);
if (forceVisible) {
this.moneyText.setVisible(true);
@ -2136,7 +2136,7 @@ export default class BattleScene extends SceneBase {
pushMovePhase(movePhase: MovePhase, priorityOverride?: integer): void {
const movePriority = new Utils.IntegerHolder(priorityOverride !== undefined ? priorityOverride : movePhase.move.getMove().priority);
applyAbAttrs(ChangeMovePriorityAbAttr, movePhase.pokemon, null, movePhase.move.getMove(), movePriority);
applyAbAttrs(ChangeMovePriorityAbAttr, movePhase.pokemon, null, false, movePhase.move.getMove(), movePriority);
const lowerPriorityPhase = this.phaseQueue.find(p => p instanceof MovePhase && p.move.getMove().priority < movePriority.value);
if (lowerPriorityPhase) {
this.phaseQueue.splice(this.phaseQueue.indexOf(lowerPriorityPhase), 0, movePhase);

File diff suppressed because it is too large Load Diff

View File

@ -269,7 +269,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
if (effectPhase instanceof MoveEffectPhase) {
const attacker = effectPhase.getUserPokemon()!;
applyMoveAttrs(IncrementMovePriorityAttr, attacker, null, move, priority);
applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, move, priority);
applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, false, move, priority);
}
return priority.value > 0;
};
@ -427,7 +427,7 @@ class WishTag extends ArenaTag {
if (user) {
this.battlerIndex = user.getBattlerIndex();
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) });
this.healHp = Math.max(Math.floor(user.getMaxHp() / 2), 1);
this.healHp = Utils.toDmgValue(user.getMaxHp() / 2);
} else {
console.warn("Failed to get source for WishTag onAdd");
}
@ -585,7 +585,7 @@ class SpikesTag extends ArenaTrapTag {
if (!cancelled.value) {
const damageHpRatio = 1 / (10 - 2 * this.layers);
const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio);
const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER);
@ -745,7 +745,7 @@ class StealthRockTag extends ArenaTrapTag {
const damageHpRatio = this.getDamageHpRatio(pokemon);
if (damageHpRatio) {
const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio);
const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER);
if (pokemon.turnData) {

View File

@ -347,7 +347,7 @@ export class ConfusedTag extends BattlerTag {
if (pokemon.randSeedInt(3) === 0) {
const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF);
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
pokemon.damageAndUpdate(damage);
pokemon.battleData.hitCount++;
@ -524,7 +524,7 @@ export class SeedTag extends BattlerTag {
if (!cancelled.value) {
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED));
const damage = pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 8), 1));
const damage = pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8));
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(),
!reverseDrain ? damage : damage * -1,
@ -570,7 +570,7 @@ export class NightmareTag extends BattlerTag {
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) {
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 4));
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4));
}
}
@ -714,7 +714,7 @@ export class IngrainTag extends TrappedTag {
new PokemonHealPhase(
pokemon.scene,
pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16),
Utils.toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
true
)
@ -777,7 +777,7 @@ export class AquaRingTag extends BattlerTag {
new PokemonHealPhase(
pokemon.scene,
pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16),
Utils.toDmgValue(pokemon.getMaxHp() / 16),
i18next.t("battlerTags:aquaRingLapse", {
moveName: this.getMoveName(),
pokemonName: getPokemonNameWithAffix(pokemon)
@ -883,7 +883,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) {
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 8));
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8));
}
}
@ -1067,7 +1067,7 @@ export class ContactDamageProtectedTag extends ProtectedTag {
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
}
}
}
@ -1541,7 +1541,7 @@ export class SaltCuredTag extends BattlerTag {
if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER);
pokemon.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1));
pokemon.damageAndUpdate(Utils.toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8));
pokemon.scene.queueMessage(
i18next.t("battlerTags:saltCuredLapse", {
@ -1587,7 +1587,7 @@ export class CursedTag extends BattlerTag {
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) {
pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 4), 1));
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4));
pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}

View File

@ -36,25 +36,25 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < threshold.value && pokemon.summonData.battleStats[battleStat] < 6;
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
};
case BerryType.STARF:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25;
};
case BerryType.LEPPA:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
return !!pokemon.getMoveset().find(m => !m?.getPpRatio());
};
}
@ -70,8 +70,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const hpHealed = new Utils.NumberHolder(Math.floor(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, hpHealed);
const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
};
@ -97,7 +97,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
}
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
const statLevels = new Utils.NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], statLevels.value));
};
case BerryType.LANSAT:
@ -113,7 +113,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
pokemon.battleData.berriesEaten.push(berryType);
}
const statLevels = new Utils.NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], statLevels.value));
};
case BerryType.LEPPA:

View File

@ -590,7 +590,7 @@ export default class Move implements Localizable {
case MoveFlags.IGNORE_ABILITIES:
if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) {
const abilityEffectsIgnored = new Utils.BooleanHolder(false);
applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, this);
applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this);
if (abilityEffectsIgnored.value) {
return true;
}
@ -686,11 +686,11 @@ export default class Move implements Localizable {
* @param target {@linkcode Pokemon} The Pokémon being targeted by the move.
* @returns The calculated accuracy of the move.
*/
calculateBattleAccuracy(user: Pokemon, target: Pokemon) {
calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated: boolean = false) {
const moveAccuracy = new Utils.NumberHolder(this.accuracy);
applyMoveAttrs(VariableAccuracyAttr, user, target, this, moveAccuracy);
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, moveAccuracy);
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy);
if (moveAccuracy.value === -1) {
return moveAccuracy.value;
@ -724,7 +724,7 @@ export default class Move implements Localizable {
* @param target {@linkcode Pokemon} The Pokémon being targeted by the move.
* @returns The calculated power of the move.
*/
calculateBattlePower(source: Pokemon, target: Pokemon): number {
calculateBattlePower(source: Pokemon, target: Pokemon, simulated: boolean = false): number {
if (this.category === MoveCategory.STATUS) {
return -1;
}
@ -732,17 +732,17 @@ export default class Move implements Localizable {
const power = new Utils.NumberHolder(this.power);
const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, target, this, typeChangeMovePowerMultiplier);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, target, this, simulated, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType();
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !source.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60;
}
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, power);
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power);
if (source.getAlly()) {
applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, power);
applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, simulated, power);
}
const fieldAuras = new Set(
@ -752,11 +752,11 @@ export default class Move implements Localizable {
);
for (const aura of fieldAuras) {
// The only relevant values are `move` and the `power` holder
aura.applyPreAttack(null, null, null, this, [power]);
aura.applyPreAttack(null, null, simulated, null, this, [power]);
}
const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField();
alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, power));
alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, simulated, power));
power.value *= typeChangeMovePowerMultiplier.value;
@ -984,9 +984,9 @@ export class MoveEffectAttr extends MoveAttr {
*/
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): integer {
const moveChance = new Utils.NumberHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, moveChance, move, target, selfEffect, showAbility);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, moveChance, move, target, selfEffect, showAbility);
if (!selfEffect) {
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, target, user, null, null, moveChance);
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, target, user, null, null, false, moveChance);
}
return moveChance.value;
}
@ -1162,7 +1162,7 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
(args[0] as Utils.IntegerHolder).value = Math.max(Math.floor(target.hp / 2), 1);
(args[0] as Utils.IntegerHolder).value = Utils.toDmgValue(target.hp / 2);
return true;
}
@ -1208,7 +1208,7 @@ export class CounterDamageAttr extends FixedDamageAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).reduce((total: integer, ar: AttackMoveResult) => total + ar.damage, 0);
(args[0] as Utils.IntegerHolder).value = Math.floor(Math.max(damage * this.multiplier, 1));
(args[0] as Utils.IntegerHolder).value = Utils.toDmgValue(damage * this.multiplier);
return true;
}
@ -1234,7 +1234,7 @@ export class RandomLevelDamageAttr extends FixedDamageAttr {
}
getDamage(user: Pokemon, target: Pokemon, move: Move): number {
return Math.max(Math.floor(user.level * (user.randSeedIntRange(50, 150) * 0.01)), 1);
return Utils.toDmgValue(user.level * (user.randSeedIntRange(50, 150) * 0.01));
}
}
@ -1293,8 +1293,9 @@ export class RecoilAttr extends MoveEffectAttr {
return false;
}
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) * this.damageRatio),
user.turnData.damageDealt ? 1 : 0);
const damageValue = (!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) * this.damageRatio;
const minValue = user.turnData.damageDealt ? 1 : 0;
const recoilDamage = Utils.toDmgValue(damageValue, minValue);
if (!recoilDamage) {
return false;
}
@ -1415,7 +1416,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
// Check to see if the Pokemon has an ability that blocks non-direct damage
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
if (!cancelled.value) {
user.damageAndUpdate(Math.ceil(user.getMaxHp()/2), HitResult.OTHER, false, true, true);
user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp()/2), HitResult.OTHER, false, true, true);
user.scene.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", {pokemonName: getPokemonNameWithAffix(user)})); // Queue recoil message
}
return true;
@ -1466,7 +1467,7 @@ export class HealAttr extends MoveEffectAttr {
*/
addHealPhase(target: Pokemon, healRatio: number) {
target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(),
Math.max(Math.floor(target.getMaxHp() * healRatio), 1), i18next.t("moveTriggers:healHp", {pokemonName: getPokemonNameWithAffix(target)}), true, !this.showAnim));
Utils.toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", {pokemonName: getPokemonNameWithAffix(target)}), true, !this.showAnim));
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
@ -1750,7 +1751,7 @@ export class HitHealAttr extends MoveEffectAttr {
message = i18next.t("battle:drainMessage", {pokemonName: getPokemonNameWithAffix(target)});
} else {
// Default healing formula used by draining moves like Absorb, Draining Kiss, Bitter Blade, etc.
healAmount = Math.max(Math.floor(user.turnData.currDamageDealt * this.healRatio), 1);
healAmount = Utils.toDmgValue(user.turnData.currDamageDealt * this.healRatio);
message = i18next.t("battle:regainHealth", {pokemonName: getPokemonNameWithAffix(user)});
}
if (reverseDrain) {
@ -1883,7 +1884,7 @@ export class MultiHitAttr extends MoveAttr {
{
const rand = user.randSeedInt(16);
const hitValue = new Utils.IntegerHolder(rand);
applyAbAttrs(MaxMultiHitAbAttr, user, null, hitValue);
applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue);
if (hitValue.value >= 10) {
return 2;
} else if (hitValue.value >= 4) {
@ -1954,7 +1955,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
}
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, this.effect);
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
return true;
}
}
@ -2710,7 +2711,7 @@ export class CutHpStatBoostAttr extends StatChangeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
user.damageAndUpdate(Math.floor(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true);
user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true);
user.updateInfo().then(() => {
const ret = super.apply(user, target, move, args);
if (this.messageCallback) {
@ -3190,7 +3191,7 @@ export class CompareWeightPowerAttr extends VariablePowerAttr {
export class HpPowerAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
(args[0] as Utils.NumberHolder).value = Math.max(Math.floor(150 * user.getHpRatio()), 1);
(args[0] as Utils.NumberHolder).value = Utils.toDmgValue(150 * user.getHpRatio());
return true;
}
@ -3218,7 +3219,7 @@ export class OpponentHighHpPowerAttr extends VariablePowerAttr {
* @returns true
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
(args[0] as Utils.NumberHolder).value = Math.max(Math.floor(this.maxBasePower * target.getHpRatio()), 1);
(args[0] as Utils.NumberHolder).value = Utils.toDmgValue(this.maxBasePower * target.getHpRatio());
return true;
}
@ -3412,7 +3413,7 @@ export class PresentPowerAttr extends VariablePowerAttr {
// If this move is multi-hit, disable all other hits
user.stopMultiHit();
target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(),
Math.max(Math.floor(target.getMaxHp() / 4), 1), i18next.t("moveTriggers:regainedHealth", {pokemonName: getPokemonNameWithAffix(target)}), true));
Utils.toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", {pokemonName: getPokemonNameWithAffix(target)}), true));
}
return true;
@ -4232,9 +4233,9 @@ const crashDamageFunc = (user: Pokemon, move: Move) => {
return false;
}
user.damageAndUpdate(Math.floor(user.getMaxHp() / 2), HitResult.OTHER, false, true);
user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / 2), HitResult.OTHER, false, true);
user.scene.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", {pokemonName: getPokemonNameWithAffix(user)}));
user.turnData.damageTaken += Math.floor(user.getMaxHp() / 2);
user.turnData.damageTaken += Utils.toDmgValue(user.getMaxHp() / 2);
return true;
};
@ -4944,7 +4945,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)];
const slotIndex = user.scene.getEnemyParty().findIndex(p => pokemon.id === p.id);
pokemon.resetStatus();
pokemon.heal(Math.min(Math.max(Math.ceil(Math.floor(0.5 * pokemon.getMaxHp())), 1), pokemon.getMaxHp()));
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
user.scene.queueMessage(`${getPokemonNameWithAffix(pokemon)} was revived!`,0,true);
if (user.scene.currentBattle.double && user.scene.getEnemyParty().length > 1) {

View File

@ -59,7 +59,7 @@ export class Terrain {
case TerrainType.PSYCHIC:
if (!move.hasAttr(ProtectAttr)) {
const priority = new Utils.IntegerHolder(move.priority);
applyAbAttrs(ChangeMovePriorityAbAttr, user, null, move, priority);
applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority);
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded());
}

View File

@ -694,7 +694,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
break;
}
}
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, opponent, null, statLevel);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, opponent, null, false, statLevel);
if (move) {
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, opponent, move, statLevel);
}
@ -1122,10 +1122,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const suppressed = new Utils.BooleanHolder(false);
this.scene.getField(true).filter(p => p !== this).map(p => {
if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) {
p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, suppressed, [ability]));
p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, false, suppressed, [ability]));
}
if (p.getPassiveAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility(true)) {
p.getPassiveAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, true, suppressed, [ability]));
p.getPassiveAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, true, false, suppressed, [ability]));
}
});
if (suppressed.value) {
@ -1177,7 +1177,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getWeight(): number {
const weight = new Utils.NumberHolder(this.species.weight);
// This will trigger the ability overlay so only call this function when necessary
applyAbAttrs(WeightMultiplierAbAttr, this, null, weight);
applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight);
return weight.value;
}
@ -1237,10 +1237,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const cancelled = new Utils.BooleanHolder(false);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (!typeless && !ignoreAbility) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true);
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, true, typeMultiplier);
}
if (!cancelled.value && !ignoreAbility) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true);
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, true, typeMultiplier);
}
return (!cancelled.value ? Number(typeMultiplier.value) : 0) as TypeDamageMultiplier;
@ -1281,7 +1281,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (source) {
const ignoreImmunity = new Utils.BooleanHolder(false);
if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) {
applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, moveType, defType);
applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, false, moveType, defType);
}
if (ignoreImmunity.value) {
return 1;
@ -1922,9 +1922,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const userAccuracyLevel = new Utils.IntegerHolder(this.summonData.battleStats[BattleStat.ACC]);
const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, targetEvasionLevel);
applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, targetEvasionLevel);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, false, userAccuracyLevel);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, false, targetEvasionLevel);
applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, false, targetEvasionLevel);
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvasionLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), TempBattleStat.ACC, userAccuracyLevel);
@ -1939,7 +1939,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6));
}
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, BattleStat.ACC, accuracyMultiplier, sourceMove);
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, BattleStat.ACC, accuracyMultiplier, false, sourceMove);
const evasionMultiplier = new Utils.NumberHolder(1);
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier);
@ -1949,6 +1949,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return accuracyMultiplier.value;
}
/**
* Apply the results of a move to this pokemon
* @param {Pokemon} source The pokemon using the move
* @param {PokemonMove} battlerMove The move being used
* @returns {HitResult} The result of the attack
*/
apply(source: Pokemon, move: Move): HitResult {
let result: HitResult;
const damage = new Utils.NumberHolder(0);
@ -1984,12 +1990,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const sourceTeraType = source.getTeraType();
if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier);
applyMoveAttrs(NeutralDamageAgainstFlyingTypeMultiplierAttr, source, this, move, typeMultiplier);
}
if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier));
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, false, typeMultiplier));
}
if (cancelled.value) {
@ -2012,7 +2018,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const critOnly = new Utils.BooleanHolder(false);
const critAlways = source.getTag(BattlerTagType.ALWAYS_CRIT);
applyMoveAttrs(CritOnlyAttr, source, this, move, critOnly);
applyAbAttrs(ConditionalCritAbAttr, source, null, critOnly, this, move);
applyAbAttrs(ConditionalCritAbAttr, source, null, false, critOnly, this, move);
if (critOnly.value || critAlways) {
isCritical = true;
} else {
@ -2022,7 +2028,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
const bonusCrit = new Utils.BooleanHolder(false);
//@ts-ignore
if (applyAbAttrs(BonusCritAbAttr, source, null, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus.
if (applyAbAttrs(BonusCritAbAttr, source, null, false, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus.
if (bonusCrit.value) {
critLevel.value += 1;
}
@ -2040,7 +2046,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (isCritical) {
const noCritTag = this.scene.arena.getTagOnSide(NoCritTag, defendingSide);
const blockCrit = new Utils.BooleanHolder(false);
applyAbAttrs(BlockCritAbAttr, this, null, blockCrit);
applyAbAttrs(BlockCritAbAttr, this, null, false, blockCrit);
if (noCritTag || blockCrit.value) {
isCritical = false;
}
@ -2048,7 +2054,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));
const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier);
applyAbAttrs(MultCritAbAttr, source, null, false, criticalMultiplier);
const screenMultiplier = new Utils.NumberHolder(1);
if (!isCritical) {
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier);
@ -2063,7 +2069,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
stabMultiplier.value += 0.5;
}
applyAbAttrs(StabBoostAbAttr, source, null, stabMultiplier);
applyAbAttrs(StabBoostAbAttr, source, null, false, stabMultiplier);
if (sourceTeraType !== Type.UNKNOWN && matchesSourceType) {
stabMultiplier.value = Math.min(stabMultiplier.value + 0.5, 2.25);
@ -2081,12 +2087,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
numTargets = effectPhase.getTargets().length;
}
const twoStrikeMultiplier = new Utils.NumberHolder(1);
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, source, this, move, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier);
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, source, this, move, false, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier);
if (!isTypeImmune) {
const levelMultiplier = (2 * source.level / 5 + 2);
const randomMultiplier = ((this.scene.randBattleSeedInt(16) + 85) / 100);
damage.value = Math.ceil((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2)
damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2)
* stabMultiplier.value
* typeMultiplier.value
* arenaAttackTypeMultiplier.value
@ -2100,14 +2106,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) {
if (!move.hasAttr(BypassBurnDamageReductionAttr)) {
const burnDamageReductionCancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled);
applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled, false);
if (!burnDamageReductionCancelled.value) {
damage.value = Math.floor(damage.value / 2);
damage.value = Utils.toDmgValue(damage.value / 2);
}
}
}
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, damage);
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, false, damage);
/**
* For each {@link HitsTagAttr} the move has, doubles the damage of the move if:
@ -2123,7 +2129,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && move.type === Type.DRAGON) {
damage.value = Math.floor(damage.value / 2);
damage.value = Utils.toDmgValue(damage.value / 2);
}
const fixedDamage = new Utils.IntegerHolder(0);
@ -2165,7 +2171,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage);
}
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, damage);
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, false, damage);
}
// This attribute may modify damage arbitrarily, so be careful about changing its order of application.
@ -2178,7 +2184,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (damage.value) {
if (this.isFullHp()) {
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, damage);
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage);
} else if (!this.isPlayer() && damage.value >= this.hp) {
this.scene.applyModifiers(EnemyEndureChanceModifier, false, this);
}
@ -2245,11 +2251,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
break;
case MoveCategory.STATUS:
if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier);
}
if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier));
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, false, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, false, typeMultiplier));
}
if (!typeMultiplier.value) {
this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(this) }));
@ -2341,6 +2347,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!));
}
canAddTag(tagType: BattlerTagType): boolean {
if (this.getTag(tagType)) {
return false;
}
const stubTag = new BattlerTag(tagType, 0, 0);
const cancelled = new Utils.BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, stubTag, cancelled, true);
const userField = this.getAlliedField();
userField.forEach(pokemon => applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, stubTag, cancelled, true));
return !cancelled.value;
}
addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean {
const existingTag = this.getTag(tagType);
if (existingTag) {
@ -2727,7 +2749,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
const cancelImmunity = new Utils.BooleanHolder(false);
if (sourcePokemon) {
applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, effect, defType);
applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType);
if (cancelImmunity.value) {
return false;
}
@ -2799,7 +2821,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (effect === StatusEffect.SLEEP) {
statusCureTurn = new Utils.IntegerHolder(this.randSeedIntRange(2, 4));
applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, effect, statusCureTurn);
applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, false, effect, statusCureTurn);
this.setFrameRate(4);
@ -3433,7 +3455,7 @@ export class PlayerPokemon extends Pokemon {
pokemon.resetTurnData();
pokemon.resetStatus();
pokemon.heal(Math.min(Math.max(Math.ceil(Math.floor(0.5 * pokemon.getMaxHp())), 1), pokemon.getMaxHp()));
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
this.scene.queueMessage(`${pokemon.name} was revived!`,0,true);
if (this.scene.currentBattle.double && this.scene.getParty().length > 1) {
@ -4360,7 +4382,7 @@ export class PokemonMove {
}
getMovePp(): integer {
return this.getMove().pp + this.ppUp * Math.max(Math.floor(this.getMove().pp / 5), 1);
return this.getMove().pp + this.ppUp * Utils.toDmgValue(this.getMove().pp / 5);
}
getPpRatio(): number {

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "₽{{formattedMoney}}"
} as const;

View File

@ -6,6 +6,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const caESConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,6 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "{{formattedMoney}} ₽"
} as const;

View File

@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const deConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "₽{{formattedMoney}}"
} as const;

View File

@ -6,6 +6,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const enConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "{{formattedMoney}} ₽"
} as const;

View File

@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const esConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "{{formattedMoney}} ₽"
} as const;

View File

@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const frConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "{{formattedMoney}} ₽"
} as const;

View File

@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const itConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "{{formattedMoney}}円"
} as const;

View File

@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -61,6 +62,7 @@ export const jaConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "₽{{formattedMoney}}"
} as const;

View File

@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const koConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "₽{{formattedMoney}}"
} as const;

View File

@ -4,6 +4,7 @@ import { PGFachv, PGMachv } from "./achv";
import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const ptBrConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "₽{{formattedMoney}}"
} as const;

View File

@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const zhCnConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -0,0 +1,5 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const battleScene: SimpleTranslationEntries = {
"moneyOwned": "₽{{formattedMoney}}"
} as const;

View File

@ -4,6 +4,7 @@ import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";
import { PGFachv, PGMachv } from "./achv";
import { battle } from "./battle";
import { battleScene } from "./battle-scene";
import { battleInfo } from "./battle-info";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { battlerTags } from "./battler-tags";
@ -60,6 +61,7 @@ export const zhTwConfig = {
arenaFlyout: arenaFlyout,
arenaTag: arenaTag,
battle: battle,
battleScene: battleScene,
battleInfo: battleInfo,
battleMessageUiHandler: battleMessageUiHandler,
battlePokemonForm: battlePokemonForm,

View File

@ -1160,7 +1160,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
if (!pokemon.isFullHp()) {
const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true));
Utils.toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true));
return true;
}
@ -1251,7 +1251,7 @@ export class HitHealModifier extends PokemonHeldItemModifier {
if (pokemon.turnData.damageDealt && !pokemon.isFullHp()) {
const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true));
Utils.toDmgValue(pokemon.turnData.damageDealt / 8) * this.stackCount, i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true));
}
return true;
@ -1386,7 +1386,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
const pokemon = args[0] as Pokemon;
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 2), 1), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true));
Utils.toDmgValue(pokemon.getMaxHp() / 2), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true));
pokemon.resetStatus(true, false, true);
return true;

View File

@ -23,7 +23,7 @@ export class AttemptRunPhase extends PokemonPhase {
const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length;
const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256);
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, escapeChance);
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
if (playerPokemon.randSeedInt(256) < escapeChance.value) {
this.scene.playSound("flee");

View File

@ -189,7 +189,7 @@ export class CommandPhase extends FieldPhase {
const batonPass = isSwitch && args[0] as boolean;
const trappedAbMessages: string[] = [];
if (!batonPass) {
enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, true, trappedAbMessages));
enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, trappedAbMessages, true));
}
if (batonPass || (!trapTag && !trapped.value)) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch

View File

@ -67,7 +67,7 @@ export class EncounterPhase extends BattlePhase {
battle.enemyParty[e].ivs = new Array(6).fill(31);
}
this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => {
applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]);
applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, false, battle.enemyParty[e]);
});
}
}

View File

@ -47,7 +47,7 @@ export class EnemyCommandPhase extends FieldPhase {
const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
const trapped = new Utils.BooleanHolder(false);
opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, true, []));
opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, [], true));
if (!trapTag && !trapped.value) {
const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true);

View File

@ -74,7 +74,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getTarget() ?? null, move, hitCount);
// If Parental Bond is applicable, double the hit count
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0));
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, targets.length, hitCount, new Utils.IntegerHolder(0));
// If Multi-Lens is applicable, multiply the hit count by 1 + the number of Multi-Lenses held by the user
if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) {
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));

View File

@ -90,7 +90,7 @@ export class MovePhase extends BattlePhase {
: null;
if (moveTarget) {
const oldTarget = moveTarget.value;
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget));
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, moveTarget));
this.pokemon.getOpponents().forEach(p => {
const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag;
if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) {

View File

@ -34,7 +34,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
break;
case StatusEffect.BURN:
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, damage);
applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage);
break;
}
if (damage.value) {

View File

@ -68,7 +68,7 @@ export class StatChangePhase extends PokemonPhase {
const levels = new Utils.IntegerHolder(this.levels);
if (!this.ignoreAbilities) {
applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels);
applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, false, levels);
}
const battleStats = this.getPokemon().summonData.battleStats;
@ -90,7 +90,7 @@ export class StatChangePhase extends PokemonPhase {
if (levels.value > 0 && this.canBeCopied) {
for (const opponent of pokemon.getOpponents()) {
applyAbAttrs(StatChangeCopyAbAttr, opponent, null, this.stats, levels.value);
applyAbAttrs(StatChangeCopyAbAttr, opponent, null, false, this.stats, levels.value);
}
}

View File

@ -34,8 +34,8 @@ export class TurnStartPhase extends FieldPhase {
this.scene.getField(true).filter(p => p.summonData).map(p => {
const bypassSpeed = new Utils.BooleanHolder(false);
const canCheckHeldItems = new Utils.BooleanHolder(true);
applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed);
applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, bypassSpeed, canCheckHeldItems);
applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed);
applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems);
if (canCheckHeldItems.value) {
this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
}
@ -64,8 +64,8 @@ export class TurnStartPhase extends FieldPhase {
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here?
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here?
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here?
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here?
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, false, aMove, aPriority); //TODO: is the bang correct here?
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, false, bMove, bPriority); //TODO: is the bang correct here?
if (aPriority.value !== bPriority.value) {
const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value);

View File

@ -26,6 +26,7 @@ const unicodeRanges = {
kana: "U+3040-30FF",
CJKCommon: "U+2E80-2EFF,U+3000-303F,U+31C0-31EF,U+3200-32FF,U+3400-4DBF,U+F900-FAFF,U+FE30-FE4F",
CJKIdeograph: "U+4E00-9FFF",
specialCharacters: "U+266A,U+2605,U+2665,U+2663" //♪.★,♥,♣
};
const rangesByLanguage = {
korean: [unicodeRanges.CJKCommon, unicodeRanges.hangul].join(","),
@ -34,6 +35,15 @@ const rangesByLanguage = {
};
const fonts: Array<LoadingFontFaceProperty> = [
// unicode (special character from PokePT)
{
face: new FontFace("emerald", "url(./fonts/PokePT_Wansung.woff2)", { unicodeRange: unicodeRanges.specialCharacters }),
},
{
face: new FontFace("pkmnems", "url(./fonts/PokePT_Wansung.woff2)", { unicodeRange: unicodeRanges.specialCharacters }),
extraOptions: { sizeAdjust: "133%" },
},
// unicode (korean)
{
face: new FontFace("emerald", "url(./fonts/PokePT_Wansung.woff2)", { unicodeRange: rangesByLanguage.korean }),
},

View File

@ -6,6 +6,7 @@ import { Species } from "#enums/species";
import { StatusEffect } from "#app/data/status-effect.js";
import { BattleStat } from "#app/data/battle-stat.js";
import { SPLASH_ONLY } from "../utils/testUtils";
import { toDmgValue } from "#app/utils";
import { Mode } from "#app/ui/ui.js";
import { MoveEffectPhase } from "#app/phases/move-effect-phase.js";
import { MoveEndPhase } from "#app/phases/move-end-phase.js";
@ -47,7 +48,7 @@ describe("Abilities - Disguise", () => {
const mimikyu = game.scene.getEnemyPokemon()!;
const maxHp = mimikyu.getMaxHp();
const disguiseDamage = Math.floor(maxHp / 8);
const disguiseDamage = toDmgValue(maxHp / 8);
expect(mimikyu.formIndex).toBe(disguisedForm);
@ -80,7 +81,7 @@ describe("Abilities - Disguise", () => {
const mimikyu = game.scene.getEnemyPokemon()!;
const maxHp = mimikyu.getMaxHp();
const disguiseDamage = Math.floor(maxHp / 8);
const disguiseDamage = toDmgValue(maxHp / 8);
expect(mimikyu.formIndex).toBe(disguisedForm);
@ -121,7 +122,7 @@ describe("Abilities - Disguise", () => {
const mimikyu = game.scene.getPlayerPokemon()!;
const maxHp = mimikyu.getMaxHp();
const disguiseDamage = Math.floor(maxHp / 8);
const disguiseDamage = toDmgValue(maxHp / 8);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));

View File

@ -8,6 +8,7 @@ import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import { StatusEffect } from "#app/enums/status-effect.js";
import { toDmgValue } from "#app/utils";
describe("Abilities - Heatproof", () => {
let phaserGame: Phaser.Game;
@ -72,6 +73,6 @@ describe("Abilities - Heatproof", () => {
await game.toNextTurn();
// Normal burn damage is /16
expect(enemy.hp).toBe(enemy.getMaxHp() - Math.floor(enemy.getMaxHp() / 32));
expect(enemy.hp).toBe(enemy.getMaxHp() - toDmgValue(enemy.getMaxHp() / 32));
});
});

View File

@ -16,6 +16,7 @@ import { DamagePhase } from "#app/phases/damage-phase.js";
import { MoveEffectPhase } from "#app/phases/move-effect-phase.js";
import { MoveEndPhase } from "#app/phases/move-end-phase.js";
import { TurnEndPhase } from "#app/phases/turn-end-phase.js";
import { toDmgValue } from "#app/utils";
const TIMEOUT = 20 * 1000;
@ -73,7 +74,7 @@ describe("Abilities - Parental Bond", () => {
const secondStrikeDamage = enemyStartingHp - enemyPokemon.hp;
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(secondStrikeDamage).toBe(Math.ceil(0.25 * firstStrikeDamage));
expect(secondStrikeDamage).toBe(toDmgValue(0.25 * firstStrikeDamage));
}, TIMEOUT
);
@ -303,7 +304,7 @@ describe("Abilities - Parental Bond", () => {
// This test will time out if the user faints
await game.phaseInterceptor.to(BerryPhase, false);
expect(leadPokemon.hp).toBe(Math.floor(leadPokemon.getMaxHp()/2));
expect(leadPokemon.hp).toBe(toDmgValue(leadPokemon.getMaxHp()/2));
}, TIMEOUT
);

View File

@ -52,7 +52,7 @@ describe("Abilities - Sand Veil", () => {
const sandVeilAttr = allAbilities[Abilities.SAND_VEIL].getAttrs(BattleStatMultiplierAbAttr)[0];
vi.spyOn(sandVeilAttr, "applyBattleStat").mockImplementation(
(pokemon, passive, battleStat, statValue, args) => {
(pokemon, passive, simulated, battleStat, statValue, args) => {
if (battleStat === BattleStat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
statValue.value *= -1; // will make all attacks miss
return true;

View File

@ -67,7 +67,7 @@ describe("Abilities - Serene Grace", () => {
const chance = new Utils.IntegerHolder(move.chance);
console.log(move.chance + " Their ability is " + phase.getUserPokemon()!.getAbility().name);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false);
expect(chance.value).toBe(30);
}, 20000);
@ -99,7 +99,7 @@ describe("Abilities - Serene Grace", () => {
expect(move.id).toBe(Moves.AIR_SLASH);
const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false);
expect(chance.value).toBe(60);
}, 20000);

View File

@ -69,8 +69,8 @@ describe("Abilities - Sheer Force", () => {
const power = new Utils.IntegerHolder(move.power);
const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, false, power);
expect(chance.value).toBe(0);
expect(power.value).toBe(move.power * 5461/4096);
@ -108,8 +108,8 @@ describe("Abilities - Sheer Force", () => {
const power = new Utils.IntegerHolder(move.power);
const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, false, power);
expect(chance.value).toBe(-1);
expect(power.value).toBe(move.power);
@ -147,8 +147,8 @@ describe("Abilities - Sheer Force", () => {
const power = new Utils.IntegerHolder(move.power);
const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, false, power);
expect(chance.value).toBe(-1);
expect(power.value).toBe(move.power);
@ -191,8 +191,8 @@ describe("Abilities - Sheer Force", () => {
const target = phase.getTarget()!;
const opponentType = target.getTypes()[0];
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, chance, move, target, false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, power);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, chance, move, target, false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, false, power);
applyPostDefendAbAttrs(PostDefendTypeChangeAbAttr, target, user, move, target.apply(user, move));
expect(chance.value).toBe(0);

View File

@ -67,8 +67,8 @@ describe("Abilities - Shield Dust", () => {
expect(move.id).toBe(Moves.AIR_SLASH);
const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getTarget()!, phase.getUserPokemon()!, null!, null!, chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getTarget(), false);
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getTarget()!, phase.getUserPokemon()!, null, null, false, chance);
expect(chance.value).toBe(0);
}, 20000);

View File

@ -0,0 +1,71 @@
import { DamagePhase } from "#app/phases/damage-phase.js";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { ArenaTagType } from "#enums/arena-tag-type";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import { toDmgValue } from "#app/utils";
describe("Round Down and Minimun 1 test in Damage Calculation", () => {
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.override.battleType("single");
game.override.startingLevel(10);
});
it("When the user fails to use Jump Kick with Wonder Guard ability, the damage should be 1.", async () => {
game.override.enemySpecies(Species.GASTLY);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.starterSpecies(Species.SHEDINJA);
game.override.moveset([Moves.JUMP_KICK]);
game.override.ability(Abilities.WONDER_GUARD);
await game.startBattle();
const shedinja = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.JUMP_KICK));
await game.phaseInterceptor.to(DamagePhase);
expect(shedinja.hp).toBe(shedinja.getMaxHp() - 1);
});
it("Charizard with odd HP survives Stealth Rock damage twice", async () => {
game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0);
game.override.seed("Charizard Stealth Rock test");
game.override.enemySpecies(Species.CHARIZARD);
game.override.enemyAbility(Abilities.BLAZE);
game.override.starterSpecies(Species.PIKACHU);
game.override.enemyLevel(100);
await game.startBattle();
const charizard = game.scene.getEnemyPokemon()!;
const maxHp = charizard.getMaxHp();
const damage_prediction = toDmgValue(charizard.getMaxHp() / 2);
const currentHp = charizard.hp;
const expectedHP = maxHp - damage_prediction;
expect(currentHp).toBe(expectedHP);
});
});

View File

@ -6,6 +6,7 @@ import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat";
import { toDmgValue } from "#app/utils";
const TIMEOUT = 20 * 1000;
// RATIO : HP Cost of Move
@ -44,7 +45,7 @@ describe("Moves - BELLY DRUM", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM));
await game.phaseInterceptor.to(TurnEndPhase);
@ -59,7 +60,7 @@ describe("Moves - BELLY DRUM", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
// Here - BattleStat.ATK -> -3 and BattleStat.SPATK -> 6
leadPokemon.summonData.battleStats[BattleStat.ATK] = -3;
@ -95,7 +96,7 @@ describe("Moves - BELLY DRUM", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
leadPokemon.hp = hpLost - PREDAMAGE;
game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM));

View File

@ -7,6 +7,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import { toDmgValue } from "#app/utils";
const TIMEOUT = 20 * 1000;
/** HP Cost of Move */
@ -45,7 +46,7 @@ describe("Moves - CLANGOROUS_SOUL", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL));
await game.phaseInterceptor.to(TurnEndPhase);
@ -64,7 +65,7 @@ describe("Moves - CLANGOROUS_SOUL", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
//Here - BattleStat.SPD -> 0 and BattleStat.SPDEF -> 4
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
@ -113,7 +114,7 @@ describe("Moves - CLANGOROUS_SOUL", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
leadPokemon.hp = hpLost - PREDAMAGE;
game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL));

View File

@ -7,6 +7,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import { toDmgValue } from "#app/utils";
const TIMEOUT = 20 * 1000;
/** HP Cost of Move */
@ -45,7 +46,7 @@ describe("Moves - FILLET AWAY", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY));
await game.phaseInterceptor.to(TurnEndPhase);
@ -62,7 +63,7 @@ describe("Moves - FILLET AWAY", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
//Here - BattleStat.SPD -> 0 and BattleStat.SPATK -> 3
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
@ -103,7 +104,7 @@ describe("Moves - FILLET AWAY", () => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
leadPokemon.hp = hpLost - PREDAMAGE;
game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY));

View File

@ -78,6 +78,6 @@ describe("Moves - Tackle", () => {
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp;
expect(hpLost).toBeGreaterThan(0);
expect(hpLost).toBe(4);
expect(hpLost).toBeLessThan(4);
}, 20000);
});

View File

@ -560,3 +560,17 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole
export function isNullOrUndefined(object: any): boolean {
return null === object || undefined === object;
}
/**
* This function is used in the context of a Pokémon battle game to calculate the actual integer damage value from a float result.
* Many damage calculation formulas involve various parameters and result in float values.
* The actual damage applied to a Pokémon's HP must be an integer.
* This function helps in ensuring that by flooring the float value and enforcing a minimum damage value.
*
* @param value - The float value to convert.
* @param minValue - The minimum integer value to return. Defaults to 1.
* @returns The converted value as an integer.
*/
export function toDmgValue(value: number, minValue: number = 1) {
return Math.max(Math.floor(value), minValue);
}