mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-14 20:32:17 +02:00
Merge branch 'beta' into starter-ability-tooltips
This commit is contained in:
commit
a64e1990ea
22
public/images/pokemon/variant/465.json
Normal file
22
public/images/pokemon/variant/465.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"1": {
|
||||
"529cc5": "8153c7",
|
||||
"d65a94": "5ad662",
|
||||
"3a73ad": "6b3aad",
|
||||
"bd216b": "21bd69",
|
||||
"5a193a": "195a2a",
|
||||
"193a63": "391963",
|
||||
"295a84": "472984"
|
||||
},
|
||||
"2": {
|
||||
"529cc5": "ffedb6",
|
||||
"d65a94": "e67d2f",
|
||||
"3a73ad": "ebc582",
|
||||
"bd216b": "b35131",
|
||||
"31313a": "3d1519",
|
||||
"5a193a": "752e2e",
|
||||
"193a63": "705040",
|
||||
"295a84": "ad875a",
|
||||
"4a4a52": "57211a"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 38 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 43 KiB |
@ -1691,8 +1691,8 @@
|
||||
],
|
||||
"465": [
|
||||
0,
|
||||
2,
|
||||
2
|
||||
1,
|
||||
1
|
||||
],
|
||||
"466": [
|
||||
1,
|
||||
@ -3980,6 +3980,11 @@
|
||||
1,
|
||||
1
|
||||
],
|
||||
"465": [
|
||||
0,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"592": [
|
||||
1,
|
||||
1,
|
||||
@ -5690,7 +5695,7 @@
|
||||
"465": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
1
|
||||
],
|
||||
"466": [
|
||||
2,
|
||||
@ -8008,6 +8013,11 @@
|
||||
1,
|
||||
1
|
||||
],
|
||||
"465": [
|
||||
0,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"592": [
|
||||
1,
|
||||
1,
|
||||
|
@ -8,5 +8,14 @@
|
||||
"bd216b": "21bd69",
|
||||
"31313a": "31313a",
|
||||
"d65a94": "5ad662"
|
||||
},
|
||||
"2": {
|
||||
"5a193a": "752e2e",
|
||||
"31313a": "3d1519",
|
||||
"d65a94": "e67d2f",
|
||||
"3a73ad": "ebc582",
|
||||
"295a84": "ad875a",
|
||||
"bd216b": "b35131",
|
||||
"193a63": "705040"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
21
public/images/pokemon/variant/back/female/465.json
Normal file
21
public/images/pokemon/variant/back/female/465.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"1": {
|
||||
"193a63": "391963",
|
||||
"295a84": "472984",
|
||||
"3a73ad": "6b3aad",
|
||||
"000000": "000000",
|
||||
"5a193a": "195a2a",
|
||||
"bd216b": "21bd69",
|
||||
"31313a": "31313a",
|
||||
"d65a94": "5ad662"
|
||||
},
|
||||
"2": {
|
||||
"5a193a": "752e2e",
|
||||
"31313a": "3d1519",
|
||||
"d65a94": "e67d2f",
|
||||
"3a73ad": "ebc582",
|
||||
"295a84": "ad875a",
|
||||
"bd216b": "b35131",
|
||||
"193a63": "705040"
|
||||
}
|
||||
}
|
22
public/images/pokemon/variant/female/465.json
Normal file
22
public/images/pokemon/variant/female/465.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"1": {
|
||||
"529cc5": "8153c7",
|
||||
"d65a94": "5ad662",
|
||||
"3a73ad": "6b3aad",
|
||||
"bd216b": "21bd69",
|
||||
"5a193a": "195a2a",
|
||||
"193a63": "391963",
|
||||
"295a84": "472984"
|
||||
},
|
||||
"2": {
|
||||
"529cc5": "ffedb6",
|
||||
"d65a94": "e67d2f",
|
||||
"3a73ad": "ebc582",
|
||||
"bd216b": "b35131",
|
||||
"31313a": "3d1519",
|
||||
"5a193a": "752e2e",
|
||||
"193a63": "705040",
|
||||
"295a84": "ad875a",
|
||||
"4a4a52": "57211a"
|
||||
}
|
||||
}
|
@ -855,7 +855,7 @@ export default class BattleScene extends SceneBase {
|
||||
overrideModifiers(this, false);
|
||||
overrideHeldItems(this, pokemon, false);
|
||||
if (boss && !dataSource) {
|
||||
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295));
|
||||
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296));
|
||||
|
||||
for (let s = 0; s < pokemon.ivs.length; s++) {
|
||||
pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75));
|
||||
@ -961,6 +961,16 @@ export default class BattleScene extends SceneBase {
|
||||
this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random number using the current battle's seed
|
||||
*
|
||||
* This calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
|
||||
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`
|
||||
*
|
||||
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
|
||||
* @param min The minimum integer to pick, default `0`
|
||||
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
|
||||
*/
|
||||
randBattleSeedInt(range: integer, min: integer = 0): integer {
|
||||
return this.currentBattle?.randSeedInt(this, range, min);
|
||||
}
|
||||
@ -1112,7 +1122,8 @@ export default class BattleScene extends SceneBase {
|
||||
doubleTrainer = false;
|
||||
}
|
||||
}
|
||||
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
|
||||
const variant = doubleTrainer ? TrainerVariant.DOUBLE : (Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
|
||||
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, variant);
|
||||
this.field.add(newTrainer);
|
||||
}
|
||||
}
|
||||
@ -2620,7 +2631,7 @@ export default class BattleScene extends SceneBase {
|
||||
if (mods.length < 1) {
|
||||
return mods;
|
||||
}
|
||||
const rand = Math.floor(Utils.randSeedInt(mods.length));
|
||||
const rand = Utils.randSeedInt(mods.length);
|
||||
return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))];
|
||||
};
|
||||
modifiers = shuffleModifiers(modifiers);
|
||||
|
@ -354,6 +354,12 @@ export default class Battle {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random number using the current battle's seed. Calls {@linkcode Utils.randSeedInt}
|
||||
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
|
||||
* @param min The minimum integer to pick, default `0`
|
||||
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
|
||||
*/
|
||||
randSeedInt(scene: BattleScene, range: number, min: number = 0): number {
|
||||
if (range <= 1) {
|
||||
return min;
|
||||
|
@ -2642,7 +2642,7 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
|
||||
if (simulated) {
|
||||
return defender.canAddTag(BattlerTagType.CONFUSED);
|
||||
} else {
|
||||
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3, 2), move.id, defender.id);
|
||||
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedIntRange(2, 5), move.id, defender.id);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -5333,8 +5333,10 @@ export function initAbilities() {
|
||||
.attr(FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 4 / 3),
|
||||
new Ability(Abilities.AURA_BREAK, 6)
|
||||
.ignorable()
|
||||
.conditionalAttr(target => target.hasAbility(Abilities.DARK_AURA), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16)
|
||||
.conditionalAttr(target => target.hasAbility(Abilities.FAIRY_AURA), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16),
|
||||
.conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16)
|
||||
.conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16)
|
||||
.conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA) || p.hasAbility(Abilities.FAIRY_AURA)),
|
||||
PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAuraBreak", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })),
|
||||
new Ability(Abilities.PRIMORDIAL_SEA, 6)
|
||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
||||
|
@ -486,7 +486,7 @@ export class ConfusedTag extends BattlerTag {
|
||||
if (pokemon.randSeedInt(3) === 0) {
|
||||
const atk = pokemon.getEffectiveStat(Stat.ATK);
|
||||
const def = pokemon.getEffectiveStat(Stat.DEF);
|
||||
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
|
||||
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
|
||||
pokemon.damageAndUpdate(damage);
|
||||
pokemon.battleData.hitCount++;
|
||||
|
@ -15,7 +15,7 @@ export const EGG_SEED = 1073741824;
|
||||
// Rates for specific random properties in 1/x
|
||||
const DEFAULT_SHINY_RATE = 128;
|
||||
const GACHA_SHINY_UP_SHINY_RATE = 64;
|
||||
const SAME_SPECIES_EGG_SHINY_RATE = 24;
|
||||
const SAME_SPECIES_EGG_SHINY_RATE = 12;
|
||||
const SAME_SPECIES_EGG_HA_RATE = 8;
|
||||
const MANAPHY_EGG_MANAPHY_RATE = 8;
|
||||
const GACHA_EGG_HA_RATE = 192;
|
||||
|
@ -757,7 +757,10 @@ export default class Move implements Localizable {
|
||||
|
||||
const fieldAuras = new Set(
|
||||
source.scene.getField(true)
|
||||
.map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[])
|
||||
.map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr).filter(attr => {
|
||||
const condition = attr.getCondition();
|
||||
return (!condition || condition(p));
|
||||
}) as FieldMoveTypePowerBoostAbAttr[])
|
||||
.flat(),
|
||||
);
|
||||
for (const aura of fieldAuras) {
|
||||
@ -4400,7 +4403,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
||||
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id);
|
||||
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedIntRange(this.turnCountMin, this.turnCountMax), move.id, user.id);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -6234,6 +6237,8 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target:
|
||||
|
||||
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE);
|
||||
|
||||
const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => user.scene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined;
|
||||
|
||||
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||
|
||||
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
|
||||
@ -6972,7 +6977,8 @@ export function initMoves() {
|
||||
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new SelfStatusMove(Moves.PROTECT, Type.NORMAL, -1, 10, -1, 4, 2)
|
||||
.attr(ProtectAttr),
|
||||
.attr(ProtectAttr)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.MACH_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2)
|
||||
.punchingMove(),
|
||||
new StatusMove(Moves.SCARY_FACE, Type.NORMAL, 100, 10, -1, 0, 2)
|
||||
@ -7023,7 +7029,8 @@ export function initMoves() {
|
||||
.windMove()
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new SelfStatusMove(Moves.DETECT, Type.FIGHTING, -1, 5, -1, 4, 2)
|
||||
.attr(ProtectAttr),
|
||||
.attr(ProtectAttr)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.BONE_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 2)
|
||||
.attr(MultiHitAttr)
|
||||
.makesContact(false),
|
||||
@ -7041,7 +7048,8 @@ export function initMoves() {
|
||||
.attr(HitHealAttr)
|
||||
.triageMove(),
|
||||
new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2)
|
||||
.attr(ProtectAttr, BattlerTagType.ENDURING),
|
||||
.attr(ProtectAttr, BattlerTagType.ENDURING)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -2),
|
||||
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
|
||||
@ -7788,7 +7796,8 @@ export function initMoves() {
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true),
|
||||
new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true),
|
||||
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.GUARD_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
||||
.attr(AverageStatsAttr, [ Stat.DEF, Stat.SPDEF ], "moveTriggers:sharedGuard"),
|
||||
new StatusMove(Moves.POWER_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
||||
@ -7876,7 +7885,8 @@ export function initMoves() {
|
||||
.attr(PositiveStatStagePowerAttr),
|
||||
new StatusMove(Moves.QUICK_GUARD, Type.FIGHTING, -1, 15, -1, 3, 5)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true),
|
||||
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true)
|
||||
.condition(failIfLastCondition),
|
||||
new SelfStatusMove(Moves.ALLY_SWITCH, Type.PSYCHIC, -1, 15, -1, 2, 5)
|
||||
.ignoresProtect()
|
||||
.unimplemented(),
|
||||
@ -8047,7 +8057,8 @@ export function initMoves() {
|
||||
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.MAT_BLOCK, 1, true, true)
|
||||
.condition(new FirstMoveCondition()),
|
||||
.condition(new FirstMoveCondition())
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
|
||||
.condition((user, target, move) => user.battleData.berriesEaten.length > 0),
|
||||
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6)
|
||||
@ -8105,7 +8116,8 @@ export function initMoves() {
|
||||
.triageMove(),
|
||||
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true),
|
||||
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
|
||||
.target(MoveTarget.ALL)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
|
||||
@ -8130,7 +8142,8 @@ export function initMoves() {
|
||||
.target(MoveTarget.BOTH_SIDES)
|
||||
.unimplemented(),
|
||||
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
|
||||
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD),
|
||||
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
||||
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
|
||||
@ -8153,7 +8166,8 @@ export function initMoves() {
|
||||
new AttackMove(Moves.MYSTICAL_FIRE, Type.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
|
||||
new SelfStatusMove(Moves.SPIKY_SHIELD, Type.GRASS, -1, 10, -1, 4, 6)
|
||||
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD),
|
||||
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPDEF ], 1)
|
||||
.target(MoveTarget.NEAR_ALLY),
|
||||
@ -8349,7 +8363,8 @@ export function initMoves() {
|
||||
new AttackMove(Moves.FIRST_IMPRESSION, Type.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7)
|
||||
.condition(new FirstMoveCondition()),
|
||||
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
|
||||
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER),
|
||||
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
|
||||
.makesContact(false),
|
||||
@ -8592,6 +8607,7 @@ export function initMoves() {
|
||||
/* Unused */
|
||||
new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8)
|
||||
.attr(ProtectAttr)
|
||||
.condition(failIfLastCondition)
|
||||
.ignoresVirtual(),
|
||||
/* End Unused */
|
||||
new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
|
||||
@ -8770,7 +8786,8 @@ export function initMoves() {
|
||||
.target(MoveTarget.USER_AND_ALLIES)
|
||||
.ignoresProtect(),
|
||||
new SelfStatusMove(Moves.OBSTRUCT, Type.DARK, 100, 10, -1, 4, 8)
|
||||
.attr(ProtectAttr, BattlerTagType.OBSTRUCT),
|
||||
.attr(ProtectAttr, BattlerTagType.OBSTRUCT)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.FALSE_SURRENDER, Type.DARK, MoveCategory.PHYSICAL, 80, -1, 10, -1, 0, 8),
|
||||
new AttackMove(Moves.METEOR_ASSAULT, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, 0, 8)
|
||||
.attr(RechargeAttr)
|
||||
@ -9058,10 +9075,10 @@ export function initMoves() {
|
||||
.attr(TeraBlastCategoryAttr)
|
||||
.attr(TeraBlastTypeAttr)
|
||||
.attr(TeraBlastPowerAttr)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR))
|
||||
.partial(),
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR)),
|
||||
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
|
||||
.attr(ProtectAttr, BattlerTagType.SILK_TRAP),
|
||||
.attr(ProtectAttr, BattlerTagType.SILK_TRAP)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.AXE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 90, 10, 30, 0, 9)
|
||||
.attr(MissEffectAttr, crashDamageFunc)
|
||||
.attr(NoEffectAttr, crashDamageFunc)
|
||||
@ -9253,7 +9270,8 @@ export function initMoves() {
|
||||
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
|
||||
.attr(DoublePowerChanceAttr),
|
||||
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9)
|
||||
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK),
|
||||
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9)
|
||||
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct?
|
||||
new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9)
|
||||
|
@ -1720,7 +1720,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
};
|
||||
|
||||
this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true);
|
||||
this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? this.fusionSpecies.ability2 ? 2 : 1 : this.fusionSpecies.ability2 ? randAbilityIndex : 0);
|
||||
this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? 2 : this.fusionSpecies.ability2 !== this.fusionSpecies.ability1 ? randAbilityIndex : 0);
|
||||
this.fusionShiny = this.shiny;
|
||||
this.fusionVariant = this.variant;
|
||||
|
||||
@ -2278,7 +2278,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
if (!isTypeImmune) {
|
||||
const levelMultiplier = (2 * source.level / 5 + 2);
|
||||
const randomMultiplier = ((this.scene.randBattleSeedInt(16) + 85) / 100);
|
||||
const randomMultiplier = (this.randSeedIntRange(85, 100) / 100);
|
||||
damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2)
|
||||
* stabMultiplier.value
|
||||
* typeMultiplier
|
||||
@ -3448,12 +3448,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
fusionCanvas.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy
|
||||
* <!-- @import "../battle".Battle -->
|
||||
* This calls either {@linkcode BattleScene.randBattleSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle-scene.ts`
|
||||
* which calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
|
||||
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`,
|
||||
* or it directly calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` if there is no current battle
|
||||
*
|
||||
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
|
||||
* @param min The minimum integer to pick, default `0`
|
||||
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
|
||||
*/
|
||||
randSeedInt(range: integer, min: integer = 0): integer {
|
||||
return this.scene.currentBattle
|
||||
? this.scene.randBattleSeedInt(range, min)
|
||||
: Utils.randSeedInt(range, min);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy
|
||||
* @param min The minimum integer to generate
|
||||
* @param max The maximum integer to generate
|
||||
* @returns a random integer between {@linkcode min} and {@linkcode max} inclusive
|
||||
*/
|
||||
randSeedIntRange(min: integer, max: integer): integer {
|
||||
return this.randSeedInt((max - min) + 1, min);
|
||||
}
|
||||
|
@ -52,6 +52,7 @@
|
||||
"postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!",
|
||||
"postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!",
|
||||
"postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!",
|
||||
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
|
||||
"postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!",
|
||||
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!",
|
||||
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!",
|
||||
|
@ -51,5 +51,7 @@
|
||||
"renamePokemon": "Rename Pokémon",
|
||||
"rename": "Rename",
|
||||
"nickname": "Nickname",
|
||||
"errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect."
|
||||
"errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect.",
|
||||
"noSaves": "You don't have any save files on record!",
|
||||
"tooManySaves": "You have too many save files on record!"
|
||||
}
|
@ -377,16 +377,16 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
return false;
|
||||
}
|
||||
|
||||
const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user!, target); // TODO: is the bang correct here?
|
||||
const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user, target);
|
||||
|
||||
if (moveAccuracy === -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove());
|
||||
const rand = user.randSeedInt(100, 1);
|
||||
const rand = user.randSeedInt(100);
|
||||
|
||||
return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct?
|
||||
return rand < (moveAccuracy * accuracyMultiplier);
|
||||
}
|
||||
|
||||
/** Returns the {@linkcode Pokemon} using this phase's invoked move */
|
||||
|
@ -857,6 +857,14 @@ export class GameData {
|
||||
|
||||
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
|
||||
|
||||
// TODO: Remove this block after save migration is implemented
|
||||
if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
|
||||
settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"];
|
||||
delete settings["REROLL_TARGET"];
|
||||
localStorage.setItem("settings", JSON.stringify(settings));
|
||||
}
|
||||
// End of block to remove
|
||||
|
||||
for (const setting of Object.keys(settings)) {
|
||||
setSetting(this.scene, setting, settings[setting]);
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
@ -33,31 +32,45 @@ describe("Abilities - Aura Break", () => {
|
||||
game.override.enemySpecies(Species.SHUCKLE);
|
||||
});
|
||||
|
||||
it("reverses the effect of fairy aura", async () => {
|
||||
it("reverses the effect of Fairy Aura", async () => {
|
||||
const moveToCheck = allMoves[Moves.MOONBLAST];
|
||||
const basePower = moveToCheck.power;
|
||||
|
||||
game.override.ability(Abilities.FAIRY_AURA);
|
||||
vi.spyOn(moveToCheck, "calculateBattlePower");
|
||||
|
||||
await game.startBattle([Species.PIKACHU]);
|
||||
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||
game.move.select(Moves.MOONBLAST);
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier));
|
||||
});
|
||||
|
||||
it("reverses the effect of dark aura", async () => {
|
||||
it("reverses the effect of Dark Aura", async () => {
|
||||
const moveToCheck = allMoves[Moves.DARK_PULSE];
|
||||
const basePower = moveToCheck.power;
|
||||
|
||||
game.override.ability(Abilities.DARK_AURA);
|
||||
vi.spyOn(moveToCheck, "calculateBattlePower");
|
||||
|
||||
await game.startBattle([Species.PIKACHU]);
|
||||
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||
game.move.select(Moves.DARK_PULSE);
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier));
|
||||
});
|
||||
|
||||
it("has no effect if neither Fairy Aura nor Dark Aura are present", async () => {
|
||||
const moveToCheck = allMoves[Moves.MOONBLAST];
|
||||
const basePower = moveToCheck.power;
|
||||
|
||||
game.override.ability(Abilities.BALL_FETCH);
|
||||
vi.spyOn(moveToCheck, "calculateBattlePower");
|
||||
|
||||
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||
game.move.select(Moves.MOONBLAST);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower);
|
||||
});
|
||||
});
|
||||
|
101
src/test/moves/dragon_cheer.test.ts
Normal file
101
src/test/moves/dragon_cheer.test.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Type } from "#app/data/type";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Moves - Dragon Cheer", () => {
|
||||
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
|
||||
.battleType("double")
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(SPLASH_ONLY)
|
||||
.enemyLevel(20)
|
||||
.moveset([Moves.DRAGON_CHEER, Moves.TACKLE, Moves.SPLASH]);
|
||||
});
|
||||
|
||||
it("increases the user's allies' critical hit ratio by one stage", async () => {
|
||||
await game.classicMode.startBattle([Species.DRAGONAIR, Species.MAGIKARP]);
|
||||
|
||||
const enemy = game.scene.getEnemyField()[0];
|
||||
|
||||
vi.spyOn(enemy, "getCritStage");
|
||||
|
||||
game.move.select(Moves.DRAGON_CHEER, 0);
|
||||
game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
||||
|
||||
// After Tackle
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
|
||||
}, TIMEOUT);
|
||||
|
||||
it("increases the user's Dragon-type allies' critical hit ratio by two stages", async () => {
|
||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.DRAGONAIR]);
|
||||
|
||||
const enemy = game.scene.getEnemyField()[0];
|
||||
|
||||
vi.spyOn(enemy, "getCritStage");
|
||||
|
||||
game.move.select(Moves.DRAGON_CHEER, 0);
|
||||
game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
||||
|
||||
// After Tackle
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemy.getCritStage).toHaveReturnedWith(2); // getCritStage is called on defender
|
||||
}, TIMEOUT);
|
||||
|
||||
it("applies the effect based on the allies' type upon use of the move, and do not change if the allies' type changes later in battle", async () => {
|
||||
await game.classicMode.startBattle([Species.DRAGONAIR, Species.MAGIKARP]);
|
||||
|
||||
const magikarp = game.scene.getPlayerField()[1];
|
||||
const enemy = game.scene.getEnemyField()[0];
|
||||
|
||||
vi.spyOn(enemy, "getCritStage");
|
||||
|
||||
game.move.select(Moves.DRAGON_CHEER, 0);
|
||||
game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
||||
|
||||
// After Tackle
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
|
||||
|
||||
await game.toNextTurn();
|
||||
|
||||
// Change Magikarp's type to Dragon
|
||||
vi.spyOn(magikarp, "getTypes").mockReturnValue([Type.DRAGON]);
|
||||
expect(magikarp.getTypes()).toEqual([Type.DRAGON]);
|
||||
|
||||
game.move.select(Moves.SPLASH, 0);
|
||||
game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
|
||||
}, TIMEOUT);
|
||||
});
|
@ -1,13 +1,12 @@
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { DamagePhase } from "#app/phases/damage-phase";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Moves - Glaive Rush", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -25,131 +24,142 @@ describe("Moves - Glaive Rush", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleType("single");
|
||||
game.override.disableCrits();
|
||||
game.override.enemySpecies(Species.MAGIKARP);
|
||||
game.override.enemyAbility(Abilities.BALL_FETCH);
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.GLAIVE_RUSH));
|
||||
game.override.starterSpecies(Species.KLINK);
|
||||
game.override.ability(Abilities.UNNERVE);
|
||||
game.override.passiveAbility(Abilities.FUR_COAT);
|
||||
game.override.moveset([Moves.SHADOW_SNEAK, Moves.AVALANCHE, Moves.SPLASH, Moves.GLAIVE_RUSH]);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Array(4).fill(Moves.GLAIVE_RUSH))
|
||||
.starterSpecies(Species.KLINK)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.moveset([Moves.SHADOW_SNEAK, Moves.AVALANCHE, Moves.SPLASH, Moves.GLAIVE_RUSH]);
|
||||
});
|
||||
|
||||
it("takes double damage from attacks", async () => {
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
enemy.hp = 1000;
|
||||
|
||||
vi.spyOn(game.scene, "randBattleSeedInt").mockReturnValue(0);
|
||||
game.move.select(Moves.SHADOW_SNEAK);
|
||||
await game.phaseInterceptor.to(DamagePhase);
|
||||
await game.phaseInterceptor.to("DamagePhase");
|
||||
const damageDealt = 1000 - enemy.hp;
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.move.select(Moves.SHADOW_SNEAK);
|
||||
await game.phaseInterceptor.to(DamagePhase);
|
||||
await game.phaseInterceptor.to("DamagePhase");
|
||||
expect(enemy.hp).toBeLessThanOrEqual(1001 - (damageDealt * 3));
|
||||
|
||||
}, 5000); // TODO: revert back to 20s
|
||||
}, TIMEOUT);
|
||||
|
||||
it("always gets hit by attacks", async () => {
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
enemy.hp = 1000;
|
||||
|
||||
allMoves[Moves.AVALANCHE].accuracy = 0;
|
||||
game.move.select(Moves.AVALANCHE);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemy.hp).toBeLessThan(1000);
|
||||
|
||||
}, 20000);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("interacts properly with multi-lens", async () => {
|
||||
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]);
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.AVALANCHE));
|
||||
await game.startBattle();
|
||||
game.override
|
||||
.startingHeldItems([{ name: "MULTI_LENS", count: 2 }])
|
||||
.enemyMoveset(Array(4).fill(Moves.AVALANCHE));
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
enemy.hp = 1000;
|
||||
player.hp = 1000;
|
||||
|
||||
allMoves[Moves.AVALANCHE].accuracy = 0;
|
||||
game.move.select(Moves.GLAIVE_RUSH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(player.hp).toBeLessThan(1000);
|
||||
player.hp = 1000;
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(player.hp).toBe(1000);
|
||||
|
||||
}, 20000);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("secondary effects only last until next move", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK));
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
enemy.hp = 1000;
|
||||
player.hp = 1000;
|
||||
allMoves[Moves.SHADOW_SNEAK].accuracy = 0;
|
||||
|
||||
game.move.select(Moves.GLAIVE_RUSH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(player.hp).toBe(1000);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
const damagedHp = player.hp;
|
||||
expect(player.hp).toBeLessThan(1000);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(player.hp).toBe(damagedHp);
|
||||
|
||||
}, 20000);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("secondary effects are removed upon switching", async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK));
|
||||
game.override.starterSpecies(0);
|
||||
await game.startBattle([Species.KLINK, Species.FEEBAS]);
|
||||
game.override
|
||||
.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK))
|
||||
.starterSpecies(0);
|
||||
await game.classicMode.startBattle([Species.KLINK, Species.FEEBAS]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
enemy.hp = 1000;
|
||||
allMoves[Moves.SHADOW_SNEAK].accuracy = 0;
|
||||
|
||||
game.move.select(Moves.GLAIVE_RUSH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(player.hp).toBe(player.getMaxHp());
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.doSwitchPokemon(1);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(player.hp).toBe(player.getMaxHp());
|
||||
|
||||
}, 20000);
|
||||
}, TIMEOUT);
|
||||
|
||||
it("secondary effects don't activate if move fails", async () => {
|
||||
game.override.moveset([Moves.SHADOW_SNEAK, Moves.PROTECT, Moves.SPLASH, Moves.GLAIVE_RUSH]);
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
enemy.hp = 1000;
|
||||
player.hp = 1000;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
game.move.select(Moves.SHADOW_SNEAK);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.SPLASH));
|
||||
const damagedHP1 = 1000 - enemy.hp;
|
||||
enemy.hp = 1000;
|
||||
|
||||
game.move.select(Moves.SHADOW_SNEAK);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
const damagedHP2 = 1000 - enemy.hp;
|
||||
|
||||
expect(damagedHP2).toBeGreaterThanOrEqual((damagedHP1 * 2) - 1);
|
||||
}, 20000);
|
||||
}, TIMEOUT);
|
||||
});
|
||||
|
@ -7,7 +7,8 @@ import { Moves } from "#enums/moves";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
|
||||
import { BerryPhase } from "#app/phases/berry-phase";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
@ -43,13 +44,13 @@ describe("Moves - Protect", () => {
|
||||
test(
|
||||
"should protect the user from attacks",
|
||||
async () => {
|
||||
await game.startBattle([Species.CHARIZARD]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
|
||||
}, TIMEOUT
|
||||
@ -61,13 +62,13 @@ describe("Moves - Protect", () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.CEASELESS_EDGE));
|
||||
vi.spyOn(allMoves[Moves.CEASELESS_EDGE], "accuracy", "get").mockReturnValue(100);
|
||||
|
||||
await game.startBattle([Species.CHARIZARD]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeUndefined();
|
||||
@ -79,13 +80,13 @@ describe("Moves - Protect", () => {
|
||||
async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.CHARM));
|
||||
|
||||
await game.startBattle([Species.CHARIZARD]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
|
||||
}, TIMEOUT
|
||||
@ -96,18 +97,38 @@ describe("Moves - Protect", () => {
|
||||
async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.TACHYON_CUTTER));
|
||||
|
||||
await game.startBattle([Species.CHARIZARD]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
|
||||
expect(enemyPokemon.turnData.hitCount).toBe(1);
|
||||
}, TIMEOUT
|
||||
);
|
||||
|
||||
test(
|
||||
"should fail if the user is the last to move in the turn",
|
||||
async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.PROTECT));
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
expect(leadPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
}, TIMEOUT
|
||||
);
|
||||
});
|
||||
|
@ -5,8 +5,8 @@ import { Species } from "#enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { BerryPhase } from "#app/phases/berry-phase";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
@ -42,19 +42,16 @@ describe("Moves - Quick Guard", () => {
|
||||
test(
|
||||
"should protect the user and allies from priority moves",
|
||||
async () => {
|
||||
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerField();
|
||||
const playerPokemon = game.scene.getPlayerField();
|
||||
|
||||
game.move.select(Moves.QUICK_GUARD);
|
||||
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
leadPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
|
||||
playerPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
|
||||
}, TIMEOUT
|
||||
);
|
||||
|
||||
@ -64,19 +61,16 @@ describe("Moves - Quick Guard", () => {
|
||||
game.override.enemyAbility(Abilities.PRANKSTER);
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.GROWL));
|
||||
|
||||
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerField();
|
||||
const playerPokemon = game.scene.getPlayerField();
|
||||
|
||||
game.move.select(Moves.QUICK_GUARD);
|
||||
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
|
||||
playerPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
|
||||
}, TIMEOUT
|
||||
);
|
||||
|
||||
@ -85,21 +79,40 @@ describe("Moves - Quick Guard", () => {
|
||||
async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.WATER_SHURIKEN));
|
||||
|
||||
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerField();
|
||||
const playerPokemon = game.scene.getPlayerField();
|
||||
const enemyPokemon = game.scene.getEnemyField();
|
||||
|
||||
game.move.select(Moves.QUICK_GUARD);
|
||||
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
|
||||
game.move.select(Moves.FOLLOW_ME, 1);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
leadPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
|
||||
playerPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
|
||||
enemyPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1));
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
"should fail if the user is the last to move in the turn",
|
||||
async () => {
|
||||
game.override.battleType("single");
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.QUICK_GUARD));
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.QUICK_GUARD);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
}, TIMEOUT
|
||||
);
|
||||
});
|
||||
|
@ -76,7 +76,7 @@ export default class GameManager {
|
||||
constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) {
|
||||
localStorage.clear();
|
||||
ErrorInterceptor.getInstance().clear();
|
||||
BattleScene.prototype.randBattleSeedInt = (arg) => arg-1;
|
||||
BattleScene.prototype.randBattleSeedInt = (range, min: number = 0) => min + range - 1; // This simulates a max roll
|
||||
this.gameWrapper = new GameWrapper(phaserGame, bypassLogin);
|
||||
this.scene = new BattleScene();
|
||||
this.phaseInterceptor = new PhaseInterceptor(this.scene);
|
||||
|
@ -207,4 +207,5 @@ export default class MockContainer implements MockGameObject {
|
||||
return this.list;
|
||||
}
|
||||
|
||||
disableInteractive = vi.fn();
|
||||
}
|
||||
|
@ -8,7 +8,21 @@ import { addTextObject, TextStyle } from "./text";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
|
||||
interface BuildInteractableImageOpts {
|
||||
scale?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
origin?: { x: number; y: number };
|
||||
}
|
||||
|
||||
export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||
private readonly ERR_USERNAME: string = "invalid username";
|
||||
private readonly ERR_PASSWORD: string = "invalid password";
|
||||
private readonly ERR_ACCOUNT_EXIST: string = "account doesn't exist";
|
||||
private readonly ERR_PASSWORD_MATCH: string = "password doesn't match";
|
||||
private readonly ERR_NO_SAVES: string = "No save files found";
|
||||
private readonly ERR_TOO_MANY_SAVES: string = "Too many save files found";
|
||||
|
||||
private googleImage: Phaser.GameObjects.Image;
|
||||
private discordImage: Phaser.GameObjects.Image;
|
||||
private usernameInfoImage: Phaser.GameObjects.Image;
|
||||
@ -21,8 +35,23 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||
}
|
||||
|
||||
setup(): void {
|
||||
|
||||
super.setup();
|
||||
this.buildExternalPartyContainer();
|
||||
|
||||
this.infoContainer = this.scene.add.container(0, 0);
|
||||
|
||||
this.usernameInfoImage = this.buildInteractableImage("settings_icon", "username-info-icon", {
|
||||
x: 20,
|
||||
scale: 0.5
|
||||
});
|
||||
|
||||
this.infoContainer.add(this.usernameInfoImage);
|
||||
this.getUi().add(this.infoContainer);
|
||||
this.infoContainer.setVisible(false);
|
||||
this.infoContainer.disableInteractive();
|
||||
}
|
||||
|
||||
private buildExternalPartyContainer() {
|
||||
this.externalPartyContainer = this.scene.add.container(0, 0);
|
||||
this.externalPartyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains);
|
||||
this.externalPartyTitle = addTextObject(this.scene, 0, 4, "", TextStyle.SETTINGS_LABEL);
|
||||
@ -31,23 +60,8 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||
this.externalPartyContainer.add(this.externalPartyBg);
|
||||
this.externalPartyContainer.add(this.externalPartyTitle);
|
||||
|
||||
this.infoContainer = this.scene.add.container(0, 0);
|
||||
this.infoContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains);
|
||||
|
||||
const googleImage = this.scene.add.image(0, 0, "google");
|
||||
googleImage.setOrigin(0, 0);
|
||||
googleImage.setScale(0.07);
|
||||
googleImage.setInteractive();
|
||||
googleImage.setName("google-icon");
|
||||
this.googleImage = googleImage;
|
||||
|
||||
const discordImage = this.scene.add.image(20, 0, "discord");
|
||||
discordImage.setOrigin(0, 0);
|
||||
discordImage.setScale(0.07);
|
||||
discordImage.setInteractive();
|
||||
discordImage.setName("discord-icon");
|
||||
|
||||
this.discordImage = discordImage;
|
||||
this.googleImage = this.buildInteractableImage("google", "google-icon");
|
||||
this.discordImage = this.buildInteractableImage("discord", "discord-icon");
|
||||
|
||||
this.externalPartyContainer.add(this.googleImage);
|
||||
this.externalPartyContainer.add(this.discordImage);
|
||||
@ -55,59 +69,52 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||
this.externalPartyContainer.add(this.googleImage);
|
||||
this.externalPartyContainer.add(this.discordImage);
|
||||
this.externalPartyContainer.setVisible(false);
|
||||
|
||||
const usernameInfoImage = this.scene.add.image(20, 0, "settings_icon");
|
||||
usernameInfoImage.setOrigin(0, 0);
|
||||
usernameInfoImage.setScale(0.5);
|
||||
usernameInfoImage.setInteractive();
|
||||
usernameInfoImage.setName("username-info-icon");
|
||||
this.usernameInfoImage = usernameInfoImage;
|
||||
|
||||
this.infoContainer.add(this.usernameInfoImage);
|
||||
this.getUi().add(this.infoContainer);
|
||||
this.infoContainer.setVisible(false);
|
||||
}
|
||||
|
||||
getModalTitle(config?: ModalConfig): string {
|
||||
override getModalTitle(_config?: ModalConfig): string {
|
||||
return i18next.t("menu:login");
|
||||
}
|
||||
|
||||
getFields(config?: ModalConfig): string[] {
|
||||
override getFields(_config?: ModalConfig): string[] {
|
||||
return [ i18next.t("menu:username"), i18next.t("menu:password") ];
|
||||
}
|
||||
|
||||
getWidth(config?: ModalConfig): number {
|
||||
override getWidth(_config?: ModalConfig): number {
|
||||
return 160;
|
||||
}
|
||||
|
||||
getMargin(config?: ModalConfig): [number, number, number, number] {
|
||||
override getMargin(_config?: ModalConfig): [number, number, number, number] {
|
||||
return [ 0, 0, 48, 0 ];
|
||||
}
|
||||
|
||||
getButtonLabels(config?: ModalConfig): string[] {
|
||||
override getButtonLabels(_config?: ModalConfig): string[] {
|
||||
return [ i18next.t("menu:login"), i18next.t("menu:register")];
|
||||
}
|
||||
|
||||
getReadableErrorMessage(error: string): string {
|
||||
override getReadableErrorMessage(error: string): string {
|
||||
const colonIndex = error?.indexOf(":");
|
||||
if (colonIndex > 0) {
|
||||
error = error.slice(0, colonIndex);
|
||||
}
|
||||
switch (error) {
|
||||
case "invalid username":
|
||||
case this.ERR_USERNAME:
|
||||
return i18next.t("menu:invalidLoginUsername");
|
||||
case "invalid password":
|
||||
case this.ERR_PASSWORD:
|
||||
return i18next.t("menu:invalidLoginPassword");
|
||||
case "account doesn't exist":
|
||||
case this.ERR_ACCOUNT_EXIST:
|
||||
return i18next.t("menu:accountNonExistent");
|
||||
case "password doesn't match":
|
||||
case this.ERR_PASSWORD_MATCH:
|
||||
return i18next.t("menu:unmatchingPassword");
|
||||
case this.ERR_NO_SAVES:
|
||||
return i18next.t("menu:noSaves");
|
||||
case this.ERR_TOO_MANY_SAVES:
|
||||
return i18next.t("menu:tooManySaves");
|
||||
}
|
||||
|
||||
return super.getReadableErrorMessage(error);
|
||||
}
|
||||
|
||||
show(args: any[]): boolean {
|
||||
override show(args: any[]): boolean {
|
||||
if (super.show(args)) {
|
||||
|
||||
const config = args[0] as ModalConfig;
|
||||
@ -148,17 +155,16 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
clear() {
|
||||
override clear() {
|
||||
super.clear();
|
||||
this.externalPartyContainer.setVisible(false);
|
||||
this.infoContainer.setVisible(false);
|
||||
this.setMouseCursorStyle("default"); //reset cursor
|
||||
|
||||
this.discordImage.off("pointerdown");
|
||||
this.googleImage.off("pointerdown");
|
||||
this.usernameInfoImage.off("pointerdown");
|
||||
[this.discordImage, this.googleImage, this.usernameInfoImage].forEach((img) => img.off("pointerdown"));
|
||||
}
|
||||
|
||||
processExternalProvider(config: ModalConfig) : void {
|
||||
private processExternalProvider(config: ModalConfig) : void {
|
||||
this.externalPartyTitle.setText(i18next.t("menu:orUse") ?? "");
|
||||
this.externalPartyTitle.setX(20+this.externalPartyTitle.text.length);
|
||||
this.externalPartyTitle.setVisible(true);
|
||||
@ -205,6 +211,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||
label: dataKeys[i].replace(keyToFind, ""),
|
||||
handler: () => {
|
||||
this.scene.ui.revertMode();
|
||||
this.infoContainer.disableInteractive();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -213,8 +220,13 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||
options: options,
|
||||
delay: 1000
|
||||
});
|
||||
this.infoContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width, this.scene.game.canvas.height), Phaser.Geom.Rectangle.Contains);
|
||||
} else {
|
||||
return onFail("You have too many save files to use this");
|
||||
if (dataKeys.length > 2) {
|
||||
return onFail(this.ERR_TOO_MANY_SAVES);
|
||||
} else {
|
||||
return onFail(this.ERR_NO_SAVES);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -236,4 +248,21 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
|
||||
alpha: 1
|
||||
});
|
||||
}
|
||||
|
||||
private buildInteractableImage(texture: string, name: string, opts: BuildInteractableImageOpts = {}) {
|
||||
const {
|
||||
scale = 0.07,
|
||||
x = 0,
|
||||
y = 0,
|
||||
origin = { x: 0, y: 0 }
|
||||
} = opts;
|
||||
const img = this.scene.add.image(x, y, texture);
|
||||
img.setName(name);
|
||||
img.setOrigin(origin.x, origin.y);
|
||||
img.setScale(scale);
|
||||
img.setInteractive();
|
||||
this.addInteractionHoverEffect(img);
|
||||
|
||||
return img;
|
||||
}
|
||||
}
|
||||
|
@ -57,29 +57,35 @@ export abstract class ModalUiHandler extends UiHandler {
|
||||
|
||||
const buttonLabels = this.getButtonLabels();
|
||||
|
||||
const buttonTopMargin = this.getButtonTopMargin();
|
||||
|
||||
for (const label of buttonLabels) {
|
||||
const buttonLabel = addTextObject(this.scene, 0, 8, label, TextStyle.TOOLTIP_CONTENT);
|
||||
buttonLabel.setOrigin(0.5, 0.5);
|
||||
|
||||
const buttonBg = addWindow(this.scene, 0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN);
|
||||
buttonBg.setOrigin(0.5, 0);
|
||||
buttonBg.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonBg.width, buttonBg.height), Phaser.Geom.Rectangle.Contains);
|
||||
|
||||
const buttonContainer = this.scene.add.container(0, buttonTopMargin);
|
||||
|
||||
this.buttonBgs.push(buttonBg);
|
||||
this.buttonContainers.push(buttonContainer);
|
||||
|
||||
buttonContainer.add(buttonBg);
|
||||
buttonContainer.add(buttonLabel);
|
||||
this.modalContainer.add(buttonContainer);
|
||||
this.addButton(label);
|
||||
}
|
||||
|
||||
this.modalContainer.setVisible(false);
|
||||
}
|
||||
|
||||
private addButton(label: string) {
|
||||
const buttonTopMargin = this.getButtonTopMargin();
|
||||
const buttonLabel = addTextObject(this.scene, 0, 8, label, TextStyle.TOOLTIP_CONTENT);
|
||||
buttonLabel.setOrigin(0.5, 0.5);
|
||||
|
||||
const buttonBg = addWindow(this.scene, 0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN);
|
||||
buttonBg.setOrigin(0.5, 0);
|
||||
buttonBg.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonBg.width, buttonBg.height), Phaser.Geom.Rectangle.Contains);
|
||||
|
||||
const buttonContainer = this.scene.add.container(0, buttonTopMargin);
|
||||
|
||||
this.buttonBgs.push(buttonBg);
|
||||
this.buttonContainers.push(buttonContainer);
|
||||
|
||||
buttonContainer.add(buttonBg);
|
||||
buttonContainer.add(buttonLabel);
|
||||
|
||||
this.addInteractionHoverEffect(buttonBg);
|
||||
|
||||
this.modalContainer.add(buttonContainer);
|
||||
}
|
||||
|
||||
show(args: any[]): boolean {
|
||||
if (args.length >= 1 && "buttonActions" in args[0]) {
|
||||
super.show(args);
|
||||
@ -135,4 +141,20 @@ export abstract class ModalUiHandler extends UiHandler {
|
||||
|
||||
this.buttonBgs.map(bg => bg.off("pointerdown"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a hover effect to a game object which changes the cursor to a `pointer` and tints it slighly
|
||||
* @param gameObject the game object to add hover events/effects to
|
||||
*/
|
||||
protected addInteractionHoverEffect(gameObject: Phaser.GameObjects.Image | Phaser.GameObjects.NineSlice | Phaser.GameObjects.Sprite) {
|
||||
gameObject.on("pointerover", () => {
|
||||
this.setMouseCursorStyle("pointer");
|
||||
gameObject.setTint(0xbbbbbb);
|
||||
});
|
||||
|
||||
gameObject.on("pointerout", () => {
|
||||
this.setMouseCursorStyle("default");
|
||||
gameObject.clearTint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,15 @@ export default abstract class UiHandler {
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the style of the mouse cursor.
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor}
|
||||
* @param cursorStyle cursor style to apply
|
||||
*/
|
||||
protected setMouseCursorStyle(cursorStyle: "pointer" | "default") {
|
||||
this.scene.input.manager.canvas.style.cursor = cursorStyle;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.active = false;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import i18next from "i18next";
|
||||
import { MoneyFormat } from "#enums/money-format";
|
||||
import i18next from "i18next";
|
||||
|
||||
export const MissingTextureKey = "__MISSING";
|
||||
|
||||
@ -82,6 +82,12 @@ export function randInt(range: integer, min: integer = 0): integer {
|
||||
return Math.floor(Math.random() * range) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random number using the global seed, or the current battle's seed if called via `Battle.randSeedInt`
|
||||
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
|
||||
* @param min The minimum integer to pick, default `0`
|
||||
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
|
||||
*/
|
||||
export function randSeedInt(range: integer, min: integer = 0): integer {
|
||||
if (range <= 1) {
|
||||
return min;
|
||||
|
Loading…
Reference in New Issue
Block a user