[Bug][Move] Shed Tail now rounds incurred damage up, not down (#5219)

* Shed Tail rounds incurred damage up

* Altered Substitute/Shed Tail success condition to account for rounding up

* Altered the test to match correct functionality

* Update src/data/move.ts

Removed default value in constructor due to it having become redundant

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
This commit is contained in:
Esca 2025-02-26 09:45:29 +01:00 committed by GitHub
parent 6e8a4b287c
commit 883bf45bc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 11 additions and 10 deletions

View File

@ -1866,11 +1866,14 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
export class AddSubstituteAttr extends MoveEffectAttr { export class AddSubstituteAttr extends MoveEffectAttr {
/** The ratio of the user's max HP that is required to apply this effect */ /** The ratio of the user's max HP that is required to apply this effect */
private hpCost: number; private hpCost: number;
/** Whether the damage taken should be rounded up (Shed Tail rounds up) */
private roundUp: boolean;
constructor(hpCost: number = 0.25) { constructor(hpCost: number, roundUp: boolean) {
super(true); super(true);
this.hpCost = hpCost; this.hpCost = hpCost;
this.roundUp = roundUp;
} }
/** /**
@ -1886,7 +1889,8 @@ export class AddSubstituteAttr extends MoveEffectAttr {
return false; return false;
} }
user.damageAndUpdate(Math.floor(user.getMaxHp() * this.hpCost), HitResult.OTHER, false, true, true); const damageTaken = this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost);
user.damageAndUpdate(damageTaken, HitResult.OTHER, false, true, true);
user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id); user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id);
return true; return true;
} }
@ -1899,7 +1903,7 @@ export class AddSubstituteAttr extends MoveEffectAttr {
} }
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > Math.floor(user.getMaxHp() * this.hpCost) && user.getMaxHp() > 1; return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > (this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost)) && user.getMaxHp() > 1;
} }
/** /**
@ -9036,7 +9040,7 @@ export function initMoves() {
.attr(HighCritAttr) .attr(HighCritAttr)
.slicingMove(), .slicingMove(),
new SelfStatusMove(Moves.SUBSTITUTE, Type.NORMAL, -1, 10, -1, 0, 1) new SelfStatusMove(Moves.SUBSTITUTE, Type.NORMAL, -1, 10, -1, 0, 1)
.attr(AddSubstituteAttr), .attr(AddSubstituteAttr, 0.25, false),
new AttackMove(Moves.STRUGGLE, Type.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1) new AttackMove(Moves.STRUGGLE, Type.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1)
.attr(RecoilAttr, true, 0.25, true) .attr(RecoilAttr, true, 0.25, true)
.attr(TypelessAttr) .attr(TypelessAttr)
@ -11382,7 +11386,7 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 5461 / 4096 : 1) .attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 5461 / 4096 : 1)
.makesContact(), .makesContact(),
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9) new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
.attr(AddSubstituteAttr, 0.5) .attr(AddSubstituteAttr, 0.5, true)
.attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL) .attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL)
.condition(failIfLastInPartyCondition), .condition(failIfLastInPartyCondition),
new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9) new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)

View File

@ -46,11 +46,8 @@ describe("Moves - Shed Tail", () => {
expect(feebas).not.toBe(magikarp); expect(feebas).not.toBe(magikarp);
expect(feebas.hp).toBe(feebas.getMaxHp()); expect(feebas.hp).toBe(feebas.getMaxHp());
// Note: Shed Tail's HP cost is currently not accurate to mainline, as it // Note: Altered the test to be consistent with the correct HP cost :yipeevee_static:
// should cost ceil(maxHP / 2) instead of max(floor(maxHp / 2), 1). The current expect(magikarp.hp).toBe(Math.floor(magikarp.getMaxHp() / 2));
// implementation is consistent with Substitute's HP cost logic, but that's not
// the case in mainline for some reason :regiDespair:.
expect(magikarp.hp).toBe(Math.ceil(magikarp.getMaxHp() / 2));
expect(substituteTag).toBeDefined(); expect(substituteTag).toBeDefined();
expect(substituteTag?.hp).toBe(Math.floor(magikarp.getMaxHp() / 4)); expect(substituteTag?.hp).toBe(Math.floor(magikarp.getMaxHp() / 4));
}); });