Begin implementing abilities

pull/1/head
Flashfyre 2023-04-26 23:33:13 -04:00
parent f790a5ff2a
commit c614295b5e
6 changed files with 234 additions and 86 deletions

View File

@ -25,6 +25,7 @@ import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { TempBattleStat } from "./data/temp-battle-stat"; import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTrapTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTrapTag, TrickRoomTag } from "./data/arena-tag";
import { ProtectStatAttr, applyPreStatChangeAbilityAttrs } from "./data/ability";
export class SelectStarterPhase extends BattlePhase { export class SelectStarterPhase extends BattlePhase {
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
@ -1080,9 +1081,10 @@ export class MoveAnimTestPhase extends BattlePhase {
export class StatChangePhase extends PokemonPhase { export class StatChangePhase extends PokemonPhase {
private stats: BattleStat[]; private stats: BattleStat[];
private selfTarget: boolean;
private levels: integer; private levels: integer;
constructor(scene: BattleScene, player: boolean, stats: BattleStat[], levels: integer) { constructor(scene: BattleScene, player: boolean, selfTarget: boolean, stats: BattleStat[], levels: integer) {
super(scene, player); super(scene, player);
const allStats = Utils.getEnumValues(BattleStat); const allStats = Utils.getEnumValues(BattleStat);
@ -1092,20 +1094,27 @@ export class StatChangePhase extends PokemonPhase {
start() { start() {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
const filteredStats = this.stats.filter(stat => {
const cancelled = new Utils.BooleanHolder(false);
if (!this.selfTarget && this.levels < 0)
applyPreStatChangeAbilityAttrs(ProtectStatAttr, this.getPokemon(), stat, cancelled);
return !cancelled.value;
});
const battleStats = this.getPokemon().summonData.battleStats; const battleStats = this.getPokemon().summonData.battleStats;
const relLevels = this.stats.map(stat => (this.levels >= 1 ? Math.min(battleStats[stat] + this.levels, 6) : Math.max(battleStats[stat] + this.levels, -6)) - battleStats[stat]); const relLevels = filteredStats.map(stat => (this.levels >= 1 ? Math.min(battleStats[stat] + this.levels, 6) : Math.max(battleStats[stat] + this.levels, -6)) - battleStats[stat]);
const end = () => { const end = () => {
const messages = this.getStatChangeMessages(relLevels); const messages = this.getStatChangeMessages(filteredStats, relLevels);
for (let message of messages) for (let message of messages)
this.scene.queueMessage(message); this.scene.queueMessage(message);
for (let stat of this.stats) for (let stat of filteredStats)
pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + this.levels, 6), -6); pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + this.levels, 6), -6);
console.log(pokemon.summonData.battleStats);
this.end(); this.end();
}; };
@ -1113,7 +1122,7 @@ export class StatChangePhase extends PokemonPhase {
pokemon.enableMask(); pokemon.enableMask();
const pokemonMaskSprite = pokemon.maskSprite; const pokemonMaskSprite = pokemon.maskSprite;
const statSprite = this.scene.add.tileSprite((this.player ? 106 : 236) * 6, ((this.player ? 148 : 84) + (this.levels >= 1 ? 160 : 0)) * 6, 156, 316, 'battle_stats', this.stats.length > 1 ? 'mix' : BattleStat[this.stats[0]].toLowerCase()); const statSprite = this.scene.add.tileSprite((this.player ? 106 : 236) * 6, ((this.player ? 148 : 84) + (this.levels >= 1 ? 160 : 0)) * 6, 156, 316, 'battle_stats', filteredStats.length > 1 ? 'mix' : BattleStat[filteredStats[0]].toLowerCase());
statSprite.setAlpha(0); statSprite.setAlpha(0);
statSprite.setScale(6); statSprite.setScale(6);
statSprite.setOrigin(0.5, 1); statSprite.setOrigin(0.5, 1);
@ -1150,11 +1159,11 @@ export class StatChangePhase extends PokemonPhase {
end(); end();
} }
getStatChangeMessages(relLevels: integer[]): string[] { getStatChangeMessages(stats: BattleStat[], relLevels: integer[]): string[] {
const messages: string[] = []; const messages: string[] = [];
for (let s = 0; s < this.stats.length; s++) for (let s = 0; s < stats.length; s++)
messages.push(getPokemonMessage(this.getPokemon(), `'s ${getBattleStatName(this.stats[s])} ${getBattleStatLevelChangeDescription(Math.abs(relLevels[s]), this.levels >= 1)}!`)); messages.push(getPokemonMessage(this.getPokemon(), `'s ${getBattleStatName(stats[s])} ${getBattleStatLevelChangeDescription(Math.abs(relLevels[s]), this.levels >= 1)}!`));
return messages; return messages;
} }
} }

View File

@ -1,15 +1,129 @@
import Pokemon, { PokemonMove } from "../pokemon";
import { Type } from "./type";
import * as Utils from "../utils";
import { BattleStat } from "./battle-stat";
import { StatChangePhase } from "../battle-phases";
export class Ability { export class Ability {
public id: Abilities; public id: Abilities;
public name: string; public name: string;
public description: string; public description: string;
public generation: integer; public generation: integer;
public attrs: AbilityAttr[];
constructor(id: Abilities, name: string, description: string, generation: integer) { constructor(id: Abilities, name: string, description: string, generation: integer) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.description = description; this.description = description;
this.generation = generation; this.generation = generation;
this.attrs = [];
} }
getAttrs(attrType: { new(...args: any[]): AbilityAttr }): AbilityAttr[] {
return this.attrs.filter(a => a instanceof attrType);
}
attr<T extends new (...args: any[]) => AbilityAttr>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
const attr = new AttrType(...args);
this.attrs.push(attr);
return this;
}
}
export abstract class AbilityAttr { }
export class PreDefendAbilityAttr extends AbilityAttr {
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
return false;
}
}
export class TypeImmunityAttr extends PreDefendAbilityAttr {
private immuneType: Type;
constructor(immuneType: Type) {
super();
this.immuneType = immuneType;
}
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (move.getMove().type === this.immuneType) {
(args[0] as Utils.NumberHolder).value = 0;
return true;
}
return false;
}
}
class TypeImmunityStatChangeAttr extends TypeImmunityAttr {
private stat: BattleStat;
private levels: integer;
constructor(immuneType: Type, stat: BattleStat, levels: integer) {
super(immuneType);
this.stat = stat;
this.levels = levels;
}
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, attacker, move, cancelled, args);
if (ret) {
cancelled.value = true;
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ this.stat ], this.levels));
}
return ret;
}
}
export class PreStatChangeAbilityAttr extends AbilityAttr {
applyPreStatChange(pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
return false;
}
}
export class ProtectStatAttr extends PreStatChangeAbilityAttr {
private protectedStats: BattleStat[];
constructor(...stats: BattleStat[]) {
super();
this.protectedStats = stats;
}
applyPreStatChange(pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (!this.protectedStats.length || this.protectedStats.indexOf(stat) > -1) {
cancelled.value = true;
return true;
}
return false;
}
}
export function applyPreDefendAbilityAttrs(attrType: { new(...args: any[]): PreDefendAbilityAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, ...args: any[]): void {
const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as PreDefendAbilityAttr[];
for (let attr of attrs) {
if (attr.applyPreDefend(pokemon, attacker, move, cancelled, args))
console.log('Applied', ability.name, attr);
}
}
export function applyPreStatChangeAbilityAttrs(attrType: { new(...args: any[]): PreStatChangeAbilityAttr },
pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]) {
const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as PreStatChangeAbilityAttr[];
for (let attr of attrs) {
if (attr.applyPreStatChange(pokemon, stat, cancelled, args))
console.log('Applied', ability.name, attr);
}
} }
export enum Abilities { export enum Abilities {
@ -193,7 +307,8 @@ export const abilities = [
new Ability(Abilities.BATTLE_ARMOR, "Battle Armor (N)", "The POKéMON is protected against critical hits.", 3), new Ability(Abilities.BATTLE_ARMOR, "Battle Armor (N)", "The POKéMON is protected against critical hits.", 3),
new Ability(Abilities.BLAZE, "Blaze (N)", "Powers up FIRE-type moves in a pinch.", 3), new Ability(Abilities.BLAZE, "Blaze (N)", "Powers up FIRE-type moves in a pinch.", 3),
new Ability(Abilities.CHLOROPHYLL, "Chlorophyll (N)", "Boosts the POKéMON's SPEED in sunshine.", 3), new Ability(Abilities.CHLOROPHYLL, "Chlorophyll (N)", "Boosts the POKéMON's SPEED in sunshine.", 3),
new Ability(Abilities.CLEAR_BODY, "Clear Body (N)", "Prevents other POKéMON from lowering its stats.", 3), new Ability(Abilities.CLEAR_BODY, "Clear Body", "Prevents other POKéMON from lowering its stats.", 3)
.attr(ProtectStatAttr),
new Ability(Abilities.CLOUD_NINE, "Cloud Nine (N)", "Eliminates the effects of weather.", 3), new Ability(Abilities.CLOUD_NINE, "Cloud Nine (N)", "Eliminates the effects of weather.", 3),
new Ability(Abilities.COLOR_CHANGE, "Color Change (N)", "Changes the POKéMON's type to the foe's move.", 3), new Ability(Abilities.COLOR_CHANGE, "Color Change (N)", "Changes the POKéMON's type to the foe's move.", 3),
new Ability(Abilities.COMPOUND_EYES, "Compound Eyes (N)", "The POKéMON's accuracy is boosted.", 3), new Ability(Abilities.COMPOUND_EYES, "Compound Eyes (N)", "The POKéMON's accuracy is boosted.", 3),
@ -209,15 +324,19 @@ export const abilities = [
new Ability(Abilities.GUTS, "Guts (N)", "Boosts ATTACK if there is a status problem.", 3), new Ability(Abilities.GUTS, "Guts (N)", "Boosts ATTACK if there is a status problem.", 3),
new Ability(Abilities.HUGE_POWER, "Huge Power (N)", "Raises the POKéMON's ATTACK stat.", 3), new Ability(Abilities.HUGE_POWER, "Huge Power (N)", "Raises the POKéMON's ATTACK stat.", 3),
new Ability(Abilities.HUSTLE, "Hustle (N)", "Boosts the ATTACK stat, but lowers accuracy.", 3), new Ability(Abilities.HUSTLE, "Hustle (N)", "Boosts the ATTACK stat, but lowers accuracy.", 3),
new Ability(Abilities.HYPER_CUTTER, "Hyper Cutter (N)", "Prevents other POKéMON from lowering ATTACK stat.", 3), new Ability(Abilities.HYPER_CUTTER, "Hyper Cutter", "Prevents other POKéMON from lowering ATTACK stat.", 3)
.attr(ProtectStatAttr, BattleStat.ATK),
new Ability(Abilities.ILLUMINATE, "Illuminate (N)", "Raises the likelihood of meeting wild POKéMON.", 3), new Ability(Abilities.ILLUMINATE, "Illuminate (N)", "Raises the likelihood of meeting wild POKéMON.", 3),
new Ability(Abilities.IMMUNITY, "Immunity (N)", "Prevents the POKéMON from getting poisoned.", 3), new Ability(Abilities.IMMUNITY, "Immunity (N)", "Prevents the POKéMON from getting poisoned.", 3),
new Ability(Abilities.INNER_FOCUS, "Inner Focus (N)", "The POKéMON is protected from flinching.", 3), new Ability(Abilities.INNER_FOCUS, "Inner Focus (N)", "The POKéMON is protected from flinching.", 3),
new Ability(Abilities.INSOMNIA, "Insomnia (N)", "Prevents the POKéMON from falling asleep.", 3), new Ability(Abilities.INSOMNIA, "Insomnia (N)", "Prevents the POKéMON from falling asleep.", 3),
new Ability(Abilities.INTIMIDATE, "Intimidate (N)", "Lowers the foe's ATTACK stat.", 3), new Ability(Abilities.INTIMIDATE, "Intimidate (N)", "Lowers the foe's ATTACK stat.", 3),
new Ability(Abilities.KEEN_EYE, "Keen Eye (N)", "Prevents other POKéMON from lowering accuracy.", 3), new Ability(Abilities.KEEN_EYE, "Keen Eye", "Prevents other POKéMON from lowering accuracy.", 3)
new Ability(Abilities.LEVITATE, "Levitate (N)", "Gives immunity to GROUND-type moves.", 3), .attr(ProtectStatAttr, BattleStat.ACC),
new Ability(Abilities.LIGHTNING_ROD, "Lightning Rod (N)", "Draws in all ELECTRIC-type moves to up SP. ATK.", 3), new Ability(Abilities.LEVITATE, "Levitate", "Gives immunity to GROUND-type moves.", 3)
.attr(TypeImmunityAttr, Type.FLYING),
new Ability(Abilities.LIGHTNING_ROD, "Lightning Rod", "Draws in all ELECTRIC-type moves to up SP. ATK.", 3)
.attr(TypeImmunityStatChangeAttr, Type.ELECTRIC, BattleStat.SPATK, 1),
new Ability(Abilities.LIMBER, "Limber (N)", "The POKéMON is protected from paralysis.", 3), new Ability(Abilities.LIMBER, "Limber (N)", "The POKéMON is protected from paralysis.", 3),
new Ability(Abilities.LIQUID_OOZE, "Liquid Ooze (N)", "Damages attackers using any draining move.", 3), new Ability(Abilities.LIQUID_OOZE, "Liquid Ooze (N)", "Damages attackers using any draining move.", 3),
new Ability(Abilities.MAGMA_ARMOR, "Magma Armor (N)", "Prevents the POKéMON from becoming frozen.", 3), new Ability(Abilities.MAGMA_ARMOR, "Magma Armor (N)", "Prevents the POKéMON from becoming frozen.", 3),
@ -262,7 +381,8 @@ export const abilities = [
new Ability(Abilities.VOLT_ABSORB, "Volt Absorb (N)", "Restores HP if hit by an ELECTRIC-type move.", 3), new Ability(Abilities.VOLT_ABSORB, "Volt Absorb (N)", "Restores HP if hit by an ELECTRIC-type move.", 3),
new Ability(Abilities.WATER_ABSORB, "Water Absorb (N)", "Restores HP if hit by a WATER-type move.", 3), new Ability(Abilities.WATER_ABSORB, "Water Absorb (N)", "Restores HP if hit by a WATER-type move.", 3),
new Ability(Abilities.WATER_VEIL, "Water Veil (N)", "Prevents the POKéMON from getting a burn.", 3), new Ability(Abilities.WATER_VEIL, "Water Veil (N)", "Prevents the POKéMON from getting a burn.", 3),
new Ability(Abilities.WHITE_SMOKE, "White Smoke (N)", "Prevents other POKéMON from lowering its stats.", 3), new Ability(Abilities.WHITE_SMOKE, "White Smoke", "Prevents other POKéMON from lowering its stats.", 3)
.attr(ProtectStatAttr),
new Ability(Abilities.WONDER_GUARD, "Wonder Guard (N)", "Only supereffective moves will hit.", 3), new Ability(Abilities.WONDER_GUARD, "Wonder Guard (N)", "Only supereffective moves will hit.", 3),
new Ability(Abilities.ADAPTABILITY, "Adaptability (N)", "Powers up moves of the same type.", 4), new Ability(Abilities.ADAPTABILITY, "Adaptability (N)", "Powers up moves of the same type.", 4),
new Ability(Abilities.AFTERMATH, "Aftermath (N)", "Damages the attacker landing the finishing hit.", 4), new Ability(Abilities.AFTERMATH, "Aftermath (N)", "Damages the attacker landing the finishing hit.", 4),
@ -304,7 +424,8 @@ export const abilities = [
new Ability(Abilities.SOLID_ROCK, "Solid Rock (N)", "Reduces damage from super-effective attacks.", 4), new Ability(Abilities.SOLID_ROCK, "Solid Rock (N)", "Reduces damage from super-effective attacks.", 4),
new Ability(Abilities.STALL, "Stall (N)", "The POKéMON moves after all other POKéMON do.", 4), new Ability(Abilities.STALL, "Stall (N)", "The POKéMON moves after all other POKéMON do.", 4),
new Ability(Abilities.STEADFAST, "Steadfast (N)", "Raises SPEED each time the POKéMON flinches.", 4), new Ability(Abilities.STEADFAST, "Steadfast (N)", "Raises SPEED each time the POKéMON flinches.", 4),
new Ability(Abilities.STORM_DRAIN, "Storm Drain (N)", "Draws in all WATER-type moves to up SP. ATK.", 4), new Ability(Abilities.STORM_DRAIN, "Storm Drain", "Draws in all WATER-type moves to up SP. ATK.", 4)
.attr(TypeImmunityStatChangeAttr, Type.WATER, BattleStat.SPATK, 1),
new Ability(Abilities.SUPER_LUCK, "Super Luck (N)", "Heightens the critical-hit ratios of moves.", 4), new Ability(Abilities.SUPER_LUCK, "Super Luck (N)", "Heightens the critical-hit ratios of moves.", 4),
new Ability(Abilities.TANGLED_FEET, "Tangled Feet (N)", "Raises evasion if the POKéMON is confused.", 4), new Ability(Abilities.TANGLED_FEET, "Tangled Feet (N)", "Raises evasion if the POKéMON is confused.", 4),
new Ability(Abilities.TECHNICIAN, "Technician (N)", "Powers up the POKéMON's weaker moves.", 4), new Ability(Abilities.TECHNICIAN, "Technician (N)", "Powers up the POKéMON's weaker moves.", 4),
@ -312,7 +433,8 @@ export const abilities = [
new Ability(Abilities.UNAWARE, "Unaware (N)", "Ignores any stat changes in the POKéMON.", 4), new Ability(Abilities.UNAWARE, "Unaware (N)", "Ignores any stat changes in the POKéMON.", 4),
new Ability(Abilities.UNBURDEN, "Unburden (N)", "Raises SPEED if a held item is used.", 4), new Ability(Abilities.UNBURDEN, "Unburden (N)", "Raises SPEED if a held item is used.", 4),
new Ability(Abilities.ANALYTIC, "Analytic (N)", "Boosts move power when the POKéMON moves last.", 5), new Ability(Abilities.ANALYTIC, "Analytic (N)", "Boosts move power when the POKéMON moves last.", 5),
new Ability(Abilities.BIG_PECKS, "Big Pecks (N)", "Protects the POKéMON from DEFENSE-lowering attacks.", 5), new Ability(Abilities.BIG_PECKS, "Big Pecks", "Protects the POKéMON from DEFENSE-lowering attacks.", 5)
.attr(ProtectStatAttr, BattleStat.DEF),
new Ability(Abilities.CONTRARY, "Contrary (N)", "Makes stat changes have an opposite effect.", 5), new Ability(Abilities.CONTRARY, "Contrary (N)", "Makes stat changes have an opposite effect.", 5),
new Ability(Abilities.CURSED_BODY, "Cursed Body (N)", "May disable a move used on the POKéMON.", 5), new Ability(Abilities.CURSED_BODY, "Cursed Body (N)", "May disable a move used on the POKéMON.", 5),
new Ability(Abilities.DEFEATIST, "Defeatist (N)", "Lowers stats when HP drops below half.", 5), new Ability(Abilities.DEFEATIST, "Defeatist (N)", "Lowers stats when HP drops below half.", 5),

View File

@ -101,13 +101,13 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.APICOT: case BerryType.APICOT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const battleStat = (berryType - BerryType.LIECHI) as BattleStat; const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), [ battleStat ], 1)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ battleStat ], 1));
}; };
case BerryType.LANSAT: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
pokemon.addTag(BattlerTagType.CRIT_BOOST); pokemon.addTag(BattlerTagType.CRIT_BOOST);
}; };
case BerryType.STARF: case BerryType.STARF:
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), [ BattleStat.RAND ], 2)); return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ BattleStat.RAND ], 2));
} }
} }

View File

@ -9,7 +9,6 @@ import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { ArenaTagType, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
import { FlinchChanceModifier } from "../modifier/modifier";
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -1095,7 +1094,7 @@ export class StatChangeAttr extends MoveEffectAttr {
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) { if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) {
const levels = this.getLevels(user); const levels = this.getLevels(user);
user.scene.unshiftPhase(new StatChangePhase(user.scene, user.isPlayer() === this.selfTarget, this.stats, levels)); user.scene.unshiftPhase(new StatChangePhase(user.scene, user.isPlayer() === this.selfTarget, this.selfTarget, this.stats, levels));
return true; return true;
} }

View File

@ -23,6 +23,7 @@ import { WeatherType } from './data/weather';
import { TempBattleStat } from './data/temp-battle-stat'; import { TempBattleStat } from './data/temp-battle-stat';
import { WeakenMoveTypeTag } from './data/arena-tag'; import { WeakenMoveTypeTag } from './data/arena-tag';
import { Biome } from './data/biome'; import { Biome } from './data/biome';
import { Abilities, Ability, TypeImmunityAttr, abilities, applyPreDefendAbilityAttrs } from './data/ability';
export default abstract class Pokemon extends Phaser.GameObjects.Container { export default abstract class Pokemon extends Phaser.GameObjects.Container {
public id: integer; public id: integer;
@ -345,6 +346,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.getTypes().indexOf(type) > -1; return this.getTypes().indexOf(type) > -1;
} }
getAbility(): Ability {
return abilities[this.species.getAbility(this.abilityIndex)];
}
getAttackMoveEffectiveness(moveType: Type): TypeDamageMultiplier { getAttackMoveEffectiveness(moveType: Type): TypeDamageMultiplier {
const types = this.getTypes(); const types = this.getTypes();
return getTypeDamageMultiplier(moveType, types[0]) * (types.length ? getTypeDamageMultiplier(moveType, types[1]) : 1) as TypeDamageMultiplier; return getTypeDamageMultiplier(moveType, types[0]) * (types.length ? getTypeDamageMultiplier(moveType, types[1]) : 1) as TypeDamageMultiplier;
@ -479,70 +484,77 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
case MoveCategory.PHYSICAL: case MoveCategory.PHYSICAL:
case MoveCategory.SPECIAL: case MoveCategory.SPECIAL:
const isPhysical = moveCategory === MoveCategory.PHYSICAL; const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const cancelled = new Utils.BooleanHolder(false);
const power = new Utils.NumberHolder(move.power); const power = new Utils.NumberHolder(move.power);
const typeMultiplier = getTypeDamageMultiplier(move.type, this.getSpeciesForm().type1) * (this.getSpeciesForm().type2 !== null ? getTypeDamageMultiplier(move.type, this.getSpeciesForm().type2) : 1); const typeMultiplier = new Utils.NumberHolder(getTypeDamageMultiplier(move.type, this.getSpeciesForm().type1) * (this.getSpeciesForm().type2 !== null ? getTypeDamageMultiplier(move.type, this.getSpeciesForm().type2) : 1));
const weatherTypeMultiplier = this.scene.arena.getAttackTypeMultiplier(move.type); const weatherTypeMultiplier = this.scene.arena.getAttackTypeMultiplier(move.type);
applyMoveAttrs(VariablePowerAttr, source, this, move, power); applyPreDefendAbilityAttrs(TypeImmunityAttr, this, source, battlerMove, cancelled, typeMultiplier);
this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power);
this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, power);
const critLevel = new Utils.IntegerHolder(0);
applyMoveAttrs(HighCritAttr, source, this, move, critLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
if (source.getTag(BattlerTagType.CRIT_BOOST))
critLevel.value += 2;
const critChance = Math.ceil(16 / Math.pow(2, critLevel.value));
let isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !Utils.randInt(critChance));
const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK);
const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF);
const stabMultiplier = source.species.type1 === move.type || (source.species.type2 !== null && source.species.type2 === move.type) ? 1.5 : 1;
const criticalMultiplier = isCritical ? 2 : 1;
damage = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier * weatherTypeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier;
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN)
damage = Math.floor(damage / 2);
move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
if (this.getTag(hta.tagType))
damage *= 2;
});
const fixedDamage = new Utils.IntegerHolder(0); if (cancelled.value)
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); result = MoveResult.NO_EFFECT;
if (damage && fixedDamage.value) { else {
damage = fixedDamage.value; applyMoveAttrs(VariablePowerAttr, source, this, move, power);
isCritical = false; this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power);
result = MoveResult.EFFECTIVE; this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, power);
} const critLevel = new Utils.IntegerHolder(0);
applyMoveAttrs(HighCritAttr, source, this, move, critLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
if (source.getTag(BattlerTagType.CRIT_BOOST))
critLevel.value += 2;
const critChance = Math.ceil(16 / Math.pow(2, critLevel.value));
let isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !Utils.randInt(critChance));
const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK);
const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF);
const stabMultiplier = source.species.type1 === move.type || (source.species.type2 !== null && source.species.type2 === move.type) ? 1.5 : 1;
const criticalMultiplier = isCritical ? 2 : 1;
damage = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier.value * weatherTypeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier;
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN)
damage = Math.floor(damage / 2);
move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
if (this.getTag(hta.tagType))
damage *= 2;
});
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef); const fixedDamage = new Utils.IntegerHolder(0);
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage);
if (!result) { if (damage && fixedDamage.value) {
if (typeMultiplier >= 2) damage = fixedDamage.value;
result = MoveResult.SUPER_EFFECTIVE; isCritical = false;
else if (typeMultiplier >= 1)
result = MoveResult.EFFECTIVE; result = MoveResult.EFFECTIVE;
else if (typeMultiplier > 0) }
result = MoveResult.NOT_VERY_EFFECTIVE;
else
result = MoveResult.NO_EFFECT;
}
if (damage) { console.log('damage', damage, move.name, move.power, sourceAtk, targetDef);
this.scene.unshiftPhase(new DamagePhase(this.scene, this.isPlayer(), result as DamageResult));
if (isCritical) if (!result) {
this.scene.queueMessage('A critical hit!'); if (typeMultiplier.value >= 2)
this.damage(damage); result = MoveResult.SUPER_EFFECTIVE;
source.turnData.damageDealt += damage; else if (typeMultiplier.value >= 1)
} result = MoveResult.EFFECTIVE;
else if (typeMultiplier.value > 0)
result = MoveResult.NOT_VERY_EFFECTIVE;
else
result = MoveResult.NO_EFFECT;
}
switch (result) { if (damage) {
case MoveResult.SUPER_EFFECTIVE: this.scene.unshiftPhase(new DamagePhase(this.scene, this.isPlayer(), result as DamageResult));
this.scene.queueMessage('It\'s super effective!'); if (isCritical)
break; this.scene.queueMessage('A critical hit!');
case MoveResult.NOT_VERY_EFFECTIVE: this.damage(damage);
this.scene.queueMessage('It\'s not very effective!'); source.turnData.damageDealt += damage;
break; }
case MoveResult.NO_EFFECT:
this.scene.queueMessage(`It doesn\'t affect ${this.name}!`); switch (result) {
break; case MoveResult.SUPER_EFFECTIVE:
this.scene.queueMessage('It\'s super effective!');
break;
case MoveResult.NOT_VERY_EFFECTIVE:
this.scene.queueMessage('It\'s not very effective!');
break;
case MoveResult.NO_EFFECT:
this.scene.queueMessage(`It doesn\'t affect ${this.name}!`);
break;
}
} }
break; break;
case MoveCategory.STATUS: case MoveCategory.STATUS:

View File

@ -94,6 +94,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonAbilityLabelText = addTextObject(this.scene, 6, 126, 'ABILITY:', TextStyle.SUMMARY, { fontSize: '64px' }); this.pokemonAbilityLabelText = addTextObject(this.scene, 6, 126, 'ABILITY:', TextStyle.SUMMARY, { fontSize: '64px' });
this.pokemonAbilityLabelText.setOrigin(0, 0); this.pokemonAbilityLabelText.setOrigin(0, 0);
this.pokemonAbilityLabelText.setVisible(false);
this.starterSelectContainer.add(this.pokemonAbilityLabelText); this.starterSelectContainer.add(this.pokemonAbilityLabelText);
this.pokemonAbilityText = addTextObject(this.scene, 38, 126, '', TextStyle.SUMMARY, { fontSize: '64px' }); this.pokemonAbilityText = addTextObject(this.scene, 38, 126, '', TextStyle.SUMMARY, { fontSize: '64px' });
@ -454,11 +455,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (species && this.speciesStarterDexEntry) { if (species && this.speciesStarterDexEntry) {
this.pokemonNumberText.setText(Utils.padInt(species.speciesId, 3)); this.pokemonNumberText.setText(Utils.padInt(species.speciesId, 3));
this.pokemonNameText.setText(species.name.toUpperCase()); this.pokemonNameText.setText(species.name.toUpperCase());
this.pokemonAbilityLabelText.setVisible(true);
this.setSpeciesDetails(species, !!this.speciesStarterDexEntry?.shiny, this.speciesStarterDexEntry?.formIndex, !!this.speciesStarterDexEntry?.female, this.speciesStarterDexEntry?.abilityIndex); this.setSpeciesDetails(species, !!this.speciesStarterDexEntry?.shiny, this.speciesStarterDexEntry?.formIndex, !!this.speciesStarterDexEntry?.female, this.speciesStarterDexEntry?.abilityIndex);
} else { } else {
this.pokemonNumberText.setText(Utils.padInt(0, 3)); this.pokemonNumberText.setText(Utils.padInt(0, 3));
this.pokemonNameText.setText(species ? '???' : ''); this.pokemonNameText.setText(species ? '???' : '');
this.pokemonAbilityLabelText.setVisible(false);
this.setSpeciesDetails(species, false, 0, false, 0); this.setSpeciesDetails(species, false, 0, false, 0);
} }
@ -563,7 +566,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.canCycleAbility = false; this.canCycleAbility = false;
} }
if (species.malePercent !== null) { if (defaultDexEntry && species.malePercent !== null) {
const gender = !female ? Gender.MALE : Gender.FEMALE; const gender = !female ? Gender.MALE : Gender.FEMALE;
this.pokemonGenderText.setText(getGenderSymbol(gender)); this.pokemonGenderText.setText(getGenderSymbol(gender));
this.pokemonGenderText.setColor(getGenderColor(gender)); this.pokemonGenderText.setColor(getGenderColor(gender));
@ -571,12 +574,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} else } else
this.pokemonGenderText.setText(''); this.pokemonGenderText.setText('');
const ability = this.lastSpecies.getAbility(abilityIndex); if (defaultDexEntry) {
this.pokemonAbilityText.setText(abilities[ability].name.toUpperCase()); const ability = this.lastSpecies.getAbility(abilityIndex);
this.pokemonAbilityText.setText(abilities[ability].name.toUpperCase());
const isHidden = ability === this.lastSpecies.abilityHidden; const isHidden = ability === this.lastSpecies.abilityHidden;
this.pokemonAbilityText.setColor(getTextColor(!isHidden ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD)); this.pokemonAbilityText.setColor(getTextColor(!isHidden ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD));
this.pokemonAbilityText.setShadowColor(getTextColor(!isHidden ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD, true)); this.pokemonAbilityText.setShadowColor(getTextColor(!isHidden ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD, true));
} else
this.pokemonAbilityText.setText('');
} else { } else {
this.pokemonGenderText.setText(''); this.pokemonGenderText.setText('');
this.pokemonAbilityText.setText(''); this.pokemonAbilityText.setText('');