Compare commits
6 Commits
8f042d9131
...
67d0ae7e2a
Author | SHA1 | Date |
---|---|---|
Opaque02 | 67d0ae7e2a | |
MokaStitcher | 51bb80cb66 | |
innerthunder | 605ae9e1c3 | |
Madmadness65 | 81ea1296b3 | |
peng06 | 0567d4b5e2 | |
flx-sta | 6030b780f2 |
|
@ -56,7 +56,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
|
|||
- Pokémon Legends: Arceus
|
||||
- Pokémon Scarlet/Violet
|
||||
- Firel (Custom Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music)
|
||||
- Lmz (Custom Jungle biome music)
|
||||
- Lmz (Custom Ancient Ruins, Jungle, and Lake biome music)
|
||||
- Andr06 (Custom Slum and Sea biome music)
|
||||
|
||||
### 🎵 Sound Effects
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
import BattleScene from "../battle-scene";
|
||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species";
|
||||
import { VariantTier } from "../enums/variant-tiers";
|
||||
import { VariantTier } from "../enums/variant-tier";
|
||||
import * as Utils from "../utils";
|
||||
import Overrides from "#app/overrides";
|
||||
import { pokemonPrevolutions } from "./pokemon-evolutions";
|
||||
|
@ -178,7 +178,7 @@ export class Egg {
|
|||
// be done because species with no variants get filtered at rollSpecies but if the
|
||||
// species is set via options or the legendary gacha pokemon gets choosen the check never happens
|
||||
if (this._species && !getPokemonSpecies(this._species).hasVariants()) {
|
||||
this._variantTier = VariantTier.COMMON;
|
||||
this._variantTier = VariantTier.STANDARD;
|
||||
}
|
||||
// Needs this._tier so it needs to be generated afer the tier override if bought from same species
|
||||
this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
|
||||
|
@ -494,12 +494,12 @@ export class Egg {
|
|||
// place but I don't want to touch the pokemon class.
|
||||
private rollVariant(): VariantTier {
|
||||
if (!this.isShiny) {
|
||||
return VariantTier.COMMON;
|
||||
return VariantTier.STANDARD;
|
||||
}
|
||||
|
||||
const rand = Utils.randSeedInt(10);
|
||||
if (rand >= 4) {
|
||||
return VariantTier.COMMON; // 6/10
|
||||
return VariantTier.STANDARD; // 6/10
|
||||
} else if (rand >= 1) {
|
||||
return VariantTier.RARE; // 3/10
|
||||
} else {
|
||||
|
|
|
@ -3839,7 +3839,7 @@ export class StormAccuracyAttr extends VariableAccuracyAttr {
|
|||
* @extends VariableAccuracyAttr
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class MinimizeAccuracyAttr extends VariableAccuracyAttr {
|
||||
export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr {
|
||||
/**
|
||||
* @see {@linkcode apply}
|
||||
* @param user N/A
|
||||
|
@ -3974,18 +3974,17 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr {
|
|||
export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const category = (args[0] as Utils.NumberHolder);
|
||||
const atkRatio = user.getEffectiveStat(Stat.ATK, target, move) / target.getEffectiveStat(Stat.DEF, user, move);
|
||||
const specialRatio = user.getEffectiveStat(Stat.SPATK, target, move) / target.getEffectiveStat(Stat.SPDEF, user, move);
|
||||
|
||||
// Shell Side Arm is much more complicated than it looks, this is a partial implementation to try to achieve something similar to the games
|
||||
if (atkRatio > specialRatio) {
|
||||
const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true);
|
||||
const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true);
|
||||
|
||||
if (predictedPhysDmg > predictedSpecDmg) {
|
||||
category.value = MoveCategory.PHYSICAL;
|
||||
return true;
|
||||
} else if (atkRatio === specialRatio && user.randSeedInt(2) === 0) {
|
||||
} else if (predictedPhysDmg === predictedSpecDmg && user.randSeedInt(2) === 0) {
|
||||
category.value = MoveCategory.PHYSICAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -4855,10 +4854,10 @@ export class RemoveAllSubstitutesAttr extends MoveEffectAttr {
|
|||
* Attribute used when a move hits a {@linkcode BattlerTagType} for double damage
|
||||
* @extends MoveAttr
|
||||
*/
|
||||
export class HitsTagAttr extends MoveAttr {
|
||||
export class DealsDoubleDamageToTagAttr extends MoveAttr {
|
||||
/** The {@linkcode BattlerTagType} this move hits */
|
||||
public tagType: BattlerTagType;
|
||||
/** Should this move deal double damage against {@linkcode HitsTagAttr.tagType}? */
|
||||
/** Should this move deal double damage against {@linkcode DealsDoubleDamageToTagAttr.tagType}? */
|
||||
public doubleDamage: boolean;
|
||||
|
||||
constructor(tagType: BattlerTagType, doubleDamage?: boolean) {
|
||||
|
@ -6752,12 +6751,11 @@ export function initMoves() {
|
|||
new AttackMove(Moves.CUT, Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1)
|
||||
.slicingMove(),
|
||||
new AttackMove(Moves.GUST, Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, 0, 1)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING, true)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, true)
|
||||
.windMove(),
|
||||
new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1),
|
||||
new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1)
|
||||
.attr(ForceSwitchOutAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
||||
.ignoresSubstitute()
|
||||
.hidesTarget()
|
||||
.windMove(),
|
||||
|
@ -6770,8 +6768,8 @@ export function initMoves() {
|
|||
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1),
|
||||
new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1),
|
||||
new AttackMove(Moves.STOMP, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 1)
|
||||
.attr(MinimizeAccuracyAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(AlwaysHitMinimizeAttr)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(FlinchAttr),
|
||||
new AttackMove(Moves.DOUBLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1)
|
||||
.attr(MultiHitAttr, MultiHitType._2),
|
||||
|
@ -6795,8 +6793,8 @@ export function initMoves() {
|
|||
.attr(OneHitKOAccuracyAttr),
|
||||
new AttackMove(Moves.TACKLE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
|
||||
new AttackMove(Moves.BODY_SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 30, 0, 1)
|
||||
.attr(MinimizeAccuracyAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(AlwaysHitMinimizeAttr)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1)
|
||||
.attr(TrapAttr, BattlerTagType.WRAP),
|
||||
|
@ -6864,7 +6862,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1),
|
||||
new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERWATER, true)
|
||||
.attr(GulpMissileTagAttr),
|
||||
new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
|
||||
.attr(StatusEffectAttr, StatusEffect.FREEZE),
|
||||
|
@ -6947,18 +6945,18 @@ export function initMoves() {
|
|||
new AttackMove(Moves.THUNDER, Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 1)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
||||
.attr(ThunderAccuracyAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false),
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false),
|
||||
new AttackMove(Moves.ROCK_THROW, Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.EARTHQUAKE, Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1)
|
||||
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, true)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
|
||||
.makesContact(false)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.FISSURE, Type.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
|
||||
.attr(OneHitKOAttr)
|
||||
.attr(OneHitKOAccuracyAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, false)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, false)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1)
|
||||
.attr(ChargeAttr, ChargeAnim.DIG_CHARGING, i18next.t("moveTriggers:dugAHole", {pokemonName: "{USER}"}), BattlerTagType.UNDERGROUND)
|
||||
|
@ -7347,7 +7345,7 @@ export function initMoves() {
|
|||
.attr(PreMoveMessageAttr, magnitudeMessageFunc)
|
||||
.attr(MagnitudePowerAttr)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
|
||||
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, true)
|
||||
.makesContact(false)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.DYNAMIC_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, 100, 0, 2)
|
||||
|
@ -7403,7 +7401,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.CROSS_CHOP, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2)
|
||||
.attr(HighCritAttr),
|
||||
new AttackMove(Moves.TWISTER, Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, 20, 0, 2)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING, true)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, true)
|
||||
.attr(FlinchAttr)
|
||||
.windMove()
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
|
@ -7435,7 +7433,7 @@ export function initMoves() {
|
|||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1),
|
||||
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2)
|
||||
.attr(TrapAttr, BattlerTagType.WHIRLPOOL)
|
||||
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERWATER, true),
|
||||
new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2)
|
||||
.attr(MultiHitAttr, MultiHitType.BEAT_UP)
|
||||
.attr(BeatUpAttr)
|
||||
|
@ -7658,7 +7656,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.EXTRASENSORY, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, 10, 0, 3)
|
||||
.attr(FlinchAttr),
|
||||
new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING)
|
||||
.punchingMove(),
|
||||
new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3)
|
||||
.attr(TrapAttr, BattlerTagType.SAND_TOMB)
|
||||
|
@ -7889,8 +7887,8 @@ export function initMoves() {
|
|||
new AttackMove(Moves.DRAGON_PULSE, Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4)
|
||||
.pulseMove(),
|
||||
new AttackMove(Moves.DRAGON_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4)
|
||||
.attr(MinimizeAccuracyAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(AlwaysHitMinimizeAttr)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(FlinchAttr),
|
||||
new AttackMove(Moves.POWER_GEM, Type.ROCK, MoveCategory.SPECIAL, 80, 100, 20, -1, 0, 4),
|
||||
new AttackMove(Moves.DRAIN_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 4)
|
||||
|
@ -8087,7 +8085,7 @@ export function initMoves() {
|
|||
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
||||
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5)
|
||||
.attr(CritOnlyAttr),
|
||||
|
@ -8100,9 +8098,9 @@ export function initMoves() {
|
|||
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
|
||||
.danceMove(),
|
||||
new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
|
||||
.attr(MinimizeAccuracyAttr)
|
||||
.attr(AlwaysHitMinimizeAttr)
|
||||
.attr(CompareWeightPowerAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true),
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true),
|
||||
new AttackMove(Moves.SYNCHRONOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||
.condition(unknownTypeCondition)
|
||||
|
@ -8253,12 +8251,14 @@ export function initMoves() {
|
|||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1)
|
||||
.slicingMove(),
|
||||
new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
|
||||
.attr(MinimizeAccuracyAttr)
|
||||
.attr(AlwaysHitMinimizeAttr)
|
||||
.attr(CompareWeightPowerAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true),
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true),
|
||||
new AttackMove(Moves.LEAF_TORNADO, Type.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5)
|
||||
.attr(StatStageChangeAttr, [ Stat.ACC ], -1),
|
||||
new AttackMove(Moves.STEAMROLLER, Type.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5)
|
||||
.attr(AlwaysHitMinimizeAttr)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(FlinchAttr),
|
||||
new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 3, true),
|
||||
|
@ -8271,7 +8271,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.HURRICANE, Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 5)
|
||||
.attr(ThunderAccuracyAttr)
|
||||
.attr(ConfuseAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false)
|
||||
.windMove(),
|
||||
new AttackMove(Moves.HEAD_CHARGE, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 5)
|
||||
.attr(RecoilAttr)
|
||||
|
@ -8325,9 +8325,9 @@ export function initMoves() {
|
|||
.attr(LastMoveDoublePowerAttr, Moves.FUSION_FLARE)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.FLYING_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 6)
|
||||
.attr(MinimizeAccuracyAttr)
|
||||
.attr(AlwaysHitMinimizeAttr)
|
||||
.attr(FlyingTypeMultiplierAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.condition(failOnGravityCondition),
|
||||
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
|
@ -8498,8 +8498,8 @@ export function initMoves() {
|
|||
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
|
||||
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
||||
.attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN, false)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MAGNET_RISEN, false)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
||||
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
|
||||
.makesContact(false)
|
||||
|
@ -8756,6 +8756,8 @@ export function initMoves() {
|
|||
.partial()
|
||||
.ignoresVirtual(),
|
||||
new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
|
||||
.attr(AlwaysHitMinimizeAttr)
|
||||
.attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.partial()
|
||||
.ignoresVirtual(),
|
||||
new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
|
||||
|
@ -9103,7 +9105,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.SHELL_SIDE_ARM, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8)
|
||||
.attr(ShellSideArmCategoryAttr)
|
||||
.attr(StatusEffectAttr, StatusEffect.POISON)
|
||||
.partial(),
|
||||
.partial(), // Physical version of the move does not make contact
|
||||
new AttackMove(Moves.MISTY_EXPLOSION, Type.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
|
||||
.attr(SacrificialAttr)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
export enum VariantTier {
|
||||
COMMON,
|
||||
RARE,
|
||||
EPIC
|
||||
}
|
|
@ -762,7 +762,7 @@ export class Arena {
|
|||
case Biome.BEACH:
|
||||
return 3.462;
|
||||
case Biome.LAKE:
|
||||
return 5.350;
|
||||
return 7.215;
|
||||
case Biome.SEABED:
|
||||
return 2.600;
|
||||
case Biome.MOUNTAIN:
|
||||
|
@ -788,7 +788,7 @@ export class Arena {
|
|||
case Biome.FACTORY:
|
||||
return 4.985;
|
||||
case Biome.RUINS:
|
||||
return 2.270;
|
||||
return 0.000;
|
||||
case Biome.WASTELAND:
|
||||
return 6.336;
|
||||
case Biome.ABYSS:
|
||||
|
|
|
@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "../battle-scene";
|
|||
import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
|
||||
import { variantData } from "#app/data/variant";
|
||||
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
|
||||
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move";
|
||||
import Move, { HighCritAttr, DealsDoubleDamageToTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move";
|
||||
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
|
||||
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import * as Utils from "../utils";
|
||||
|
@ -1513,7 +1513,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
|
||||
const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType);
|
||||
for (const tag of immuneTags) {
|
||||
if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) {
|
||||
if (move && !move.getAttrs(DealsDoubleDamageToTagAttr).some(attr => attr.tagType === tag.tagType)) {
|
||||
typeMultiplier.value = 0;
|
||||
break;
|
||||
}
|
||||
|
@ -2322,11 +2322,61 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
return accuracyMultiplier.value / evasionMultiplier.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the base damage of the given move against this Pokemon when attacked by the given source.
|
||||
* Used during damage calculation and for Shell Side Arm's forecasting effect.
|
||||
* @param source the attacking {@linkcode Pokemon}.
|
||||
* @param move the {@linkcode Move} used in the attack.
|
||||
* @param moveCategory the move's {@linkcode MoveCategory} after variable-category effects are applied.
|
||||
* @param ignoreAbility if `true`, ignores this Pokemon's defensive ability effects (defaults to `false`).
|
||||
* @param ignoreSourceAbility if `true`, ignore's the attacking Pokemon's ability effects (defaults to `false`).
|
||||
* @param isCritical if `true`, calculates effective stats as if the hit were critical (defaults to `false`).
|
||||
* @param simulated if `true`, suppresses changes to game state during calculation (defaults to `true`).
|
||||
* @returns The move's base damage against this Pokemon when used by the source Pokemon.
|
||||
*/
|
||||
getBaseDamage(source: Pokemon, move: Move, moveCategory: MoveCategory, ignoreAbility: boolean = false, ignoreSourceAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): number {
|
||||
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
|
||||
|
||||
/** A base damage multiplier based on the source's level */
|
||||
const levelMultiplier = (2 * source.level / 5 + 2);
|
||||
|
||||
/** The power of the move after power boosts from abilities, etc. have applied */
|
||||
const power = move.calculateBattlePower(source, this, simulated);
|
||||
|
||||
/**
|
||||
* The attacker's offensive stat for the given move's category.
|
||||
* Critical hits cause negative stat stages to be ignored.
|
||||
*/
|
||||
const sourceAtk = new Utils.NumberHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, ignoreSourceAbility, ignoreAbility, isCritical, simulated));
|
||||
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk);
|
||||
|
||||
/**
|
||||
* This Pokemon's defensive stat for the given move's category.
|
||||
* Critical hits cause positive stat stages to be ignored.
|
||||
*/
|
||||
const targetDef = new Utils.NumberHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, ignoreAbility, ignoreSourceAbility, isCritical, simulated));
|
||||
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef);
|
||||
|
||||
/**
|
||||
* The attack's base damage, as determined by the source's level, move power
|
||||
* and Attack stat as well as this Pokemon's Defense stat
|
||||
*/
|
||||
const baseDamage = ((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2;
|
||||
|
||||
/** Debug message for non-simulated calls (i.e. when damage is actually dealt) */
|
||||
if (!simulated) {
|
||||
console.log("base damage", baseDamage, move.name, power, sourceAtk.value, targetDef.value);
|
||||
}
|
||||
|
||||
return baseDamage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the damage of an attack made by another Pokemon against this Pokemon
|
||||
* @param source {@linkcode Pokemon} the attacking Pokemon
|
||||
* @param move {@linkcode Pokemon} the move used in the attack
|
||||
* @param ignoreAbility If `true`, ignores this Pokemon's defensive ability effects
|
||||
* @param ignoreSourceAbility If `true`, ignores the attacking Pokemon's ability effects
|
||||
* @param isCritical If `true`, calculates damage for a critical hit.
|
||||
* @param simulated If `true`, suppresses changes to game state during the calculation.
|
||||
* @returns a {@linkcode DamageCalculationResult} object with three fields:
|
||||
|
@ -2395,35 +2445,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
};
|
||||
}
|
||||
|
||||
// ----- BEGIN BASE DAMAGE MULTIPLIERS -----
|
||||
|
||||
/** A base damage multiplier based on the source's level */
|
||||
const levelMultiplier = (2 * source.level / 5 + 2);
|
||||
|
||||
/** The power of the move after power boosts from abilities, etc. have applied */
|
||||
const power = move.calculateBattlePower(source, this, simulated);
|
||||
|
||||
/**
|
||||
* The attacker's offensive stat for the given move's category.
|
||||
* Critical hits ignore negative stat stages.
|
||||
*/
|
||||
const sourceAtk = new Utils.NumberHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, ignoreSourceAbility, ignoreAbility, isCritical, simulated));
|
||||
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk);
|
||||
|
||||
/**
|
||||
* This Pokemon's defensive stat for the given move's category.
|
||||
* Critical hits ignore positive stat stages.
|
||||
*/
|
||||
const targetDef = new Utils.NumberHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, ignoreAbility, ignoreSourceAbility, isCritical, simulated));
|
||||
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef);
|
||||
|
||||
/**
|
||||
* The attack's base damage, as determined by the source's level, move power
|
||||
* and Attack stat as well as this Pokemon's Defense stat
|
||||
*/
|
||||
const baseDamage = ((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2;
|
||||
|
||||
// ------ END BASE DAMAGE MULTIPLIERS ------
|
||||
const baseDamage = this.getBaseDamage(source, move, moveCategory, ignoreAbility, ignoreSourceAbility, isCritical, simulated);
|
||||
|
||||
/** 25% damage debuff on moves hitting more than one non-fainted target (regardless of immunities) */
|
||||
const { targets, multiple } = getMoveTargets(source, move.id);
|
||||
|
@ -2489,13 +2515,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier);
|
||||
|
||||
/**
|
||||
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
|
||||
* For each {@linkcode DealsDoubleDamageToTagAttr} the move has, doubles the damage of the move if:
|
||||
* The target has a {@linkcode BattlerTagType} that this move interacts with
|
||||
* AND
|
||||
* The move doubles damage when used against that tag
|
||||
*/
|
||||
const hitsTagMultiplier = new Utils.NumberHolder(1);
|
||||
move.getAttrs(HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
|
||||
move.getAttrs(DealsDoubleDamageToTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
|
||||
if (this.getTag(hta.tagType)) {
|
||||
hitsTagMultiplier.value *= 2;
|
||||
}
|
||||
|
@ -2549,7 +2575,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
|
||||
// debug message for when damage is applied (i.e. not simulated)
|
||||
if (!simulated) {
|
||||
console.log("damage", damage.value, move.name, power, sourceAtk, targetDef);
|
||||
console.log("damage", damage.value, move.name);
|
||||
}
|
||||
|
||||
let hitResult: HitResult;
|
||||
|
|
|
@ -112,13 +112,13 @@
|
|||
"island": "PMD EoS Craggy Coast",
|
||||
"jungle": "Lmz - Jungle",
|
||||
"laboratory": "Firel - Laboratory",
|
||||
"lake": "PMD EoS Crystal Cave",
|
||||
"lake": "Lmz - Lake",
|
||||
"meadow": "PMD EoS Sky Peak Forest",
|
||||
"metropolis": "Firel - Metropolis",
|
||||
"mountain": "PMD EoS Mt. Horn",
|
||||
"plains": "Firel - Route 888",
|
||||
"power_plant": "Firel - The Klink",
|
||||
"ruins": "PMD EoS Deep Sealed Ruin",
|
||||
"ruins": "Lmz - Ancient Ruins",
|
||||
"sea": "Andr06 - Marine Mystique",
|
||||
"seabed": "Firel - Seabed",
|
||||
"slum": "Andr06 - Sneaky Snom",
|
||||
|
|
|
@ -6,7 +6,7 @@ import { PokeballType } from "#enums/pokeball";
|
|||
import { Species } from "#enums/species";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { VariantTier } from "#enums/variant-tiers";
|
||||
import { VariantTier } from "#enums/variant-tier";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { type PokeballCounts } from "./battle-scene";
|
||||
import { Gender } from "./data/gender";
|
||||
|
|
|
@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr,
|
|||
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
|
||||
import { MoveAnim } from "#app/data/battle-anims";
|
||||
import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags";
|
||||
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move";
|
||||
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, DealsDoubleDamageToTagAttr } from "#app/data/move";
|
||||
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
|
@ -394,7 +394,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||
}
|
||||
|
||||
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
||||
if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) {
|
||||
if (semiInvulnerableTag && !this.move.getMove().getAttrs(DealsDoubleDamageToTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { EggTier } from "#enums/egg-type";
|
||||
import { Species } from "#enums/species";
|
||||
import { VariantTier } from "#enums/variant-tiers";
|
||||
import { VariantTier } from "#enums/variant-tier";
|
||||
import { EGG_SEED, Egg } from "../data/egg";
|
||||
import { EggSourceType } from "#app/enums/egg-source-types";
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Egg, getLegendaryGachaSpeciesForTimestamp } from "#app/data/egg";
|
||||
import { EggSourceType } from "#app/enums/egg-source-types";
|
||||
import { EggTier } from "#app/enums/egg-type";
|
||||
import { VariantTier } from "#app/enums/variant-tiers";
|
||||
import { VariantTier } from "#app/enums/variant-tier";
|
||||
import EggData from "#app/system/egg-data";
|
||||
import * as Utils from "#app/utils";
|
||||
import { Species } from "#enums/species";
|
||||
|
@ -136,9 +136,9 @@ describe("Egg Generation Tests", () => {
|
|||
|
||||
expect(result).toBe(expectedResult);
|
||||
});
|
||||
it("should return a shiny common variant", () => {
|
||||
it("should return a shiny standard variant", () => {
|
||||
const scene = game.scene;
|
||||
const expectedVariantTier = VariantTier.COMMON;
|
||||
const expectedVariantTier = VariantTier.STANDARD;
|
||||
|
||||
const result = new Egg({ scene, isShiny: true, variantTier: expectedVariantTier, species: Species.BULBASAUR }).generatePlayerPokemon(scene).variant;
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import { BattlerIndex } from "#app/battle";
|
||||
import { allMoves, ShellSideArmCategoryAttr } from "#app/data/move";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||
|
||||
describe("Moves - Shell Side Arm", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.SHELL_SIDE_ARM])
|
||||
.battleType("single")
|
||||
.startingLevel(100)
|
||||
.enemyLevel(100)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("becomes a physical attack if forecasted to deal more damage as physical", async () => {
|
||||
game.override.enemySpecies(Species.SNORLAX);
|
||||
|
||||
await game.classicMode.startBattle([Species.MANAPHY]);
|
||||
|
||||
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
|
||||
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
|
||||
vi.spyOn(shellSideArmAttr, "apply");
|
||||
|
||||
game.move.select(Moves.SHELL_SIDE_ARM);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(true);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("remains a special attack if forecasted to deal more damage as special", async () => {
|
||||
game.override.enemySpecies(Species.SLOWBRO);
|
||||
|
||||
await game.classicMode.startBattle([Species.MANAPHY]);
|
||||
|
||||
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
|
||||
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
|
||||
vi.spyOn(shellSideArmAttr, "apply");
|
||||
|
||||
game.move.select(Moves.SHELL_SIDE_ARM);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("respects stat stage changes when forecasting base damage", async () => {
|
||||
game.override
|
||||
.enemySpecies(Species.SNORLAX)
|
||||
.enemyMoveset(Moves.COTTON_GUARD);
|
||||
|
||||
await game.classicMode.startBattle([Species.MANAPHY]);
|
||||
|
||||
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
|
||||
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
|
||||
vi.spyOn(shellSideArmAttr, "apply");
|
||||
|
||||
game.move.select(Moves.SHELL_SIDE_ARM);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false);
|
||||
}, TIMEOUT);
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
import { BattlerIndex } from "#app/battle";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { DamageCalculationResult } from "#app/field/pokemon";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Moves - Steamroller", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.moveset([Moves.STEAMROLLER]).battleType("single").enemyAbility(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
it("should always hit a minimzed target with double damage", async () => {
|
||||
game.override.enemySpecies(Species.DITTO).enemyMoveset(Moves.MINIMIZE);
|
||||
await game.classicMode.startBattle([Species.IRON_BOULDER]);
|
||||
|
||||
const ditto = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(ditto, "getAttackDamage");
|
||||
ditto.hp = 5000;
|
||||
const steamroller = allMoves[Moves.STEAMROLLER];
|
||||
vi.spyOn(steamroller, "calculateBattleAccuracy");
|
||||
const ironBoulder = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(ironBoulder, "getAccuracyMultiplier");
|
||||
// Turn 1
|
||||
game.move.select(Moves.STEAMROLLER);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.toNextTurn();
|
||||
// Turn 2
|
||||
game.move.select(Moves.STEAMROLLER);
|
||||
await game.toNextTurn();
|
||||
|
||||
const [dmgCalcTurn1, dmgCalcTurn2]: DamageCalculationResult[] = vi
|
||||
.mocked(ditto.getAttackDamage)
|
||||
.mock.results.map((r) => r.value);
|
||||
|
||||
expect(dmgCalcTurn2.damage).toBeGreaterThanOrEqual(dmgCalcTurn1.damage * 2);
|
||||
expect(ditto.getTag(BattlerTagType.MINIMIZED)).toBeDefined();
|
||||
expect(steamroller.calculateBattleAccuracy).toHaveReturnedWith(-1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
import { BattlerIndex } from "#app/battle";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||
|
||||
describe("Moves - Whirlwind", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.WHIRLWIND)
|
||||
.enemySpecies(Species.PIDGEY);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ move: Moves.FLY, name: "Fly" },
|
||||
{ move: Moves.BOUNCE, name: "Bounce" },
|
||||
{ move: Moves.SKY_DROP, name: "Sky Drop" },
|
||||
])("should not hit a flying target: $name (=$move)", async ({ move }) => {
|
||||
game.override.moveset([move]);
|
||||
await game.classicMode.startBattle([Species.STARAPTOR]);
|
||||
|
||||
const staraptor = game.scene.getPlayerPokemon()!;
|
||||
const whirlwind = allMoves[Moves.WHIRLWIND];
|
||||
vi.spyOn(whirlwind, "getFailedText");
|
||||
|
||||
game.move.select(move);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(staraptor.findTag((t) => t.tagType === BattlerTagType.FLYING)).toBeDefined();
|
||||
expect(whirlwind.getFailedText).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
|
@ -344,6 +344,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
|
|||
super.clear();
|
||||
this.config = null;
|
||||
this.optionSelectContainer.setVisible(false);
|
||||
this.scrollCursor = 0;
|
||||
this.eraseCursor();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import BattleScene from "../battle-scene";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { Button } from "#enums/buttons";
|
||||
import i18next from "i18next";
|
||||
import { Achv, achvs, getAchievementDescription } from "../system/achv";
|
||||
import { Voucher, getVoucherTypeIcon, getVoucherTypeName, vouchers } from "../system/voucher";
|
||||
import MessageUiHandler from "./message-ui-handler";
|
||||
import { addTextObject, TextStyle } from "./text";
|
||||
import { Mode } from "./ui";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import { Achv, achvs, getAchievementDescription } from "#app/system/achv";
|
||||
import { Voucher, getVoucherTypeIcon, getVoucherTypeName, vouchers } from "#app/system/voucher";
|
||||
import MessageUiHandler from "#app/ui/message-ui-handler";
|
||||
import { addTextObject, TextStyle } from "#app/ui/text";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { addWindow } from "#app/ui/ui-theme";
|
||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
|
||||
enum Page {
|
||||
|
@ -49,6 +50,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
private vouchersTotal: number;
|
||||
private currentTotal: number;
|
||||
|
||||
private scrollBar: ScrollBar;
|
||||
private scrollCursor: number;
|
||||
private cursorObj: Phaser.GameObjects.NineSlice | null;
|
||||
private currentPage: Page;
|
||||
|
@ -91,7 +93,10 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
this.iconsBg = addWindow(this.scene, 0, this.headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - this.headerBg.height - 68);
|
||||
this.iconsBg.setOrigin(0, 0);
|
||||
|
||||
this.iconsContainer = this.scene.add.container(6, this.headerBg.height + 6);
|
||||
const yOffset = 6;
|
||||
this.scrollBar = new ScrollBar(this.scene, this.iconsBg.width - 9, this.iconsBg.y + yOffset, 4, this.iconsBg.height - yOffset * 2, this.ROWS);
|
||||
|
||||
this.iconsContainer = this.scene.add.container(5, this.headerBg.height + 8);
|
||||
|
||||
this.icons = [];
|
||||
|
||||
|
@ -148,6 +153,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
this.mainContainer.add(this.headerText);
|
||||
this.mainContainer.add(this.headerActionText);
|
||||
this.mainContainer.add(this.iconsBg);
|
||||
this.mainContainer.add(this.scrollBar);
|
||||
this.mainContainer.add(this.iconsContainer);
|
||||
this.mainContainer.add(titleBg);
|
||||
this.mainContainer.add(this.titleText);
|
||||
|
@ -162,6 +168,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
|
||||
this.currentPage = Page.ACHIEVEMENTS;
|
||||
this.setCursor(0);
|
||||
this.setScrollCursor(0);
|
||||
|
||||
this.mainContainer.setVisible(false);
|
||||
}
|
||||
|
@ -175,6 +182,8 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
this.mainContainer.setVisible(true);
|
||||
this.setCursor(0);
|
||||
this.setScrollCursor(0);
|
||||
this.scrollBar.setTotalRows(Math.ceil(this.currentTotal / this.COLS));
|
||||
this.scrollBar.setScrollCursor(0);
|
||||
|
||||
this.getUi().moveTo(this.mainContainer, this.getUi().length - 1);
|
||||
|
||||
|
@ -224,6 +233,8 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
this.updateAchvIcons();
|
||||
}
|
||||
this.setCursor(0, true);
|
||||
this.scrollBar.setTotalRows(Math.ceil(this.currentTotal / this.COLS));
|
||||
this.scrollBar.setScrollCursor(0);
|
||||
this.mainContainer.update();
|
||||
}
|
||||
if (button === Button.CANCEL) {
|
||||
|
@ -237,32 +248,44 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
if (this.cursor < this.COLS) {
|
||||
if (this.scrollCursor) {
|
||||
success = this.setScrollCursor(this.scrollCursor - 1);
|
||||
} else {
|
||||
// Wrap around to the last row
|
||||
success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS);
|
||||
let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS;
|
||||
if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS -1) {
|
||||
newCursorIndex -= this.COLS;
|
||||
}
|
||||
success = success && this.setCursor(newCursorIndex);
|
||||
}
|
||||
} else {
|
||||
success = this.setCursor(this.cursor - this.COLS);
|
||||
}
|
||||
break;
|
||||
case Button.DOWN:
|
||||
const canMoveDown = (this.cursor + itemOffset) + this.COLS < this.currentTotal;
|
||||
const canMoveDown = itemOffset + 1 < this.currentTotal;
|
||||
if (rowIndex >= this.ROWS - 1) {
|
||||
if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) {
|
||||
// scroll down one row
|
||||
success = this.setScrollCursor(this.scrollCursor + 1);
|
||||
} else {
|
||||
// wrap back to the first row
|
||||
success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS);
|
||||
}
|
||||
} else if (canMoveDown) {
|
||||
success = this.setCursor(this.cursor + this.COLS);
|
||||
success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1));
|
||||
}
|
||||
break;
|
||||
case Button.LEFT:
|
||||
if (!this.cursor && this.scrollCursor) {
|
||||
success = this.setScrollCursor(this.scrollCursor - 1) && this.setCursor(this.cursor + (this.COLS - 1));
|
||||
} else if (this.cursor) {
|
||||
if (this.cursor % this.COLS === 0) {
|
||||
success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1));
|
||||
} else {
|
||||
success = this.setCursor(this.cursor - 1);
|
||||
}
|
||||
break;
|
||||
case Button.RIGHT:
|
||||
if (this.cursor + 1 === this.ROWS * this.COLS && this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS) {
|
||||
success = this.setScrollCursor(this.scrollCursor + 1) && this.setCursor(this.cursor - (this.COLS - 1));
|
||||
} else if (this.cursor + itemOffset < this.currentTotal - 1) {
|
||||
if ((this.cursor + 1) % this.COLS === 0 || (this.cursor + itemOffset) === (this.currentTotal - 1)) {
|
||||
success = this.setCursor(this.cursor - this.cursor % this.COLS);
|
||||
} else {
|
||||
success = this.setCursor(this.cursor + 1);
|
||||
}
|
||||
break;
|
||||
|
@ -315,15 +338,22 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
}
|
||||
|
||||
this.scrollCursor = scrollCursor;
|
||||
this.scrollBar.setScrollCursor(this.scrollCursor);
|
||||
|
||||
// Cursor cannot go farther than the last element in the list
|
||||
const maxCursor = Math.min(this.cursor, this.currentTotal - this.scrollCursor * this.COLS - 1);
|
||||
if (maxCursor !== this.cursor) {
|
||||
this.setCursor(maxCursor);
|
||||
}
|
||||
|
||||
switch (this.currentPage) {
|
||||
case Page.ACHIEVEMENTS:
|
||||
this.updateAchvIcons();
|
||||
this.showAchv(achvs[Object.keys(achvs)[Math.min(this.cursor + this.scrollCursor * this.COLS, Object.values(achvs).length - 1)]]);
|
||||
this.showAchv(achvs[Object.keys(achvs)[this.cursor + this.scrollCursor * this.COLS]]);
|
||||
break;
|
||||
case Page.VOUCHERS:
|
||||
this.updateVoucherIcons();
|
||||
this.showVoucher(vouchers[Object.keys(vouchers)[Math.min(this.cursor + this.scrollCursor * this.COLS, Object.values(vouchers).length - 1)]]);
|
||||
this.showVoucher(vouchers[Object.keys(vouchers)[this.cursor + this.scrollCursor * this.COLS]]);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
|
@ -411,6 +441,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
|||
super.clear();
|
||||
this.currentPage = Page.ACHIEVEMENTS;
|
||||
this.mainContainer.setVisible(false);
|
||||
this.setScrollCursor(0);
|
||||
this.eraseCursor();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +1,65 @@
|
|||
/**
|
||||
* A vertical scrollbar element that resizes dynamically based on the current scrolling
|
||||
* and number of elements that can be shown on screen
|
||||
*/
|
||||
export class ScrollBar extends Phaser.GameObjects.Container {
|
||||
private bg: Phaser.GameObjects.Image;
|
||||
private bg: Phaser.GameObjects.NineSlice;
|
||||
private handleBody: Phaser.GameObjects.Rectangle;
|
||||
private handleBottom: Phaser.GameObjects.Image;
|
||||
private pages: number;
|
||||
private page: number;
|
||||
private handleBottom: Phaser.GameObjects.NineSlice;
|
||||
private currentRow: number;
|
||||
private totalRows: number;
|
||||
private maxRows: number;
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, pages: number) {
|
||||
/**
|
||||
* @param scene the current scene
|
||||
* @param x the scrollbar's x position (origin: top left)
|
||||
* @param y the scrollbar's y position (origin: top left)
|
||||
* @param width the scrollbar's width
|
||||
* @param height the scrollbar's height
|
||||
* @param maxRows the maximum number of rows that can be shown at once
|
||||
*/
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, width: number, height: number, maxRows: number) {
|
||||
super(scene, x, y);
|
||||
|
||||
this.bg = scene.add.image(0, 0, "scroll_bar");
|
||||
this.maxRows = maxRows;
|
||||
|
||||
const borderSize = 2;
|
||||
width = Math.max(width, 4);
|
||||
|
||||
this.bg = scene.add.nineslice(0, 0, "scroll_bar", undefined, width, height, borderSize, borderSize, borderSize, borderSize);
|
||||
this.bg.setOrigin(0, 0);
|
||||
this.add(this.bg);
|
||||
|
||||
this.handleBody = scene.add.rectangle(1, 1, 3, 4, 0xaaaaaa);
|
||||
this.handleBody = scene.add.rectangle(1, 1, width - 2, 4, 0xaaaaaa);
|
||||
this.handleBody.setOrigin(0, 0);
|
||||
this.add(this.handleBody);
|
||||
|
||||
this.handleBottom = scene.add.image(1, 1, "scroll_bar_handle");
|
||||
this.handleBottom = scene.add.nineslice(1, 1, "scroll_bar_handle", undefined, width - 2, 2, 2, 0, 0, 0);
|
||||
this.handleBottom.setOrigin(0, 0);
|
||||
this.add(this.handleBottom);
|
||||
}
|
||||
|
||||
setPage(page: number): void {
|
||||
this.page = page;
|
||||
this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.pages * page;
|
||||
/**
|
||||
* Set the current row that is displayed
|
||||
* Moves the bar handle up or down accordingly
|
||||
* @param scrollCursor how many times the view was scrolled down
|
||||
*/
|
||||
setScrollCursor(scrollCursor: number): void {
|
||||
this.currentRow = scrollCursor;
|
||||
this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.totalRows * this.currentRow;
|
||||
this.handleBottom.y = this.handleBody.y + this.handleBody.displayHeight;
|
||||
}
|
||||
|
||||
setPages(pages: number): void {
|
||||
this.pages = pages;
|
||||
this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) * 9 / this.pages;
|
||||
/**
|
||||
* Set the total number of rows to display
|
||||
* If it's smaller than the maximum number of rows on screen the bar will get hidden
|
||||
* Otherwise the scrollbar handle gets resized based on the ratio to the maximum number of rows
|
||||
* @param rows how many rows of data there are in total
|
||||
*/
|
||||
setTotalRows(rows: number): void {
|
||||
this.totalRows = rows;
|
||||
this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) * this.maxRows / this.totalRows;
|
||||
|
||||
this.setVisible(this.pages > 9);
|
||||
this.setVisible(this.totalRows > this.maxRows);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import UiHandler from "../ui-handler";
|
||||
import BattleScene from "../../battle-scene";
|
||||
import {Mode} from "../ui";
|
||||
import {InterfaceConfig} from "../../inputs-controller";
|
||||
import {addWindow} from "../ui-theme";
|
||||
import {addTextObject, TextStyle} from "../text";
|
||||
import UiHandler from "#app/ui/ui-handler";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { InterfaceConfig } from "#app/inputs-controller";
|
||||
import { addWindow } from "#app/ui/ui-theme";
|
||||
import { addTextObject, TextStyle } from "#app/ui/text";
|
||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||
import { getIconWithSettingName } from "#app/configs/inputs/configHandler";
|
||||
import NavigationMenu, { NavigationManager } from "#app/ui/settings/navigationMenu";
|
||||
import { Device } from "#enums/devices";
|
||||
|
@ -19,7 +20,7 @@ export interface LayoutConfig {
|
|||
inputsIcons: InputsIcons;
|
||||
settingLabels: Phaser.GameObjects.Text[];
|
||||
optionValueLabels: Phaser.GameObjects.Text[][];
|
||||
optionCursors: integer[];
|
||||
optionCursors: number[];
|
||||
keys: string[];
|
||||
bindingSettings: Array<String>;
|
||||
}
|
||||
|
@ -31,8 +32,9 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
protected optionsContainer: Phaser.GameObjects.Container;
|
||||
protected navigationContainer: NavigationMenu;
|
||||
|
||||
protected scrollCursor: integer;
|
||||
protected optionCursors: integer[];
|
||||
protected scrollBar: ScrollBar;
|
||||
protected scrollCursor: number;
|
||||
protected optionCursors: number[];
|
||||
protected cursorObj: Phaser.GameObjects.NineSlice | null;
|
||||
|
||||
protected optionsBg: Phaser.GameObjects.NineSlice;
|
||||
|
@ -65,7 +67,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
protected device: Device;
|
||||
|
||||
abstract saveSettingToLocalStorage(setting, cursor): void;
|
||||
abstract setSetting(scene: BattleScene, setting, value: integer): boolean;
|
||||
abstract setSetting(scene: BattleScene, setting, value: number): boolean;
|
||||
|
||||
/**
|
||||
* Constructor for the AbstractSettingsUiHandler.
|
||||
|
@ -241,7 +243,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
|
||||
// Calculate the total available space for placing option labels next to their setting label
|
||||
// We reserve space for the setting label and then distribute the remaining space evenly
|
||||
const totalSpace = (300 - labelWidth) - totalWidth / 6;
|
||||
const totalSpace = (297 - labelWidth) - totalWidth / 6;
|
||||
// Calculate the spacing between options based on the available space divided by the number of gaps between labels
|
||||
const optionSpacing = Math.floor(totalSpace / (optionValueLabels[s].length - 1));
|
||||
|
||||
|
@ -269,6 +271,11 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
// Add the options container to the overall settings container to be displayed in the UI.
|
||||
this.settingsContainer.add(optionsContainer);
|
||||
}
|
||||
|
||||
// Add vertical scrollbar
|
||||
this.scrollBar = new ScrollBar(this.scene, this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay);
|
||||
this.settingsContainer.add(this.scrollBar);
|
||||
|
||||
// Add the settings container to the UI.
|
||||
ui.add(this.settingsContainer);
|
||||
|
||||
|
@ -413,6 +420,8 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
this.optionCursors = layout.optionCursors;
|
||||
this.inputsIcons = layout.inputsIcons;
|
||||
this.bindingSettings = layout.bindingSettings;
|
||||
this.scrollBar.setTotalRows(layout.settingLabels.length);
|
||||
this.scrollBar.setScrollCursor(0);
|
||||
|
||||
// Return true indicating the layout was successfully applied.
|
||||
return true;
|
||||
|
@ -538,7 +547,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
* @param cursor - The cursor position to set.
|
||||
* @returns `true` if the cursor was set successfully.
|
||||
*/
|
||||
setCursor(cursor: integer): boolean {
|
||||
setCursor(cursor: number): boolean {
|
||||
const ret = super.setCursor(cursor);
|
||||
// If the optionsContainer is not initialized, return the result from the parent class directly.
|
||||
if (!this.optionsContainer) {
|
||||
|
@ -547,7 +556,8 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
|
||||
// Check if the cursor object exists, if not, create it.
|
||||
if (!this.cursorObj) {
|
||||
this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1);
|
||||
const cursorWidth = (this.scene.game.canvas.width / 6) - (this.scrollBar.visible? 16 : 10);
|
||||
this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1);
|
||||
this.cursorObj.setOrigin(0, 0); // Set the origin to the top-left corner.
|
||||
this.optionsContainer.add(this.cursorObj); // Add the cursor to the options container.
|
||||
}
|
||||
|
@ -564,7 +574,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
* @param scrollCursor - The scroll cursor position to set.
|
||||
* @returns `true` if the scroll cursor was set successfully.
|
||||
*/
|
||||
setScrollCursor(scrollCursor: integer): boolean {
|
||||
setScrollCursor(scrollCursor: number): boolean {
|
||||
// Check if the new scroll position is the same as the current one; if so, do not update.
|
||||
if (scrollCursor === this.scrollCursor) {
|
||||
return false;
|
||||
|
@ -572,6 +582,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
|
||||
// Update the internal scroll cursor state
|
||||
this.scrollCursor = scrollCursor;
|
||||
this.scrollBar.setScrollCursor(this.scrollCursor);
|
||||
|
||||
// Apply the new scroll position to the settings UI.
|
||||
this.updateSettingsScroll();
|
||||
|
@ -590,7 +601,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||
* @param save - Whether to save the setting to local storage.
|
||||
* @returns `true` if the option cursor was set successfully.
|
||||
*/
|
||||
setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean {
|
||||
setOptionCursor(settingIndex: number, cursor: number, save?: boolean): boolean {
|
||||
// Retrieve the specific setting using the settingIndex from the settingDevice enumeration.
|
||||
const setting = this.setting[Object.keys(this.setting)[settingIndex]];
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import BattleScene from "../../battle-scene";
|
||||
import { hasTouchscreen, isMobile } from "../../touch-controls";
|
||||
import { TextStyle, addTextObject } from "../text";
|
||||
import { Mode } from "../ui";
|
||||
import UiHandler from "../ui-handler";
|
||||
import { addWindow } from "../ui-theme";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { hasTouchscreen, isMobile } from "#app/touch-controls";
|
||||
import { TextStyle, addTextObject } from "#app/ui/text";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import UiHandler from "#app/ui/ui-handler";
|
||||
import { addWindow } from "#app/ui/ui-theme";
|
||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { InputsIcons } from "#app/ui/settings/abstract-control-settings-ui-handler";
|
||||
import NavigationMenu, { NavigationManager } from "#app/ui/settings/navigationMenu";
|
||||
|
@ -19,11 +20,12 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
private optionsContainer: Phaser.GameObjects.Container;
|
||||
private navigationContainer: NavigationMenu;
|
||||
|
||||
private scrollCursor: integer;
|
||||
private scrollCursor: number;
|
||||
private scrollBar: ScrollBar;
|
||||
|
||||
private optionsBg: Phaser.GameObjects.NineSlice;
|
||||
|
||||
private optionCursors: integer[];
|
||||
private optionCursors: number[];
|
||||
|
||||
private settingLabels: Phaser.GameObjects.Text[];
|
||||
private optionValueLabels: Phaser.GameObjects.Text[][];
|
||||
|
@ -117,7 +119,7 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
|
||||
const labelWidth = Math.max(78, this.settingLabels[s].displayWidth + 8);
|
||||
|
||||
const totalSpace = (300 - labelWidth) - totalWidth / 6;
|
||||
const totalSpace = (297 - labelWidth) - totalWidth / 6;
|
||||
const optionSpacing = Math.floor(totalSpace / (this.optionValueLabels[s].length - 1));
|
||||
|
||||
let xOffset = 0;
|
||||
|
@ -130,7 +132,11 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
|
||||
this.optionCursors = this.settings.map(setting => setting.default);
|
||||
|
||||
this.scrollBar = new ScrollBar(this.scene, this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay);
|
||||
this.scrollBar.setTotalRows(this.settings.length);
|
||||
|
||||
this.settingsContainer.add(this.optionsBg);
|
||||
this.settingsContainer.add(this.scrollBar);
|
||||
this.settingsContainer.add(this.navigationContainer);
|
||||
this.settingsContainer.add(actionsBg);
|
||||
this.settingsContainer.add(this.optionsContainer);
|
||||
|
@ -186,6 +192,7 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
|
||||
this.settingsContainer.setVisible(true);
|
||||
this.setCursor(0);
|
||||
this.setScrollCursor(0);
|
||||
|
||||
this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1);
|
||||
|
||||
|
@ -301,11 +308,12 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
* @param cursor - The cursor position to set.
|
||||
* @returns `true` if the cursor was set successfully.
|
||||
*/
|
||||
setCursor(cursor: integer): boolean {
|
||||
setCursor(cursor: number): boolean {
|
||||
const ret = super.setCursor(cursor);
|
||||
|
||||
if (!this.cursorObj) {
|
||||
this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1);
|
||||
const cursorWidth = (this.scene.game.canvas.width / 6) - (this.scrollBar.visible? 16 : 10);
|
||||
this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1);
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
this.optionsContainer.add(this.cursorObj);
|
||||
}
|
||||
|
@ -323,7 +331,7 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
* @param save - Whether to save the setting to local storage.
|
||||
* @returns `true` if the option cursor was set successfully.
|
||||
*/
|
||||
setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean {
|
||||
setOptionCursor(settingIndex: number, cursor: number, save?: boolean): boolean {
|
||||
const setting = this.settings[settingIndex];
|
||||
|
||||
if (setting.key === SettingKeys.Touch_Controls && cursor && hasTouchscreen() && isMobile()) {
|
||||
|
@ -359,12 +367,13 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
* @param scrollCursor - The scroll cursor position to set.
|
||||
* @returns `true` if the scroll cursor was set successfully.
|
||||
*/
|
||||
setScrollCursor(scrollCursor: integer): boolean {
|
||||
setScrollCursor(scrollCursor: number): boolean {
|
||||
if (scrollCursor === this.scrollCursor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.scrollCursor = scrollCursor;
|
||||
this.scrollBar.setScrollCursor(this.scrollCursor);
|
||||
|
||||
this.updateSettingsScroll();
|
||||
|
||||
|
@ -394,6 +403,7 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
clear() {
|
||||
super.clear();
|
||||
this.settingsContainer.setVisible(false);
|
||||
this.setScrollCursor(0);
|
||||
this.eraseCursor();
|
||||
this.getUi().bgmBar.toggleBgmBar(this.scene.showBgmBar);
|
||||
if (this.reloadRequired) {
|
||||
|
|
|
@ -627,7 +627,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||
|
||||
const starterBoxContainer = this.scene.add.container(speciesContainerX + 6, 9); //115
|
||||
|
||||
this.starterSelectScrollBar = new ScrollBar(this.scene, 161, 12, 0);
|
||||
this.starterSelectScrollBar = new ScrollBar(this.scene, 161, 12, 5, starterContainerWindow.height - 6, 9);
|
||||
|
||||
starterBoxContainer.add(this.starterSelectScrollBar);
|
||||
|
||||
|
@ -2540,8 +2540,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||
}
|
||||
});
|
||||
|
||||
this.starterSelectScrollBar.setPages(Math.max(Math.ceil(this.filteredStarterContainers.length / 9), 1));
|
||||
this.starterSelectScrollBar.setPage(0);
|
||||
this.starterSelectScrollBar.setTotalRows(Math.max(Math.ceil(this.filteredStarterContainers.length / 9), 1));
|
||||
this.starterSelectScrollBar.setScrollCursor(0);
|
||||
|
||||
// sort
|
||||
const sort = this.filterBar.getVals(DropDownColumn.SORT)[0];
|
||||
|
@ -2576,7 +2576,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||
const onScreenFirstIndex = this.scrollCursor * maxColumns;
|
||||
const onScreenLastIndex = Math.min(this.filteredStarterContainers.length - 1, onScreenFirstIndex + maxRows * maxColumns -1);
|
||||
|
||||
this.starterSelectScrollBar.setPage(this.scrollCursor);
|
||||
this.starterSelectScrollBar.setScrollCursor(this.scrollCursor);
|
||||
|
||||
let pokerusCursorIndex = 0;
|
||||
this.filteredStarterContainers.forEach((container, i) => {
|
||||
|
|
Loading…
Reference in New Issue