diff --git a/src/data/move.ts b/src/data/move.ts index bb88066ca..313aa8a42 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2863,6 +2863,7 @@ export class RemoveTypeAttr extends MoveEffectAttr { const userTypes = user.getTypes(true) const modifiedTypes = userTypes.filter(type => type !== this.removedType); user.summonData.types = modifiedTypes; + user.updateInfo(); if (this.messageCallback) { @@ -2883,11 +2884,16 @@ export class CopyTypeAttr extends MoveEffectAttr { return false; user.summonData.types = target.getTypes(true); + user.updateInfo(); user.scene.queueMessage(getPokemonMessage(user, `'s type\nchanged to match ${target.name}'s!`)); return true; } + + getCondition(): MoveConditionFunc { + return (user, target, move) => target.getTypes()[0] !== Type.UNKNOWN; + } } export class CopyBiomeTypeAttr extends MoveEffectAttr { @@ -2902,6 +2908,7 @@ export class CopyBiomeTypeAttr extends MoveEffectAttr { const biomeType = user.scene.arena.getTypeForBiome(); user.summonData.types = [ biomeType ]; + user.updateInfo(); user.scene.queueMessage(getPokemonMessage(user, ` transformed\ninto the ${Utils.toReadableString(Type[biomeType])} type!`)); @@ -2909,6 +2916,54 @@ export class CopyBiomeTypeAttr extends MoveEffectAttr { } } +export class ChangeTypeAttr extends MoveEffectAttr { + private type: Type; + + constructor(type: Type) { + super(false, MoveEffectTrigger.HIT); + + this.type = type; + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + target.summonData.types = [this.type]; + target.updateInfo(); + + user.scene.queueMessage(getPokemonMessage(target, ` transformed\ninto the ${Utils.toReadableString(Type[this.type])} type!`)); + + return true; + } + + getCondition(): MoveConditionFunc { + return (user, target, move) => !target.isTerastallized() && !target.hasAbility(Abilities.MULTITYPE) && !target.hasAbility(Abilities.RKS_SYSTEM) && !(target.getTypes().length === 1 && target.getTypes()[0] === this.type); + } +} + +export class AddTypeAttr extends MoveEffectAttr { + private type: Type; + + constructor(type: Type) { + super(false, MoveEffectTrigger.HIT); + + this.type = type; + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN); // TODO: Figure out some way to actually check if another version of this effect is already applied + types.push(this.type); + target.summonData.types = types; + target.updateInfo(); + + user.scene.queueMessage(`${Utils.toReadableString(Type[this.type])} was added to\n` + getPokemonMessage(target, '!')); + + return true; + } + + getCondition(): MoveConditionFunc { + return (user, target, move) => !target.isTerastallized()&& !target.getTypes().includes(this.type); + } +} + export class FirstMoveTypeAttr extends MoveEffectAttr { constructor() { super(true); @@ -4889,7 +4944,7 @@ export function initMoves() { .attr(BattleStatRatioPowerAttr, Stat.SPD) .ballBombMove(), new StatusMove(Moves.SOAK, Type.WATER, 100, 20, -1, 0, 5) - .unimplemented(), + .attr(ChangeTypeAttr, Type.WATER), new AttackMove(Moves.FLAME_CHARGE, Type.FIRE, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 5) .attr(StatChangeAttr, BattleStat.SPD, 1, true), new SelfStatusMove(Moves.COIL, Type.POISON, -1, 20, -1, 0, 5) @@ -5095,7 +5150,8 @@ export function initMoves() { .ignoresProtect() .ignoresVirtual(), new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6) - .unimplemented(), + .attr(AddTypeAttr, Type.GHOST) + .partial(), new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, 100, 0, 6) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1) .soundBased(), @@ -5107,7 +5163,8 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_OTHERS) .triageMove(), new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6) - .unimplemented(), + .attr(AddTypeAttr, Type.GRASS) + .partial(), new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6) .windMove() .makesContact(false) diff --git a/src/data/type.ts b/src/data/type.ts index fa2b41673..14f9f932a 100644 --- a/src/data/type.ts +++ b/src/data/type.ts @@ -21,9 +21,12 @@ export enum Type { STELLAR }; -export type TypeDamageMultiplier = 0 | 0.25 | 0.5 | 1 | 2 | 4; +export type TypeDamageMultiplier = 0 | 0.125 | 0.25 | 0.5 | 1 | 2 | 4 | 8; export function getTypeDamageMultiplier(attackType: integer, defType: integer): TypeDamageMultiplier { + if (attackType === Type.UNKNOWN || defType === Type.UNKNOWN) + return 1; + switch (defType) { case Type.NORMAL: switch (attackType) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f54779898..83cd9f066 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -745,7 +745,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (!types.length) - types.push(Type.NORMAL); + types.push(Type.UNKNOWN); return types; } @@ -859,7 +859,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (moveType === Type.STELLAR) return this.isTerastallized() ? 2 : 1; const types = this.getTypes(true, true); - let multiplier = getTypeDamageMultiplier(moveType, types[0]) * (types.length > 1 ? getTypeDamageMultiplier(moveType, types[1]) : 1) as TypeDamageMultiplier; + let multiplier = getTypeDamageMultiplier(moveType, types[0]) * (types.length > 1 ? getTypeDamageMultiplier(moveType, types[1]) : 1) * (types.length > 2 ? getTypeDamageMultiplier(moveType, types[2]) : 1) as TypeDamageMultiplier; // Handle strong winds lowering effectiveness of types super effective against pure flying if (this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && multiplier >= 2 && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) multiplier /= 2; diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index b4968f26b..c26214b59 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -40,6 +40,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { private hpNumbersContainer: Phaser.GameObjects.Container; private type1Icon: Phaser.GameObjects.Sprite; private type2Icon: Phaser.GameObjects.Sprite; + private type3Icon: Phaser.GameObjects.Sprite; private expBar: Phaser.GameObjects.Image; public expMaskRect: Phaser.GameObjects.Graphics; @@ -137,6 +138,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.type2Icon.setOrigin(0, 0); this.add(this.type2Icon); + this.type3Icon = this.scene.add.sprite(player ? -154 : 0, player ? -17 : -15.5, `pbinfo_${player ? 'player' : 'enemy'}_type`); + this.type3Icon.setOrigin(0, 0); + this.add(this.type3Icon); + if (this.player) { this.hpNumbersContainer = this.scene.add.container(-15, 10); this.add(this.hpNumbersContainer); @@ -221,8 +226,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.type1Icon.setTexture(`pbinfo_${this.player ? 'player' : 'enemy'}_type${types.length > 1 ? '1' : ''}`); this.type1Icon.setFrame(Type[types[0]].toLowerCase()); this.type2Icon.setVisible(types.length > 1); + this.type3Icon.setVisible(types.length > 2); if (types.length > 1) this.type2Icon.setFrame(Type[types[1]].toLowerCase()); + if (types.length > 2) + this.type3Icon.setFrame(Type[types[2]].toLowerCase()); if (this.player) { this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510; @@ -249,7 +257,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { const offsetElements = [ this.nameText, this.genderText, this.teraIcon, this.splicedIcon, this.shinyIcon, this.statusIndicator, this.levelContainer ]; offsetElements.forEach(el => el.y += 1.5 * (mini ? -1 : 1)); - [ this.type1Icon, this.type2Icon ].forEach(el => { + [ this.type1Icon, this.type2Icon, this.type3Icon ].forEach(el => { el.x += 4 * (mini ? 1 : -1); el.y += -8 * (mini ? 1 : -1); }); @@ -346,8 +354,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.type1Icon.setTexture(`pbinfo_${this.player ? 'player' : 'enemy'}_type${types.length > 1 ? '1' : ''}`); this.type1Icon.setFrame(Type[types[0]].toLowerCase()); this.type2Icon.setVisible(types.length > 1); + this.type3Icon.setVisible(types.length > 2); if (types.length > 1) this.type2Icon.setFrame(Type[types[1]].toLowerCase()); + if (types.length > 2) + this.type3Icon.setFrame(Type[types[2]].toLowerCase()); const updateHpFrame = () => { const hpFrame = this.hpBar.scaleX > 0.5 ? 'high' : this.hpBar.scaleX > 0.25 ? 'medium' : 'low';