From ed8d162125e87f7942a39a8ab8f7684948af0140 Mon Sep 17 00:00:00 2001
From: Xavion3
Date: Fri, 21 Feb 2025 10:50:39 +1100
Subject: [PATCH 2/3] [Balance] Make stat a much larger factor in moveset gen
#5383
---
src/field/pokemon.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts
index 714f1ec7026..246f82b8164 100644
--- a/src/field/pokemon.ts
+++ b/src/field/pokemon.ts
@@ -2348,12 +2348,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const maxPower = Math.min(movePool.reduce((v, m) => Math.max(allMoves[m[0]].power, 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)) ]);
- // Weight damaging moves against the lower stat
+ // 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.
+ // One third weight at ~1.58x higher, one quarter weight at ~1.73x higher, one fifth at ~1.87x, and one tenth at ~2.35x higher.
const atk = this.getStat(Stat.ATK);
const spAtk = this.getStat(Stat.SPATK);
const worseCategory: MoveCategory = atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL;
const statRatio = worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk;
- movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === worseCategory ? statRatio : 1) ]);
+ movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === worseCategory ? Math.min(Math.pow(statRatio, 3) * 1.3, 1) : 1) ]);
/** The higher this is the more the game weights towards higher level moves. At `0` all moves are equal weight. */
let weightMultiplier = 0.9;
From 5072460f4c33dbec9a138a0bd7cebdf8ac60fdc0 Mon Sep 17 00:00:00 2001
From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
Date: Thu, 20 Feb 2025 18:16:41 -0600
Subject: [PATCH 3/3] [Bug] Fix endless tokens allowing attacks to deal 0
damage (#5347)
---
src/data/ability.ts | 2 +-
src/modifier/modifier.ts | 2 +-
src/test/battle/damage_calculation.test.ts | 24 ++++++++++++++++++++++
3 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/src/data/ability.ts b/src/data/ability.ts
index 940b5f0c7d7..95601dc2010 100644
--- a/src/data/ability.ts
+++ b/src/data/ability.ts
@@ -1404,7 +1404,7 @@ export class DamageBoostAbAttr extends PreAttackAbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) {
const power = args[0] as Utils.NumberHolder;
- power.value = Math.floor(power.value * this.damageMultiplier);
+ power.value = Utils.toDmgValue(power.value * this.damageMultiplier);
return true;
}
diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts
index fe61eadaccd..3af2aa2144f 100644
--- a/src/modifier/modifier.ts
+++ b/src/modifier/modifier.ts
@@ -3390,7 +3390,7 @@ abstract class EnemyDamageMultiplierModifier extends EnemyPersistentModifier {
* @returns always `true`
*/
override apply(multiplier: NumberHolder): boolean {
- multiplier.value = Math.floor(multiplier.value * Math.pow(this.damageMultiplier, this.getStackCount()));
+ multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageMultiplier, this.getStackCount()));
return true;
}
diff --git a/src/test/battle/damage_calculation.test.ts b/src/test/battle/damage_calculation.test.ts
index e6aca828156..22d072f313c 100644
--- a/src/test/battle/damage_calculation.test.ts
+++ b/src/test/battle/damage_calculation.test.ts
@@ -1,4 +1,6 @@
import { allMoves } from "#app/data/move";
+import type { EnemyPersistentModifier } from "#app/modifier/modifier";
+import { modifierTypes } from "#app/modifier/modifier-type";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { Moves } from "#enums/moves";
@@ -65,6 +67,28 @@ describe("Battle Mechanics - Damage Calculation", () => {
expect(aggron.hp).toBe(aggron.getMaxHp() - 1);
});
+ it("Attacks deal 1 damage at minimum even with many tokens", async () => {
+ game.override
+ .startingLevel(1)
+ .enemySpecies(Species.AGGRON)
+ .enemyAbility(Abilities.STURDY)
+ .enemyLevel(10000);
+
+ await game.classicMode.startBattle([ Species.SHUCKLE ]);
+
+ const dmg_redux_modifier = modifierTypes.ENEMY_DAMAGE_REDUCTION().newModifier() as EnemyPersistentModifier;
+ dmg_redux_modifier.stackCount = 1000;
+ await game.scene.addEnemyModifier(modifierTypes.ENEMY_DAMAGE_REDUCTION().newModifier() as EnemyPersistentModifier);
+
+ const aggron = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.TACKLE);
+
+ await game.phaseInterceptor.to("BerryPhase", false);
+
+ expect(aggron.hp).toBe(aggron.getMaxHp() - 1);
+ });
+
it("Fixed-damage moves ignore damage multipliers", async () => {
game.override
.enemySpecies(Species.DRAGONITE)