Compare commits

...

8 Commits

Author SHA1 Message Date
Flashfyre
b9ce5cfaaa Mints permanently unlock natures 2024-05-07 00:58:36 -04:00
Madmadness65
fd0f21943e Implement Supersweet Syrup ability
Uses the new once per battle condition.
2024-05-06 23:47:20 -05:00
alpaca
36cde21ce3
Adds ribbon in starter screen if mon has beat classic mode (#370)
* adds the ribbon asset, hooking it up

* works if override. need to add field on server side I imagine

* moves count to starterData, increments on win

* formatting

* increment works properly

* recursively check for prevolution

* cleaned up to use getRootSpeciesId()

* changes ribbon to gold medal version

* adds Akuma's ribbon achievements

* ribbons increment correctly

* missed ui handler update

* reorder achievements

* ribbon correct, vouchers not. currently investigating

* increments properly, but voucher reward phase not appearing

* some cleanup

* works great, need to better reflect who is getting ribbon in message and cry

* plays level fanfare, tabling cry for now

* reran items.bat

* Minor fixes

---------

Co-authored-by: Flashfyre <flashfireex@gmail.com>
2024-05-07 00:44:41 -04:00
Xavion3
2d740f1952 Implement once per battle restriction on abilities
Currently just Intrepid Sword and Dauntless Shield
2024-05-07 00:39:16 -04:00
kenniky
52b546c924
add hazard removal for Rapid Spin, Defog, Tidy Up, Mortal Spin (#248)
* add hazard removal for Rapid Spin, Defog, Tidy Up, Mortal Spin

* Updated templating and fixed some errors

support for either pokemon using it
added better templating

* revert logic, and remove partial tags

---------

Co-authored-by: kenwang <kenwang@umich.edu>
Co-authored-by: Gwen Valentine <gwenvalentine@starshine.dev>
2024-05-06 22:00:31 -05:00
Alvin Zou
0beb3a0f89
Show number of Pokeballs in inventory when selecting a pokeball in the reward phase (#573)
* Show number of Pokeballs in inventory when selecting a pokeball in the reward phase

* Address comments
2024-05-06 21:43:01 -05:00
Benjamin Odom
f18ff5b6cc
Update Learnsets for Gen3 to Indigo Disk (#571)
* Updated Learnset of Gen1 to Indigo Disc

Not many changes here for how much time it takes to comb through, but here it is.

Only notable change is Power-Up Punch got removed this gen so it had to be removed from Hitmonchan's learnset. Are we okay with this change? If so, this is good to go for Gen 1.

* Updated Learnset of Gen2 to Indigo Disc

Notable changes:

Porygon 2 loses Magic Coat with no replacement.

Ho-oh loses Burn Up but gains Overheat.

The rest seems better or neutral.

* Update Learnsets for Gen3 to Indigo Disk

A good chunk of these changes is just reordering the moves to match bulbapedia so that it's easier to see what changed in the future. Otherwise, it's really just replacing moves that don't exist anymore.

Also, Zangoose got a ton of new moves that work with a move relearner, but not by level-up. Weird, but good if you like Zangoose.

* Fix Duplicates
2024-05-06 19:54:02 -05:00
Reldnahc
84f6f06ca4 add override to force double battles.
add override for passive abilities.
2024-05-06 19:11:34 -04:00
24 changed files with 5908 additions and 5606 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

View File

@ -59,7 +59,7 @@ import { SceneBase } from './scene-base';
import CandyBar from './ui/candy-bar';
import { Variant, variantData } from './data/variant';
import { Localizable } from './plugins/i18n';
import { STARTING_WAVE_OVERRIDE, OPP_SPECIES_OVERRIDE, SEED_OVERRIDE, STARTING_BIOME_OVERRIDE } from './overrides';
import { STARTING_WAVE_OVERRIDE, OPP_SPECIES_OVERRIDE, SEED_OVERRIDE, STARTING_BIOME_OVERRIDE, DOUBLE_BATTLE_OVERRIDE } from './overrides';
import {InputsController} from "./inputs-controller";
import {UiInputs} from "./ui-inputs";
@ -842,6 +842,9 @@ export default class BattleScene extends SceneBase {
} else if (!battleConfig)
newDouble = !!double;
if (DOUBLE_BATTLE_OVERRIDE)
newDouble = true;
const lastBattle = this.currentBattle;
if (lastBattle?.double && !newDouble)

View File

@ -1826,6 +1826,19 @@ function getAnticipationCondition(): AbAttrCondition {
};
}
/**
* Creates an ability condition that causes the ability to fail if that ability
* has already been used by that pokemon that battle. It requires an ability to
* be specified due to current limitations in how conditions on abilities work.
* @param {Abilities} ability The ability to check if it's already been applied
* @returns {AbAttrCondition} The condition
*/
function getOncePerBattleCondition(ability: Abilities): AbAttrCondition {
return (pokemon: Pokemon) => {
return !pokemon.battleData?.abilitiesApplied.includes(ability);
}
}
export class ForewarnAbAttr extends PostSummonAbAttr {
constructor() {
super(true);
@ -2522,6 +2535,9 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
return applyNextAbAttr();
pokemon.scene.setPhaseQueueSplice();
const onApplySuccess = () => {
if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(ability.id)) {
pokemon.battleData.abilitiesApplied.push(ability.id);
}
if (attr.showAbility && !quiet) {
if (showAbilityInstant)
pokemon.scene.abilityBar.showAbility(pokemon, passive);
@ -3379,9 +3395,11 @@ export function initAbilities() {
new Ability(Abilities.NEUROFORCE, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25),
new Ability(Abilities.INTREPID_SWORD, 8)
.attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true),
.attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true)
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
new Ability(Abilities.DAUNTLESS_SHIELD, 8)
.attr(PostSummonStatChangeAbAttr, BattleStat.DEF, 1, true),
.attr(PostSummonStatChangeAbAttr, BattleStat.DEF, 1, true)
.condition(getOncePerBattleCondition(Abilities.DAUNTLESS_SHIELD)),
new Ability(Abilities.LIBERO, 8)
.unimplemented(),
new Ability(Abilities.BALL_FETCH, 8)
@ -3590,7 +3608,8 @@ export function initAbilities() {
.attr(IgnoreTypeImmunityAbAttr, Type.GHOST, [Type.NORMAL, Type.FIGHTING])
.ignorable(), // TODO: evasiveness bypass should not be ignored, but accuracy immunity should
new Ability(Abilities.SUPERSWEET_SYRUP, 9)
.unimplemented(),
.attr(PostSummonStatChangeAbAttr, BattleStat.EVA, -1)
.condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)),
new Ability(Abilities.HOSPITALITY, 9)
.attr(PostSummonAllyHealAbAttr, 4, true),
new Ability(Abilities.TOXIC_CHAIN, 9)

View File

@ -2961,6 +2961,42 @@ export class AddArenaTrapTagAttr extends AddArenaTagAttr {
}
}
export class RemoveArenaTrapAttr extends MoveEffectAttr {
private targetBothSides: boolean;
constructor(targetBothSides: boolean = false) {
super(true, MoveEffectTrigger.PRE_APPLY);
this.targetBothSides = targetBothSides;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(user, target, move, args))
return false;
if(this.targetBothSides){
user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.PLAYER);
user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.PLAYER);
user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.PLAYER);
user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER);
user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY);
user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY);
user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.ENEMY);
user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.ENEMY);
}
else {
user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
}
return true;
}
}
export class RemoveScreensAttr extends MoveEffectAttr {
private targetBothSides: boolean;
@ -4602,7 +4638,7 @@ export function initMoves() {
BattlerTagType.SEEDED,
BattlerTagType.INFESTATION
], true)
.partial(),
.attr(RemoveArenaTrapAttr),
new StatusMove(Moves.SWEET_SCENT, Type.NORMAL, 100, 20, -1, 0, 2)
.attr(StatChangeAttr, BattleStat.EVA, -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
@ -5144,7 +5180,8 @@ export function initMoves() {
.attr(StatChangeAttr, BattleStat.EVA, -1)
.attr(ClearWeatherAttr, WeatherType.FOG)
.attr(ClearTerrainAttr)
.attr(RemoveScreensAttr, true),
.attr(RemoveScreensAttr, false)
.attr(RemoveArenaTrapAttr, true),
new StatusMove(Moves.TRICK_ROOM, Type.PSYCHIC, -1, 5, -1, -7, 4)
.attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5)
.ignoresProtect()
@ -6444,6 +6481,7 @@ export function initMoves() {
BattlerTagType.INFESTATION
], true)
.attr(StatusEffectAttr, StatusEffect.POISON)
.attr(RemoveArenaTrapAttr)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.DOODLE, Type.NORMAL, 100, 10, -1, 0, 9)
.attr(AbilityCopyAttr, true),
@ -6487,7 +6525,7 @@ export function initMoves() {
.target(MoveTarget.BOTH_SIDES),
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, 100, 0, 9)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPD ], 1, true)
.partial(),
.attr(RemoveArenaTrapAttr),
new StatusMove(Moves.SNOWSCAPE, Type.ICE, -1, 10, -1, 0, 9)
.attr(WeatherChangeAttr, WeatherType.SNOW)
.target(MoveTarget.BOTH_SIDES),

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,7 @@ import { Nature, getNatureStatMultiplier } from '../data/nature';
import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from '../data/pokemon-forms';
import { TerrainType } from '../data/terrain';
import { TrainerSlot } from '../data/trainer-config';
import { ABILITY_OVERRIDE, MOVE_OVERRIDE, MOVE_OVERRIDE_2, OPP_ABILITY_OVERRIDE, OPP_MOVE_OVERRIDE, OPP_MOVE_OVERRIDE_2, OPP_SHINY_OVERRIDE, OPP_VARIANT_OVERRIDE } from '../overrides';
import { ABILITY_OVERRIDE, MOVE_OVERRIDE, MOVE_OVERRIDE_2, OPP_ABILITY_OVERRIDE, OPP_MOVE_OVERRIDE, OPP_MOVE_OVERRIDE_2, OPP_PASSIVE_ABILITY_OVERRIDE, OPP_SHINY_OVERRIDE, OPP_VARIANT_OVERRIDE, PASSIVE_ABILITY_OVERRIDE } from '../overrides';
import { BerryType } from '../data/berry';
import i18next from '../plugins/i18n';
@ -811,6 +811,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
getPassiveAbility(): Ability {
if (PASSIVE_ABILITY_OVERRIDE && this.isPlayer())
return allAbilities[PASSIVE_ABILITY_OVERRIDE];
if (OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer())
return allAbilities[OPP_PASSIVE_ABILITY_OVERRIDE];
let starterSpeciesId = this.species.speciesId;
while (pokemonPrevolutions.hasOwnProperty(starterSpeciesId))
starterSpeciesId = pokemonPrevolutions[starterSpeciesId];
@ -818,6 +823,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
hasPassive(): boolean {
if ((PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && this.isPlayer()) || (OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer()))
return true;
return this.passive || this.isBoss();
}
@ -1701,18 +1708,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
});
}
cry(soundConfig?: Phaser.Types.Sound.SoundConfig): AnySound {
const cry = this.getSpeciesForm().cry(this.scene, soundConfig);
cry(soundConfig?: Phaser.Types.Sound.SoundConfig, sceneOverride?: BattleScene): AnySound {
const scene = sceneOverride || this.scene;
const cry = this.getSpeciesForm().cry(scene, soundConfig);
let duration = cry.totalDuration * 1000;
if (this.fusionSpecies) {
let fusionCry = this.getFusionSpeciesForm().cry(this.scene, soundConfig, true);
let fusionCry = this.getFusionSpeciesForm().cry(scene, soundConfig, true);
duration = Math.min(duration, fusionCry.totalDuration * 1000);
fusionCry.destroy();
this.scene.time.delayedCall(Utils.fixedInt(Math.ceil(duration * 0.4)), () => {
scene.time.delayedCall(Utils.fixedInt(Math.ceil(duration * 0.4)), () => {
try {
SoundFade.fadeOut(this.scene, cry, Utils.fixedInt(Math.ceil(duration * 0.2)));
fusionCry = this.getFusionSpeciesForm().cry(this.scene, Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig));
SoundFade.fadeIn(this.scene, fusionCry, Utils.fixedInt(Math.ceil(duration * 0.2)), this.scene.masterVolume * this.scene.seVolume, 0);
SoundFade.fadeOut(scene, cry, Utils.fixedInt(Math.ceil(duration * 0.2)));
fusionCry = this.getFusionSpeciesForm().cry(scene, Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig));
SoundFade.fadeIn(scene, fusionCry, Utils.fixedInt(Math.ceil(duration * 0.2)), scene.masterVolume * scene.seVolume, 0);
} catch (err) {
console.error(err);
}
@ -3126,6 +3134,7 @@ export class PokemonBattleData {
public hitCount: integer = 0;
public endured: boolean = false;
public berriesEaten: BerryType[] = [];
public abilitiesApplied: Abilities[] = [];
}
export class PokemonBattleSummonData {

View File

@ -77,6 +77,7 @@ export class LoadingScene extends SceneBase {
this.loadImage('shiny_star_small_1', 'ui', 'shiny_small_1.png');
this.loadImage('shiny_star_small_2', 'ui', 'shiny_small_2.png');
this.loadImage('ha_capsule', 'ui', 'ha_capsule.png');
this.loadImage('champion_ribbon', 'ui', 'champion_ribbon.png');
this.loadImage('icon_spliced', 'ui');
this.loadImage('icon_tera', 'ui');
this.loadImage('type_tera', 'ui');

View File

@ -144,9 +144,16 @@ class AddPokeballModifierType extends ModifierType implements Localizable {
}
localize(): void {
// TODO: Actually use i18n to localize this description.
this.name = `${this.count}x ${getPokeballName(this.pokeballType)}`;
this.description = `Receive ${getPokeballName(this.pokeballType)} x${this.count}\nCatch Rate: ${getPokeballCatchMultiplier(this.pokeballType) > -1 ? `${getPokeballCatchMultiplier(this.pokeballType)}x` : 'Certain'}`;
this.description = `Receive ${getPokeballName(this.pokeballType)} x${this.count} (Inventory: {AMOUNT}) \nCatch Rate: ${getPokeballCatchMultiplier(this.pokeballType) > -1 ? `${getPokeballCatchMultiplier(this.pokeballType)}x` : 'Certain'}`;
}
getDescription(scene: BattleScene): string {
this.localize();
return this.description.replace('{AMOUNT}', scene.pokeballCounts[this.pokeballType].toString());
}
}
class AddVoucherModifierType extends ModifierType {
@ -299,7 +306,7 @@ export class PokemonNatureChangeModifierType extends PokemonModifierType {
protected nature: Nature;
constructor(nature: Nature) {
super(`${getNatureName(nature)} Mint`, `Changes a Pokémon\'s nature to ${getNatureName(nature, true, true, true)}`, ((_type, args) => new Modifiers.PokemonNatureChangeModifier(this, (args[0] as PlayerPokemon).id, this.nature)),
super(`${getNatureName(nature)} Mint`, `Changes a Pokémon\'s nature to ${getNatureName(nature, true, true, true)} and permanently unlocks the nature for the starter.`, ((_type, args) => new Modifiers.PokemonNatureChangeModifier(this, (args[0] as PlayerPokemon).id, this.nature)),
((pokemon: PlayerPokemon) => {
if (pokemon.getNature() === this.nature)
return PartyUiHandler.NoEffectMessage;

View File

@ -8,15 +8,14 @@ import { Stat } from "../data/pokemon-stat";
import { addTextObject, TextStyle } from "../ui/text";
import { Type } from '../data/type';
import { EvolutionPhase } from '../evolution-phase';
import { FusionSpeciesFormEvolution, pokemonEvolutions } from '../data/pokemon-evolutions';
import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from '../data/pokemon-evolutions';
import { getPokemonMessage } from '../messages';
import * as Utils from "../utils";
import { TempBattleStat } from '../data/temp-battle-stat';
import { BerryType, getBerryEffectFunc, getBerryPredicate } from '../data/berry';
import { StatusEffect, getStatusEffectHealText } from '../data/status-effect';
import { MoneyAchv, achvs } from '../system/achv';
import { achvs } from '../system/achv';
import { VoucherType } from '../system/voucher';
import { PreventBerryUseAbAttr, applyAbAttrs } from '../data/ability';
import { FormChangeItem, SpeciesFormChangeItemTrigger } from '../data/pokemon-forms';
import { Nature } from '#app/data/nature';
import { BattlerTagType } from '#app/data/enums/battler-tag-type';
@ -1090,6 +1089,13 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier {
apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon;
pokemon.natureOverride = this.nature;
let speciesId = pokemon.species.speciesId;
pokemon.scene.gameData.dexData[speciesId].natureAttr |= Math.pow(2, this.nature + 1);
while (pokemonPrevolutions.hasOwnProperty(speciesId)) {
speciesId = pokemonPrevolutions[speciesId];
pokemon.scene.gameData.dexData[speciesId].natureAttr |= Math.pow(2, this.nature + 1);
}
return true;
}

View File

@ -12,12 +12,15 @@ export const STARTING_WAVE_OVERRIDE = 0;
export const STARTING_BIOME_OVERRIDE = Biome.TOWN;
export const STARTING_MONEY_OVERRIDE = 0;
export const WEATHER_OVERRIDE = WeatherType.NONE;
export const DOUBLE_BATTLE_OVERRIDE = false;
export const ABILITY_OVERRIDE = Abilities.NONE;
export const PASSIVE_ABILITY_OVERRIDE = Abilities.NONE;
export const MOVE_OVERRIDE = Moves.NONE;
export const MOVE_OVERRIDE_2 = Moves.NONE;
export const OPP_SPECIES_OVERRIDE = 0;
export const OPP_ABILITY_OVERRIDE = Abilities.NONE;
export const OPP_PASSIVE_ABILITY_OVERRIDE = Abilities.NONE;
export const OPP_MOVE_OVERRIDE = Moves.NONE;
export const OPP_MOVE_OVERRIDE_2 = Moves.NONE;

View File

@ -1,4 +1,4 @@
import BattleScene, { bypassLogin, startingWave } from "./battle-scene";
import BattleScene, { AnySound, bypassLogin, startingWave } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
import * as Utils from './utils';
import { Moves } from "./data/enums/moves";
@ -55,7 +55,7 @@ import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select
import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler";
import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run";
import { GameModes, gameModes } from "./game-mode";
import { getPokemonSpecies, speciesStarters } from "./data/pokemon-species";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./data/pokemon-species";
import i18next from './plugins/i18n';
import { Abilities } from "./data/enums/abilities";
import { STARTER_FORM_OVERRIDE, STARTER_SPECIES_OVERRIDE } from './overrides';
@ -3472,8 +3472,40 @@ export class GameOverModifierRewardPhase extends ModifierRewardPhase {
}
}
export class RibbonModifierRewardPhase extends ModifierRewardPhase {
private species: PokemonSpecies;
constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) {
super(scene, modifierTypeFunc);
this.species = species;
}
doReward(): Promise<void> {
return new Promise<void>(resolve => {
const newModifier = this.modifierType.newModifier();
this.scene.addModifier(newModifier).then(() => {
this.scene.gameData.saveSystem().then(success => {
if (success) {
this.scene.playSound('level_up_fanfare');
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.arenaBg.setVisible(false);
this.scene.ui.fadeIn(250).then(() => {
this.scene.ui.showText(`${this.species.name} beat ${this.scene.gameMode.getName()} Mode for the first time!\nYou received ${newModifier.type.name}!`, null, () => {
resolve();
}, null, true, 1500);
});
} else
this.scene.reset(true);
});
});
})
}
}
export class GameOverPhase extends BattlePhase {
private victory: boolean;
private firstRibbons: PokemonSpecies[] = [];
constructor(scene: BattleScene, victory?: boolean) {
super(scene);
@ -3525,6 +3557,13 @@ export class GameOverPhase extends BattlePhase {
if (this.scene.gameMode.isClassic) {
firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY);
this.scene.gameData.gameStats.sessionsWon++;
for (let pokemon of this.scene.getParty()) {
this.awardRibbon(pokemon);
if (pokemon.species.getRootSpeciesId() != pokemon.species.getRootSpeciesId(true)) {
this.awardRibbon(pokemon, true);
}
}
} else if (this.scene.gameMode.isDaily && success[1])
this.scene.gameData.gameStats.dailyRunSessionsWon++;
}
@ -3536,8 +3575,11 @@ export class GameOverPhase extends BattlePhase {
this.scene.clearPhaseQueue();
this.scene.ui.clearText();
this.handleUnlocks();
if (this.victory && !firstClear && success[1])
if (this.victory && !firstClear && success[1]) {
for (let species of this.firstRibbons)
this.scene.unshiftPhase(new RibbonModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PLUS, species));
this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM));
}
this.scene.reset();
this.scene.unshiftPhase(new TitlePhase(this.scene));
this.end();
@ -3556,6 +3598,15 @@ export class GameOverPhase extends BattlePhase {
this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.MINI_BLACK_HOLE));
}
}
awardRibbon(pokemon: Pokemon, forStarter: boolean = false): void {
const speciesId = getPokemonSpecies(pokemon.species.speciesId)
const speciesRibbonCount = this.scene.gameData.incrementRibbonCount(speciesId, forStarter);
// first time classic win, award voucher
if (speciesRibbonCount === 1) {
this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter)));
}
}
}
export class UnlockPhase extends Phase {

View File

@ -51,9 +51,9 @@ export class Achv {
}
getTier(): AchvTier {
if (this.score >= 150)
return AchvTier.MASTER;
if (this.score >= 100)
return AchvTier.MASTER;
if (this.score >= 75)
return AchvTier.ROGUE;
if (this.score >= 50)
return AchvTier.ULTRA;
@ -73,6 +73,16 @@ export class MoneyAchv extends Achv {
}
}
export class RibbonAchv extends Achv {
private ribbonAmount: integer;
constructor(name: string, ribbonAmount: integer, iconImage: string, score: integer) {
super(name, `Accumulate a total of ${ribbonAmount.toLocaleString('en-US')} Ribbons`, iconImage, score, (scene: BattleScene, _args: any[]) => scene.gameData.gameStats.ribbonsOwned >= this.ribbonAmount);
this.ribbonAmount = ribbonAmount;
}
}
export class DamageAchv extends Achv {
private damageAmount: integer;
@ -125,6 +135,11 @@ export const achvs = {
LV_100: new LevelAchv('But Wait, There\'s More!', 100, 'rare_candy', 25).setSecret(),
LV_250: new LevelAchv('Elite', 250, 'rarer_candy', 50).setSecret(true),
LV_1000: new LevelAchv('To Go Even Further Beyond', 1000, 'candy_jar', 100).setSecret(true),
_10_RIBBONS: new RibbonAchv('Pokémon League Champion', 10, 'bronze_ribbon', 10),
_25_RIBBONS: new RibbonAchv('Great League Champion', 25, 'great_ribbon', 25).setSecret(true),
_50_RIBBONS: new RibbonAchv('Ultra League Champion', 50, 'ultra_ribbon', 50).setSecret(true),
_75_RIBBONS: new RibbonAchv('Rogue League Champion', 75, 'rogue_ribbon', 75).setSecret(true),
_100_RIBBONS: new RibbonAchv('Master League Champion', 100, 'master_ribbon', 100).setSecret(true),
TRANSFER_MAX_BATTLE_STAT: new Achv('Teamwork', 'Baton pass to another party member with at least one stat maxed out', 'stick', 20),
MAX_FRIENDSHIP: new Achv('Friendmaxxing', 'Reach max friendship on a Pokémon', 'soothe_bell', 25),
MEGA_EVOLVE: new Achv('Megamorph', 'Mega evolve a Pokémon', 'mega_bracelet', 50),

View File

@ -173,6 +173,7 @@ export interface StarterDataEntry {
abilityAttr: integer;
passiveAttr: integer;
valueReduction: integer;
classicWinCount: integer;
}
export interface StarterData {
@ -194,7 +195,8 @@ const systemShortKeys = {
eggMoves: '$em',
candyCount: '$x',
passive: '$p',
valueReduction: '$vr'
valueReduction: '$vr',
classicWinCount: '$wc'
};
export class GameData {
@ -995,7 +997,8 @@ export class GameData {
friendship: 0,
abilityAttr: defaultStarterSpecies.includes(speciesId) ? AbilityAttr.ABILITY_1 : 0,
passiveAttr: 0,
valueReduction: 0
valueReduction: 0,
classicWinCount: 0
};
}
@ -1089,6 +1092,32 @@ export class GameData {
});
}
incrementRibbonCount(species: PokemonSpecies, forStarter: boolean = false): integer {
const speciesIdToIncrement: Species = species.getRootSpeciesId(forStarter);
if (!this.starterData[speciesIdToIncrement].classicWinCount) {
this.starterData[speciesIdToIncrement].classicWinCount = 0;
}
if (!this.starterData[speciesIdToIncrement].classicWinCount)
this.scene.gameData.gameStats.ribbonsOwned++;
const ribbonsInStats: integer = this.scene.gameData.gameStats.ribbonsOwned;
if (ribbonsInStats >= 100)
this.scene.validateAchv(achvs._100_RIBBONS);
if (ribbonsInStats >= 75)
this.scene.validateAchv(achvs._75_RIBBONS);
if (ribbonsInStats >= 50)
this.scene.validateAchv(achvs._50_RIBBONS);
if (ribbonsInStats >= 25)
this.scene.validateAchv(achvs._25_RIBBONS);
if (ribbonsInStats >= 10)
this.scene.validateAchv(achvs._10_RIBBONS);
return ++this.starterData[speciesIdToIncrement].classicWinCount;
}
addStarterCandy(species: PokemonSpecies, count: integer): void {
this.scene.candyBar.showStarterSpeciesCandy(species.speciesId, count);
this.starterData[species.speciesId].candyCount += count;

View File

@ -6,6 +6,7 @@ export class GameStats {
public battles: integer;
public classicSessionsPlayed: integer;
public sessionsWon: integer;
public ribbonsOwned: integer;
public dailyRunSessionsPlayed: integer;
public dailyRunSessionsWon: integer;
public endlessSessionsPlayed: integer;
@ -43,6 +44,7 @@ export class GameStats {
this.battles = source?.battles || 0;
this.classicSessionsPlayed = source?.classicSessionsPlayed || 0;
this.sessionsWon = source?.sessionsWon || 0;
this.ribbonsOwned = source?.ribbonsOwned || 0;
this.dailyRunSessionsPlayed = source?.dailyRunSessionsPlayed || 0;
this.dailyRunSessionsWon = source?.dailyRunSessionsWon || 0;
this.endlessSessionsPlayed = source?.endlessSessionsPlayed || 0;

View File

@ -51,6 +51,7 @@ const displayStats: DisplayStats = {
return `${caughtCount} (${Math.floor((caughtCount / Object.keys(gameData.dexData).length) * 1000) / 10}%)`;
}
},
ribbonsOwned: 'Ribbons Owned',
classicSessionsPlayed: 'Classic Runs',
sessionsWon: 'Classic Wins',
dailyRunSessionsPlayed: 'Daily Run Attempts',

View File

@ -174,6 +174,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private starterValueLabels: Phaser.GameObjects.Text[];
private shinyIcons: Phaser.GameObjects.Image[][];
private hiddenAbilityIcons: Phaser.GameObjects.Image[];
private classicWinIcons: Phaser.GameObjects.Image[];
private iconAnimHandler: PokemonIconAnimHandler;
@ -410,6 +411,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return ret;
});
this.classicWinIcons = new Array(81).fill(null).map((_, i) => {
const x = (i % 9) * 18;
const y = Math.floor(i / 9) * 18;
const ret = this.scene.add.image(x + 152, y + 16, 'champion_ribbon');
ret.setOrigin(0, 0);
ret.setScale(0.5);
ret.setVisible(false);
this.starterSelectContainer.add(ret);
return ret;
});
this.pokemonSprite = this.scene.add.sprite(53, 63, `pkmn__sub`);
this.pokemonSprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
this.starterSelectContainer.add(this.pokemonSprite);
@ -1192,6 +1204,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.shinyIcons[s][v].setTint(getVariantTint(speciesVariants[v] === DexAttr.DEFAULT_VARIANT ? 0 : speciesVariants[v] === DexAttr.VARIANT_2 ? 1 : 2));
}
this.hiddenAbilityIcons[s].setVisible(slotVisible && !!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4));
this.classicWinIcons[s].setVisible(slotVisible && this.scene.gameData.starterData[speciesId].classicWinCount > 0);
}
} else {
changed = super.setCursor(cursor);