Begin implementing abilities
parent
f790a5ff2a
commit
c614295b5e
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
126
src/pokemon.ts
126
src/pokemon.ts
|
@ -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:
|
||||||
|
|
|
@ -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('');
|
||||||
|
|
Loading…
Reference in New Issue