mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-19 22:09:27 +02:00
Compare commits
86 Commits
b5669a12fb
...
7b56e5b90d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b56e5b90d | ||
|
|
8e61b642a3 | ||
|
|
8f546ce4bb | ||
|
|
2bbc7cfdaf | ||
|
|
da7903ab92 | ||
|
|
70e7f8b4d4 | ||
|
|
b2990aaa15 | ||
|
|
ee4950633e | ||
|
|
30058ed70e | ||
|
|
140e4ab142 | ||
|
|
76d8357d0b | ||
|
|
f42237d415 | ||
|
|
b44f0a4176 | ||
|
|
076ef81691 | ||
|
|
23271901cf | ||
|
|
1517e0512e | ||
|
|
6133e3c39f | ||
|
|
0da37a0f0c | ||
|
|
907e3c8208 | ||
|
|
cb3ae4ab87 | ||
|
|
6c0253ada4 | ||
|
|
b263ff7ee4 | ||
|
|
e4f236c73a | ||
|
|
1e8f06da03 | ||
|
|
238691c8bf | ||
|
|
2e3146912a | ||
|
|
3f3bbf7d85 | ||
|
|
f5d08567c9 | ||
|
|
c6b4f273d0 | ||
|
|
3c8ba17cbf | ||
|
|
0d4439630d | ||
|
|
f83eb054f4 | ||
|
|
949467f9f4 | ||
|
|
07dad0981d | ||
|
|
6f0fb543ac | ||
|
|
b0680fd9ad | ||
|
|
a8e4f76a4f | ||
|
|
6660e33836 | ||
|
|
2aca187c66 | ||
|
|
acc12318d7 | ||
|
|
dac9e202a0 | ||
|
|
ad26adf426 | ||
|
|
5e7a5a7a94 | ||
|
|
e50296d14c | ||
|
|
466c4aede2 | ||
|
|
d3f2659cdf | ||
|
|
985d352be8 | ||
|
|
ccd968d5b8 | ||
|
|
e1edb87990 | ||
|
|
315d4cf408 | ||
|
|
97e5ab882a | ||
|
|
4ed0fd0384 | ||
|
|
c7a1b0fac5 | ||
|
|
b8324e85f7 | ||
|
|
e034d7e04e | ||
|
|
eaead476e8 | ||
|
|
e7bb3a7443 | ||
|
|
d411702716 | ||
|
|
8118d00a07 | ||
|
|
ff5d891c6e | ||
|
|
882c256933 | ||
|
|
4494debaa0 | ||
|
|
c697a47f5b | ||
|
|
cd2f0b8a6e | ||
|
|
414a6b10ea | ||
|
|
9925b0c358 | ||
|
|
0d2bae0fcc | ||
|
|
5b14a5899c | ||
|
|
2344bdf32a | ||
|
|
88634ca081 | ||
|
|
6fc7db8939 | ||
|
|
ee2412cafb | ||
|
|
e312a4b8f2 | ||
|
|
dbfbc24c8a | ||
|
|
849550808a | ||
|
|
f2e9ea0e93 | ||
|
|
2744567b21 | ||
|
|
175f2b74e9 | ||
|
|
61cd122223 | ||
|
|
d0ebb32f9e | ||
|
|
fee6320502 | ||
|
|
d576d66617 | ||
|
|
79779765e2 | ||
|
|
e64117d6a6 | ||
|
|
1f77eb4dff | ||
|
|
7e2418b957 |
@ -81,7 +81,7 @@ For example, here is how you could test a scenario where the player Pokemon has
|
||||
```typescript
|
||||
const overrides = {
|
||||
ABILITY_OVERRIDE: AbilityId.DROUGHT,
|
||||
OPP_MOVESET_OVERRIDE: MoveId.WATER_GUN,
|
||||
ENEMY_MOVESET_OVERRIDE: MoveId.WATER_GUN,
|
||||
} satisfies Partial<InstanceType<typeof DefaultOverrides>>;
|
||||
```
|
||||
|
||||
|
||||
@ -90,9 +90,13 @@ If this feature requires new text, the text should be integrated into the code w
|
||||
- For any feature pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), it's best practice to include a source link for any added text.
|
||||
[Poké Corpus](https://abcboy101.github.io/poke-corpus/) is a great resource for finding text from the mainline games; otherwise, a video/picture showing the text being displayed should suffice.
|
||||
- You should also [notify the current Head of Translation](#notifying-translation) to ensure a fast response.
|
||||
3. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
|
||||
4. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
|
||||
5. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
|
||||
3. Your locales should use the following format:
|
||||
- File names should be in `kebab-case`. Example: `trainer-names.json`
|
||||
- Key names should be in `camelCase`. Example: `aceTrainer`
|
||||
- If you make use of i18next's inbuilt [context support](https://www.i18next.com/translation-function/context), you need to use `snake_case` for the context key. Example: `aceTrainer_male`
|
||||
4. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
|
||||
5. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
|
||||
6. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
|
||||
|
||||
[^2]: For those wondering, the reason for choosing English specifically is due to it being the master language set in Pontoon (the program used by the Translation Team to perform locale updates).
|
||||
If a key is present in any language _except_ the master language, it won't appear anywhere else in the translation tool, rendering missing English keys quite a hassle.
|
||||
|
||||
@ -183,7 +183,7 @@ input:-internal-autofill-selected {
|
||||
/* Show #apadStats only in battle and shop */
|
||||
#touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not(
|
||||
[data-ui-mode="BALL"]
|
||||
):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="MODIFIER_SELECT"])
|
||||
):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="REWARD_SELECT"])
|
||||
#apadStats {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@ls-lint/ls-lint": "2.3.1",
|
||||
"@types/crypto-js": "^4.2.0",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^22.16.5",
|
||||
"@vitest/coverage-istanbul": "^3.2.4",
|
||||
|
||||
@ -48,6 +48,9 @@ importers:
|
||||
'@ls-lint/ls-lint':
|
||||
specifier: 2.3.1
|
||||
version: 2.3.1
|
||||
'@types/crypto-js':
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.2
|
||||
'@types/jsdom':
|
||||
specifier: ^21.1.7
|
||||
version: 21.1.7
|
||||
@ -718,6 +721,9 @@ packages:
|
||||
'@types/cookie@0.6.0':
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
|
||||
'@types/crypto-js@4.2.2':
|
||||
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
@ -2525,6 +2531,8 @@ snapshots:
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
'@types/crypto-js@4.2.2': {}
|
||||
|
||||
'@types/deep-eql@4.0.2': {}
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
public/images/ui/champion_ribbon_emerald.png
Normal file
BIN
public/images/ui/champion_ribbon_emerald.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 B |
BIN
public/images/ui/legacy/champion_ribbon_emerald.png
Normal file
BIN
public/images/ui/legacy/champion_ribbon_emerald.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 B |
Binary file not shown.
|
Before Width: | Height: | Size: 837 B After Width: | Height: | Size: 799 B |
146
public/images/ui/party_slot_main_short.json
Normal file
146
public/images/ui/party_slot_main_short.json
Normal file
@ -0,0 +1,146 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "party_slot_main_short.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 110,
|
||||
"h": 294
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "party_slot_main_short",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_sel",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 41,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_fnt",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 82,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_fnt_sel",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 123,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_swap",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 164,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "party_slot_main_short_swap_sel",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 205,
|
||||
"w": 110,
|
||||
"h": 41
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:29685f2f538901cf5bf7f0ed2ea867c3:a080ea6c8cccd1e03244214053e79796:565f7afc5ca419b6ba8dbce51ea30818$"
|
||||
}
|
||||
}
|
||||
BIN
public/images/ui/party_slot_main_short.png
Normal file
BIN
public/images/ui/party_slot_main_short.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
51
scripts/create-test/boilerplates/rewards/reward.ts
Normal file
51
scripts/create-test/boilerplates/rewards/reward.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { getHeldItemCategory, HeldItemCategoryId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { HeldItemReward } from "#items/reward";
|
||||
import { GameManager } from "#test/test-utils/game-manager";
|
||||
import { generateRewardForTest } from "#test/test-utils/reward-test-utils";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("{{description}}", () => {
|
||||
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
|
||||
.ability(AbilityId.BALL_FETCH)
|
||||
.battleStyle("single")
|
||||
.criticalHits(false)
|
||||
.enemySpecies(SpeciesId.MAGIKARP)
|
||||
.enemyAbility(AbilityId.BALL_FETCH)
|
||||
.enemyMoveset(MoveId.SPLASH)
|
||||
.startingLevel(100)
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
it("should do XYZ when applied", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||
|
||||
const feebas = game.field.getPlayerPokemon();
|
||||
|
||||
const reward = generateRewardForTest(RewardId.BERRY)!;
|
||||
expect(reward).toBeInstanceOf(HeldItemReward); // Replace with actual reward instance
|
||||
expect(getHeldItemCategory(reward["itemId"])).toBe(HeldItemCategoryId.BERRY);
|
||||
game.scene.applyReward(reward, { pokemon: feebas });
|
||||
|
||||
expect(feebas).toHaveHeldItem(HeldItemCategoryId.BERRY);
|
||||
});
|
||||
});
|
||||
@ -101,8 +101,8 @@ async function promptFileName(selectedType) {
|
||||
*/
|
||||
function getBoilerplatePath(choiceType) {
|
||||
switch (choiceType) {
|
||||
// case "Reward":
|
||||
// return path.join(__dirname, "boilerplates/reward.ts");
|
||||
case "Reward":
|
||||
return path.join(__dirname, "boilerplates/rewards/reward.ts");
|
||||
default:
|
||||
return path.join(__dirname, "boilerplates/default.ts");
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { RibbonData } from "#system/ribbons/ribbon-data";
|
||||
|
||||
export interface DexData {
|
||||
[key: number]: DexEntry;
|
||||
}
|
||||
@ -10,4 +12,5 @@ export interface DexEntry {
|
||||
caughtCount: number;
|
||||
hatchedCount: number;
|
||||
ivs: number[];
|
||||
ribbons: RibbonData;
|
||||
}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
|
||||
export interface HeldModifierConfig {
|
||||
modifier: PokemonHeldItemModifierType | PokemonHeldItemModifier;
|
||||
stackCount?: number;
|
||||
isTransferable?: boolean;
|
||||
}
|
||||
@ -103,3 +103,12 @@ export type CoerceNullPropertiesToUndefined<T extends object> = {
|
||||
* @typeParam T - The type to render partial
|
||||
*/
|
||||
export type AtLeastOne<T extends object> = Partial<T> & ObjectValues<{ [K in keyof T]: Pick<Required<T>, K> }>;
|
||||
|
||||
/** Type helper that adds a brand to a type, used for nominal typing.
|
||||
*
|
||||
* @remarks
|
||||
* Brands should be either a string or unique symbol. This prevents overlap with other types.
|
||||
*/
|
||||
export declare class Brander<B> {
|
||||
private __brand: B;
|
||||
}
|
||||
|
||||
@ -24,15 +24,15 @@ export interface AbilityTranslationEntries {
|
||||
[key: string]: AbilityTranslationEntry;
|
||||
}
|
||||
|
||||
export interface ModifierTypeTranslationEntry {
|
||||
export interface RewardTranslationEntry {
|
||||
name?: string;
|
||||
description?: string;
|
||||
extra?: SimpleTranslationEntries;
|
||||
}
|
||||
|
||||
export interface ModifierTypeTranslationEntries {
|
||||
ModifierType: { [key: string]: ModifierTypeTranslationEntry };
|
||||
SpeciesBoosterItem: { [key: string]: ModifierTypeTranslationEntry };
|
||||
export interface RewardTranslationEntries {
|
||||
Reward: { [key: string]: RewardTranslationEntry };
|
||||
SpeciesBoosterItem: { [key: string]: RewardTranslationEntry };
|
||||
AttackTypeBoosterItem: SimpleTranslationEntries;
|
||||
TempStatStageBoosterItem: SimpleTranslationEntries;
|
||||
BaseStatBoosterItem: SimpleTranslationEntries;
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
// Intentionally re-exports `ModifierConstructorMap` from `modifier.ts`
|
||||
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { ModifierConstructorMap } from "#modifiers/modifier";
|
||||
import type { ModifierType, WeightedModifierType } from "#modifiers/modifier-type";
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
export type ModifierTypeFunc = () => ModifierType;
|
||||
export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
|
||||
|
||||
export type { ModifierConstructorMap } from "#modifiers/modifier";
|
||||
|
||||
/**
|
||||
* Map of modifier names to their respective instance types
|
||||
*/
|
||||
export type ModifierInstanceMap = {
|
||||
[K in keyof ModifierConstructorMap]: InstanceType<ModifierConstructorMap[K]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Union type of all modifier constructors.
|
||||
*/
|
||||
export type ModifierClass = ObjectValues<ModifierConstructorMap>;
|
||||
|
||||
/**
|
||||
* Union type of all modifier names as strings.
|
||||
*/
|
||||
export type ModifierString = keyof ModifierConstructorMap;
|
||||
|
||||
export type ModifierPool = {
|
||||
[tier: string]: WeightedModifierType[];
|
||||
};
|
||||
@ -1,13 +1,24 @@
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type {
|
||||
AttackMove,
|
||||
ChargingAttackMove,
|
||||
ChargingSelfStatusMove,
|
||||
Move,
|
||||
MoveAttr,
|
||||
MoveAttrConstructorMap,
|
||||
SelfStatusMove,
|
||||
StatusMove,
|
||||
} from "#moves/move";
|
||||
|
||||
/**
|
||||
* A generic function producing a message during a Move's execution.
|
||||
* @param user - The {@linkcode Pokemon} using the move
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
* @param move - The {@linkcode Move} being used
|
||||
* @returns a string
|
||||
*/
|
||||
export type MoveMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => string;
|
||||
|
||||
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||
|
||||
export type * from "#moves/move";
|
||||
|
||||
53
src/@types/rewards.ts
Normal file
53
src/@types/rewards.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { RewardId } from "#enums/reward-id";
|
||||
import type { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { allRewardsType } from "#items/all-rewards";
|
||||
import type { RewardGenerator } from "#items/reward";
|
||||
|
||||
// TODO: Remove party from arguments can be accessed from `globalScene`
|
||||
export type WeightedRewardWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
|
||||
|
||||
export type RewardPoolId = RewardId | HeldItemId | TrainerItemId;
|
||||
|
||||
type allRewardGenerators = {
|
||||
[k in keyof allRewardsType as allRewardsType[k] extends RewardGenerator ? k : never]: allRewardsType[k];
|
||||
};
|
||||
|
||||
type RewardGeneratorArgMap = {
|
||||
[k in keyof allRewardGenerators]: Exclude<Parameters<allRewardGenerators[k]["generateReward"]>[0], undefined>;
|
||||
};
|
||||
|
||||
/** Union type containing all {@linkcode RewardId}s corresponding to valid {@linkcode RewardGenerator}s. */
|
||||
type RewardGeneratorId = keyof allRewardGenerators;
|
||||
|
||||
type RewardGeneratorSpecs<T extends RewardGeneratorId = RewardGeneratorId> = {
|
||||
id: T;
|
||||
args: RewardGeneratorArgMap[T];
|
||||
};
|
||||
|
||||
/** Union type used to specify fixed rewards used in generation. */
|
||||
export type RewardSpecs<T extends RewardPoolId = RewardPoolId> = T extends RewardGeneratorId
|
||||
? T | RewardGeneratorSpecs<T>
|
||||
: T;
|
||||
|
||||
export type RewardPoolEntry = {
|
||||
id: RewardPoolId;
|
||||
weight: number | WeightedRewardWeightFunc;
|
||||
maxWeight?: number;
|
||||
};
|
||||
|
||||
export type RewardPool = {
|
||||
[tier: string]: RewardPoolEntry[];
|
||||
};
|
||||
|
||||
export interface RewardPoolWeights {
|
||||
[tier: string]: number[];
|
||||
}
|
||||
|
||||
export type SilentReward =
|
||||
| TrainerItemId
|
||||
| typeof RewardId.VOUCHER
|
||||
| typeof RewardId.VOUCHER_PLUS
|
||||
| typeof RewardId.VOUCHER_PREMIUM
|
||||
| typeof RewardId.ROGUE_BALL;
|
||||
@ -1,13 +1,13 @@
|
||||
import type { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import type { EnemyPokemon } from "#field/pokemon";
|
||||
import type { PersistentModifier } from "#modifiers/modifier";
|
||||
import type { TrainerItemConfiguration } from "#items/trainer-item-data-types";
|
||||
import type { TrainerConfig } from "#trainers/trainer-config";
|
||||
import type { TrainerPartyTemplate } from "#trainers/trainer-party-template";
|
||||
|
||||
export type PartyTemplateFunc = () => TrainerPartyTemplate;
|
||||
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;
|
||||
export type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[];
|
||||
export type GenTrainerItemsFunc = (party: EnemyPokemon[]) => TrainerItemConfiguration;
|
||||
export type GenAIFunc = (party: EnemyPokemon[]) => void;
|
||||
|
||||
export interface TrainerTierPools {
|
||||
|
||||
@ -17,45 +17,42 @@ export function initLoggedInUser(): void {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateUserInfo(): Promise<[boolean, number]> {
|
||||
return new Promise<[boolean, number]>(resolve => {
|
||||
if (bypassLogin) {
|
||||
loggedInUser = {
|
||||
username: "Guest",
|
||||
lastSessionSlot: -1,
|
||||
discordId: "",
|
||||
googleId: "",
|
||||
hasAdminRole: false,
|
||||
};
|
||||
let lastSessionSlot = -1;
|
||||
for (let s = 0; s < 5; s++) {
|
||||
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
|
||||
lastSessionSlot = s;
|
||||
break;
|
||||
}
|
||||
export async function updateUserInfo(): Promise<[boolean, number]> {
|
||||
if (bypassLogin) {
|
||||
loggedInUser = {
|
||||
username: "Guest",
|
||||
lastSessionSlot: -1,
|
||||
discordId: "",
|
||||
googleId: "",
|
||||
hasAdminRole: false,
|
||||
};
|
||||
let lastSessionSlot = -1;
|
||||
for (let s = 0; s < 5; s++) {
|
||||
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
|
||||
lastSessionSlot = s;
|
||||
break;
|
||||
}
|
||||
loggedInUser.lastSessionSlot = lastSessionSlot;
|
||||
// Migrate old data from before the username was appended
|
||||
["data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4"].map(d => {
|
||||
const lsItem = localStorage.getItem(d);
|
||||
if (lsItem && !!loggedInUser?.username) {
|
||||
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
|
||||
if (lsUserItem) {
|
||||
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, lsUserItem);
|
||||
}
|
||||
localStorage.setItem(`${d}_${loggedInUser.username}`, lsItem);
|
||||
localStorage.removeItem(d);
|
||||
}
|
||||
});
|
||||
return resolve([true, 200]);
|
||||
}
|
||||
pokerogueApi.account.getInfo().then(([accountInfo, status]) => {
|
||||
if (!accountInfo) {
|
||||
resolve([false, status]);
|
||||
return;
|
||||
loggedInUser.lastSessionSlot = lastSessionSlot;
|
||||
// Migrate old data from before the username was appended
|
||||
["data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4"].forEach(d => {
|
||||
const lsItem = localStorage.getItem(d);
|
||||
if (lsItem && !!loggedInUser?.username) {
|
||||
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
|
||||
if (lsUserItem) {
|
||||
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, lsUserItem);
|
||||
}
|
||||
localStorage.setItem(`${d}_${loggedInUser.username}`, lsItem);
|
||||
localStorage.removeItem(d);
|
||||
}
|
||||
loggedInUser = accountInfo;
|
||||
resolve([true, 200]);
|
||||
});
|
||||
});
|
||||
return [true, 200];
|
||||
}
|
||||
|
||||
const [accountInfo, status] = await pokerogueApi.account.getInfo();
|
||||
if (!accountInfo) {
|
||||
return [false, status];
|
||||
}
|
||||
loggedInUser = accountInfo;
|
||||
return [true, 200];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ import { BattleSpec } from "#enums/battle-spec";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import type { Command } from "#enums/command";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -15,8 +16,8 @@ import { TrainerType } from "#enums/trainer-type";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { Trainer } from "#field/trainer";
|
||||
import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import type { CustomModifierSettings } from "#modifiers/modifier-type";
|
||||
import type { CustomRewardSettings } from "#items/reward-pool-utils";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import i18next from "#plugins/i18n";
|
||||
import { MusicPreference } from "#system/settings";
|
||||
@ -68,7 +69,7 @@ export class Battle {
|
||||
public turnCommands: TurnCommands;
|
||||
public playerParticipantIds: Set<number> = new Set<number>();
|
||||
public battleScore = 0;
|
||||
public postBattleLoot: PokemonHeldItemModifier[] = [];
|
||||
public postBattleLoot: HeldItemId[] = [];
|
||||
public escapeAttempts = 0;
|
||||
public lastMove: MoveId;
|
||||
public battleSeed: string = randomString(16, true);
|
||||
@ -173,24 +174,12 @@ export class Battle {
|
||||
}
|
||||
|
||||
addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
|
||||
this.postBattleLoot.push(
|
||||
...globalScene
|
||||
.findModifiers(
|
||||
m => m.is("PokemonHeldItemModifier") && m.pokemonId === enemyPokemon.id && m.isTransferable,
|
||||
false,
|
||||
)
|
||||
.map(i => {
|
||||
const ret = i as PokemonHeldItemModifier;
|
||||
//@ts-expect-error - this is awful to fix/change
|
||||
ret.pokemonId = null;
|
||||
return ret;
|
||||
}),
|
||||
);
|
||||
this.postBattleLoot.push(...enemyPokemon.getHeldItems());
|
||||
}
|
||||
|
||||
pickUpScatteredMoney(): void {
|
||||
const moneyAmount = new NumberHolder(globalScene.currentBattle.moneyScattered);
|
||||
globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount });
|
||||
|
||||
if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) {
|
||||
moneyAmount.value *= 2;
|
||||
@ -491,7 +480,7 @@ export class FixedBattleConfig {
|
||||
public getTrainer: GetTrainerFunc;
|
||||
public getEnemyParty: GetEnemyPartyFunc;
|
||||
public seedOffsetWaveIndex: number;
|
||||
public customModifierRewardSettings?: CustomModifierSettings;
|
||||
public customRewardSettings?: CustomRewardSettings;
|
||||
|
||||
setBattleType(battleType: BattleType): FixedBattleConfig {
|
||||
this.battleType = battleType;
|
||||
@ -518,8 +507,8 @@ export class FixedBattleConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
setCustomModifierRewards(customModifierRewardSettings: CustomModifierSettings) {
|
||||
this.customModifierRewardSettings = customModifierRewardSettings;
|
||||
setCustomRewards(customRewardSettings: CustomRewardSettings) {
|
||||
this.customRewardSettings = customRewardSettings;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,3 +101,9 @@ export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15;
|
||||
* Default: `10000` (0.01%)
|
||||
*/
|
||||
export const FAKE_TITLE_LOGO_CHANCE = 10000;
|
||||
|
||||
/**
|
||||
* The ceiling on friendship amount that can be reached through the use of rare candies.
|
||||
* Using rare candies will never increase friendship beyond this value.
|
||||
*/
|
||||
export const RARE_CANDY_FRIENDSHIP_CAP = 200;
|
||||
|
||||
@ -10,7 +10,7 @@ import type { ArenaTrapTag, SuppressAbilitiesTag } from "#data/arena-tag";
|
||||
import type { BattlerTag } from "#data/battler-tags";
|
||||
import { GroundedTag } from "#data/battler-tags";
|
||||
import { getBerryEffectFunc } from "#data/berry";
|
||||
import { allAbilities, allMoves } from "#data/data-lists";
|
||||
import { allAbilities, allHeldItems, allMoves } from "#data/data-lists";
|
||||
import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeWeatherTrigger } from "#data/form-change-triggers";
|
||||
import { Gender } from "#data/gender";
|
||||
import { getPokeballName } from "#data/pokeball";
|
||||
@ -28,6 +28,7 @@ import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import { Command } from "#enums/command";
|
||||
import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { CommonAnim } from "#enums/move-anims-common";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
@ -46,8 +47,7 @@ import { SwitchType } from "#enums/switch-type";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { BerryUsedEvent } from "#events/battle-scene";
|
||||
import type { EnemyPokemon, Pokemon } from "#field/pokemon";
|
||||
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import { BerryModifierType } from "#modifiers/modifier-type";
|
||||
import { type BerryHeldItem, berryTypeToHeldItem } from "#items/berry";
|
||||
import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
import { noAbilityTypeOverrideMoves } from "#moves/invalid-moves";
|
||||
import type { Move } from "#moves/move";
|
||||
@ -74,6 +74,7 @@ import {
|
||||
randSeedItem,
|
||||
toDmgValue,
|
||||
} from "#utils/common";
|
||||
import { toCamelCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class Ability implements Localizable {
|
||||
@ -109,13 +110,9 @@ export class Ability implements Localizable {
|
||||
}
|
||||
|
||||
localize(): void {
|
||||
const i18nKey = AbilityId[this.id]
|
||||
.split("_")
|
||||
.filter(f => f)
|
||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||
.join("") as string;
|
||||
const i18nKey = toCamelCase(AbilityId[this.id]);
|
||||
|
||||
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
|
||||
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`)}${this.nameAppend}` : "";
|
||||
this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : "";
|
||||
}
|
||||
|
||||
@ -1670,6 +1667,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
constructor(
|
||||
private newType: PokemonType,
|
||||
private powerMultiplier: number,
|
||||
// TODO: all moves with this attr solely check the move being used...
|
||||
private condition?: PokemonAttackCondition,
|
||||
) {
|
||||
super(false);
|
||||
@ -2137,7 +2135,7 @@ export abstract class PostAttackAbAttr extends AbAttr {
|
||||
|
||||
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
||||
private stealCondition: PokemonAttackCondition | null;
|
||||
private stolenItem?: PokemonHeldItemModifier;
|
||||
private stolenItem?: HeldItemId;
|
||||
|
||||
constructor(stealCondition?: PokemonAttackCondition) {
|
||||
super();
|
||||
@ -2156,11 +2154,11 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
||||
hitResult < HitResult.NO_EFFECT &&
|
||||
(!this.stealCondition || this.stealCondition(pokemon, opponent, move))
|
||||
) {
|
||||
const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable);
|
||||
const heldItems = opponent.heldItemManager.getTransferableHeldItems();
|
||||
if (heldItems.length) {
|
||||
// Ensure that the stolen item in testing is the same as when the effect is applied
|
||||
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
||||
if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) {
|
||||
if (globalScene.canTransferHeldItem(this.stolenItem, opponent, pokemon)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2170,28 +2168,21 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
||||
}
|
||||
|
||||
override apply({ opponent, pokemon }: PostMoveInteractionAbAttrParams): void {
|
||||
const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable);
|
||||
const heldItems = opponent.heldItemManager.getTransferableHeldItems();
|
||||
if (!this.stolenItem) {
|
||||
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
||||
}
|
||||
if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) {
|
||||
if (globalScene.tryTransferHeldItem(this.stolenItem, opponent, pokemon, false)) {
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("abilityTriggers:postAttackStealHeldItem", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
defenderName: opponent.name,
|
||||
stolenItemType: this.stolenItem.type.name,
|
||||
stolenItemType: allHeldItems[this.stolenItem].name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
this.stolenItem = undefined;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id,
|
||||
target.isPlayer(),
|
||||
) as PokemonHeldItemModifier[];
|
||||
}
|
||||
}
|
||||
|
||||
export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
|
||||
@ -2282,7 +2273,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
|
||||
|
||||
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
private condition?: PokemonDefendCondition;
|
||||
private stolenItem?: PokemonHeldItemModifier;
|
||||
private stolenItem?: HeldItemId;
|
||||
|
||||
constructor(condition?: PokemonDefendCondition) {
|
||||
super();
|
||||
@ -2292,10 +2283,10 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
|
||||
override canApply({ simulated, pokemon, opponent, move, hitResult }: PostMoveInteractionAbAttrParams): boolean {
|
||||
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, opponent, move))) {
|
||||
const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable);
|
||||
const heldItems = opponent.heldItemManager.getTransferableHeldItems();
|
||||
if (heldItems.length) {
|
||||
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
||||
if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) {
|
||||
if (globalScene.canTransferHeldItem(this.stolenItem, opponent, pokemon)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2304,28 +2295,21 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
}
|
||||
|
||||
override apply({ pokemon, opponent }: PostMoveInteractionAbAttrParams): void {
|
||||
const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable);
|
||||
const heldItems = opponent.heldItemManager.getTransferableHeldItems();
|
||||
if (!this.stolenItem) {
|
||||
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
||||
}
|
||||
if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) {
|
||||
if (globalScene.tryTransferHeldItem(this.stolenItem, opponent, pokemon, false)) {
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("abilityTriggers:postDefendStealHeldItem", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
attackerName: opponent.name,
|
||||
stolenItemType: this.stolenItem.type.name,
|
||||
stolenItemType: allHeldItems[this.stolenItem].name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
this.stolenItem = undefined;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id,
|
||||
target.isPlayer(),
|
||||
) as PokemonHeldItemModifier[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4661,10 +4645,14 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
override canApply({ pokemon }: AbAttrBaseParams): boolean {
|
||||
// Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped)
|
||||
const cappedBerries = new Set(
|
||||
globalScene
|
||||
.getModifiers(BerryModifier, pokemon.isPlayer())
|
||||
.filter(bm => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1)
|
||||
.map(bm => bm.berryType),
|
||||
pokemon
|
||||
.getHeldItems()
|
||||
.filter(
|
||||
bm =>
|
||||
isItemInCategory(bm, HeldItemCategoryId.BERRY) &&
|
||||
pokemon.heldItemManager.getStack(bm) < allHeldItems[bm].maxStackCount,
|
||||
)
|
||||
.map(bm => (allHeldItems[bm] as BerryHeldItem).berryType),
|
||||
);
|
||||
|
||||
this.berriesUnderCap = pokemon.battleData.berriesEaten.filter(bt => !cappedBerries.has(bt));
|
||||
@ -4694,30 +4682,15 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
const randomIdx = randSeedInt(this.berriesUnderCap.length);
|
||||
const chosenBerryType = this.berriesUnderCap[randomIdx];
|
||||
pokemon.battleData.berriesEaten.splice(randomIdx, 1); // Remove berry from memory
|
||||
const chosenBerry = new BerryModifierType(chosenBerryType);
|
||||
const chosenBerry = berryTypeToHeldItem[chosenBerryType];
|
||||
|
||||
// Add the randomly chosen berry or update the existing one
|
||||
const berryModifier = globalScene.findModifier(
|
||||
m => m instanceof BerryModifier && m.berryType === chosenBerryType && m.pokemonId === pokemon.id,
|
||||
pokemon.isPlayer(),
|
||||
) as BerryModifier | undefined;
|
||||
pokemon.heldItemManager.add(chosenBerry);
|
||||
|
||||
if (berryModifier) {
|
||||
berryModifier.stackCount++;
|
||||
} else {
|
||||
const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1);
|
||||
if (pokemon.isPlayer()) {
|
||||
globalScene.addModifier(newBerry);
|
||||
} else {
|
||||
globalScene.addEnemyModifier(newBerry);
|
||||
}
|
||||
}
|
||||
|
||||
globalScene.updateModifiers(pokemon.isPlayer());
|
||||
globalScene.updateItems(pokemon.isPlayer());
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
berryName: chosenBerry.name,
|
||||
berryName: allHeldItems[chosenBerry].name,
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
@ -4751,8 +4724,7 @@ export class CudChewConsumeBerryAbAttr extends AbAttr {
|
||||
// This doesn't count as "eating" a berry (for unnerve/stuff cheeks/unburden) as no item is consumed.
|
||||
for (const berryType of pokemon.summonData.berriesEatenLast) {
|
||||
getBerryEffectFunc(berryType)(pokemon);
|
||||
const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1);
|
||||
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(bMod)); // trigger message
|
||||
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(pokemon, berryType)); // trigger message
|
||||
}
|
||||
|
||||
// uncomment to make cheek pouch work with cud chew
|
||||
@ -5343,13 +5315,13 @@ export abstract class PostBattleAbAttr extends AbAttr {
|
||||
}
|
||||
|
||||
export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
||||
private randItem?: PokemonHeldItemModifier;
|
||||
private randItem?: HeldItemId;
|
||||
|
||||
override canApply({ simulated, victory, pokemon }: PostBattleAbAttrParams): boolean {
|
||||
const postBattleLoot = globalScene.currentBattle.postBattleLoot;
|
||||
if (!simulated && postBattleLoot.length && victory) {
|
||||
this.randItem = randSeedItem(postBattleLoot);
|
||||
return globalScene.canTransferHeldItemModifier(this.randItem, pokemon, 1);
|
||||
return pokemon.heldItemManager.getStack(this.randItem) < allHeldItems[this.randItem].maxStackCount;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -5360,12 +5332,12 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
||||
this.randItem = randSeedItem(postBattleLoot);
|
||||
}
|
||||
|
||||
if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) {
|
||||
if (pokemon.heldItemManager.add(this.randItem)) {
|
||||
postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1);
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("abilityTriggers:postBattleLoot", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
itemName: this.randItem.type.name,
|
||||
itemName: allHeldItems[this.randItem].name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -6312,8 +6284,6 @@ class ForceSwitchOutHelper {
|
||||
}
|
||||
|
||||
if (!allyPokemon?.isActive(true)) {
|
||||
globalScene.clearEnemyHeldItemModifiers();
|
||||
|
||||
if (switchOutTarget.hp) {
|
||||
globalScene.phaseManager.pushNew("BattleEndPhase", false);
|
||||
|
||||
@ -6397,11 +6367,9 @@ class ForceSwitchOutHelper {
|
||||
* @returns The amount of health recovered by Shell Bell.
|
||||
*/
|
||||
function calculateShellBellRecovery(pokemon: Pokemon): number {
|
||||
const shellBellModifier = pokemon.getHeldItems().find(m => m instanceof HitHealModifier);
|
||||
if (shellBellModifier) {
|
||||
return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellModifier.stackCount;
|
||||
}
|
||||
return 0;
|
||||
// Returns 0 if no Shell Bell is present
|
||||
const shellBellStack = pokemon.heldItemManager.getStack(HeldItemId.SHELL_BELL);
|
||||
return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellStack;
|
||||
}
|
||||
|
||||
export interface PostDamageAbAttrParams extends AbAttrBaseParams {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { allMoves } from "#data/data-lists";
|
||||
import { allHeldItems, allMoves } from "#data/data-lists";
|
||||
import { Gender, getGenderSymbol } from "#data/gender";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
@ -12,7 +13,6 @@ import { SpeciesId } from "#enums/species-id";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { SpeciesStatBoosterItem, SpeciesStatBoosterModifierType } from "#modifiers/modifier-type";
|
||||
import { coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
@ -99,7 +99,7 @@ const EvoCondKey = {
|
||||
SPECIES_CAUGHT: 12,
|
||||
GENDER: 13,
|
||||
NATURE: 14,
|
||||
HELD_ITEM: 15, // Currently checks only for species stat booster items
|
||||
HELD_ITEM: 15,
|
||||
} as const;
|
||||
|
||||
type EvolutionConditionData =
|
||||
@ -110,7 +110,7 @@ type EvolutionConditionData =
|
||||
{key: typeof EvoCondKey.GENDER, gender: Gender} |
|
||||
{key: typeof EvoCondKey.MOVE_TYPE | typeof EvoCondKey.PARTY_TYPE, pkmnType: PokemonType} |
|
||||
{key: typeof EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId} |
|
||||
{key: typeof EvoCondKey.HELD_ITEM, itemKey: SpeciesStatBoosterItem} |
|
||||
{key: typeof EvoCondKey.HELD_ITEM, itemKey: HeldItemId} |
|
||||
{key: typeof EvoCondKey.NATURE, nature: Nature[]} |
|
||||
{key: typeof EvoCondKey.WEATHER, weather: WeatherType[]} |
|
||||
{key: typeof EvoCondKey.TYROGUE, move: TyrogueMove} |
|
||||
@ -177,10 +177,7 @@ export class SpeciesEvolutionCondition {
|
||||
case EvoCondKey.PARTY_TYPE:
|
||||
return globalScene.getPlayerParty().some(p => p.getTypes(false, false, true).includes(cond.pkmnType))
|
||||
case EvoCondKey.EVO_TREASURE_TRACKER:
|
||||
return pokemon.getHeldItems().some(m =>
|
||||
m.is("EvoTrackerModifier") &&
|
||||
m.getStackCount() + pokemon.getPersistentTreasureCount() >= cond.value
|
||||
);
|
||||
return allHeldItems[HeldItemId.GIMMIGHOUL_EVO_TRACKER].getStackCount(pokemon) >= cond.value;
|
||||
case EvoCondKey.GENDER:
|
||||
return pokemon.gender === cond.gender;
|
||||
case EvoCondKey.SHEDINJA: // Shedinja cannot be evolved into directly
|
||||
@ -201,7 +198,7 @@ export class SpeciesEvolutionCondition {
|
||||
case EvoCondKey.SPECIES_CAUGHT:
|
||||
return !!globalScene.gameData.dexData[cond.speciesCaught].caughtAttr;
|
||||
case EvoCondKey.HELD_ITEM:
|
||||
return pokemon.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === cond.itemKey)
|
||||
return pokemon.heldItemManager.hasItem(cond.itemKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1765,8 +1762,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
||||
new SpeciesEvolution(SpeciesId.DUSKNOIR, 1, EvolutionItem.REAPER_CLOTH, null, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
],
|
||||
[SpeciesId.CLAMPERL]: [
|
||||
new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_TOOTH"}, SpeciesWildEvolutionDelay.VERY_LONG),
|
||||
new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_SCALE"}, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: HeldItemId.DEEP_SEA_TOOTH}, SpeciesWildEvolutionDelay.VERY_LONG),
|
||||
new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: HeldItemId.DEEP_SEA_SCALE}, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
],
|
||||
[SpeciesId.BOLDORE]: [
|
||||
new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
@ -1866,17 +1863,16 @@ interface PokemonPrevolutions {
|
||||
export const pokemonPrevolutions: PokemonPrevolutions = {};
|
||||
|
||||
export function initPokemonPrevolutions(): void {
|
||||
const megaFormKeys = [ SpeciesFormKey.MEGA, "", SpeciesFormKey.MEGA_X, "", SpeciesFormKey.MEGA_Y ].map(sfk => sfk as string);
|
||||
const prevolutionKeys = Object.keys(pokemonEvolutions);
|
||||
prevolutionKeys.forEach(pk => {
|
||||
const evolutions = pokemonEvolutions[pk];
|
||||
// TODO: Why do we have empty strings in our array?
|
||||
const megaFormKeys = [ SpeciesFormKey.MEGA, "", SpeciesFormKey.MEGA_X, "", SpeciesFormKey.MEGA_Y ];
|
||||
for (const [pk, evolutions] of Object.entries(pokemonEvolutions)) {
|
||||
for (const ev of evolutions) {
|
||||
if (ev.evoFormKey && megaFormKeys.indexOf(ev.evoFormKey) > -1) {
|
||||
continue;
|
||||
}
|
||||
pokemonPrevolutions[ev.speciesId] = Number.parseInt(pk) as SpeciesId;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
|
||||
@ -68591,324 +68591,324 @@ function transposeTmSpecies(): SpeciesTmMoves {
|
||||
export const speciesTmMoves: SpeciesTmMoves = transposeTmSpecies();
|
||||
|
||||
interface TmPoolTiers {
|
||||
[key: number]: ModifierTier
|
||||
[key: number]: RarityTier
|
||||
}
|
||||
|
||||
export const tmPoolTiers: TmPoolTiers = {
|
||||
[MoveId.MEGA_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.PAY_DAY]: ModifierTier.ULTRA,
|
||||
[MoveId.FIRE_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.ICE_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.THUNDER_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.SWORDS_DANCE]: ModifierTier.COMMON,
|
||||
[MoveId.CUT]: ModifierTier.COMMON,
|
||||
[MoveId.FLY]: ModifierTier.COMMON,
|
||||
[MoveId.MEGA_KICK]: ModifierTier.GREAT,
|
||||
[MoveId.BODY_SLAM]: ModifierTier.GREAT,
|
||||
[MoveId.TAKE_DOWN]: ModifierTier.GREAT,
|
||||
[MoveId.DOUBLE_EDGE]: ModifierTier.ULTRA,
|
||||
[MoveId.PIN_MISSILE]: ModifierTier.COMMON,
|
||||
[MoveId.ROAR]: ModifierTier.COMMON,
|
||||
[MoveId.FLAMETHROWER]: ModifierTier.ULTRA,
|
||||
[MoveId.HYDRO_PUMP]: ModifierTier.ULTRA,
|
||||
[MoveId.SURF]: ModifierTier.ULTRA,
|
||||
[MoveId.ICE_BEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.BLIZZARD]: ModifierTier.ULTRA,
|
||||
[MoveId.PSYBEAM]: ModifierTier.GREAT,
|
||||
[MoveId.HYPER_BEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.LOW_KICK]: ModifierTier.COMMON,
|
||||
[MoveId.COUNTER]: ModifierTier.COMMON,
|
||||
[MoveId.STRENGTH]: ModifierTier.GREAT,
|
||||
[MoveId.SOLAR_BEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.FIRE_SPIN]: ModifierTier.COMMON,
|
||||
[MoveId.THUNDERBOLT]: ModifierTier.ULTRA,
|
||||
[MoveId.THUNDER_WAVE]: ModifierTier.COMMON,
|
||||
[MoveId.THUNDER]: ModifierTier.ULTRA,
|
||||
[MoveId.EARTHQUAKE]: ModifierTier.ULTRA,
|
||||
[MoveId.DIG]: ModifierTier.GREAT,
|
||||
[MoveId.TOXIC]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCHIC]: ModifierTier.ULTRA,
|
||||
[MoveId.AGILITY]: ModifierTier.COMMON,
|
||||
[MoveId.NIGHT_SHADE]: ModifierTier.COMMON,
|
||||
[MoveId.SCREECH]: ModifierTier.COMMON,
|
||||
[MoveId.DOUBLE_TEAM]: ModifierTier.COMMON,
|
||||
[MoveId.CONFUSE_RAY]: ModifierTier.COMMON,
|
||||
[MoveId.LIGHT_SCREEN]: ModifierTier.COMMON,
|
||||
[MoveId.HAZE]: ModifierTier.COMMON,
|
||||
[MoveId.REFLECT]: ModifierTier.COMMON,
|
||||
[MoveId.FOCUS_ENERGY]: ModifierTier.COMMON,
|
||||
[MoveId.METRONOME]: ModifierTier.COMMON,
|
||||
[MoveId.SELF_DESTRUCT]: ModifierTier.GREAT,
|
||||
[MoveId.FIRE_BLAST]: ModifierTier.ULTRA,
|
||||
[MoveId.WATERFALL]: ModifierTier.GREAT,
|
||||
[MoveId.SWIFT]: ModifierTier.COMMON,
|
||||
[MoveId.AMNESIA]: ModifierTier.COMMON,
|
||||
[MoveId.DREAM_EATER]: ModifierTier.GREAT,
|
||||
[MoveId.LEECH_LIFE]: ModifierTier.ULTRA,
|
||||
[MoveId.FLASH]: ModifierTier.COMMON,
|
||||
[MoveId.EXPLOSION]: ModifierTier.GREAT,
|
||||
[MoveId.REST]: ModifierTier.COMMON,
|
||||
[MoveId.ROCK_SLIDE]: ModifierTier.GREAT,
|
||||
[MoveId.TRI_ATTACK]: ModifierTier.ULTRA,
|
||||
[MoveId.SUPER_FANG]: ModifierTier.COMMON,
|
||||
[MoveId.SUBSTITUTE]: ModifierTier.COMMON,
|
||||
[MoveId.THIEF]: ModifierTier.GREAT,
|
||||
[MoveId.SNORE]: ModifierTier.COMMON,
|
||||
[MoveId.CURSE]: ModifierTier.COMMON,
|
||||
[MoveId.REVERSAL]: ModifierTier.COMMON,
|
||||
[MoveId.SPITE]: ModifierTier.COMMON,
|
||||
[MoveId.PROTECT]: ModifierTier.COMMON,
|
||||
[MoveId.SCARY_FACE]: ModifierTier.COMMON,
|
||||
[MoveId.SLUDGE_BOMB]: ModifierTier.GREAT,
|
||||
[MoveId.MUD_SLAP]: ModifierTier.COMMON,
|
||||
[MoveId.SPIKES]: ModifierTier.COMMON,
|
||||
[MoveId.ICY_WIND]: ModifierTier.GREAT,
|
||||
[MoveId.OUTRAGE]: ModifierTier.ULTRA,
|
||||
[MoveId.SANDSTORM]: ModifierTier.COMMON,
|
||||
[MoveId.GIGA_DRAIN]: ModifierTier.ULTRA,
|
||||
[MoveId.ENDURE]: ModifierTier.COMMON,
|
||||
[MoveId.CHARM]: ModifierTier.COMMON,
|
||||
[MoveId.FALSE_SWIPE]: ModifierTier.COMMON,
|
||||
[MoveId.SWAGGER]: ModifierTier.COMMON,
|
||||
[MoveId.STEEL_WING]: ModifierTier.GREAT,
|
||||
[MoveId.ATTRACT]: ModifierTier.COMMON,
|
||||
[MoveId.SLEEP_TALK]: ModifierTier.COMMON,
|
||||
[MoveId.HEAL_BELL]: ModifierTier.COMMON,
|
||||
[MoveId.RETURN]: ModifierTier.ULTRA,
|
||||
[MoveId.FRUSTRATION]: ModifierTier.COMMON,
|
||||
[MoveId.SAFEGUARD]: ModifierTier.COMMON,
|
||||
[MoveId.PAIN_SPLIT]: ModifierTier.COMMON,
|
||||
[MoveId.MEGAHORN]: ModifierTier.ULTRA,
|
||||
[MoveId.BATON_PASS]: ModifierTier.COMMON,
|
||||
[MoveId.ENCORE]: ModifierTier.COMMON,
|
||||
[MoveId.IRON_TAIL]: ModifierTier.GREAT,
|
||||
[MoveId.METAL_CLAW]: ModifierTier.COMMON,
|
||||
[MoveId.SYNTHESIS]: ModifierTier.GREAT,
|
||||
[MoveId.HIDDEN_POWER]: ModifierTier.GREAT,
|
||||
[MoveId.RAIN_DANCE]: ModifierTier.COMMON,
|
||||
[MoveId.SUNNY_DAY]: ModifierTier.COMMON,
|
||||
[MoveId.CRUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCH_UP]: ModifierTier.COMMON,
|
||||
[MoveId.SHADOW_BALL]: ModifierTier.ULTRA,
|
||||
[MoveId.FUTURE_SIGHT]: ModifierTier.GREAT,
|
||||
[MoveId.ROCK_SMASH]: ModifierTier.COMMON,
|
||||
[MoveId.WHIRLPOOL]: ModifierTier.COMMON,
|
||||
[MoveId.BEAT_UP]: ModifierTier.COMMON,
|
||||
[MoveId.UPROAR]: ModifierTier.GREAT,
|
||||
[MoveId.HEAT_WAVE]: ModifierTier.ULTRA,
|
||||
[MoveId.HAIL]: ModifierTier.COMMON,
|
||||
[MoveId.TORMENT]: ModifierTier.COMMON,
|
||||
[MoveId.WILL_O_WISP]: ModifierTier.COMMON,
|
||||
[MoveId.FACADE]: ModifierTier.GREAT,
|
||||
[MoveId.FOCUS_PUNCH]: ModifierTier.COMMON,
|
||||
[MoveId.NATURE_POWER]: ModifierTier.COMMON,
|
||||
[MoveId.CHARGE]: ModifierTier.COMMON,
|
||||
[MoveId.TAUNT]: ModifierTier.COMMON,
|
||||
[MoveId.HELPING_HAND]: ModifierTier.COMMON,
|
||||
[MoveId.TRICK]: ModifierTier.COMMON,
|
||||
[MoveId.SUPERPOWER]: ModifierTier.ULTRA,
|
||||
[MoveId.RECYCLE]: ModifierTier.COMMON,
|
||||
[MoveId.REVENGE]: ModifierTier.GREAT,
|
||||
[MoveId.BRICK_BREAK]: ModifierTier.GREAT,
|
||||
[MoveId.KNOCK_OFF]: ModifierTier.GREAT,
|
||||
[MoveId.ENDEAVOR]: ModifierTier.COMMON,
|
||||
[MoveId.SKILL_SWAP]: ModifierTier.COMMON,
|
||||
[MoveId.IMPRISON]: ModifierTier.COMMON,
|
||||
[MoveId.SECRET_POWER]: ModifierTier.COMMON,
|
||||
[MoveId.DIVE]: ModifierTier.GREAT,
|
||||
[MoveId.FEATHER_DANCE]: ModifierTier.COMMON,
|
||||
[MoveId.BLAZE_KICK]: ModifierTier.GREAT,
|
||||
[MoveId.HYPER_VOICE]: ModifierTier.ULTRA,
|
||||
[MoveId.BLAST_BURN]: ModifierTier.ULTRA,
|
||||
[MoveId.HYDRO_CANNON]: ModifierTier.ULTRA,
|
||||
[MoveId.WEATHER_BALL]: ModifierTier.COMMON,
|
||||
[MoveId.FAKE_TEARS]: ModifierTier.COMMON,
|
||||
[MoveId.AIR_CUTTER]: ModifierTier.GREAT,
|
||||
[MoveId.OVERHEAT]: ModifierTier.ULTRA,
|
||||
[MoveId.ROCK_TOMB]: ModifierTier.GREAT,
|
||||
[MoveId.METAL_SOUND]: ModifierTier.COMMON,
|
||||
[MoveId.COSMIC_POWER]: ModifierTier.COMMON,
|
||||
[MoveId.SIGNAL_BEAM]: ModifierTier.GREAT,
|
||||
[MoveId.SAND_TOMB]: ModifierTier.COMMON,
|
||||
[MoveId.MUDDY_WATER]: ModifierTier.GREAT,
|
||||
[MoveId.BULLET_SEED]: ModifierTier.GREAT,
|
||||
[MoveId.AERIAL_ACE]: ModifierTier.GREAT,
|
||||
[MoveId.ICICLE_SPEAR]: ModifierTier.GREAT,
|
||||
[MoveId.IRON_DEFENSE]: ModifierTier.GREAT,
|
||||
[MoveId.DRAGON_CLAW]: ModifierTier.ULTRA,
|
||||
[MoveId.FRENZY_PLANT]: ModifierTier.ULTRA,
|
||||
[MoveId.BULK_UP]: ModifierTier.COMMON,
|
||||
[MoveId.BOUNCE]: ModifierTier.GREAT,
|
||||
[MoveId.MUD_SHOT]: ModifierTier.GREAT,
|
||||
[MoveId.POISON_TAIL]: ModifierTier.GREAT,
|
||||
[MoveId.COVET]: ModifierTier.GREAT,
|
||||
[MoveId.MAGICAL_LEAF]: ModifierTier.GREAT,
|
||||
[MoveId.CALM_MIND]: ModifierTier.GREAT,
|
||||
[MoveId.LEAF_BLADE]: ModifierTier.ULTRA,
|
||||
[MoveId.DRAGON_DANCE]: ModifierTier.GREAT,
|
||||
[MoveId.ROCK_BLAST]: ModifierTier.GREAT,
|
||||
[MoveId.WATER_PULSE]: ModifierTier.GREAT,
|
||||
[MoveId.ROOST]: ModifierTier.GREAT,
|
||||
[MoveId.GRAVITY]: ModifierTier.COMMON,
|
||||
[MoveId.GYRO_BALL]: ModifierTier.COMMON,
|
||||
[MoveId.BRINE]: ModifierTier.GREAT,
|
||||
[MoveId.PLUCK]: ModifierTier.GREAT,
|
||||
[MoveId.TAILWIND]: ModifierTier.GREAT,
|
||||
[MoveId.U_TURN]: ModifierTier.GREAT,
|
||||
[MoveId.CLOSE_COMBAT]: ModifierTier.ULTRA,
|
||||
[MoveId.PAYBACK]: ModifierTier.COMMON,
|
||||
[MoveId.ASSURANCE]: ModifierTier.COMMON,
|
||||
[MoveId.EMBARGO]: ModifierTier.COMMON,
|
||||
[MoveId.FLING]: ModifierTier.COMMON,
|
||||
[MoveId.GASTRO_ACID]: ModifierTier.GREAT,
|
||||
[MoveId.POWER_SWAP]: ModifierTier.COMMON,
|
||||
[MoveId.GUARD_SWAP]: ModifierTier.COMMON,
|
||||
[MoveId.WORRY_SEED]: ModifierTier.GREAT,
|
||||
[MoveId.TOXIC_SPIKES]: ModifierTier.GREAT,
|
||||
[MoveId.FLARE_BLITZ]: ModifierTier.ULTRA,
|
||||
[MoveId.AURA_SPHERE]: ModifierTier.GREAT,
|
||||
[MoveId.ROCK_POLISH]: ModifierTier.COMMON,
|
||||
[MoveId.POISON_JAB]: ModifierTier.GREAT,
|
||||
[MoveId.DARK_PULSE]: ModifierTier.GREAT,
|
||||
[MoveId.AQUA_TAIL]: ModifierTier.GREAT,
|
||||
[MoveId.SEED_BOMB]: ModifierTier.GREAT,
|
||||
[MoveId.AIR_SLASH]: ModifierTier.GREAT,
|
||||
[MoveId.X_SCISSOR]: ModifierTier.GREAT,
|
||||
[MoveId.BUG_BUZZ]: ModifierTier.GREAT,
|
||||
[MoveId.DRAGON_PULSE]: ModifierTier.GREAT,
|
||||
[MoveId.POWER_GEM]: ModifierTier.GREAT,
|
||||
[MoveId.DRAIN_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.VACUUM_WAVE]: ModifierTier.COMMON,
|
||||
[MoveId.FOCUS_BLAST]: ModifierTier.GREAT,
|
||||
[MoveId.ENERGY_BALL]: ModifierTier.GREAT,
|
||||
[MoveId.BRAVE_BIRD]: ModifierTier.ULTRA,
|
||||
[MoveId.EARTH_POWER]: ModifierTier.ULTRA,
|
||||
[MoveId.GIGA_IMPACT]: ModifierTier.GREAT,
|
||||
[MoveId.NASTY_PLOT]: ModifierTier.COMMON,
|
||||
[MoveId.AVALANCHE]: ModifierTier.GREAT,
|
||||
[MoveId.SHADOW_CLAW]: ModifierTier.GREAT,
|
||||
[MoveId.THUNDER_FANG]: ModifierTier.GREAT,
|
||||
[MoveId.ICE_FANG]: ModifierTier.GREAT,
|
||||
[MoveId.FIRE_FANG]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCHO_CUT]: ModifierTier.GREAT,
|
||||
[MoveId.ZEN_HEADBUTT]: ModifierTier.GREAT,
|
||||
[MoveId.FLASH_CANNON]: ModifierTier.GREAT,
|
||||
[MoveId.ROCK_CLIMB]: ModifierTier.GREAT,
|
||||
[MoveId.DEFOG]: ModifierTier.COMMON,
|
||||
[MoveId.TRICK_ROOM]: ModifierTier.COMMON,
|
||||
[MoveId.DRACO_METEOR]: ModifierTier.ULTRA,
|
||||
[MoveId.LEAF_STORM]: ModifierTier.ULTRA,
|
||||
[MoveId.POWER_WHIP]: ModifierTier.ULTRA,
|
||||
[MoveId.CROSS_POISON]: ModifierTier.GREAT,
|
||||
[MoveId.GUNK_SHOT]: ModifierTier.ULTRA,
|
||||
[MoveId.IRON_HEAD]: ModifierTier.GREAT,
|
||||
[MoveId.STONE_EDGE]: ModifierTier.ULTRA,
|
||||
[MoveId.STEALTH_ROCK]: ModifierTier.COMMON,
|
||||
[MoveId.GRASS_KNOT]: ModifierTier.ULTRA,
|
||||
[MoveId.BUG_BITE]: ModifierTier.GREAT,
|
||||
[MoveId.CHARGE_BEAM]: ModifierTier.GREAT,
|
||||
[MoveId.HONE_CLAWS]: ModifierTier.COMMON,
|
||||
[MoveId.WONDER_ROOM]: ModifierTier.COMMON,
|
||||
[MoveId.PSYSHOCK]: ModifierTier.GREAT,
|
||||
[MoveId.VENOSHOCK]: ModifierTier.GREAT,
|
||||
[MoveId.MAGIC_ROOM]: ModifierTier.COMMON,
|
||||
[MoveId.SMACK_DOWN]: ModifierTier.COMMON,
|
||||
[MoveId.SLUDGE_WAVE]: ModifierTier.GREAT,
|
||||
[MoveId.HEAVY_SLAM]: ModifierTier.GREAT,
|
||||
[MoveId.ELECTRO_BALL]: ModifierTier.GREAT,
|
||||
[MoveId.FLAME_CHARGE]: ModifierTier.GREAT,
|
||||
[MoveId.LOW_SWEEP]: ModifierTier.GREAT,
|
||||
[MoveId.ACID_SPRAY]: ModifierTier.COMMON,
|
||||
[MoveId.FOUL_PLAY]: ModifierTier.ULTRA,
|
||||
[MoveId.ROUND]: ModifierTier.COMMON,
|
||||
[MoveId.ECHOED_VOICE]: ModifierTier.COMMON,
|
||||
[MoveId.STORED_POWER]: ModifierTier.COMMON,
|
||||
[MoveId.ALLY_SWITCH]: ModifierTier.COMMON,
|
||||
[MoveId.SCALD]: ModifierTier.GREAT,
|
||||
[MoveId.HEX]: ModifierTier.GREAT,
|
||||
[MoveId.SKY_DROP]: ModifierTier.GREAT,
|
||||
[MoveId.INCINERATE]: ModifierTier.GREAT,
|
||||
[MoveId.QUASH]: ModifierTier.COMMON,
|
||||
[MoveId.ACROBATICS]: ModifierTier.GREAT,
|
||||
[MoveId.RETALIATE]: ModifierTier.GREAT,
|
||||
[MoveId.WATER_PLEDGE]: ModifierTier.GREAT,
|
||||
[MoveId.FIRE_PLEDGE]: ModifierTier.GREAT,
|
||||
[MoveId.GRASS_PLEDGE]: ModifierTier.GREAT,
|
||||
[MoveId.VOLT_SWITCH]: ModifierTier.GREAT,
|
||||
[MoveId.STRUGGLE_BUG]: ModifierTier.COMMON,
|
||||
[MoveId.BULLDOZE]: ModifierTier.GREAT,
|
||||
[MoveId.FROST_BREATH]: ModifierTier.GREAT,
|
||||
[MoveId.DRAGON_TAIL]: ModifierTier.GREAT,
|
||||
[MoveId.WORK_UP]: ModifierTier.COMMON,
|
||||
[MoveId.ELECTROWEB]: ModifierTier.GREAT,
|
||||
[MoveId.WILD_CHARGE]: ModifierTier.GREAT,
|
||||
[MoveId.DRILL_RUN]: ModifierTier.GREAT,
|
||||
[MoveId.RAZOR_SHELL]: ModifierTier.GREAT,
|
||||
[MoveId.HEAT_CRASH]: ModifierTier.GREAT,
|
||||
[MoveId.TAIL_SLAP]: ModifierTier.GREAT,
|
||||
[MoveId.HURRICANE]: ModifierTier.ULTRA,
|
||||
[MoveId.SNARL]: ModifierTier.COMMON,
|
||||
[MoveId.PHANTOM_FORCE]: ModifierTier.ULTRA,
|
||||
[MoveId.PETAL_BLIZZARD]: ModifierTier.GREAT,
|
||||
[MoveId.DISARMING_VOICE]: ModifierTier.GREAT,
|
||||
[MoveId.DRAINING_KISS]: ModifierTier.GREAT,
|
||||
[MoveId.GRASSY_TERRAIN]: ModifierTier.COMMON,
|
||||
[MoveId.MISTY_TERRAIN]: ModifierTier.COMMON,
|
||||
[MoveId.PLAY_ROUGH]: ModifierTier.GREAT,
|
||||
[MoveId.CONFIDE]: ModifierTier.COMMON,
|
||||
[MoveId.MYSTICAL_FIRE]: ModifierTier.GREAT,
|
||||
[MoveId.EERIE_IMPULSE]: ModifierTier.COMMON,
|
||||
[MoveId.VENOM_DRENCH]: ModifierTier.COMMON,
|
||||
[MoveId.ELECTRIC_TERRAIN]: ModifierTier.COMMON,
|
||||
[MoveId.DAZZLING_GLEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.INFESTATION]: ModifierTier.COMMON,
|
||||
[MoveId.POWER_UP_PUNCH]: ModifierTier.GREAT,
|
||||
[MoveId.DARKEST_LARIAT]: ModifierTier.GREAT,
|
||||
[MoveId.HIGH_HORSEPOWER]: ModifierTier.ULTRA,
|
||||
[MoveId.SOLAR_BLADE]: ModifierTier.GREAT,
|
||||
[MoveId.THROAT_CHOP]: ModifierTier.GREAT,
|
||||
[MoveId.POLLEN_PUFF]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCHIC_TERRAIN]: ModifierTier.COMMON,
|
||||
[MoveId.LUNGE]: ModifierTier.GREAT,
|
||||
[MoveId.SPEED_SWAP]: ModifierTier.COMMON,
|
||||
[MoveId.SMART_STRIKE]: ModifierTier.GREAT,
|
||||
[MoveId.BRUTAL_SWING]: ModifierTier.GREAT,
|
||||
[MoveId.AURORA_VEIL]: ModifierTier.COMMON,
|
||||
[MoveId.PSYCHIC_FANGS]: ModifierTier.GREAT,
|
||||
[MoveId.STOMPING_TANTRUM]: ModifierTier.GREAT,
|
||||
[MoveId.LIQUIDATION]: ModifierTier.ULTRA,
|
||||
[MoveId.BODY_PRESS]: ModifierTier.ULTRA,
|
||||
[MoveId.BREAKING_SWIPE]: ModifierTier.GREAT,
|
||||
[MoveId.STEEL_BEAM]: ModifierTier.ULTRA,
|
||||
[MoveId.EXPANDING_FORCE]: ModifierTier.GREAT,
|
||||
[MoveId.STEEL_ROLLER]: ModifierTier.COMMON,
|
||||
[MoveId.SCALE_SHOT]: ModifierTier.ULTRA,
|
||||
[MoveId.METEOR_BEAM]: ModifierTier.GREAT,
|
||||
[MoveId.MISTY_EXPLOSION]: ModifierTier.COMMON,
|
||||
[MoveId.GRASSY_GLIDE]: ModifierTier.COMMON,
|
||||
[MoveId.RISING_VOLTAGE]: ModifierTier.COMMON,
|
||||
[MoveId.TERRAIN_PULSE]: ModifierTier.COMMON,
|
||||
[MoveId.SKITTER_SMACK]: ModifierTier.GREAT,
|
||||
[MoveId.BURNING_JEALOUSY]: ModifierTier.GREAT,
|
||||
[MoveId.LASH_OUT]: ModifierTier.GREAT,
|
||||
[MoveId.POLTERGEIST]: ModifierTier.ULTRA,
|
||||
[MoveId.CORROSIVE_GAS]: ModifierTier.COMMON,
|
||||
[MoveId.COACHING]: ModifierTier.COMMON,
|
||||
[MoveId.FLIP_TURN]: ModifierTier.COMMON,
|
||||
[MoveId.TRIPLE_AXEL]: ModifierTier.COMMON,
|
||||
[MoveId.DUAL_WINGBEAT]: ModifierTier.COMMON,
|
||||
[MoveId.SCORCHING_SANDS]: ModifierTier.GREAT,
|
||||
[MoveId.TERA_BLAST]: ModifierTier.GREAT,
|
||||
[MoveId.ICE_SPINNER]: ModifierTier.GREAT,
|
||||
[MoveId.SNOWSCAPE]: ModifierTier.COMMON,
|
||||
[MoveId.POUNCE]: ModifierTier.COMMON,
|
||||
[MoveId.TRAILBLAZE]: ModifierTier.COMMON,
|
||||
[MoveId.CHILLING_WATER]: ModifierTier.COMMON,
|
||||
[MoveId.HARD_PRESS]: ModifierTier.GREAT,
|
||||
[MoveId.DRAGON_CHEER]: ModifierTier.COMMON,
|
||||
[MoveId.ALLURING_VOICE]: ModifierTier.GREAT,
|
||||
[MoveId.TEMPER_FLARE]: ModifierTier.GREAT,
|
||||
[MoveId.SUPERCELL_SLAM]: ModifierTier.GREAT,
|
||||
[MoveId.PSYCHIC_NOISE]: ModifierTier.GREAT,
|
||||
[MoveId.UPPER_HAND]: ModifierTier.COMMON,
|
||||
[MoveId.MEGA_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.PAY_DAY]: RarityTier.ULTRA,
|
||||
[MoveId.FIRE_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.ICE_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.THUNDER_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.SWORDS_DANCE]: RarityTier.COMMON,
|
||||
[MoveId.CUT]: RarityTier.COMMON,
|
||||
[MoveId.FLY]: RarityTier.COMMON,
|
||||
[MoveId.MEGA_KICK]: RarityTier.GREAT,
|
||||
[MoveId.BODY_SLAM]: RarityTier.GREAT,
|
||||
[MoveId.TAKE_DOWN]: RarityTier.GREAT,
|
||||
[MoveId.DOUBLE_EDGE]: RarityTier.ULTRA,
|
||||
[MoveId.PIN_MISSILE]: RarityTier.COMMON,
|
||||
[MoveId.ROAR]: RarityTier.COMMON,
|
||||
[MoveId.FLAMETHROWER]: RarityTier.ULTRA,
|
||||
[MoveId.HYDRO_PUMP]: RarityTier.ULTRA,
|
||||
[MoveId.SURF]: RarityTier.ULTRA,
|
||||
[MoveId.ICE_BEAM]: RarityTier.ULTRA,
|
||||
[MoveId.BLIZZARD]: RarityTier.ULTRA,
|
||||
[MoveId.PSYBEAM]: RarityTier.GREAT,
|
||||
[MoveId.HYPER_BEAM]: RarityTier.ULTRA,
|
||||
[MoveId.LOW_KICK]: RarityTier.COMMON,
|
||||
[MoveId.COUNTER]: RarityTier.COMMON,
|
||||
[MoveId.STRENGTH]: RarityTier.GREAT,
|
||||
[MoveId.SOLAR_BEAM]: RarityTier.ULTRA,
|
||||
[MoveId.FIRE_SPIN]: RarityTier.COMMON,
|
||||
[MoveId.THUNDERBOLT]: RarityTier.ULTRA,
|
||||
[MoveId.THUNDER_WAVE]: RarityTier.COMMON,
|
||||
[MoveId.THUNDER]: RarityTier.ULTRA,
|
||||
[MoveId.EARTHQUAKE]: RarityTier.ULTRA,
|
||||
[MoveId.DIG]: RarityTier.GREAT,
|
||||
[MoveId.TOXIC]: RarityTier.GREAT,
|
||||
[MoveId.PSYCHIC]: RarityTier.ULTRA,
|
||||
[MoveId.AGILITY]: RarityTier.COMMON,
|
||||
[MoveId.NIGHT_SHADE]: RarityTier.COMMON,
|
||||
[MoveId.SCREECH]: RarityTier.COMMON,
|
||||
[MoveId.DOUBLE_TEAM]: RarityTier.COMMON,
|
||||
[MoveId.CONFUSE_RAY]: RarityTier.COMMON,
|
||||
[MoveId.LIGHT_SCREEN]: RarityTier.COMMON,
|
||||
[MoveId.HAZE]: RarityTier.COMMON,
|
||||
[MoveId.REFLECT]: RarityTier.COMMON,
|
||||
[MoveId.FOCUS_ENERGY]: RarityTier.COMMON,
|
||||
[MoveId.METRONOME]: RarityTier.COMMON,
|
||||
[MoveId.SELF_DESTRUCT]: RarityTier.GREAT,
|
||||
[MoveId.FIRE_BLAST]: RarityTier.ULTRA,
|
||||
[MoveId.WATERFALL]: RarityTier.GREAT,
|
||||
[MoveId.SWIFT]: RarityTier.COMMON,
|
||||
[MoveId.AMNESIA]: RarityTier.COMMON,
|
||||
[MoveId.DREAM_EATER]: RarityTier.GREAT,
|
||||
[MoveId.LEECH_LIFE]: RarityTier.ULTRA,
|
||||
[MoveId.FLASH]: RarityTier.COMMON,
|
||||
[MoveId.EXPLOSION]: RarityTier.GREAT,
|
||||
[MoveId.REST]: RarityTier.COMMON,
|
||||
[MoveId.ROCK_SLIDE]: RarityTier.GREAT,
|
||||
[MoveId.TRI_ATTACK]: RarityTier.ULTRA,
|
||||
[MoveId.SUPER_FANG]: RarityTier.COMMON,
|
||||
[MoveId.SUBSTITUTE]: RarityTier.COMMON,
|
||||
[MoveId.THIEF]: RarityTier.GREAT,
|
||||
[MoveId.SNORE]: RarityTier.COMMON,
|
||||
[MoveId.CURSE]: RarityTier.COMMON,
|
||||
[MoveId.REVERSAL]: RarityTier.COMMON,
|
||||
[MoveId.SPITE]: RarityTier.COMMON,
|
||||
[MoveId.PROTECT]: RarityTier.COMMON,
|
||||
[MoveId.SCARY_FACE]: RarityTier.COMMON,
|
||||
[MoveId.SLUDGE_BOMB]: RarityTier.GREAT,
|
||||
[MoveId.MUD_SLAP]: RarityTier.COMMON,
|
||||
[MoveId.SPIKES]: RarityTier.COMMON,
|
||||
[MoveId.ICY_WIND]: RarityTier.GREAT,
|
||||
[MoveId.OUTRAGE]: RarityTier.ULTRA,
|
||||
[MoveId.SANDSTORM]: RarityTier.COMMON,
|
||||
[MoveId.GIGA_DRAIN]: RarityTier.ULTRA,
|
||||
[MoveId.ENDURE]: RarityTier.COMMON,
|
||||
[MoveId.CHARM]: RarityTier.COMMON,
|
||||
[MoveId.FALSE_SWIPE]: RarityTier.COMMON,
|
||||
[MoveId.SWAGGER]: RarityTier.COMMON,
|
||||
[MoveId.STEEL_WING]: RarityTier.GREAT,
|
||||
[MoveId.ATTRACT]: RarityTier.COMMON,
|
||||
[MoveId.SLEEP_TALK]: RarityTier.COMMON,
|
||||
[MoveId.HEAL_BELL]: RarityTier.COMMON,
|
||||
[MoveId.RETURN]: RarityTier.ULTRA,
|
||||
[MoveId.FRUSTRATION]: RarityTier.COMMON,
|
||||
[MoveId.SAFEGUARD]: RarityTier.COMMON,
|
||||
[MoveId.PAIN_SPLIT]: RarityTier.COMMON,
|
||||
[MoveId.MEGAHORN]: RarityTier.ULTRA,
|
||||
[MoveId.BATON_PASS]: RarityTier.COMMON,
|
||||
[MoveId.ENCORE]: RarityTier.COMMON,
|
||||
[MoveId.IRON_TAIL]: RarityTier.GREAT,
|
||||
[MoveId.METAL_CLAW]: RarityTier.COMMON,
|
||||
[MoveId.SYNTHESIS]: RarityTier.GREAT,
|
||||
[MoveId.HIDDEN_POWER]: RarityTier.GREAT,
|
||||
[MoveId.RAIN_DANCE]: RarityTier.COMMON,
|
||||
[MoveId.SUNNY_DAY]: RarityTier.COMMON,
|
||||
[MoveId.CRUNCH]: RarityTier.GREAT,
|
||||
[MoveId.PSYCH_UP]: RarityTier.COMMON,
|
||||
[MoveId.SHADOW_BALL]: RarityTier.ULTRA,
|
||||
[MoveId.FUTURE_SIGHT]: RarityTier.GREAT,
|
||||
[MoveId.ROCK_SMASH]: RarityTier.COMMON,
|
||||
[MoveId.WHIRLPOOL]: RarityTier.COMMON,
|
||||
[MoveId.BEAT_UP]: RarityTier.COMMON,
|
||||
[MoveId.UPROAR]: RarityTier.GREAT,
|
||||
[MoveId.HEAT_WAVE]: RarityTier.ULTRA,
|
||||
[MoveId.HAIL]: RarityTier.COMMON,
|
||||
[MoveId.TORMENT]: RarityTier.COMMON,
|
||||
[MoveId.WILL_O_WISP]: RarityTier.COMMON,
|
||||
[MoveId.FACADE]: RarityTier.GREAT,
|
||||
[MoveId.FOCUS_PUNCH]: RarityTier.COMMON,
|
||||
[MoveId.NATURE_POWER]: RarityTier.COMMON,
|
||||
[MoveId.CHARGE]: RarityTier.COMMON,
|
||||
[MoveId.TAUNT]: RarityTier.COMMON,
|
||||
[MoveId.HELPING_HAND]: RarityTier.COMMON,
|
||||
[MoveId.TRICK]: RarityTier.COMMON,
|
||||
[MoveId.SUPERPOWER]: RarityTier.ULTRA,
|
||||
[MoveId.RECYCLE]: RarityTier.COMMON,
|
||||
[MoveId.REVENGE]: RarityTier.GREAT,
|
||||
[MoveId.BRICK_BREAK]: RarityTier.GREAT,
|
||||
[MoveId.KNOCK_OFF]: RarityTier.GREAT,
|
||||
[MoveId.ENDEAVOR]: RarityTier.COMMON,
|
||||
[MoveId.SKILL_SWAP]: RarityTier.COMMON,
|
||||
[MoveId.IMPRISON]: RarityTier.COMMON,
|
||||
[MoveId.SECRET_POWER]: RarityTier.COMMON,
|
||||
[MoveId.DIVE]: RarityTier.GREAT,
|
||||
[MoveId.FEATHER_DANCE]: RarityTier.COMMON,
|
||||
[MoveId.BLAZE_KICK]: RarityTier.GREAT,
|
||||
[MoveId.HYPER_VOICE]: RarityTier.ULTRA,
|
||||
[MoveId.BLAST_BURN]: RarityTier.ULTRA,
|
||||
[MoveId.HYDRO_CANNON]: RarityTier.ULTRA,
|
||||
[MoveId.WEATHER_BALL]: RarityTier.COMMON,
|
||||
[MoveId.FAKE_TEARS]: RarityTier.COMMON,
|
||||
[MoveId.AIR_CUTTER]: RarityTier.GREAT,
|
||||
[MoveId.OVERHEAT]: RarityTier.ULTRA,
|
||||
[MoveId.ROCK_TOMB]: RarityTier.GREAT,
|
||||
[MoveId.METAL_SOUND]: RarityTier.COMMON,
|
||||
[MoveId.COSMIC_POWER]: RarityTier.COMMON,
|
||||
[MoveId.SIGNAL_BEAM]: RarityTier.GREAT,
|
||||
[MoveId.SAND_TOMB]: RarityTier.COMMON,
|
||||
[MoveId.MUDDY_WATER]: RarityTier.GREAT,
|
||||
[MoveId.BULLET_SEED]: RarityTier.GREAT,
|
||||
[MoveId.AERIAL_ACE]: RarityTier.GREAT,
|
||||
[MoveId.ICICLE_SPEAR]: RarityTier.GREAT,
|
||||
[MoveId.IRON_DEFENSE]: RarityTier.GREAT,
|
||||
[MoveId.DRAGON_CLAW]: RarityTier.ULTRA,
|
||||
[MoveId.FRENZY_PLANT]: RarityTier.ULTRA,
|
||||
[MoveId.BULK_UP]: RarityTier.COMMON,
|
||||
[MoveId.BOUNCE]: RarityTier.GREAT,
|
||||
[MoveId.MUD_SHOT]: RarityTier.GREAT,
|
||||
[MoveId.POISON_TAIL]: RarityTier.GREAT,
|
||||
[MoveId.COVET]: RarityTier.GREAT,
|
||||
[MoveId.MAGICAL_LEAF]: RarityTier.GREAT,
|
||||
[MoveId.CALM_MIND]: RarityTier.GREAT,
|
||||
[MoveId.LEAF_BLADE]: RarityTier.ULTRA,
|
||||
[MoveId.DRAGON_DANCE]: RarityTier.GREAT,
|
||||
[MoveId.ROCK_BLAST]: RarityTier.GREAT,
|
||||
[MoveId.WATER_PULSE]: RarityTier.GREAT,
|
||||
[MoveId.ROOST]: RarityTier.GREAT,
|
||||
[MoveId.GRAVITY]: RarityTier.COMMON,
|
||||
[MoveId.GYRO_BALL]: RarityTier.COMMON,
|
||||
[MoveId.BRINE]: RarityTier.GREAT,
|
||||
[MoveId.PLUCK]: RarityTier.GREAT,
|
||||
[MoveId.TAILWIND]: RarityTier.GREAT,
|
||||
[MoveId.U_TURN]: RarityTier.GREAT,
|
||||
[MoveId.CLOSE_COMBAT]: RarityTier.ULTRA,
|
||||
[MoveId.PAYBACK]: RarityTier.COMMON,
|
||||
[MoveId.ASSURANCE]: RarityTier.COMMON,
|
||||
[MoveId.EMBARGO]: RarityTier.COMMON,
|
||||
[MoveId.FLING]: RarityTier.COMMON,
|
||||
[MoveId.GASTRO_ACID]: RarityTier.GREAT,
|
||||
[MoveId.POWER_SWAP]: RarityTier.COMMON,
|
||||
[MoveId.GUARD_SWAP]: RarityTier.COMMON,
|
||||
[MoveId.WORRY_SEED]: RarityTier.GREAT,
|
||||
[MoveId.TOXIC_SPIKES]: RarityTier.GREAT,
|
||||
[MoveId.FLARE_BLITZ]: RarityTier.ULTRA,
|
||||
[MoveId.AURA_SPHERE]: RarityTier.GREAT,
|
||||
[MoveId.ROCK_POLISH]: RarityTier.COMMON,
|
||||
[MoveId.POISON_JAB]: RarityTier.GREAT,
|
||||
[MoveId.DARK_PULSE]: RarityTier.GREAT,
|
||||
[MoveId.AQUA_TAIL]: RarityTier.GREAT,
|
||||
[MoveId.SEED_BOMB]: RarityTier.GREAT,
|
||||
[MoveId.AIR_SLASH]: RarityTier.GREAT,
|
||||
[MoveId.X_SCISSOR]: RarityTier.GREAT,
|
||||
[MoveId.BUG_BUZZ]: RarityTier.GREAT,
|
||||
[MoveId.DRAGON_PULSE]: RarityTier.GREAT,
|
||||
[MoveId.POWER_GEM]: RarityTier.GREAT,
|
||||
[MoveId.DRAIN_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.VACUUM_WAVE]: RarityTier.COMMON,
|
||||
[MoveId.FOCUS_BLAST]: RarityTier.GREAT,
|
||||
[MoveId.ENERGY_BALL]: RarityTier.GREAT,
|
||||
[MoveId.BRAVE_BIRD]: RarityTier.ULTRA,
|
||||
[MoveId.EARTH_POWER]: RarityTier.ULTRA,
|
||||
[MoveId.GIGA_IMPACT]: RarityTier.GREAT,
|
||||
[MoveId.NASTY_PLOT]: RarityTier.COMMON,
|
||||
[MoveId.AVALANCHE]: RarityTier.GREAT,
|
||||
[MoveId.SHADOW_CLAW]: RarityTier.GREAT,
|
||||
[MoveId.THUNDER_FANG]: RarityTier.GREAT,
|
||||
[MoveId.ICE_FANG]: RarityTier.GREAT,
|
||||
[MoveId.FIRE_FANG]: RarityTier.GREAT,
|
||||
[MoveId.PSYCHO_CUT]: RarityTier.GREAT,
|
||||
[MoveId.ZEN_HEADBUTT]: RarityTier.GREAT,
|
||||
[MoveId.FLASH_CANNON]: RarityTier.GREAT,
|
||||
[MoveId.ROCK_CLIMB]: RarityTier.GREAT,
|
||||
[MoveId.DEFOG]: RarityTier.COMMON,
|
||||
[MoveId.TRICK_ROOM]: RarityTier.COMMON,
|
||||
[MoveId.DRACO_METEOR]: RarityTier.ULTRA,
|
||||
[MoveId.LEAF_STORM]: RarityTier.ULTRA,
|
||||
[MoveId.POWER_WHIP]: RarityTier.ULTRA,
|
||||
[MoveId.CROSS_POISON]: RarityTier.GREAT,
|
||||
[MoveId.GUNK_SHOT]: RarityTier.ULTRA,
|
||||
[MoveId.IRON_HEAD]: RarityTier.GREAT,
|
||||
[MoveId.STONE_EDGE]: RarityTier.ULTRA,
|
||||
[MoveId.STEALTH_ROCK]: RarityTier.COMMON,
|
||||
[MoveId.GRASS_KNOT]: RarityTier.ULTRA,
|
||||
[MoveId.BUG_BITE]: RarityTier.GREAT,
|
||||
[MoveId.CHARGE_BEAM]: RarityTier.GREAT,
|
||||
[MoveId.HONE_CLAWS]: RarityTier.COMMON,
|
||||
[MoveId.WONDER_ROOM]: RarityTier.COMMON,
|
||||
[MoveId.PSYSHOCK]: RarityTier.GREAT,
|
||||
[MoveId.VENOSHOCK]: RarityTier.GREAT,
|
||||
[MoveId.MAGIC_ROOM]: RarityTier.COMMON,
|
||||
[MoveId.SMACK_DOWN]: RarityTier.COMMON,
|
||||
[MoveId.SLUDGE_WAVE]: RarityTier.GREAT,
|
||||
[MoveId.HEAVY_SLAM]: RarityTier.GREAT,
|
||||
[MoveId.ELECTRO_BALL]: RarityTier.GREAT,
|
||||
[MoveId.FLAME_CHARGE]: RarityTier.GREAT,
|
||||
[MoveId.LOW_SWEEP]: RarityTier.GREAT,
|
||||
[MoveId.ACID_SPRAY]: RarityTier.COMMON,
|
||||
[MoveId.FOUL_PLAY]: RarityTier.ULTRA,
|
||||
[MoveId.ROUND]: RarityTier.COMMON,
|
||||
[MoveId.ECHOED_VOICE]: RarityTier.COMMON,
|
||||
[MoveId.STORED_POWER]: RarityTier.COMMON,
|
||||
[MoveId.ALLY_SWITCH]: RarityTier.COMMON,
|
||||
[MoveId.SCALD]: RarityTier.GREAT,
|
||||
[MoveId.HEX]: RarityTier.GREAT,
|
||||
[MoveId.SKY_DROP]: RarityTier.GREAT,
|
||||
[MoveId.INCINERATE]: RarityTier.GREAT,
|
||||
[MoveId.QUASH]: RarityTier.COMMON,
|
||||
[MoveId.ACROBATICS]: RarityTier.GREAT,
|
||||
[MoveId.RETALIATE]: RarityTier.GREAT,
|
||||
[MoveId.WATER_PLEDGE]: RarityTier.GREAT,
|
||||
[MoveId.FIRE_PLEDGE]: RarityTier.GREAT,
|
||||
[MoveId.GRASS_PLEDGE]: RarityTier.GREAT,
|
||||
[MoveId.VOLT_SWITCH]: RarityTier.GREAT,
|
||||
[MoveId.STRUGGLE_BUG]: RarityTier.COMMON,
|
||||
[MoveId.BULLDOZE]: RarityTier.GREAT,
|
||||
[MoveId.FROST_BREATH]: RarityTier.GREAT,
|
||||
[MoveId.DRAGON_TAIL]: RarityTier.GREAT,
|
||||
[MoveId.WORK_UP]: RarityTier.COMMON,
|
||||
[MoveId.ELECTROWEB]: RarityTier.GREAT,
|
||||
[MoveId.WILD_CHARGE]: RarityTier.GREAT,
|
||||
[MoveId.DRILL_RUN]: RarityTier.GREAT,
|
||||
[MoveId.RAZOR_SHELL]: RarityTier.GREAT,
|
||||
[MoveId.HEAT_CRASH]: RarityTier.GREAT,
|
||||
[MoveId.TAIL_SLAP]: RarityTier.GREAT,
|
||||
[MoveId.HURRICANE]: RarityTier.ULTRA,
|
||||
[MoveId.SNARL]: RarityTier.COMMON,
|
||||
[MoveId.PHANTOM_FORCE]: RarityTier.ULTRA,
|
||||
[MoveId.PETAL_BLIZZARD]: RarityTier.GREAT,
|
||||
[MoveId.DISARMING_VOICE]: RarityTier.GREAT,
|
||||
[MoveId.DRAINING_KISS]: RarityTier.GREAT,
|
||||
[MoveId.GRASSY_TERRAIN]: RarityTier.COMMON,
|
||||
[MoveId.MISTY_TERRAIN]: RarityTier.COMMON,
|
||||
[MoveId.PLAY_ROUGH]: RarityTier.GREAT,
|
||||
[MoveId.CONFIDE]: RarityTier.COMMON,
|
||||
[MoveId.MYSTICAL_FIRE]: RarityTier.GREAT,
|
||||
[MoveId.EERIE_IMPULSE]: RarityTier.COMMON,
|
||||
[MoveId.VENOM_DRENCH]: RarityTier.COMMON,
|
||||
[MoveId.ELECTRIC_TERRAIN]: RarityTier.COMMON,
|
||||
[MoveId.DAZZLING_GLEAM]: RarityTier.ULTRA,
|
||||
[MoveId.INFESTATION]: RarityTier.COMMON,
|
||||
[MoveId.POWER_UP_PUNCH]: RarityTier.GREAT,
|
||||
[MoveId.DARKEST_LARIAT]: RarityTier.GREAT,
|
||||
[MoveId.HIGH_HORSEPOWER]: RarityTier.ULTRA,
|
||||
[MoveId.SOLAR_BLADE]: RarityTier.GREAT,
|
||||
[MoveId.THROAT_CHOP]: RarityTier.GREAT,
|
||||
[MoveId.POLLEN_PUFF]: RarityTier.GREAT,
|
||||
[MoveId.PSYCHIC_TERRAIN]: RarityTier.COMMON,
|
||||
[MoveId.LUNGE]: RarityTier.GREAT,
|
||||
[MoveId.SPEED_SWAP]: RarityTier.COMMON,
|
||||
[MoveId.SMART_STRIKE]: RarityTier.GREAT,
|
||||
[MoveId.BRUTAL_SWING]: RarityTier.GREAT,
|
||||
[MoveId.AURORA_VEIL]: RarityTier.COMMON,
|
||||
[MoveId.PSYCHIC_FANGS]: RarityTier.GREAT,
|
||||
[MoveId.STOMPING_TANTRUM]: RarityTier.GREAT,
|
||||
[MoveId.LIQUIDATION]: RarityTier.ULTRA,
|
||||
[MoveId.BODY_PRESS]: RarityTier.ULTRA,
|
||||
[MoveId.BREAKING_SWIPE]: RarityTier.GREAT,
|
||||
[MoveId.STEEL_BEAM]: RarityTier.ULTRA,
|
||||
[MoveId.EXPANDING_FORCE]: RarityTier.GREAT,
|
||||
[MoveId.STEEL_ROLLER]: RarityTier.COMMON,
|
||||
[MoveId.SCALE_SHOT]: RarityTier.ULTRA,
|
||||
[MoveId.METEOR_BEAM]: RarityTier.GREAT,
|
||||
[MoveId.MISTY_EXPLOSION]: RarityTier.COMMON,
|
||||
[MoveId.GRASSY_GLIDE]: RarityTier.COMMON,
|
||||
[MoveId.RISING_VOLTAGE]: RarityTier.COMMON,
|
||||
[MoveId.TERRAIN_PULSE]: RarityTier.COMMON,
|
||||
[MoveId.SKITTER_SMACK]: RarityTier.GREAT,
|
||||
[MoveId.BURNING_JEALOUSY]: RarityTier.GREAT,
|
||||
[MoveId.LASH_OUT]: RarityTier.GREAT,
|
||||
[MoveId.POLTERGEIST]: RarityTier.ULTRA,
|
||||
[MoveId.CORROSIVE_GAS]: RarityTier.COMMON,
|
||||
[MoveId.COACHING]: RarityTier.COMMON,
|
||||
[MoveId.FLIP_TURN]: RarityTier.COMMON,
|
||||
[MoveId.TRIPLE_AXEL]: RarityTier.COMMON,
|
||||
[MoveId.DUAL_WINGBEAT]: RarityTier.COMMON,
|
||||
[MoveId.SCORCHING_SANDS]: RarityTier.GREAT,
|
||||
[MoveId.TERA_BLAST]: RarityTier.GREAT,
|
||||
[MoveId.ICE_SPINNER]: RarityTier.GREAT,
|
||||
[MoveId.SNOWSCAPE]: RarityTier.COMMON,
|
||||
[MoveId.POUNCE]: RarityTier.COMMON,
|
||||
[MoveId.TRAILBLAZE]: RarityTier.COMMON,
|
||||
[MoveId.CHILLING_WATER]: RarityTier.COMMON,
|
||||
[MoveId.HARD_PRESS]: RarityTier.GREAT,
|
||||
[MoveId.DRAGON_CHEER]: RarityTier.COMMON,
|
||||
[MoveId.ALLURING_VOICE]: RarityTier.GREAT,
|
||||
[MoveId.TEMPER_FLARE]: RarityTier.GREAT,
|
||||
[MoveId.SUPERCELL_SLAM]: RarityTier.GREAT,
|
||||
[MoveId.PSYCHIC_NOISE]: RarityTier.GREAT,
|
||||
[MoveId.UPPER_HAND]: RarityTier.COMMON,
|
||||
};
|
||||
|
||||
@ -404,22 +404,18 @@ export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimCon
|
||||
export const commonAnims = new Map<CommonAnim, AnimConfig>();
|
||||
export const encounterAnims = new Map<EncounterAnim, AnimConfig>();
|
||||
|
||||
export function initCommonAnims(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const commonAnimNames = getEnumKeys(CommonAnim);
|
||||
const commonAnimIds = getEnumValues(CommonAnim);
|
||||
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
|
||||
for (let ca = 0; ca < commonAnimIds.length; ca++) {
|
||||
const commonAnimId = commonAnimIds[ca];
|
||||
commonAnimFetches.push(
|
||||
globalScene
|
||||
.cachedFetch(`./battle-anims/common-${toKebabCase(commonAnimNames[ca])}.json`)
|
||||
.then(response => response.json())
|
||||
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
|
||||
);
|
||||
}
|
||||
Promise.allSettled(commonAnimFetches).then(() => resolve());
|
||||
});
|
||||
export async function initCommonAnims(): Promise<void> {
|
||||
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
|
||||
for (const commonAnimName of getEnumKeys(CommonAnim)) {
|
||||
const commonAnimId = CommonAnim[commonAnimName];
|
||||
commonAnimFetches.push(
|
||||
globalScene
|
||||
.cachedFetch(`./battle-anims/common-${toKebabCase(commonAnimName)}.json`)
|
||||
.then(response => response.json())
|
||||
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
|
||||
);
|
||||
}
|
||||
await Promise.allSettled(commonAnimFetches);
|
||||
}
|
||||
|
||||
export function initMoveAnim(move: MoveId): Promise<void> {
|
||||
@ -1396,279 +1392,3 @@ export class EncounterBattleAnim extends BattleAnim {
|
||||
return this.oppAnim;
|
||||
}
|
||||
}
|
||||
|
||||
export async function populateAnims() {
|
||||
const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase());
|
||||
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/_/g, ""));
|
||||
const commonAnimIds = getEnumValues(CommonAnim);
|
||||
const chargeAnimNames = getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
|
||||
const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/_/g, " "));
|
||||
const chargeAnimIds = getEnumValues(ChargeAnim);
|
||||
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
|
||||
const moveNameToId = {};
|
||||
// Exclude MoveId.NONE;
|
||||
for (const move of getEnumValues(MoveId).slice(1)) {
|
||||
// KARATE_CHOP => KARATECHOP
|
||||
const moveName = MoveId[move].toUpperCase().replace(/_/g, "");
|
||||
moveNameToId[moveName] = move;
|
||||
}
|
||||
|
||||
const seNames: string[] = []; //(await fs.readdir('./public/audio/se/battle_anims/')).map(se => se.toString());
|
||||
|
||||
const animsData: any[] = []; //battleAnimRawData.split('!ruby/array:PBAnimation').slice(1); // TODO: add a proper type
|
||||
for (let a = 0; a < animsData.length; a++) {
|
||||
const fields = animsData[a].split("@").slice(1);
|
||||
|
||||
const nameField = fields.find(f => f.startsWith("name: "));
|
||||
|
||||
let isOppMove: boolean | undefined;
|
||||
let commonAnimId: CommonAnim | undefined;
|
||||
let chargeAnimId: ChargeAnim | undefined;
|
||||
if (!nameField.startsWith("name: Move:") && !(isOppMove = nameField.startsWith("name: OppMove:"))) {
|
||||
const nameMatch = commonNamePattern.exec(nameField)!; // TODO: is this bang correct?
|
||||
const name = nameMatch[2].toLowerCase();
|
||||
if (commonAnimMatchNames.indexOf(name) > -1) {
|
||||
commonAnimId = commonAnimIds[commonAnimMatchNames.indexOf(name)];
|
||||
} else if (chargeAnimMatchNames.indexOf(name) > -1) {
|
||||
isOppMove = nameField.startsWith("name: Opp ");
|
||||
chargeAnimId = chargeAnimIds[chargeAnimMatchNames.indexOf(name)];
|
||||
}
|
||||
}
|
||||
const nameIndex = nameField.indexOf(":", 5) + 1;
|
||||
const animName = nameField.slice(nameIndex, nameField.indexOf("\n", nameIndex));
|
||||
if (!moveNameToId.hasOwnProperty(animName) && !commonAnimId && !chargeAnimId) {
|
||||
continue;
|
||||
}
|
||||
const anim = commonAnimId || chargeAnimId ? new AnimConfig() : new AnimConfig();
|
||||
if (anim instanceof AnimConfig) {
|
||||
(anim as AnimConfig).id = moveNameToId[animName];
|
||||
}
|
||||
if (commonAnimId) {
|
||||
commonAnims.set(commonAnimId, anim);
|
||||
} else if (chargeAnimId) {
|
||||
chargeAnims.set(chargeAnimId, !isOppMove ? anim : [chargeAnims.get(chargeAnimId) as AnimConfig, anim]);
|
||||
} else {
|
||||
moveAnims.set(
|
||||
moveNameToId[animName],
|
||||
!isOppMove ? (anim as AnimConfig) : [moveAnims.get(moveNameToId[animName]) as AnimConfig, anim as AnimConfig],
|
||||
);
|
||||
}
|
||||
for (let f = 0; f < fields.length; f++) {
|
||||
const field = fields[f];
|
||||
const fieldName = field.slice(0, field.indexOf(":"));
|
||||
const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim();
|
||||
switch (fieldName) {
|
||||
case "array": {
|
||||
const framesData = fieldData.split(" - - - ").slice(1);
|
||||
for (let fd = 0; fd < framesData.length; fd++) {
|
||||
anim.frames.push([]);
|
||||
const frameData = framesData[fd];
|
||||
const focusFramesData = frameData.split(" - - ");
|
||||
for (let tf = 0; tf < focusFramesData.length; tf++) {
|
||||
const values = focusFramesData[tf].replace(/ {6}- /g, "").split("\n");
|
||||
const targetFrame = new AnimFrame(
|
||||
Number.parseFloat(values[0]),
|
||||
Number.parseFloat(values[1]),
|
||||
Number.parseFloat(values[2]),
|
||||
Number.parseFloat(values[11]),
|
||||
Number.parseFloat(values[3]),
|
||||
Number.parseInt(values[4]) === 1,
|
||||
Number.parseInt(values[6]) === 1,
|
||||
Number.parseInt(values[5]),
|
||||
Number.parseInt(values[7]),
|
||||
Number.parseInt(values[8]),
|
||||
Number.parseInt(values[12]),
|
||||
Number.parseInt(values[13]),
|
||||
Number.parseInt(values[14]),
|
||||
Number.parseInt(values[15]),
|
||||
Number.parseInt(values[16]),
|
||||
Number.parseInt(values[17]),
|
||||
Number.parseInt(values[18]),
|
||||
Number.parseInt(values[19]),
|
||||
Number.parseInt(values[21]),
|
||||
Number.parseInt(values[22]),
|
||||
Number.parseInt(values[23]),
|
||||
Number.parseInt(values[24]),
|
||||
Number.parseInt(values[20]) === 1,
|
||||
Number.parseInt(values[25]),
|
||||
Number.parseInt(values[26]) as AnimFocus,
|
||||
);
|
||||
anim.frames[fd].push(targetFrame);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "graphic": {
|
||||
const graphic = fieldData !== "''" ? fieldData : "";
|
||||
anim.graphic = graphic.indexOf(".") > -1 ? graphic.slice(0, fieldData.indexOf(".")) : graphic;
|
||||
break;
|
||||
}
|
||||
case "timing": {
|
||||
const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1);
|
||||
for (let t = 0; t < timingEntries.length; t++) {
|
||||
const timingData = timingEntries[t]
|
||||
.replace(/\n/g, " ")
|
||||
.replace(/[ ]{2,}/g, " ")
|
||||
.replace(/[a-z]+: ! '', /gi, "")
|
||||
.replace(/name: (.*?),/, 'name: "$1",')
|
||||
.replace(
|
||||
/flashColor: !ruby\/object:Color { alpha: ([\d.]+), blue: ([\d.]+), green: ([\d.]+), red: ([\d.]+)}/,
|
||||
"flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1",
|
||||
);
|
||||
const frameIndex = Number.parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
|
||||
let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct?
|
||||
const timingType = Number.parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct?
|
||||
let timedEvent: AnimTimedEvent | undefined;
|
||||
switch (timingType) {
|
||||
case 0:
|
||||
if (resourceName && resourceName.indexOf(".") === -1) {
|
||||
let ext: string | undefined;
|
||||
["wav", "mp3", "m4a"].every(e => {
|
||||
if (seNames.indexOf(`${resourceName}.${e}`) > -1) {
|
||||
ext = e;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!ext) {
|
||||
ext = ".wav";
|
||||
}
|
||||
resourceName += `.${ext}`;
|
||||
}
|
||||
timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName);
|
||||
break;
|
||||
case 1:
|
||||
timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf(".")));
|
||||
break;
|
||||
case 2:
|
||||
timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf(".")));
|
||||
break;
|
||||
}
|
||||
if (!timedEvent) {
|
||||
continue;
|
||||
}
|
||||
const propPattern = /([a-z]+): (.*?)(?:,|\})/gi;
|
||||
let propMatch: RegExpExecArray;
|
||||
while ((propMatch = propPattern.exec(timingData)!)) {
|
||||
// TODO: is this bang correct?
|
||||
const prop = propMatch[1];
|
||||
let value: any = propMatch[2];
|
||||
switch (prop) {
|
||||
case "bgX":
|
||||
case "bgY":
|
||||
value = Number.parseFloat(value);
|
||||
break;
|
||||
case "volume":
|
||||
case "pitch":
|
||||
case "opacity":
|
||||
case "colorRed":
|
||||
case "colorGreen":
|
||||
case "colorBlue":
|
||||
case "colorAlpha":
|
||||
case "duration":
|
||||
case "flashScope":
|
||||
case "flashRed":
|
||||
case "flashGreen":
|
||||
case "flashBlue":
|
||||
case "flashAlpha":
|
||||
case "flashDuration":
|
||||
value = Number.parseInt(value);
|
||||
break;
|
||||
}
|
||||
if (timedEvent.hasOwnProperty(prop)) {
|
||||
timedEvent[prop] = value;
|
||||
}
|
||||
}
|
||||
if (!anim.frameTimedEvents.has(frameIndex)) {
|
||||
anim.frameTimedEvents.set(frameIndex, []);
|
||||
}
|
||||
anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct?
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "position":
|
||||
anim.position = Number.parseInt(fieldData);
|
||||
break;
|
||||
case "hue":
|
||||
anim.hue = Number.parseInt(fieldData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/noUnusedVariables: used in commented code
|
||||
const animReplacer = (k, v) => {
|
||||
if (k === "id" && !v) {
|
||||
return undefined;
|
||||
}
|
||||
if (v instanceof Map) {
|
||||
return Object.fromEntries(v);
|
||||
}
|
||||
if (v instanceof AnimTimedEvent) {
|
||||
v["eventType"] = v.getEventType();
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
const animConfigProps = ["id", "graphic", "frames", "frameTimedEvents", "position", "hue"];
|
||||
const animFrameProps = [
|
||||
"x",
|
||||
"y",
|
||||
"zoomX",
|
||||
"zoomY",
|
||||
"angle",
|
||||
"mirror",
|
||||
"visible",
|
||||
"blendType",
|
||||
"target",
|
||||
"graphicFrame",
|
||||
"opacity",
|
||||
"color",
|
||||
"tone",
|
||||
"flash",
|
||||
"locked",
|
||||
"priority",
|
||||
"focus",
|
||||
];
|
||||
const propSets = [animConfigProps, animFrameProps];
|
||||
|
||||
// biome-ignore lint/correctness/noUnusedVariables: used in commented code
|
||||
const animComparator = (a: Element, b: Element) => {
|
||||
let props: string[];
|
||||
for (let p = 0; p < propSets.length; p++) {
|
||||
props = propSets[p];
|
||||
// @ts-expect-error TODO
|
||||
const ai = props.indexOf(a.key);
|
||||
if (ai === -1) {
|
||||
continue;
|
||||
}
|
||||
// @ts-expect-error TODO
|
||||
const bi = props.indexOf(b.key);
|
||||
|
||||
return ai < bi ? -1 : ai > bi ? 1 : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
/*for (let ma of moveAnims.keys()) {
|
||||
const data = moveAnims.get(ma);
|
||||
(async () => {
|
||||
await fs.writeFile(`../public/battle-anims/${Moves[ma].toLowerCase().replace(/\_/g, '-')}.json`, stringify(data, { replacer: animReplacer, cmp: animComparator, space: ' ' }));
|
||||
})();
|
||||
}
|
||||
|
||||
for (let ca of chargeAnims.keys()) {
|
||||
const data = chargeAnims.get(ca);
|
||||
(async () => {
|
||||
await fs.writeFile(`../public/battle-anims/${chargeAnimNames[chargeAnimIds.indexOf(ca)].replace(/\_/g, '-')}.json`, stringify(data, { replacer: animReplacer, cmp: animComparator, space: ' ' }));
|
||||
})();
|
||||
}
|
||||
|
||||
for (let cma of commonAnims.keys()) {
|
||||
const data = commonAnims.get(cma);
|
||||
(async () => {
|
||||
await fs.writeFile(`../public/battle-anims/common-${commonAnimNames[commonAnimIds.indexOf(cma)].replace(/\_/g, '-')}.json`, stringify(data, { replacer: animReplacer, cmp: animComparator, space: ' ' }));
|
||||
})();
|
||||
}*/
|
||||
}
|
||||
|
||||
@ -7,19 +7,20 @@ import { BattleType } from "#enums/battle-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { TypeColor, TypeShadow } from "#enums/color";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import type { MoveSourceType } from "#enums/move-source-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { Trainer } from "#field/trainer";
|
||||
import type { ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import type { RewardOption } from "#items/reward";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import type { DexAttrProps, GameData } from "#system/game-data";
|
||||
import { RibbonData, type RibbonFlag } from "#system/ribbons/ribbon-data";
|
||||
import { type BooleanHolder, isBetween, type NumberHolder, randSeedItem } from "#utils/common";
|
||||
import { deepCopy } from "#utils/data";
|
||||
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
|
||||
@ -42,6 +43,15 @@ export abstract class Challenge {
|
||||
|
||||
public conditions: ChallengeCondition[];
|
||||
|
||||
/**
|
||||
* The Ribbon awarded on challenge completion, or 0 if the challenge has no ribbon or is not enabled
|
||||
*
|
||||
* @defaultValue 0
|
||||
*/
|
||||
public get ribbonAwarded(): RibbonFlag {
|
||||
return 0 as RibbonFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id {@link Challenges} The enum value for the challenge
|
||||
*/
|
||||
@ -393,7 +403,7 @@ export abstract class Challenge {
|
||||
* @param _status - Whether the item should be added to the shop or not
|
||||
* @returns Whether this function did anything
|
||||
*/
|
||||
applyShopItem(_shopItem: ModifierTypeOption | null, _status: BooleanHolder): boolean {
|
||||
applyShopItem(_shopItem: RewardOption | null, _status: BooleanHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -403,7 +413,7 @@ export abstract class Challenge {
|
||||
* @param _status - Whether the reward should be added to the reward options or not
|
||||
* @returns Whether this function did anything
|
||||
*/
|
||||
applyWaveReward(_reward: ModifierTypeOption | null, _status: BooleanHolder): boolean {
|
||||
applyWaveReward(_reward: RewardOption | null, _status: BooleanHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -423,6 +433,12 @@ type ChallengeCondition = (data: GameData) => boolean;
|
||||
* Implements a mono generation challenge.
|
||||
*/
|
||||
export class SingleGenerationChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
// NOTE: This logic will not work for the eventual mono gen 10 ribbon, as
|
||||
// as its flag will not be in sequence with the other mono gen ribbons.
|
||||
return this.value ? ((RibbonData.MONO_GEN_1 << (this.value - 1)) as RibbonFlag) : 0;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(Challenges.SINGLE_GENERATION, 9);
|
||||
}
|
||||
@ -527,13 +543,13 @@ export class SingleGenerationChallenge extends Challenge {
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
});
|
||||
@ -544,14 +560,14 @@ export class SingleGenerationChallenge extends Challenge {
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
});
|
||||
@ -686,6 +702,12 @@ interface monotypeOverride {
|
||||
* Implements a mono type challenge.
|
||||
*/
|
||||
export class SingleTypeChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
// `this.value` represents the 1-based index of pokemon type
|
||||
// `RibbonData.MONO_NORMAL` starts the flag position for the types,
|
||||
// and we shift it by 1 for the specific type.
|
||||
return this.value ? ((RibbonData.MONO_NORMAL << (this.value - 1)) as RibbonFlag) : 0;
|
||||
}
|
||||
private static TYPE_OVERRIDES: monotypeOverride[] = [
|
||||
{ species: SpeciesId.CASTFORM, type: PokemonType.NORMAL, fusion: false },
|
||||
];
|
||||
@ -755,6 +777,9 @@ export class SingleTypeChallenge extends Challenge {
|
||||
* Implements a fresh start challenge.
|
||||
*/
|
||||
export class FreshStartChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.FRESH_START : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.FRESH_START, 2);
|
||||
}
|
||||
@ -828,6 +853,9 @@ export class FreshStartChallenge extends Challenge {
|
||||
* Implements an inverse battle challenge.
|
||||
*/
|
||||
export class InverseBattleChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.INVERSE : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.INVERSE_BATTLE, 1);
|
||||
}
|
||||
@ -861,6 +889,9 @@ export class InverseBattleChallenge extends Challenge {
|
||||
* Implements a flip stat challenge.
|
||||
*/
|
||||
export class FlipStatChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.FLIP_STATS : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.FLIP_STAT, 1);
|
||||
}
|
||||
@ -941,6 +972,9 @@ export class LowerStarterPointsChallenge extends Challenge {
|
||||
* Implements a No Support challenge
|
||||
*/
|
||||
export class LimitedSupportChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? ((RibbonData.NO_HEAL << (this.value - 1)) as RibbonFlag) : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.LIMITED_SUPPORT, 3);
|
||||
}
|
||||
@ -973,6 +1007,9 @@ export class LimitedSupportChallenge extends Challenge {
|
||||
* Implements a Limited Catch challenge
|
||||
*/
|
||||
export class LimitedCatchChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.LIMITED_CATCH : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.LIMITED_CATCH, 1);
|
||||
}
|
||||
@ -997,6 +1034,9 @@ export class LimitedCatchChallenge extends Challenge {
|
||||
* Implements a Permanent Faint challenge
|
||||
*/
|
||||
export class HardcoreChallenge extends Challenge {
|
||||
public override get ribbonAwarded(): RibbonFlag {
|
||||
return this.value ? RibbonData.HARDCORE : 0;
|
||||
}
|
||||
constructor() {
|
||||
super(Challenges.HARDCORE, 1);
|
||||
}
|
||||
@ -1009,12 +1049,12 @@ export class HardcoreChallenge extends Challenge {
|
||||
return false;
|
||||
}
|
||||
|
||||
override applyShopItem(shopItem: ModifierTypeOption | null, status: BooleanHolder): boolean {
|
||||
override applyShopItem(shopItem: RewardOption | null, status: BooleanHolder): boolean {
|
||||
status.value = shopItem?.type.group !== "revive";
|
||||
return true;
|
||||
}
|
||||
|
||||
override applyWaveReward(reward: ModifierTypeOption | null, status: BooleanHolder): boolean {
|
||||
override applyWaveReward(reward: RewardOption | null, status: BooleanHolder): boolean {
|
||||
return this.applyShopItem(reward, status);
|
||||
}
|
||||
|
||||
|
||||
@ -5,10 +5,9 @@ import type { PokemonSpeciesForm } from "#data/pokemon-species";
|
||||
import { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import { PlayerPokemon } from "#field/pokemon";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import type { Starter } from "#ui/starter-select-ui-handler";
|
||||
import { randSeedGauss, randSeedInt, randSeedItem } from "#utils/common";
|
||||
import { isNullOrUndefined, randSeedGauss, randSeedInt, randSeedItem } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
|
||||
|
||||
@ -32,15 +31,9 @@ export function getDailyRunStarters(seed: string): Starter[] {
|
||||
() => {
|
||||
const startingLevel = globalScene.gameMode.getStartingLevel();
|
||||
|
||||
if (/\d{18}$/.test(seed)) {
|
||||
for (let s = 0; s < 3; s++) {
|
||||
const offset = 6 + s * 6;
|
||||
const starterSpeciesForm = getPokemonSpeciesForm(
|
||||
Number.parseInt(seed.slice(offset, offset + 4)) as SpeciesId,
|
||||
Number.parseInt(seed.slice(offset + 4, offset + 6)),
|
||||
);
|
||||
starters.push(getDailyRunStarter(starterSpeciesForm, startingLevel));
|
||||
}
|
||||
const eventStarters = getDailyEventSeedStarters(seed);
|
||||
if (!isNullOrUndefined(eventStarters)) {
|
||||
starters.push(...eventStarters);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -72,18 +65,7 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve
|
||||
const starterSpecies =
|
||||
starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId);
|
||||
const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex;
|
||||
const pokemon = new PlayerPokemon(
|
||||
starterSpecies,
|
||||
startingLevel,
|
||||
undefined,
|
||||
formIndex,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
const pokemon = globalScene.addPlayerPokemon(starterSpecies, startingLevel, undefined, formIndex);
|
||||
const starter: Starter = {
|
||||
species: starterSpecies,
|
||||
dexAttr: pokemon.getDexAttr(),
|
||||
@ -145,6 +127,11 @@ const dailyBiomeWeights: BiomeWeights = {
|
||||
};
|
||||
|
||||
export function getDailyStartingBiome(): BiomeId {
|
||||
const eventBiome = getDailyEventSeedBiome(globalScene.seed);
|
||||
if (!isNullOrUndefined(eventBiome)) {
|
||||
return eventBiome;
|
||||
}
|
||||
|
||||
const biomes = getEnumValues(BiomeId).filter(b => b !== BiomeId.TOWN && b !== BiomeId.END);
|
||||
|
||||
let totalWeight = 0;
|
||||
@ -169,3 +156,126 @@ export function getDailyStartingBiome(): BiomeId {
|
||||
// TODO: should this use `randSeedItem`?
|
||||
return biomes[randSeedInt(biomes.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is Daily Mode and the seed is longer than a default seed
|
||||
* then it has been modified and could contain a custom event seed. \
|
||||
* Default seeds are always exactly 24 characters.
|
||||
* @returns `true` if it is a Daily Event Seed.
|
||||
*/
|
||||
export function isDailyEventSeed(seed: string): boolean {
|
||||
return globalScene.gameMode.isDaily && seed.length > 24;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects the seed to contain `/starters\d{18}/`
|
||||
* where the digits alternate between 4 digits for the species ID and 2 digits for the form index
|
||||
* (left padded with `0`s as necessary).
|
||||
* @returns An array of {@linkcode Starter}s, or `null` if no valid match.
|
||||
*/
|
||||
export function getDailyEventSeedStarters(seed: string): Starter[] | null {
|
||||
if (!isDailyEventSeed(seed)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const starters: Starter[] = [];
|
||||
const match = /starters(\d{4})(\d{2})(\d{4})(\d{2})(\d{4})(\d{2})/g.exec(seed);
|
||||
|
||||
if (!match || match.length !== 7) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = 1; i < match.length; i += 2) {
|
||||
const speciesId = Number.parseInt(match[i]) as SpeciesId;
|
||||
const formIndex = Number.parseInt(match[i + 1]);
|
||||
|
||||
if (!getEnumValues(SpeciesId).includes(speciesId)) {
|
||||
console.warn("Invalid species ID used for custom daily run seed starter:", speciesId);
|
||||
return null;
|
||||
}
|
||||
|
||||
const starterForm = getPokemonSpeciesForm(speciesId, formIndex);
|
||||
const startingLevel = globalScene.gameMode.getStartingLevel();
|
||||
const starter = getDailyRunStarter(starterForm, startingLevel);
|
||||
starters.push(starter);
|
||||
}
|
||||
|
||||
return starters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects the seed to contain `/boss\d{4}\d{2}/`
|
||||
* where the first 4 digits are the species ID and the next 2 digits are the form index
|
||||
* (left padded with `0`s as necessary).
|
||||
* @returns A {@linkcode PokemonSpeciesForm} to be used for the boss, or `null` if no valid match.
|
||||
*/
|
||||
export function getDailyEventSeedBoss(seed: string): PokemonSpeciesForm | null {
|
||||
if (!isDailyEventSeed(seed)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = /boss(\d{4})(\d{2})/g.exec(seed);
|
||||
if (!match || match.length !== 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const speciesId = Number.parseInt(match[1]) as SpeciesId;
|
||||
const formIndex = Number.parseInt(match[2]);
|
||||
|
||||
if (!getEnumValues(SpeciesId).includes(speciesId)) {
|
||||
console.warn("Invalid species ID used for custom daily run seed boss:", speciesId);
|
||||
return null;
|
||||
}
|
||||
|
||||
const starterForm = getPokemonSpeciesForm(speciesId, formIndex);
|
||||
return starterForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects the seed to contain `/biome\d{2}/` where the 2 digits are a biome ID (left padded with `0` if necessary).
|
||||
* @returns The biome to use or `null` if no valid match.
|
||||
*/
|
||||
export function getDailyEventSeedBiome(seed: string): BiomeId | null {
|
||||
if (!isDailyEventSeed(seed)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = /biome(\d{2})/g.exec(seed);
|
||||
if (!match || match.length !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const startingBiome = Number.parseInt(match[1]) as BiomeId;
|
||||
|
||||
if (!getEnumValues(BiomeId).includes(startingBiome)) {
|
||||
console.warn("Invalid biome ID used for custom daily run seed:", startingBiome);
|
||||
return null;
|
||||
}
|
||||
|
||||
return startingBiome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects the seed to contain `/luck\d{2}/` where the 2 digits are a number between `0` and `14`
|
||||
* (left padded with `0` if necessary).
|
||||
* @returns The custom luck value or `null` if no valid match.
|
||||
*/
|
||||
export function getDailyEventSeedLuck(seed: string): number | null {
|
||||
if (!isDailyEventSeed(seed)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = /luck(\d{2})/g.exec(seed);
|
||||
if (!match || match.length !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const luck = Number.parseInt(match[1]);
|
||||
|
||||
if (luck < 0 || luck > 14) {
|
||||
console.warn("Invalid luck value used for custom daily run seed:", luck);
|
||||
return null;
|
||||
}
|
||||
|
||||
return luck;
|
||||
}
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
import type { Ability } from "#abilities/ability";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import type { ModifierTypes } from "#modifiers/modifier-type";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { HeldItem } from "#items/held-item";
|
||||
import type { TrainerItem } from "#items/trainer-item";
|
||||
import type { Move } from "#moves/move";
|
||||
|
||||
export const allAbilities: Ability[] = [];
|
||||
export const allMoves: Move[] = [];
|
||||
export const allSpecies: PokemonSpecies[] = [];
|
||||
|
||||
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
|
||||
export const modifierTypes = {} as ModifierTypes;
|
||||
//@ts-expect-error
|
||||
export const allHeldItems: Record<HeldItemId, HeldItem> = {};
|
||||
//@ts-expect-error
|
||||
export const allTrainerItems: Record<TrainerItemId, TrainerItem> = {};
|
||||
|
||||
@ -47,6 +47,7 @@ export class EggHatchData {
|
||||
caughtCount: currDexEntry.caughtCount,
|
||||
hatchedCount: currDexEntry.hatchedCount,
|
||||
ivs: [...currDexEntry.ivs],
|
||||
ribbons: currDexEntry.ribbons,
|
||||
};
|
||||
this.starterDataEntryBeforeUpdate = {
|
||||
moveset: currStarterDataEntry.moveset,
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
TypeBoostTag,
|
||||
} from "#data/battler-tags";
|
||||
import { getBerryEffectFunc } from "#data/berry";
|
||||
import { allAbilities, allMoves } from "#data/data-lists";
|
||||
import { allAbilities, allHeldItems, allMoves } from "#data/data-lists";
|
||||
import { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-change-triggers";
|
||||
import { DelayedAttackTag } from "#data/positional-tags/positional-tag";
|
||||
import {
|
||||
@ -42,8 +42,8 @@ import { BiomeId } from "#enums/biome-id";
|
||||
import { ChallengeType } from "#enums/challenge-type";
|
||||
import { Command } from "#enums/command";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ChargeAnim } from "#enums/move-anims-common";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
@ -68,14 +68,10 @@ import { SwitchType } from "#enums/switch-type";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { MoveUsedEvent } from "#events/battle-scene";
|
||||
import type { EnemyPokemon, Pokemon } from "#field/pokemon";
|
||||
import {
|
||||
AttackTypeBoosterModifier,
|
||||
BerryModifier,
|
||||
PokemonHeldItemModifier,
|
||||
PokemonMoveAccuracyBoosterModifier,
|
||||
PokemonMultiHitModifier,
|
||||
PreserveBerryModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import { applyHeldItems } from "#items/all-held-items";
|
||||
import { BerryHeldItem, berryTypeToHeldItem } from "#items/berry";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSketchMoves, invalidSleepTalkMoves } from "#moves/invalid-moves";
|
||||
import { frenzyMissFunc, getMoveTargets } from "#moves/move-utils";
|
||||
@ -86,11 +82,11 @@ import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
||||
import { SwitchSummonPhase } from "#phases/switch-summon-phase";
|
||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||
import type { Localizable } from "#types/locales";
|
||||
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString } from "#types/move-types";
|
||||
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString, MoveMessageFunc } from "#types/move-types";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
import { toCamelCase, toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
import { applyChallenges } from "#utils/challenge-utils";
|
||||
|
||||
@ -162,10 +158,16 @@ export abstract class Move implements Localizable {
|
||||
}
|
||||
|
||||
localize(): void {
|
||||
const i18nKey = MoveId[this.id].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string;
|
||||
const i18nKey = toCamelCase(MoveId[this.id])
|
||||
|
||||
this.name = this.id ? `${i18next.t(`move:${i18nKey}.name`)}${this.nameAppend}` : "";
|
||||
this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}` : "";
|
||||
if (this.id === MoveId.NONE) {
|
||||
this.name = "";
|
||||
this.effect = ""
|
||||
return;
|
||||
}
|
||||
|
||||
this.name = `${i18next.t(`move:${i18nKey}.name`)}${this.nameAppend}`;
|
||||
this.effect = `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -779,7 +781,7 @@ export abstract class Move implements Localizable {
|
||||
const isOhko = this.hasAttr("OneHitKOAccuracyAttr");
|
||||
|
||||
if (!isOhko) {
|
||||
globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
|
||||
applyHeldItems(HeldItemEffect.ACCURACY_BOOSTER, { pokemon: user, moveAccuracy: moveAccuracy });
|
||||
}
|
||||
|
||||
if (globalScene.arena.weather?.weatherType === WeatherType.FOG) {
|
||||
@ -833,9 +835,15 @@ export abstract class Move implements Localizable {
|
||||
}
|
||||
|
||||
// Non-priority, single-hit moves of the user's Tera Type are always a bare minimum of 60 power
|
||||
|
||||
const sourceTeraType = source.getTeraType();
|
||||
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
||||
if (
|
||||
source.isTerastallized
|
||||
&& sourceTeraType === this.type
|
||||
&& power.value < 60
|
||||
&& this.priority <= 0
|
||||
&& !this.hasAttr("MultiHitAttr")
|
||||
&& !source.heldItemManager.hasItem(HeldItemId.MULTI_LENS)
|
||||
) {
|
||||
power.value = 60;
|
||||
}
|
||||
|
||||
@ -865,7 +873,11 @@ export abstract class Move implements Localizable {
|
||||
|
||||
if (!this.hasAttr("TypelessAttr")) {
|
||||
globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power);
|
||||
globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power);
|
||||
applyHeldItems(HeldItemEffect.ATTACK_TYPE_BOOST, {
|
||||
pokemon: source,
|
||||
moveType: typeChangeHolder.value,
|
||||
movePower: power,
|
||||
});
|
||||
}
|
||||
|
||||
if (source.getTag(HelpingHandTag)) {
|
||||
@ -928,7 +940,7 @@ export abstract class Move implements Localizable {
|
||||
* Returns `true` if this move can be given additional strikes
|
||||
* by enhancing effects.
|
||||
* Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond}
|
||||
* and {@linkcode PokemonMultiHitModifier | Multi-Lens}.
|
||||
* and {@linkcode MultiHitHeldItem | Multi-Lens}.
|
||||
* @param user The {@linkcode Pokemon} using the move
|
||||
* @param restrictSpread `true` if the enhancing effect
|
||||
* should not affect multi-target moves (default `false`)
|
||||
@ -1357,20 +1369,20 @@ export class MoveHeaderAttr extends MoveAttr {
|
||||
|
||||
/**
|
||||
* Header attribute to queue a message at the beginning of a turn.
|
||||
* @see {@link MoveHeaderAttr}
|
||||
*/
|
||||
export class MessageHeaderAttr extends MoveHeaderAttr {
|
||||
private message: string | ((user: Pokemon, move: Move) => string);
|
||||
/** The message to display, or a function producing one. */
|
||||
private message: string | MoveMessageFunc;
|
||||
|
||||
constructor(message: string | ((user: Pokemon, move: Move) => string)) {
|
||||
constructor(message: string | MoveMessageFunc) {
|
||||
super();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||
const message = typeof this.message === "string"
|
||||
? this.message
|
||||
: this.message(user, move);
|
||||
: this.message(user, target, move);
|
||||
|
||||
if (message) {
|
||||
globalScene.phaseManager.queueMessage(message);
|
||||
@ -1418,21 +1430,21 @@ export class BeakBlastHeaderAttr extends AddBattlerTagHeaderAttr {
|
||||
*/
|
||||
export class PreMoveMessageAttr extends MoveAttr {
|
||||
/** The message to display or a function returning one */
|
||||
private message: string | ((user: Pokemon, target: Pokemon, move: Move) => string | undefined);
|
||||
private message: string | MoveMessageFunc;
|
||||
|
||||
/**
|
||||
* Create a new {@linkcode PreMoveMessageAttr} to display a message before move execution.
|
||||
* @param message - The message to display before move use, either as a string or a function producing one.
|
||||
* @param message - The message to display before move use, either` a literal string or a function producing one.
|
||||
* @remarks
|
||||
* If {@linkcode message} evaluates to an empty string (`''`), no message will be displayed
|
||||
* If {@linkcode message} evaluates to an empty string (`""`), no message will be displayed
|
||||
* (though the move will still succeed).
|
||||
*/
|
||||
constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string)) {
|
||||
constructor(message: string | MoveMessageFunc) {
|
||||
super();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||
const message = typeof this.message === "function"
|
||||
? this.message(user, target, move)
|
||||
: this.message;
|
||||
@ -1453,18 +1465,17 @@ export class PreMoveMessageAttr extends MoveAttr {
|
||||
* @extends MoveAttr
|
||||
*/
|
||||
export class PreUseInterruptAttr extends MoveAttr {
|
||||
protected message?: string | ((user: Pokemon, target: Pokemon, move: Move) => string);
|
||||
protected overridesFailedMessage: boolean;
|
||||
protected message: string | MoveMessageFunc;
|
||||
protected conditionFunc: MoveConditionFunc;
|
||||
|
||||
/**
|
||||
* Create a new MoveInterruptedMessageAttr.
|
||||
* @param message The message to display when the move is interrupted, or a function that formats the message based on the user, target, and move.
|
||||
*/
|
||||
constructor(message?: string | ((user: Pokemon, target: Pokemon, move: Move) => string), conditionFunc?: MoveConditionFunc) {
|
||||
constructor(message: string | MoveMessageFunc, conditionFunc: MoveConditionFunc) {
|
||||
super();
|
||||
this.message = message;
|
||||
this.conditionFunc = conditionFunc ?? (() => true);
|
||||
this.conditionFunc = conditionFunc;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1485,11 +1496,9 @@ export class PreUseInterruptAttr extends MoveAttr {
|
||||
*/
|
||||
override getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined {
|
||||
if (this.message && this.conditionFunc(user, target, move)) {
|
||||
const message =
|
||||
typeof this.message === "string"
|
||||
? (this.message as string)
|
||||
return typeof this.message === "string"
|
||||
? this.message
|
||||
: this.message(user, target, move);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1577,7 +1586,7 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr {
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
// first, determine if the hit is coming from multi lens or not
|
||||
const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0;
|
||||
const lensCount = user.heldItemManager.getStack(HeldItemId.MULTI_LENS);
|
||||
if (lensCount <= 0) {
|
||||
// no multi lenses; we can just halve the target's hp and call it a day
|
||||
(args[0] as NumberHolder).value = toDmgValue(target.hp / 2);
|
||||
@ -1694,17 +1703,30 @@ export class SurviveDamageAttr extends ModifiedDamageAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class SplashAttr extends MoveEffectAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:splash"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Move attribute to display arbitrary text during a move's execution.
|
||||
*/
|
||||
export class MessageAttr extends MoveEffectAttr {
|
||||
/** The message to display, either as a string or a function returning one. */
|
||||
private message: string | MoveMessageFunc;
|
||||
|
||||
export class CelebrateAttr extends MoveEffectAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:celebrate", { playerName: loggedInUser?.username }));
|
||||
return true;
|
||||
constructor(message: string | MoveMessageFunc, options?: MoveEffectAttrOptions) {
|
||||
// TODO: Do we need to respect `selfTarget` if we're just displaying text?
|
||||
super(false, options)
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||
const message = typeof this.message === "function"
|
||||
? this.message(user, target, move)
|
||||
: this.message;
|
||||
|
||||
// TODO: Consider changing if/when MoveAttr `apply` return values become significant
|
||||
if (message) {
|
||||
globalScene.phaseManager.queueMessage(message, 500);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2636,35 +2658,33 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable);
|
||||
const heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
if (!heldItems.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
|
||||
const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
|
||||
const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier);
|
||||
const stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)];
|
||||
if (!globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) {
|
||||
const stolenItem = heldItems[user.randBattleSeedInt(heldItems.length)];
|
||||
|
||||
if (!globalScene.tryTransferHeldItem(stolenItem, target, user, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem",
|
||||
{ pokemonName: getPokemonNameWithAffix(user),
|
||||
targetName: getPokemonNameWithAffix(target),
|
||||
itemName: allHeldItems[stolenItem].name
|
||||
}
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
const heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
return heldItems.length ? 5 : 0;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
const heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
return heldItems.length ? -5 : 0;
|
||||
}
|
||||
}
|
||||
@ -2710,10 +2730,10 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
|
||||
// Considers entire transferrable item pool by default (Knock Off).
|
||||
// Otherwise only consider berries (Incinerate).
|
||||
let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable);
|
||||
let heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
|
||||
if (this.berriesOnly) {
|
||||
heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer());
|
||||
heldItems = heldItems.filter(m => m in Object.values(berryTypeToHeldItem));
|
||||
}
|
||||
|
||||
if (!heldItems.length) {
|
||||
@ -2724,29 +2744,26 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
|
||||
// Decrease item amount and update icon
|
||||
target.loseHeldItem(removedItem);
|
||||
globalScene.updateModifiers(target.isPlayer());
|
||||
globalScene.updateItems(target.isPlayer());
|
||||
|
||||
if (this.berriesOnly) {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem",
|
||||
{ pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name }));
|
||||
} else {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem",
|
||||
{ pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name }));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
const heldItems = target.getHeldItems();
|
||||
return heldItems.length ? 5 : 0;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
const heldItems = target.getHeldItems();
|
||||
return heldItems.length ? -5 : 0;
|
||||
}
|
||||
}
|
||||
@ -2755,7 +2772,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
* Attribute that causes targets of the move to eat a berry. Used for Teatime, Stuff Cheeks
|
||||
*/
|
||||
export class EatBerryAttr extends MoveEffectAttr {
|
||||
protected chosenBerry: BerryModifier;
|
||||
protected chosenBerry: HeldItemId;
|
||||
constructor(selfTarget: boolean) {
|
||||
super(selfTarget);
|
||||
}
|
||||
@ -2784,9 +2801,9 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
|
||||
const preserve = new BooleanHolder(false);
|
||||
// check for berry pouch preservation
|
||||
globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.PRESERVE_BERRY, {pokemon: pokemon, doPreserve: preserve});
|
||||
if (!preserve.value) {
|
||||
this.reduceBerryModifier(pokemon);
|
||||
this.reduceBerryItem(pokemon);
|
||||
}
|
||||
|
||||
// Don't update harvest for berries preserved via Berry pouch (no item dupes lol)
|
||||
@ -2795,16 +2812,15 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
return true;
|
||||
}
|
||||
|
||||
getTargetHeldBerries(target: Pokemon): BerryModifier[] {
|
||||
return globalScene.findModifiers(m => m instanceof BerryModifier
|
||||
&& (m as BerryModifier).pokemonId === target.id, target.isPlayer()) as BerryModifier[];
|
||||
getTargetHeldBerries(target: Pokemon): HeldItemId[] {
|
||||
return target.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY));
|
||||
}
|
||||
|
||||
reduceBerryModifier(target: Pokemon) {
|
||||
reduceBerryItem(target: Pokemon) {
|
||||
if (this.chosenBerry) {
|
||||
target.loseHeldItem(this.chosenBerry);
|
||||
}
|
||||
globalScene.updateModifiers(target.isPlayer());
|
||||
globalScene.updateItems(target.isPlayer());
|
||||
}
|
||||
|
||||
|
||||
@ -2818,10 +2834,10 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
*/
|
||||
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
|
||||
// consumer eats berry, owner triggers unburden and similar effects
|
||||
getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
|
||||
getBerryEffectFunc((allHeldItems[this.chosenBerry] as BerryHeldItem).berryType)(consumer);
|
||||
applyAbAttrs("PostItemLostAbAttr", {pokemon: berryOwner});
|
||||
applyAbAttrs("HealFromBerryUseAbAttr", {pokemon: consumer});
|
||||
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
|
||||
consumer.recordEatenBerry((allHeldItems[this.chosenBerry] as BerryHeldItem).berryType, updateHarvest);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2860,9 +2876,9 @@ export class StealEatBerryAttr extends EatBerryAttr {
|
||||
// pick a random berry and eat it
|
||||
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
|
||||
applyAbAttrs("PostItemLostAbAttr", {pokemon: target});
|
||||
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
|
||||
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: allHeldItems[this.chosenBerry].name });
|
||||
globalScene.phaseManager.queueMessage(message);
|
||||
this.reduceBerryModifier(target);
|
||||
this.reduceBerryItem(target);
|
||||
this.eatBerry(user, target);
|
||||
|
||||
return true;
|
||||
@ -5931,38 +5947,6 @@ export class ProtectAttr extends AddBattlerTagAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class IgnoreAccuracyAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.IGNORE_ACCURACY, true, false, 2);
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class FaintCountdownAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.PERISH_SONG, false, true, 4);
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: this.turnCountMin - 1 }));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute to remove all Substitutes from the field.
|
||||
* @extends MoveEffectAttr
|
||||
@ -6480,9 +6464,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
}
|
||||
}
|
||||
|
||||
// clear out enemy held item modifiers of the switch out target
|
||||
globalScene.clearEnemyHeldItemModifiers(switchOutTarget);
|
||||
|
||||
if (!allyPokemon?.isActive(true) && switchOutTarget.hp) {
|
||||
globalScene.phaseManager.pushNew("BattleEndPhase", false);
|
||||
|
||||
@ -6603,8 +6584,10 @@ export class ChillyReceptionAttr extends ForceSwitchOutAttr {
|
||||
return (user, target, move) => globalScene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveTypeAttr extends MoveEffectAttr {
|
||||
|
||||
// TODO: Remove the message callback
|
||||
private removedType: PokemonType;
|
||||
private messageCallback: ((user: Pokemon) => void) | undefined;
|
||||
|
||||
@ -8083,14 +8066,14 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po
|
||||
|
||||
const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(PokemonType.GHOST);
|
||||
|
||||
const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.getHeldItems().filter(i => i.isTransferable)?.length > 0;
|
||||
const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.heldItemManager.getTransferableHeldItems().length > 0;
|
||||
|
||||
const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
const heldItems = target.getHeldItems().filter(i => i.isTransferable);
|
||||
const heldItems = target.heldItemManager.getTransferableHeldItems();
|
||||
if (heldItems.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const itemName = heldItems[0]?.type?.name ?? "item";
|
||||
const itemName = allHeldItems[heldItems[0]].name ?? "item";
|
||||
const message: string = i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName });
|
||||
return message;
|
||||
};
|
||||
@ -8299,8 +8282,6 @@ const MoveAttrs = Object.freeze({
|
||||
RandomLevelDamageAttr,
|
||||
ModifiedDamageAttr,
|
||||
SurviveDamageAttr,
|
||||
SplashAttr,
|
||||
CelebrateAttr,
|
||||
RecoilAttr,
|
||||
SacrificialAttr,
|
||||
SacrificialAttrOnHit,
|
||||
@ -8443,8 +8424,7 @@ const MoveAttrs = Object.freeze({
|
||||
RechargeAttr,
|
||||
TrapAttr,
|
||||
ProtectAttr,
|
||||
IgnoreAccuracyAttr,
|
||||
FaintCountdownAttr,
|
||||
MessageAttr,
|
||||
RemoveAllSubstitutesAttr,
|
||||
HitsTagAttr,
|
||||
HitsTagForDoubleDamageAttr,
|
||||
@ -8938,7 +8918,7 @@ export function initMoves() {
|
||||
new AttackMove(MoveId.PSYWAVE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1)
|
||||
.attr(RandomLevelDamageAttr),
|
||||
new SelfStatusMove(MoveId.SPLASH, PokemonType.NORMAL, -1, 40, -1, 0, 1)
|
||||
.attr(SplashAttr)
|
||||
.attr(MessageAttr, i18next.t("moveTriggers:splash"))
|
||||
.condition(failOnGravityCondition),
|
||||
new SelfStatusMove(MoveId.ACID_ARMOR, PokemonType.POISON, -1, 20, -1, 0, 1)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
|
||||
@ -9000,7 +8980,10 @@ export function initMoves() {
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1)
|
||||
.reflectable(),
|
||||
new StatusMove(MoveId.MIND_READER, PokemonType.NORMAL, -1, 5, -1, 0, 2)
|
||||
.attr(IgnoreAccuracyAttr),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_ACCURACY, true, false, 2)
|
||||
.attr(MessageAttr, (user, target) =>
|
||||
i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })
|
||||
),
|
||||
new StatusMove(MoveId.NIGHTMARE, PokemonType.GHOST, 100, 15, -1, 0, 2)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.NIGHTMARE)
|
||||
.condition(targetSleptOrComatoseCondition),
|
||||
@ -9088,7 +9071,9 @@ export function initMoves() {
|
||||
return lastTurnMove.length === 0 || lastTurnMove[0].move !== move.id || lastTurnMove[0].result !== MoveResult.SUCCESS;
|
||||
}),
|
||||
new StatusMove(MoveId.PERISH_SONG, PokemonType.NORMAL, -1, 5, -1, 0, 2)
|
||||
.attr(FaintCountdownAttr)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.PERISH_SONG, false, true, 4)
|
||||
.attr(MessageAttr, (_user, target) =>
|
||||
i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: 3 }))
|
||||
.ignoresProtect()
|
||||
.soundBased()
|
||||
.condition(failOnBossCondition)
|
||||
@ -9104,7 +9089,10 @@ export function initMoves() {
|
||||
.attr(MultiHitAttr)
|
||||
.makesContact(false),
|
||||
new StatusMove(MoveId.LOCK_ON, PokemonType.NORMAL, -1, 5, -1, 0, 2)
|
||||
.attr(IgnoreAccuracyAttr),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_ACCURACY, true, false, 2)
|
||||
.attr(MessageAttr, (user, target) =>
|
||||
i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })
|
||||
),
|
||||
new AttackMove(MoveId.OUTRAGE, PokemonType.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 2)
|
||||
.attr(FrenzyAttr)
|
||||
.attr(MissEffectAttr, frenzyMissFunc)
|
||||
@ -9331,8 +9319,8 @@ export function initMoves() {
|
||||
&& (user.status.effect === StatusEffect.BURN || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.PARALYSIS) ? 2 : 1)
|
||||
.attr(BypassBurnDamageReductionAttr),
|
||||
new AttackMove(MoveId.FOCUS_PUNCH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3)
|
||||
.attr(MessageHeaderAttr, (user, move) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) }))
|
||||
.attr(PreUseInterruptAttr, (user, target, move) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => !!user.turnData.attacksReceived.find(r => r.damage))
|
||||
.attr(MessageHeaderAttr, (user) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) }))
|
||||
.attr(PreUseInterruptAttr, (user) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => user.turnData.attacksReceived.some(r => r.damage > 0))
|
||||
.punchingMove(),
|
||||
new AttackMove(MoveId.SMELLING_SALTS, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1)
|
||||
@ -9387,7 +9375,7 @@ export function initMoves() {
|
||||
.condition((user, target, move) => !target.status && !target.isSafeguarded(user))
|
||||
.reflectable(),
|
||||
new AttackMove(MoveId.KNOCK_OFF, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.heldItemManager.getTransferableHeldItems().length > 0 ? 1.5 : 1)
|
||||
.attr(RemoveHeldItemAttr, false)
|
||||
.edgeCase(),
|
||||
// Should not be able to remove held item if user faints due to Rough Skin, Iron Barbs, etc.
|
||||
@ -10113,7 +10101,7 @@ export function initMoves() {
|
||||
.condition((user, target, move) => !target.turnData.acted)
|
||||
.attr(ForceLastAttr),
|
||||
new AttackMove(MoveId.ACROBATICS, PokemonType.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))),
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.heldItemManager.getTransferableHeldItems().reduce((v, m) => v + user.heldItemManager.getStack(m), 0))),
|
||||
new StatusMove(MoveId.REFLECT_TYPE, PokemonType.NORMAL, -1, 15, -1, 0, 5)
|
||||
.ignoresSubstitute()
|
||||
.attr(CopyTypeAttr),
|
||||
@ -10433,7 +10421,8 @@ export function initMoves() {
|
||||
new AttackMove(MoveId.DAZZLING_GLEAM, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new SelfStatusMove(MoveId.CELEBRATE, PokemonType.NORMAL, -1, 40, -1, 0, 6)
|
||||
.attr(CelebrateAttr),
|
||||
// NB: This needs a lambda function as the user will not be logged in by the time the moves are initialized
|
||||
.attr(MessageAttr, () => i18next.t("moveTriggers:celebrate", { playerName: loggedInUser?.username })),
|
||||
new StatusMove(MoveId.HOLD_HANDS, PokemonType.NORMAL, -1, 40, -1, 0, 6)
|
||||
.ignoresSubstitute()
|
||||
.target(MoveTarget.NEAR_ALLY),
|
||||
@ -10608,7 +10597,12 @@ export function initMoves() {
|
||||
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
|
||||
.reflectable(),
|
||||
new SelfStatusMove(MoveId.LASER_FOCUS, PokemonType.NORMAL, -1, 30, -1, 0, 7)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false)
|
||||
.attr(MessageAttr, (user) =>
|
||||
i18next.t("battlerTags:laserFocusOnAdd", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(user),
|
||||
}),
|
||||
),
|
||||
new StatusMove(MoveId.GEAR_UP, PokemonType.STEEL, -1, 20, -1, 0, 7)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, { condition: (user, target, move) => !![ AbilityId.PLUS, AbilityId.MINUS ].find(a => target.hasAbility(a, false)) })
|
||||
.ignoresSubstitute()
|
||||
@ -10862,7 +10856,7 @@ export function initMoves() {
|
||||
.attr(EatBerryAttr, true)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
|
||||
.condition((user) => {
|
||||
const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
|
||||
const userBerries = user.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY));
|
||||
return userBerries.length > 0;
|
||||
})
|
||||
.edgeCase(), // Stuff Cheeks should not be selectable when the user does not have a berry, see wiki
|
||||
|
||||
@ -11,7 +11,7 @@ import { BooleanHolder, toDmgValue } from "#utils/common";
|
||||
* These are the moves assigned to a {@linkcode Pokemon} object.
|
||||
* It links to {@linkcode Move} class via the move ID.
|
||||
* Compared to {@linkcode Move}, this class also tracks things like
|
||||
* PP Ups recieved, PP used, etc.
|
||||
* PP Ups received, PP used, etc.
|
||||
* @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
|
||||
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
|
||||
* @see {@linkcode usePp} - removes a point of PP from the move.
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import type { IEggOptions } from "#data/egg";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
@ -164,8 +164,8 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.RELIC_GOLD],
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
||||
guaranteedRewardSpecs: [RewardId.RELIC_GOLD],
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE],
|
||||
fillRemaining: true,
|
||||
},
|
||||
[eggOptions],
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -12,37 +12,44 @@ import { PokeballType } from "#enums/pokeball";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import type { MysteryEncounterSpriteConfig } from "#field/mystery-encounter-intro";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { EnemyPokemon } from "#field/pokemon";
|
||||
import { BerryModifier, PokemonInstantReviveModifier } from "#modifiers/modifier";
|
||||
import type { BerryModifierType, PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import type { HeldItemConfiguration, HeldItemSpecs, PokemonItemMap } from "#items/held-item-data-types";
|
||||
import { getPartyBerries } from "#items/item-utility";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
catchPokemon,
|
||||
getHighestLevelPlayerPokemon,
|
||||
} from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import { catchPokemon, getHighestLevelPlayerPokemon } from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import { PersistentModifierRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import { randInt } from "#utils/common";
|
||||
import { HeldItemRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { pickWeightedIndex, randInt } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounters/absoluteAvarice";
|
||||
|
||||
function berrySprite(spriteKey: string, x: number, y: number): MysteryEncounterSpriteConfig {
|
||||
return {
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: x,
|
||||
y: y,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute Avarice encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805}
|
||||
@ -53,7 +60,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(20, 180)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 6)) // Must have at least 6 berries to spawn
|
||||
.withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 6)) // Must have at least 6 berries to spawn
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
@ -74,105 +81,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
repeat: true,
|
||||
x: -5,
|
||||
},
|
||||
{
|
||||
spriteKey: "lum_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: -14,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "salac_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 2,
|
||||
y: 4,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "lansat_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 32,
|
||||
y: 5,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "liechi_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 6,
|
||||
y: -5,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "sitrus_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: 8,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "enigma_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 26,
|
||||
y: -4,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "leppa_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -27,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "petaya_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 30,
|
||||
y: -17,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "ganlon_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -11,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "apicot_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 14,
|
||||
y: -2,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "starf_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 18,
|
||||
y: 9,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
berrySprite("lum_berry", 7, -14),
|
||||
berrySprite("salac_berry", 2, 4),
|
||||
berrySprite("lansat_berry", 32, 5),
|
||||
berrySprite("liechi_berry", 6, -5),
|
||||
berrySprite("sitrus_berry", 7, 8),
|
||||
berrySprite("enigma_berry", 26, -4),
|
||||
berrySprite("leppa_berry", 16, -27),
|
||||
berrySprite("petaya_berry", 30, -17),
|
||||
berrySprite("ganlon_berry", 16, -11),
|
||||
berrySprite("apicot_berry", 14, -2),
|
||||
berrySprite("starf_berry", 18, 9),
|
||||
])
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
@ -191,35 +110,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
|
||||
globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
|
||||
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
// Get all berries in party, with references to the pokemon
|
||||
const berryItems = getPartyBerries();
|
||||
|
||||
// Sort berries by party member ID to more easily re-add later if necessary
|
||||
const berryItemsMap = new Map<number, BerryModifier[]>();
|
||||
globalScene.getPlayerParty().forEach(pokemon => {
|
||||
const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
|
||||
if (pokemonBerries?.length > 0) {
|
||||
berryItemsMap.set(pokemon.id, pokemonBerries);
|
||||
}
|
||||
encounter.misc = { berryItemsMap: berryItems };
|
||||
|
||||
// Adds stolen berries to the Greedent item configuration
|
||||
const bossHeldItemConfig: HeldItemConfiguration = [];
|
||||
berryItems.forEach(map => {
|
||||
bossHeldItemConfig.push({ entry: map.item, count: 1 });
|
||||
});
|
||||
|
||||
encounter.misc = { berryItemsMap };
|
||||
|
||||
// Generates copies of the stolen berries to put on the Greedent
|
||||
const bossModifierConfigs: HeldModifierConfig[] = [];
|
||||
berryItems.forEach(berryMod => {
|
||||
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
|
||||
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
|
||||
for (let i = 0; i < berryMod.stackCount; i++) {
|
||||
const modifierType = generateModifierType(modifierTypes.BERRY, [
|
||||
berryMod.berryType,
|
||||
]) as PokemonHeldItemModifierType;
|
||||
bossModifierConfigs.push({ modifier: modifierType });
|
||||
}
|
||||
});
|
||||
|
||||
// Do NOT remove the real berries yet or else it will be persisted in the session data
|
||||
|
||||
// +1 SpDef below wave 50, SpDef and Speed otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
|
||||
globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.SPDEF, Stat.SPD];
|
||||
@ -234,7 +135,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
bossSegments: 3,
|
||||
shiny: false, // Shiny lock because of consistency issues between the different options
|
||||
moveSet: [MoveId.THRASH, MoveId.CRUNCH, MoveId.BODY_PRESS, MoveId.SLACK_OFF],
|
||||
modifierConfigs: bossModifierConfigs,
|
||||
heldItemConfig: bossHeldItemConfig,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
|
||||
@ -261,12 +162,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
// Remove the berries from the party
|
||||
// Session has been safely saved at this point, so data won't be lost
|
||||
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
berryItems.forEach(berryMod => {
|
||||
globalScene.removeModifier(berryMod);
|
||||
const berryItems = getPartyBerries();
|
||||
berryItems.forEach(map => {
|
||||
globalScene.getPokemonById(map.pokemonId)?.heldItemManager.remove(map.item.id as HeldItemId);
|
||||
});
|
||||
|
||||
globalScene.updateModifiers(true);
|
||||
globalScene.updateItems(true);
|
||||
|
||||
return true;
|
||||
})
|
||||
@ -286,19 +187,14 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Provides 1x Reviver Seed to each party member at end of battle
|
||||
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED);
|
||||
encounter.setDialogueToken(
|
||||
"foodReward",
|
||||
revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
|
||||
allHeldItems[HeldItemId.REVIVER_SEED].name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
|
||||
);
|
||||
const givePartyPokemonReviverSeeds = () => {
|
||||
const party = globalScene.getPlayerParty();
|
||||
party.forEach(p => {
|
||||
const heldItems = p.getHeldItems();
|
||||
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
|
||||
const seedModifier = revSeed.newModifier(p);
|
||||
globalScene.addModifier(seedModifier, false, false, false, true);
|
||||
}
|
||||
p.heldItemManager.add(HeldItemId.REVIVER_SEED);
|
||||
});
|
||||
queueEncounterMessage(`${namespace}:option.1.food_stash`);
|
||||
};
|
||||
@ -329,28 +225,27 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const berryMap = encounter.misc.berryItemsMap;
|
||||
const berryMap = encounter.misc.berryItemsMap as PokemonItemMap[];
|
||||
|
||||
// Returns 2/5 of the berries stolen to each Pokemon
|
||||
const party = globalScene.getPlayerParty();
|
||||
party.forEach(pokemon => {
|
||||
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
|
||||
const berryTypesAsArray: BerryType[] = [];
|
||||
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
|
||||
const returnedBerryCount = Math.floor(((berryTypesAsArray.length ?? 0) * 2) / 5);
|
||||
const stolenBerries = berryMap.filter(map => map.pokemonId === pokemon.id);
|
||||
const stolenBerryCount = stolenBerries.reduce((a, b) => a + (b.item as HeldItemSpecs).stack, 0);
|
||||
const returnedBerryCount = Math.floor(((stolenBerryCount ?? 0) * 2) / 5);
|
||||
|
||||
if (returnedBerryCount > 0) {
|
||||
for (let i = 0; i < returnedBerryCount; i++) {
|
||||
// Shuffle remaining berry types and pop
|
||||
Phaser.Math.RND.shuffle(berryTypesAsArray);
|
||||
const randBerryType = berryTypesAsArray.pop();
|
||||
|
||||
const berryModType = generateModifierType(modifierTypes.BERRY, [randBerryType]) as BerryModifierType;
|
||||
applyModifierTypeToPlayerPokemon(pokemon, berryModType);
|
||||
const berryWeights = stolenBerries.map(b => (b.item as HeldItemSpecs).stack);
|
||||
const which = pickWeightedIndex(berryWeights) ?? 0;
|
||||
const randBerry = stolenBerries[which];
|
||||
pokemon.heldItemManager.add(randBerry.item.id as HeldItemId);
|
||||
(randBerry.item as HeldItemSpecs).stack -= 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
await globalScene.updateModifiers(true);
|
||||
await globalScene.updateItems(true);
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allTrainerItems } from "#data/data-lists";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterExp,
|
||||
updatePlayerMoney,
|
||||
@ -109,8 +109,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
|
||||
}
|
||||
}
|
||||
|
||||
const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM);
|
||||
encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
|
||||
const name = allTrainerItems[TrainerItemId.SHINY_CHARM].name;
|
||||
encounter.setDialogueToken("itemName", name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
|
||||
encounter.setDialogueToken("liepardName", getPokemonSpecies(SpeciesId.LIEPARD).getName());
|
||||
|
||||
return true;
|
||||
@ -136,7 +136,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player a Shiny Charm
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.SHINY_CHARM);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", TrainerItemId.SHINY_CHARM);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RewardPoolType } from "#enums/reward-pool-type";
|
||||
import { PERMANENT_STATS, Stat } from "#enums/stat";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { BerryModifier } from "#modifiers/modifier";
|
||||
import type { BerryModifierType, ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import { regenerateModifierPoolThresholds } from "#modifiers/modifier-type";
|
||||
import { berryTypeToHeldItem } from "#items/berry";
|
||||
import type { RewardOption } from "#items/reward";
|
||||
import { generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils";
|
||||
import { generateRewardOptionFromId } from "#items/reward-utils";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
getRandomEncounterSpecies,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
@ -25,7 +24,6 @@ import {
|
||||
setEncounterRewards,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
getEncounterPokemonLevelForWave,
|
||||
getHighestStatPlayerPokemon,
|
||||
getSpriteKeysFromPokemon,
|
||||
@ -90,7 +88,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
: globalScene.currentBattle.waveIndex > 40
|
||||
? 4
|
||||
: 2;
|
||||
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
||||
generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), globalScene.getPlayerParty(), 0);
|
||||
encounter.misc = { numBerries };
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
@ -161,20 +159,16 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
}
|
||||
};
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
const shopOptions: RewardOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
const mod = generateModifierTypeOption(modifierTypes.BERRY);
|
||||
const mod = generateRewardOptionFromId(RewardId.BERRY);
|
||||
if (mod) {
|
||||
shopOptions.push(mod);
|
||||
}
|
||||
}
|
||||
|
||||
setEncounterRewards(
|
||||
{ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false },
|
||||
undefined,
|
||||
doBerryRewards,
|
||||
);
|
||||
setEncounterRewards({ guaranteedRewardOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
},
|
||||
)
|
||||
@ -192,10 +186,10 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
|
||||
const numBerries: number = encounter.misc.numBerries;
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
const shopOptions: RewardOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
const mod = generateModifierTypeOption(modifierTypes.BERRY);
|
||||
const mod = generateRewardOptionFromId(RewardId.BERRY);
|
||||
if (mod) {
|
||||
shopOptions.push(mod);
|
||||
}
|
||||
@ -248,7 +242,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
};
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeOptions: shopOptions,
|
||||
guaranteedRewardOptions: shopOptions,
|
||||
fillRemaining: false,
|
||||
},
|
||||
undefined,
|
||||
@ -281,7 +275,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeOptions: shopOptions,
|
||||
guaranteedRewardOptions: shopOptions,
|
||||
fillRemaining: false,
|
||||
},
|
||||
undefined,
|
||||
@ -312,35 +306,17 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
|
||||
function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
|
||||
const berryType = randSeedItem(getEnumValues(BerryType));
|
||||
const berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType;
|
||||
const berry = berryTypeToHeldItem[berryType];
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
|
||||
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
|
||||
if (prioritizedPokemon) {
|
||||
const heldBerriesOfType = globalScene.findModifier(
|
||||
m =>
|
||||
m instanceof BerryModifier &&
|
||||
m.pokemonId === prioritizedPokemon.id &&
|
||||
(m as BerryModifier).berryType === berryType,
|
||||
true,
|
||||
) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
|
||||
applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry);
|
||||
return;
|
||||
}
|
||||
// Will give the berry to a Pokemon, starting from the prioritized one
|
||||
if (prioritizedPokemon?.heldItemManager.add(berry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over the party until berry was successfully given
|
||||
for (const pokemon of party) {
|
||||
const heldBerriesOfType = globalScene.findModifier(
|
||||
m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType,
|
||||
true,
|
||||
) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
|
||||
applyModifierTypeToPlayerPokemon(pokemon, berry);
|
||||
if (pokemon.heldItemManager.add(berry)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +1,26 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allMoves, modifierTypes } from "#data/data-lists";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { allHeldItems, allMoves } from "#data/data-lists";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import {
|
||||
AttackTypeBoosterModifier,
|
||||
BypassSpeedChanceModifier,
|
||||
ContactHeldItemTransferChanceModifier,
|
||||
GigantamaxAccessModifier,
|
||||
MegaEvolutionAccessModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import type { RewardOption } from "#items/reward";
|
||||
import { generateRewardOptionFromId } from "#items/reward-utils";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { getEncounterText, showEncounterDialogue } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectOptionThenPokemon,
|
||||
@ -39,9 +33,8 @@ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import {
|
||||
AttackTypeBoosterHeldItemTypeRequirement,
|
||||
CombinationPokemonRequirement,
|
||||
HeldItemRequirement,
|
||||
HoldingItemRequirement,
|
||||
TypeRequirement,
|
||||
} from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { getRandomPartyMemberFunc, trainerConfigs } from "#trainers/trainer-config";
|
||||
@ -140,6 +133,8 @@ const POOL_3_POKEMON: { species: SpeciesId; formIndex?: number }[] = [
|
||||
|
||||
const POOL_4_POKEMON = [SpeciesId.GENESECT, SpeciesId.SLITHER_WING, SpeciesId.BUZZWOLE, SpeciesId.PHEROMOSA];
|
||||
|
||||
const REQUIRED_ITEMS = [HeldItemId.QUICK_CLAW, HeldItemId.GRIP_CLAW, HeldItemId.SILVER_POWDER];
|
||||
|
||||
const PHYSICAL_TUTOR_MOVES = [
|
||||
MoveId.MEGAHORN,
|
||||
MoveId.ATTACK_ORDER,
|
||||
@ -183,8 +178,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
|
||||
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
|
||||
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
|
||||
new HoldingItemRequirement(REQUIRED_ITEMS, 1),
|
||||
new TypeRequirement(PokemonType.BUG, false, 1),
|
||||
),
|
||||
)
|
||||
@ -256,13 +250,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
},
|
||||
];
|
||||
|
||||
const requiredItems = [
|
||||
generateModifierType(modifierTypes.QUICK_CLAW),
|
||||
generateModifierType(modifierTypes.GRIP_CLAW),
|
||||
generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.BUG]),
|
||||
];
|
||||
|
||||
const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/");
|
||||
const requiredItemString = REQUIRED_ITEMS.map(m => allHeldItems[m].name ?? "unknown").join("/");
|
||||
encounter.setDialogueToken("requiredBugItems", requiredItemString);
|
||||
|
||||
return true;
|
||||
@ -298,7 +286,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
moveTutorOptions,
|
||||
};
|
||||
|
||||
// Assigns callback that teaches move before continuing to rewards
|
||||
// Assigns callback that teaches move before continuing to RewardId
|
||||
encounter.onRewards = doBugTypeMoveTutor;
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
@ -318,7 +306,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
// Player shows off their bug types
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Player gets different rewards depending on the number of bug types they have
|
||||
// Player gets different RewardId depending on the number of bug types they have
|
||||
const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(PokemonType.BUG, true)).length;
|
||||
const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, {
|
||||
count: numBugTypes,
|
||||
@ -327,7 +315,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
if (numBugTypes < 2) {
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL],
|
||||
guaranteedRewardSpecs: [RewardId.SUPER_LURE, RewardId.GREAT_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
@ -338,7 +326,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
];
|
||||
} else if (numBugTypes < 4) {
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL],
|
||||
guaranteedRewardSpecs: [HeldItemId.QUICK_CLAW, RewardId.MAX_LURE, RewardId.ULTRA_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
@ -349,7 +337,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
];
|
||||
} else if (numBugTypes < 6) {
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL],
|
||||
guaranteedRewardSpecs: [HeldItemId.GRIP_CLAW, RewardId.MAX_LURE, RewardId.ROGUE_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
@ -361,38 +349,38 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
} else {
|
||||
// If the player has any evolution/form change items that are valid for their party,
|
||||
// spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball
|
||||
const modifierOptions: ModifierTypeOption[] = [generateModifierTypeOption(modifierTypes.MASTER_BALL)!];
|
||||
const specialOptions: ModifierTypeOption[] = [];
|
||||
const rewardOptions: RewardOption[] = [generateRewardOptionFromId(RewardId.MASTER_BALL)!];
|
||||
const specialOptions: RewardOption[] = [];
|
||||
|
||||
if (!globalScene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) {
|
||||
modifierOptions.push(generateModifierTypeOption(modifierTypes.MEGA_BRACELET)!);
|
||||
if (!globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) {
|
||||
rewardOptions.push(generateRewardOptionFromId(TrainerItemId.MEGA_BRACELET)!);
|
||||
}
|
||||
if (!globalScene.findModifier(m => m instanceof GigantamaxAccessModifier)) {
|
||||
modifierOptions.push(generateModifierTypeOption(modifierTypes.DYNAMAX_BAND)!);
|
||||
if (!globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) {
|
||||
rewardOptions.push(generateRewardOptionFromId(TrainerItemId.DYNAMAX_BAND)!);
|
||||
}
|
||||
const nonRareEvolutionModifier = generateModifierTypeOption(modifierTypes.EVOLUTION_ITEM);
|
||||
if (nonRareEvolutionModifier) {
|
||||
specialOptions.push(nonRareEvolutionModifier);
|
||||
const nonRareEvolutionReward = generateRewardOptionFromId(RewardId.EVOLUTION_ITEM);
|
||||
if (nonRareEvolutionReward) {
|
||||
specialOptions.push(nonRareEvolutionReward);
|
||||
}
|
||||
const rareEvolutionModifier = generateModifierTypeOption(modifierTypes.RARE_EVOLUTION_ITEM);
|
||||
if (rareEvolutionModifier) {
|
||||
specialOptions.push(rareEvolutionModifier);
|
||||
const rareEvolutionReward = generateRewardOptionFromId(RewardId.RARE_EVOLUTION_ITEM);
|
||||
if (rareEvolutionReward) {
|
||||
specialOptions.push(rareEvolutionReward);
|
||||
}
|
||||
const formChangeModifier = generateModifierTypeOption(modifierTypes.FORM_CHANGE_ITEM);
|
||||
if (formChangeModifier) {
|
||||
specialOptions.push(formChangeModifier);
|
||||
const formChangeReward = generateRewardOptionFromId(RewardId.FORM_CHANGE_ITEM);
|
||||
if (formChangeReward) {
|
||||
specialOptions.push(formChangeReward);
|
||||
}
|
||||
const rareFormChangeModifier = generateModifierTypeOption(modifierTypes.RARE_FORM_CHANGE_ITEM);
|
||||
if (rareFormChangeModifier) {
|
||||
specialOptions.push(rareFormChangeModifier);
|
||||
const rareFormChangeReward = generateRewardOptionFromId(RewardId.RARE_FORM_CHANGE_ITEM);
|
||||
if (rareFormChangeReward) {
|
||||
specialOptions.push(rareFormChangeReward);
|
||||
}
|
||||
if (specialOptions.length > 0) {
|
||||
// TODO: should this use `randSeedItem`?
|
||||
modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
|
||||
rewardOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifierOptions,
|
||||
guaranteedRewardOptions: rewardOptions,
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
@ -414,8 +402,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Meets one or both of the below reqs
|
||||
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
|
||||
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
|
||||
new HoldingItemRequirement(REQUIRED_ITEMS, 1),
|
||||
),
|
||||
)
|
||||
.withDialogue({
|
||||
@ -438,25 +425,19 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(item => {
|
||||
return (
|
||||
(item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier &&
|
||||
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) &&
|
||||
item.isTransferable
|
||||
);
|
||||
});
|
||||
const validItems = pokemon.heldItemManager
|
||||
.getTransferableHeldItems()
|
||||
.filter(item => REQUIRED_ITEMS.some(i => i === item));
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
return validItems.map((item: HeldItemId) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
label: allHeldItems[item].name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("selectedItem", modifier.type.name);
|
||||
encounter.setDialogueToken("selectedItem", allHeldItems[item].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
chosenItem: item,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
@ -467,14 +448,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has valid item, it can be selected
|
||||
const hasValidItem = pokemon.getHeldItems().some(item => {
|
||||
return (
|
||||
item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier &&
|
||||
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)
|
||||
);
|
||||
});
|
||||
const hasValidItem = pokemon.getHeldItems().some(item => REQUIRED_ITEMS.some(i => i === item));
|
||||
if (!hasValidItem) {
|
||||
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
|
||||
}
|
||||
@ -486,18 +460,18 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
const lostItem = encounter.misc.chosenItem;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
globalScene.updateModifiers(true, true);
|
||||
chosenPokemon.loseHeldItem(lostItem, false);
|
||||
globalScene.updateItems(true);
|
||||
|
||||
const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!;
|
||||
bugNet.type.tier = ModifierTier.ROGUE;
|
||||
const bugNet = generateRewardOptionFromId(TrainerItemId.GOLDEN_BUG_NET)!;
|
||||
bugNet.type.tier = RarityTier.ROGUE;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [bugNet],
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.REVIVER_SEED],
|
||||
guaranteedRewardOptions: [bugNet],
|
||||
guaranteedRewardSpecs: [HeldItemId.REVIVER_SEED],
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle(true);
|
||||
@ -769,7 +743,7 @@ function doBugTypeMoveTutor(): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
// Complete battle and go to rewards
|
||||
// Complete battle and go to RewardId
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { EncounterBattleAnim } from "#data/battle-anims";
|
||||
import { allAbilities, modifierTypes } from "#data/data-lists";
|
||||
import { allAbilities } from "#data/data-lists";
|
||||
import { CustomPokemonData } from "#data/pokemon-data";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
@ -18,17 +16,17 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import type { PlayerPokemon } from "#field/pokemon";
|
||||
import { BerryModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { getHeldItemTier } from "#items/held-item-default-tiers";
|
||||
import { assignItemsFromConfiguration } from "#items/held-item-pool";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { showEncounterDialogue, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
@ -36,10 +34,7 @@ import {
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyAbilityOverrideToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
} from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import { applyAbilityOverrideToPokemon } from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
@ -283,16 +278,16 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
let mostHeldItemsPokemon = party[0];
|
||||
let count = mostHeldItemsPokemon
|
||||
.getHeldItems()
|
||||
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
let count = mostHeldItemsPokemon.heldItemManager
|
||||
.getTransferableHeldItems()
|
||||
.filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY))
|
||||
.reduce((v, m) => v + mostHeldItemsPokemon.heldItemManager.getStack(m), 0);
|
||||
|
||||
for (const pokemon of party) {
|
||||
const nextCount = pokemon
|
||||
.getHeldItems()
|
||||
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
const nextCount = pokemon.heldItemManager
|
||||
.getTransferableHeldItems()
|
||||
.filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY))
|
||||
.reduce((v, m) => v + pokemon.heldItemManager.getStack(m), 0);
|
||||
if (nextCount > count) {
|
||||
mostHeldItemsPokemon = pokemon;
|
||||
count = nextCount;
|
||||
@ -301,16 +296,31 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
|
||||
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
|
||||
|
||||
const items = mostHeldItemsPokemon.getHeldItems();
|
||||
const items = mostHeldItemsPokemon.heldItemManager
|
||||
.getTransferableHeldItems()
|
||||
.filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY));
|
||||
|
||||
// Shuffles Berries (if they have any)
|
||||
const oldBerries = mostHeldItemsPokemon.heldItemManager
|
||||
.getHeldItems()
|
||||
.filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY));
|
||||
|
||||
let numBerries = 0;
|
||||
for (const m of items.filter(m => m instanceof BerryModifier)) {
|
||||
numBerries += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
for (const berry of oldBerries) {
|
||||
const stack = mostHeldItemsPokemon.heldItemManager.getStack(berry);
|
||||
numBerries += stack;
|
||||
mostHeldItemsPokemon.heldItemManager.remove(berry, stack);
|
||||
}
|
||||
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries");
|
||||
assignItemsFromConfiguration(
|
||||
[
|
||||
{
|
||||
entry: HeldItemCategoryId.BERRY,
|
||||
count: numBerries,
|
||||
},
|
||||
],
|
||||
mostHeldItemsPokemon,
|
||||
);
|
||||
|
||||
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||
// For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier
|
||||
@ -318,20 +328,36 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
let numUltra = 0;
|
||||
let numRogue = 0;
|
||||
|
||||
for (const m of items.filter(m => m.isTransferable && !(m instanceof BerryModifier))) {
|
||||
const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party);
|
||||
const tier = type.tier ?? ModifierTier.ULTRA;
|
||||
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||
numRogue += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
} else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) {
|
||||
numUltra += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
for (const m of items) {
|
||||
const tier = getHeldItemTier(m) ?? RarityTier.ULTRA;
|
||||
const stack = mostHeldItemsPokemon.heldItemManager.getStack(m);
|
||||
if (tier === RarityTier.ROGUE) {
|
||||
numRogue += stack;
|
||||
} else if (tier === RarityTier.ULTRA) {
|
||||
numUltra += stack;
|
||||
}
|
||||
mostHeldItemsPokemon.heldItemManager.remove(m, stack);
|
||||
}
|
||||
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
|
||||
assignItemsFromConfiguration(
|
||||
[
|
||||
{
|
||||
entry: ultraPool,
|
||||
count: numUltra,
|
||||
},
|
||||
],
|
||||
mostHeldItemsPokemon,
|
||||
);
|
||||
|
||||
assignItemsFromConfiguration(
|
||||
[
|
||||
{
|
||||
entry: roguePool,
|
||||
count: numRogue,
|
||||
},
|
||||
],
|
||||
mostHeldItemsPokemon,
|
||||
);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
leaveEncounterWithoutBattle(true);
|
||||
@ -487,68 +513,21 @@ function onYesAbilitySwap(resolve) {
|
||||
selectPokemonForOption(onPokemonSelected, onPokemonNotSelected);
|
||||
}
|
||||
|
||||
function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") {
|
||||
// These pools have to be defined at runtime so that modifierTypes exist
|
||||
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
|
||||
// This is to prevent "over-generating" a random item of a certain type during item swaps
|
||||
const ultraPool = [
|
||||
[modifierTypes.REVIVER_SEED, 1],
|
||||
[modifierTypes.GOLDEN_PUNCH, 5],
|
||||
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
|
||||
[modifierTypes.QUICK_CLAW, 3],
|
||||
[modifierTypes.WIDE_LENS, 3],
|
||||
];
|
||||
const ultraPool = [
|
||||
{ entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 1 },
|
||||
{ entry: HeldItemId.REVIVER_SEED, weight: 1 },
|
||||
{ entry: HeldItemId.GOLDEN_PUNCH, weight: 1 },
|
||||
{ entry: HeldItemId.QUICK_CLAW, weight: 1 },
|
||||
{ entry: HeldItemId.WIDE_LENS, weight: 1 },
|
||||
];
|
||||
|
||||
const roguePool = [
|
||||
[modifierTypes.LEFTOVERS, 4],
|
||||
[modifierTypes.SHELL_BELL, 4],
|
||||
[modifierTypes.SOUL_DEW, 10],
|
||||
[modifierTypes.SCOPE_LENS, 1],
|
||||
[modifierTypes.BATON, 1],
|
||||
[modifierTypes.FOCUS_BAND, 5],
|
||||
[modifierTypes.KINGS_ROCK, 3],
|
||||
[modifierTypes.GRIP_CLAW, 5],
|
||||
];
|
||||
|
||||
const berryPool = [
|
||||
[BerryType.APICOT, 3],
|
||||
[BerryType.ENIGMA, 2],
|
||||
[BerryType.GANLON, 3],
|
||||
[BerryType.LANSAT, 3],
|
||||
[BerryType.LEPPA, 2],
|
||||
[BerryType.LIECHI, 3],
|
||||
[BerryType.LUM, 2],
|
||||
[BerryType.PETAYA, 3],
|
||||
[BerryType.SALAC, 2],
|
||||
[BerryType.SITRUS, 2],
|
||||
[BerryType.STARF, 3],
|
||||
];
|
||||
|
||||
let pool: any[];
|
||||
if (tier === "Berries") {
|
||||
pool = berryPool;
|
||||
} else {
|
||||
pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool;
|
||||
}
|
||||
|
||||
for (let i = 0; i < numItems; i++) {
|
||||
if (pool.length === 0) {
|
||||
// Stop generating new items if somehow runs out of items to spawn
|
||||
return;
|
||||
}
|
||||
const randIndex = randSeedInt(pool.length);
|
||||
const newItemType = pool[randIndex];
|
||||
let newMod: PokemonHeldItemModifierType;
|
||||
if (tier === "Berries") {
|
||||
newMod = generateModifierType(modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
|
||||
} else {
|
||||
newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType;
|
||||
}
|
||||
applyModifierTypeToPlayerPokemon(pokemon, newMod);
|
||||
// Decrement max stacks and remove from pool if at max
|
||||
newItemType[1]--;
|
||||
if (newItemType[1] <= 0) {
|
||||
pool.splice(randIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
const roguePool = [
|
||||
{ entry: HeldItemId.LEFTOVERS, weight: 1 },
|
||||
{ entry: HeldItemId.SHELL_BELL, weight: 1 },
|
||||
{ entry: HeldItemId.SOUL_DEW, weight: 1 },
|
||||
{ entry: HeldItemId.SCOPE_LENS, weight: 1 },
|
||||
{ entry: HeldItemId.BATON, weight: 1 },
|
||||
{ entry: HeldItemId.FOCUS_BAND, weight: 1 },
|
||||
{ entry: HeldItemId.KINGS_ROCK, weight: 1 },
|
||||
{ entry: HeldItemId.GRIP_CLAW, weight: 1 },
|
||||
];
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { EncounterBattleAnim } from "#data/battle-anims";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -153,7 +153,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
}
|
||||
|
||||
const oricorioData = new PokemonData(enemyPokemon);
|
||||
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
|
||||
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, [], oricorioData);
|
||||
|
||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||
for (const enemyPokemon of globalScene.getEnemyParty()) {
|
||||
@ -219,7 +219,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
|
||||
await hideOricorioPokemon();
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.BATON],
|
||||
guaranteedRewardSpecs: [HeldItemId.BATON],
|
||||
fillRemaining: true,
|
||||
});
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import { PokemonFormChangeItemModifier } from "#modifiers/modifier";
|
||||
import type { HeldItemConfiguration } from "#items/held-item-data-types";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle } from "#mystery-encounters/encounter-phase-utils";
|
||||
import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#mystery-encounters/encounter-pokemon-utils";
|
||||
@ -149,7 +148,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
const removedPokemon = getRandomPlayerPokemon(true, false, true);
|
||||
|
||||
// Get all the pokemon's held items
|
||||
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
const itemConfig = removedPokemon.heldItemManager.generateHeldItemConfiguration();
|
||||
globalScene.removePokemonFromPlayerParty(removedPokemon);
|
||||
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
@ -158,13 +157,13 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
// Store removed pokemon types
|
||||
encounter.misc = {
|
||||
removedTypes: removedPokemon.getTypes(),
|
||||
modifiers,
|
||||
itemConfig: itemConfig,
|
||||
};
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player 5 Rogue Balls
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.ROGUE_BALL);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", RewardId.ROGUE_BALL);
|
||||
|
||||
// Start encounter with random legendary (7-10 starter strength) that has level additive
|
||||
// If this is a mono-type challenge, always ensure the required type is filtered for
|
||||
@ -176,7 +175,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as PokemonType);
|
||||
}
|
||||
|
||||
const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers;
|
||||
const bossItemConfig: HeldItemConfiguration = encounter.misc.itemConfig;
|
||||
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
|
||||
const roll = randSeedInt(100);
|
||||
const starterTier: number | [number, number] = roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10];
|
||||
@ -184,12 +183,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
modifierConfigs: bossModifiers.map(m => {
|
||||
return {
|
||||
modifier: m,
|
||||
stackCount: m.getStackCount(),
|
||||
};
|
||||
}),
|
||||
heldItemConfig: bossItemConfig,
|
||||
};
|
||||
if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) {
|
||||
pokemonConfig.formIndex = 0;
|
||||
|
||||
@ -1,35 +1,27 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#modifiers/modifier";
|
||||
import {
|
||||
BerryModifier,
|
||||
HealingBoosterModifier,
|
||||
LevelIncrementBoosterModifier,
|
||||
MoneyMultiplierModifier,
|
||||
PreserveBerryModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { getEncounterText, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
updatePlayerMoney,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import { applyModifierTypeToPlayerPokemon } from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import {
|
||||
CombinationPokemonRequirement,
|
||||
HeldItemRequirement,
|
||||
HoldingItemRequirement,
|
||||
MoneyRequirement,
|
||||
} from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import i18next from "#plugins/i18n";
|
||||
@ -41,32 +33,39 @@ import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
const namespace = "mysteryEncounters/delibirdy";
|
||||
|
||||
/** Berries only */
|
||||
const OPTION_2_ALLOWED_MODIFIERS = ["BerryModifier", "PokemonInstantReviveModifier"];
|
||||
const OPTION_2_ALLOWED_HELD_ITEMS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED];
|
||||
|
||||
/** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */
|
||||
const OPTION_3_DISALLOWED_MODIFIERS = [
|
||||
"BerryModifier",
|
||||
"PokemonInstantReviveModifier",
|
||||
"TerastallizeModifier",
|
||||
"PokemonBaseStatModifier",
|
||||
"PokemonBaseStatTotalModifier",
|
||||
];
|
||||
/** Disallowed items are berries, Reviver Seeds, and Vitamins */
|
||||
const OPTION_3_DISALLOWED_HELD_ITEMS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED];
|
||||
|
||||
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
|
||||
|
||||
async function backupOption() {
|
||||
globalScene.getPlayerPokemon()?.heldItemManager.add(HeldItemId.SHELL_BELL);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: allHeldItems[HeldItemId.SHELL_BELL].name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
const doEventReward = () => {
|
||||
const event_buff = timedEventManager.getDelibirdyBuff();
|
||||
if (event_buff.length > 0) {
|
||||
const candidates = event_buff.filter(c => {
|
||||
const mtype = generateModifierType(modifierTypes[c]);
|
||||
const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id);
|
||||
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(c);
|
||||
return !fullStack;
|
||||
});
|
||||
if (candidates.length > 0) {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes[randSeedItem(candidates)]);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", randSeedItem(candidates));
|
||||
} else {
|
||||
// At max stacks, give a Voucher instead
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.VOUCHER);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", RewardId.VOUCHER);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -85,8 +84,8 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Must also have either option 2 or 3 available to spawn
|
||||
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
|
||||
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true),
|
||||
new HoldingItemRequirement(OPTION_2_ALLOWED_HELD_ITEMS),
|
||||
new HoldingItemRequirement(OPTION_3_DISALLOWED_HELD_ITEMS, 1, true),
|
||||
),
|
||||
)
|
||||
.withIntroSpriteConfigs([
|
||||
@ -164,22 +163,13 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player an Amulet Coin
|
||||
// Check if the player has max stacks of that item already
|
||||
const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.AMULET_COIN);
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
if (fullStack) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
backupOption();
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", TrainerItemId.AMULET_COIN);
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
@ -189,7 +179,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
|
||||
.withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_2_ALLOWED_HELD_ITEMS))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
@ -204,19 +194,17 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(it => {
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_2_ALLOWED_HELD_ITEMS, true);
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
return validItems.map((item: HeldItemId) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
label: allHeldItems[item].name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.setDialogueToken("chosenItem", allHeldItems[item].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
chosenItem: item,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
@ -239,59 +227,35 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier;
|
||||
const chosenItem: HeldItemId = encounter.misc.chosenItem;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed
|
||||
if (modifier instanceof BerryModifier) {
|
||||
if (isItemInCategory(chosenItem, HeldItemCategoryId.BERRY)) {
|
||||
// Check if the player has max stacks of that Candy Jar already
|
||||
const existing = globalScene.findModifier(
|
||||
m => m instanceof LevelIncrementBoosterModifier,
|
||||
) as LevelIncrementBoosterModifier;
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.CANDY_JAR);
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
if (fullStack) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: shellBell.name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
backupOption();
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", TrainerItemId.CANDY_JAR);
|
||||
doEventReward();
|
||||
}
|
||||
} else {
|
||||
// Check if the player has max stacks of that Berry Pouch already
|
||||
const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.BERRY_POUCH);
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
if (fullStack) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: shellBell.name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
backupOption();
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", TrainerItemId.BERRY_POUCH);
|
||||
doEventReward();
|
||||
}
|
||||
}
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
chosenPokemon.loseHeldItem(chosenItem, false);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
@ -299,7 +263,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
|
||||
.withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_3_DISALLOWED_HELD_ITEMS, 1, true))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
@ -314,21 +278,17 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(it => {
|
||||
return (
|
||||
!OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable
|
||||
);
|
||||
});
|
||||
const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_3_DISALLOWED_HELD_ITEMS, true, true);
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
return validItems.map((item: HeldItemId) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
label: allHeldItems[item].name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.setDialogueToken("chosenItem", allHeldItems[item].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
chosenItem: item,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
@ -351,30 +311,22 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
const chosenItem = encounter.misc.chosenItem;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Check if the player has max stacks of Healing Charm already
|
||||
const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.HEALING_CHARM);
|
||||
|
||||
if (fullStack) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
backupOption();
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM);
|
||||
globalScene.phaseManager.unshiftNew("RewardPhase", TrainerItemId.HEALING_CHARM);
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
chosenPokemon.loseHeldItem(chosenItem, false);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { leaveEncounterWithoutBattle, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import type { ModifierTypeFunc } from "#types/modifier-types";
|
||||
import type { RewardSpecs } from "#types/rewards";
|
||||
import { randSeedInt } from "#utils/common";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
@ -59,23 +59,23 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
},
|
||||
async () => {
|
||||
// Choose TMs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
const rewards: RewardSpecs[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 2/2/1 weight on TM rarity
|
||||
const roll = randSeedInt(5);
|
||||
if (roll < 2) {
|
||||
modifiers.push(modifierTypes.TM_COMMON);
|
||||
rewards.push(RewardId.TM_COMMON);
|
||||
} else if (roll < 4) {
|
||||
modifiers.push(modifierTypes.TM_GREAT);
|
||||
rewards.push(RewardId.TM_GREAT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TM_ULTRA);
|
||||
rewards.push(RewardId.TM_ULTRA);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
guaranteedRewardSpecs: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -88,21 +88,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
},
|
||||
async () => {
|
||||
// Choose Vitamins
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
const rewards: RewardSpecs[] = [];
|
||||
let i = 0;
|
||||
while (i < 3) {
|
||||
// 2/1 weight on base stat booster vs PP Up
|
||||
const roll = randSeedInt(3);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.PP_UP);
|
||||
rewards.push(RewardId.PP_UP);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.BASE_STAT_BOOSTER);
|
||||
rewards.push(RewardId.BASE_STAT_BOOSTER);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
guaranteedRewardSpecs: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -115,21 +115,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
},
|
||||
async () => {
|
||||
// Choose X Items
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
const rewards: RewardSpecs[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 4/1 weight on base stat booster vs Dire Hit
|
||||
const roll = randSeedInt(5);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.DIRE_HIT);
|
||||
rewards.push(RewardId.DIRE_HIT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TEMP_STAT_STAGE_BOOSTER);
|
||||
rewards.push(RewardId.TEMP_STAT_STAGE_BOOSTER);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
guaranteedRewardSpecs: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -142,25 +142,25 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu
|
||||
},
|
||||
async () => {
|
||||
// Choose Pokeballs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
const rewards: RewardSpecs[] = [];
|
||||
let i = 0;
|
||||
while (i < 4) {
|
||||
// 10/30/20/5 weight on pokeballs
|
||||
const roll = randSeedInt(65);
|
||||
if (roll < 10) {
|
||||
modifiers.push(modifierTypes.POKEBALL);
|
||||
rewards.push(RewardId.POKEBALL);
|
||||
} else if (roll < 40) {
|
||||
modifiers.push(modifierTypes.GREAT_BALL);
|
||||
rewards.push(RewardId.GREAT_BALL);
|
||||
} else if (roll < 60) {
|
||||
modifiers.push(modifierTypes.ULTRA_BALL);
|
||||
rewards.push(RewardId.ULTRA_BALL);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.ROGUE_BALL);
|
||||
rewards.push(RewardId.ROGUE_BALL);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
guaranteedRewardSpecs: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { PlayerPokemon } from "#field/pokemon";
|
||||
import { generateRewardOptionFromId } from "#items/reward-utils";
|
||||
import type { PokemonMove } from "#moves/pokemon-move";
|
||||
import {
|
||||
generateModifierTypeOption,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterExp,
|
||||
@ -95,16 +96,16 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
const rewards = [
|
||||
generateRewardOptionFromId({ id: RewardId.TEMP_STAT_STAGE_BOOSTER, args: Stat.ATK })!,
|
||||
generateRewardOptionFromId({ id: RewardId.TEMP_STAT_STAGE_BOOSTER, args: Stat.DEF })!,
|
||||
generateRewardOptionFromId({ id: RewardId.TEMP_STAT_STAGE_BOOSTER, args: Stat.SPD })!,
|
||||
generateRewardOptionFromId(RewardId.DIRE_HIT)!,
|
||||
generateRewardOptionFromId(RewardId.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
guaranteedRewardOptions: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
@ -143,16 +144,16 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
const rewards = [
|
||||
generateRewardOptionFromId({ id: RewardId.TEMP_STAT_STAGE_BOOSTER, args: Stat.SPATK })!,
|
||||
generateRewardOptionFromId({ id: RewardId.TEMP_STAT_STAGE_BOOSTER, args: Stat.SPDEF })!,
|
||||
generateRewardOptionFromId({ id: RewardId.TEMP_STAT_STAGE_BOOSTER, args: Stat.SPD })!,
|
||||
generateRewardOptionFromId(RewardId.DIRE_HIT)!,
|
||||
generateRewardOptionFromId(RewardId.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
guaranteedRewardOptions: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
@ -191,16 +192,16 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.GREAT_BALL)!,
|
||||
generateModifierTypeOption(modifierTypes.IV_SCANNER)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
const rewards = [
|
||||
generateRewardOptionFromId({ id: RewardId.TEMP_STAT_STAGE_BOOSTER, args: Stat.ACC })!,
|
||||
generateRewardOptionFromId({ id: RewardId.TEMP_STAT_STAGE_BOOSTER, args: Stat.SPD })!,
|
||||
generateRewardOptionFromId(RewardId.GREAT_BALL)!,
|
||||
generateRewardOptionFromId(TrainerItemId.IV_SCANNER)!,
|
||||
generateRewardOptionFromId(RewardId.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
guaranteedRewardOptions: rewards,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { EncounterBattleAnim } from "#data/battle-anims";
|
||||
import { allAbilities, modifierTypes } from "#data/data-lists";
|
||||
import { allAbilities, allHeldItems } from "#data/data-lists";
|
||||
import { Gender } from "#data/gender";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -18,12 +19,11 @@ import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { AttackTypeBoosterModifierType } from "#modifiers/modifier-type";
|
||||
import { getNewHeldItemFromCategory } from "#items/held-item-pool";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
@ -31,11 +31,7 @@ import {
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyAbilityOverrideToPokemon,
|
||||
applyDamageToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
} from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import { applyAbilityOverrideToPokemon, applyDamageToPokemon } from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
@ -254,7 +250,6 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
}
|
||||
}
|
||||
|
||||
// No rewards
|
||||
leaveEncounterWithoutBattle(true);
|
||||
},
|
||||
)
|
||||
@ -299,19 +294,17 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
|
||||
function giveLeadPokemonAttackTypeBoostItem() {
|
||||
// Give first party pokemon attack type boost item for free at end of battle
|
||||
const leadPokemon = globalScene.getPlayerParty()?.[0];
|
||||
const leadPokemon = globalScene.getPlayerParty()[0];
|
||||
if (leadPokemon) {
|
||||
// Generate type booster held item, default to Charcoal if item fails to generate
|
||||
let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType;
|
||||
if (!boosterModifierType) {
|
||||
boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.FIRE,
|
||||
]) as AttackTypeBoosterModifierType;
|
||||
let item = getNewHeldItemFromCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER, leadPokemon);
|
||||
if (!item) {
|
||||
item = HeldItemId.CHARCOAL;
|
||||
}
|
||||
applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType);
|
||||
leadPokemon.heldItemManager.add(item);
|
||||
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
encounter.setDialogueToken("itemName", boosterModifierType.name);
|
||||
encounter.setDialogueToken("itemName", allHeldItems[item].name);
|
||||
encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
|
||||
queueEncounterMessage(`${namespace}:found_item`);
|
||||
}
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RewardPoolType } from "#enums/reward-pool-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#modifiers/modifier-type";
|
||||
import type { RewardOption, TrainerItemReward } from "#items/reward";
|
||||
import { generatePlayerRewardOptions, generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils";
|
||||
import { isTmReward } from "#items/reward-utils";
|
||||
import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
@ -89,18 +91,18 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
// Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
|
||||
const tier =
|
||||
globalScene.currentBattle.waveIndex > 160
|
||||
? ModifierTier.MASTER
|
||||
? RarityTier.MASTER
|
||||
: globalScene.currentBattle.waveIndex > 120
|
||||
? ModifierTier.ROGUE
|
||||
? RarityTier.ROGUE
|
||||
: globalScene.currentBattle.waveIndex > 40
|
||||
? ModifierTier.ULTRA
|
||||
: ModifierTier.GREAT;
|
||||
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
||||
let item: ModifierTypeOption | null = null;
|
||||
? RarityTier.ULTRA
|
||||
: RarityTier.GREAT;
|
||||
generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), globalScene.getPlayerParty(), 0);
|
||||
let item: RewardOption | null = null;
|
||||
// TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward
|
||||
while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") {
|
||||
item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], {
|
||||
guaranteedModifierTiers: [tier],
|
||||
while (!item || isTmReward(item.type) || (item.type as TrainerItemReward).itemId === TrainerItemId.CANDY_JAR) {
|
||||
item = generatePlayerRewardOptions(1, globalScene.getPlayerParty(), [], {
|
||||
guaranteedRarityTiers: [tier],
|
||||
allowLuckUpgrades: false,
|
||||
})[0];
|
||||
}
|
||||
@ -151,9 +153,9 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
async () => {
|
||||
// Pick battle
|
||||
// Pokemon will randomly boost 1 stat by 2 stages
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as RewardOption;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [item],
|
||||
guaranteedRewardOptions: [item],
|
||||
fillRemaining: false,
|
||||
});
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
@ -175,9 +177,9 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
.withOptionPhase(async () => {
|
||||
// Pick steal
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as RewardOption;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [item],
|
||||
guaranteedRewardOptions: [item],
|
||||
fillRemaining: false,
|
||||
});
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { SpeciesFormChangeActiveTrigger } from "#data/form-change-triggers";
|
||||
import { getPokeballAtlasKey, getPokeballTintColor } from "#data/pokeball";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -160,7 +160,7 @@ export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.wi
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
// Leave encounter with no RewardId or exp
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
@ -281,21 +281,21 @@ function handleNextTurn() {
|
||||
if (healthRatio < 0.03) {
|
||||
// Grand prize
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.MULTI_LENS],
|
||||
guaranteedRewardSpecs: [HeldItemId.MULTI_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:best_result`;
|
||||
} else if (healthRatio < 0.15) {
|
||||
// 2nd prize
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SCOPE_LENS],
|
||||
guaranteedRewardSpecs: [HeldItemId.SCOPE_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:great_result`;
|
||||
} else if (healthRatio < 0.33) {
|
||||
// 3rd prize
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.WIDE_LENS],
|
||||
guaranteedRewardSpecs: [HeldItemId.WIDE_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:good_result`;
|
||||
@ -387,7 +387,7 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
|
||||
globalScene.add.existing(pokemon);
|
||||
globalScene.field.add(pokemon);
|
||||
addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.pokeball);
|
||||
globalScene.updateModifiers(true);
|
||||
globalScene.updateItems(true);
|
||||
globalScene.updateFieldScale();
|
||||
pokemon.showInfo();
|
||||
pokemon.playAnim();
|
||||
|
||||
@ -1,33 +1,30 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allSpecies } from "#data/data-lists";
|
||||
import { allHeldItems, allSpecies } from "#data/data-lists";
|
||||
import { Gender, getGenderSymbol } from "#data/gender";
|
||||
import { getNatureName } from "#data/nature";
|
||||
import { getPokeballAtlasKey, getPokeballTintColor } from "#data/pokeball";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { getTypeRgb } from "#data/type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import { RewardPoolType } from "#enums/reward-pool-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { doShinySparkleAnim } from "#field/anims";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { EnemyPokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import {
|
||||
HiddenAbilityRateBoosterModifier,
|
||||
PokemonFormChangeItemModifier,
|
||||
ShinyRateBoosterModifier,
|
||||
SpeciesStatBoosterModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import type { ModifierTypeOption } from "#modifiers/modifier-type";
|
||||
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#modifiers/modifier-type";
|
||||
import { getHeldItemTier } from "#items/held-item-default-tiers";
|
||||
import type { RewardOption } from "#items/reward";
|
||||
import { generatePlayerRewardOptions, generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils";
|
||||
import { isTmReward } from "#items/reward-utils";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { getEncounterText, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
@ -209,9 +206,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
|
||||
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
|
||||
const modifiers = tradedPokemon
|
||||
.getHeldItems()
|
||||
.filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
|
||||
const heldItemConfig = tradedPokemon.heldItemManager
|
||||
.generateHeldItemConfiguration()
|
||||
.filter(ic => !isItemInCategory(ic.entry as HeldItemId, HeldItemCategoryId.SPECIES_STAT_BOOSTER));
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
@ -235,16 +232,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
dataSource.variant,
|
||||
dataSource.ivs,
|
||||
dataSource.nature,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
globalScene.getPlayerParty().push(newPlayerPokemon);
|
||||
await newPlayerPokemon.loadAssets();
|
||||
|
||||
for (const mod of modifiers) {
|
||||
mod.pokemonId = newPlayerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
|
||||
// Show the trade animation
|
||||
await showTradeBackground();
|
||||
await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon);
|
||||
@ -278,7 +271,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
if (timedEventManager.isEventActive()) {
|
||||
shinyThreshold.value *= timedEventManager.getShinyMultiplier();
|
||||
}
|
||||
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold });
|
||||
|
||||
// Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms
|
||||
// Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that
|
||||
@ -292,7 +285,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
if (tradePokemon.species.abilityHidden) {
|
||||
if (tradePokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new NumberHolder(64);
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
@ -331,9 +326,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
|
||||
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
|
||||
const modifiers = tradedPokemon
|
||||
.getHeldItems()
|
||||
.filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
|
||||
const heldItemConfig = tradedPokemon.heldItemManager
|
||||
.generateHeldItemConfiguration()
|
||||
.filter(ic => !isItemInCategory(ic.entry as HeldItemId, HeldItemCategoryId.SPECIES_STAT_BOOSTER));
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
@ -356,16 +351,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
dataSource.variant,
|
||||
dataSource.ivs,
|
||||
dataSource.nature,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
globalScene.getPlayerParty().push(newPlayerPokemon);
|
||||
await newPlayerPokemon.loadAssets();
|
||||
|
||||
for (const mod of modifiers) {
|
||||
mod.pokemonId = newPlayerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
|
||||
// Show the trade animation
|
||||
await showTradeBackground();
|
||||
await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon);
|
||||
@ -390,17 +381,15 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(it => {
|
||||
return it.isTransferable;
|
||||
});
|
||||
const validItems = pokemon.heldItemManager.getTransferableHeldItems();
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
return validItems.map((id: HeldItemId) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
label: allHeldItems[id].name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc.chosenModifier = modifier;
|
||||
encounter.setDialogueToken("chosenItem", allHeldItems[id].name);
|
||||
encounter.misc.chosenHeldItem = id;
|
||||
encounter.misc.chosenPokemon = pokemon;
|
||||
return true;
|
||||
},
|
||||
@ -411,10 +400,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has items to trade
|
||||
const meetsReqs =
|
||||
pokemon.getHeldItems().filter(it => {
|
||||
return it.isTransferable;
|
||||
}).length > 0;
|
||||
const meetsReqs = pokemon.heldItemManager.getTransferableHeldItems().length > 0;
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
|
||||
}
|
||||
@ -426,44 +412,36 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier as PokemonHeldItemModifier;
|
||||
const heldItemId = encounter.misc.chosenHeldItem as HeldItemId;
|
||||
const party = globalScene.getPlayerParty();
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Check tier of the traded item, the received item will be one tier up
|
||||
const type = modifier.type.withTierFromPool(ModifierPoolType.PLAYER, party);
|
||||
let tier = type.tier ?? ModifierTier.GREAT;
|
||||
// Eggs and White Herb are not in the pool
|
||||
if (type.id === "WHITE_HERB") {
|
||||
tier = ModifierTier.GREAT;
|
||||
} else if (type.id === "LUCKY_EGG") {
|
||||
tier = ModifierTier.ULTRA;
|
||||
} else if (type.id === "GOLDEN_EGG") {
|
||||
tier = ModifierTier.ROGUE;
|
||||
}
|
||||
let tier = getHeldItemTier(heldItemId) ?? RarityTier.GREAT;
|
||||
|
||||
// Increment tier by 1
|
||||
if (tier < ModifierTier.MASTER) {
|
||||
if (tier < RarityTier.MASTER) {
|
||||
tier++;
|
||||
}
|
||||
|
||||
regenerateModifierPoolThresholds(party, ModifierPoolType.PLAYER, 0);
|
||||
let item: ModifierTypeOption | null = null;
|
||||
generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), party, 0);
|
||||
let item: RewardOption | null = null;
|
||||
// TMs excluded from possible rewards
|
||||
while (!item || item.type.id.includes("TM_")) {
|
||||
item = getPlayerModifierTypeOptions(1, party, [], {
|
||||
guaranteedModifierTiers: [tier],
|
||||
while (!item || isTmReward(item.type)) {
|
||||
item = generatePlayerRewardOptions(1, party, [], {
|
||||
guaranteedRarityTiers: [tier],
|
||||
allowLuckUpgrades: false,
|
||||
})[0];
|
||||
}
|
||||
|
||||
encounter.setDialogueToken("itemName", item.type.name);
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [item],
|
||||
guaranteedRewardOptions: [item],
|
||||
fillRemaining: false,
|
||||
});
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
await globalScene.updateModifiers(true, true);
|
||||
chosenPokemon.heldItemManager.remove(heldItemId);
|
||||
await globalScene.updateItems(true);
|
||||
|
||||
// Generate a trainer name
|
||||
const traderName = generateRandomTraderName();
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
@ -147,7 +147,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM],
|
||||
guaranteedRewardSpecs: [RewardId.TM_COMMON, RewardId.TM_GREAT, RewardId.MEMORY_MUSHROOM],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
@ -175,7 +175,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
@ -206,7 +206,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter
|
||||
encounter.expMultiplier = 0.9;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
@ -141,7 +141,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
|
||||
// Choose between 2 COMMON / 2 GREAT tier items (20%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
guaranteedRarityTiers: [RarityTier.COMMON, RarityTier.COMMON, RarityTier.GREAT, RarityTier.GREAT],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.normal`);
|
||||
@ -149,7 +149,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
|
||||
// Choose between 3 ULTRA tier items (30%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.good`);
|
||||
@ -157,7 +157,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
|
||||
// Choose between 2 ROGUE tier items (10%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.great`);
|
||||
@ -168,7 +168,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
) {
|
||||
// Choose 1 MASTER tier item (5%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.MASTER],
|
||||
guaranteedRarityTiers: [RarityTier.MASTER],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.amazing`);
|
||||
|
||||
@ -8,9 +8,10 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import type { EnemyPokemon } from "#field/pokemon";
|
||||
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#modifiers/modifier";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { getEncounterText, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
initSubsequentOptionSelect,
|
||||
@ -297,7 +298,9 @@ async function summonSafariPokemon() {
|
||||
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
||||
if (pokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new NumberHolder(256);
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
@ -332,8 +335,7 @@ async function summonSafariPokemon() {
|
||||
// shows up and the IV scanner breaks. For now, we place the IV scanner code
|
||||
// separately so that at least the IV scanner works.
|
||||
|
||||
const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
if (globalScene.trainerItems.hasItem(TrainerItemId.IV_SCANNER)) {
|
||||
globalScene.phaseManager.pushNew("ScanIvsPhase", pokemon.getBattlerIndex());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { getNatureName } from "#data/nature";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
@ -8,9 +8,9 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type { Nature } from "#enums/nature";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { getNewVitaminHeldItem } from "#items/held-item-pool";
|
||||
import { getEncounterText, queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterExp,
|
||||
@ -18,7 +18,6 @@ import {
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
applyDamageToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
isPokemonValidForEncounterOptionSelection,
|
||||
} from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
@ -96,15 +95,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Update money
|
||||
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
const items = [getNewVitaminHeldItem(), getNewVitaminHeldItem()];
|
||||
encounter.setDialogueToken("boost1", allHeldItems[items[0]].name);
|
||||
encounter.setDialogueToken("boost2", allHeldItems[items[1]].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
items: items,
|
||||
};
|
||||
};
|
||||
|
||||
@ -131,10 +127,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Choose Cheap Option
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
const items = encounter.misc.items;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
|
||||
for (const item of items) {
|
||||
chosenPokemon.heldItemManager.add(item);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
@ -179,15 +175,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Update money
|
||||
updatePlayerMoney(-(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
const items = [getNewVitaminHeldItem(), getNewVitaminHeldItem()];
|
||||
encounter.setDialogueToken("boost1", allHeldItems[items[0]].name);
|
||||
encounter.setDialogueToken("boost2", allHeldItems[items[1]].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
items: items,
|
||||
};
|
||||
};
|
||||
|
||||
@ -202,10 +195,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
// Choose Expensive Option
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
const items = encounter.misc.items;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
|
||||
for (const item of items) {
|
||||
chosenPokemon.heldItemManager.add(item);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { CustomPokemonData } from "#data/pokemon-data";
|
||||
import { AiType } from "#enums/ai-type";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -11,14 +10,11 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
@ -78,24 +74,12 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
status: [StatusEffect.SLEEP, 6], // Extra turns on timer for Snorlax's start of fight moves
|
||||
nature: Nature.DOCILE,
|
||||
moveSet: [MoveId.BODY_SLAM, MoveId.CRUNCH, MoveId.SLEEP_TALK, MoveId.REST],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 0),
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 0),
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.SITRUS_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.ENIGMA_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.HP_UP, count: 1 },
|
||||
{ entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 0) },
|
||||
{ entry: HeldItemId.LUCKY_EGG, count: randSeedInt(2, 0) },
|
||||
],
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
aiType: AiType.SMART, // Required to ensure Snorlax uses Sleep Talk while it is asleep
|
||||
@ -131,7 +115,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
|
||||
guaranteedRewardSpecs: [HeldItemId.LEFTOVERS],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push({
|
||||
@ -178,7 +162,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
// Steal the Snorlax's Leftovers
|
||||
const instance = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
|
||||
guaranteedRewardSpecs: [HeldItemId.LEFTOVERS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
// Snorlax exp to Pokemon that did the stealing
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -13,11 +13,9 @@ import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { getBiomeKey } from "#field/arena";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { EnemyPokemon } from "#field/pokemon";
|
||||
import { getPartyLuckValue } from "#modifiers/modifier-type";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
@ -34,6 +32,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou
|
||||
import { MoneyRequirement, WaveModulusRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import { randSeedInt } from "#utils/common";
|
||||
import { getPartyLuckValue } from "#utils/party";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounters/teleportingHijinks";
|
||||
@ -173,10 +172,8 @@ export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
],
|
||||
};
|
||||
|
||||
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!;
|
||||
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [magnet, metalCoat],
|
||||
guaranteedRewardSpecs: [HeldItemId.MAGNET, HeldItemId.METAL_COAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import type { IEggOptions } from "#data/egg";
|
||||
import { getPokeballTintColor } from "#data/pokeball";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
@ -302,7 +302,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
@ -361,7 +361,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
@ -420,7 +420,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount
|
||||
const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
@ -622,7 +622,6 @@ function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, ch
|
||||
party[chosenIndex] = party[0];
|
||||
party[0] = chosenPokemon;
|
||||
encounter.misc.originalParty = globalScene.getPlayerParty().slice(1);
|
||||
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty.map(p => p.getHeldItems());
|
||||
globalScene["party"] = [chosenPokemon];
|
||||
}
|
||||
|
||||
@ -631,14 +630,7 @@ function restorePartyAndHeldItems() {
|
||||
// Restore original party
|
||||
globalScene.getPlayerParty().push(...encounter.misc.originalParty);
|
||||
|
||||
// Restore held items
|
||||
const originalHeldItems = encounter.misc.originalPartyHeldItems;
|
||||
for (const pokemonHeldItemsList of originalHeldItems) {
|
||||
for (const heldItem of pokemonHeldItemsList) {
|
||||
globalScene.addModifier(heldItem, true, false, false, true);
|
||||
}
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
globalScene.updateItems(true);
|
||||
}
|
||||
|
||||
function onGameOver() {
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { CustomPokemonData } from "#data/pokemon-data";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
@ -13,19 +12,16 @@ import { Nature } from "#enums/nature";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import { modifyPlayerPokemonBST } from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
@ -95,23 +91,12 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
nature: Nature.HARDY,
|
||||
moveSet: [MoveId.INFESTATION, MoveId.SALT_CURE, MoveId.GASTRO_ACID, MoveId.HEAL_ORDER],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.SITRUS_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.ENIGMA_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.APICOT_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.GANLON_BERRY, count: 1 },
|
||||
{ entry: HeldItemId.LUM_BERRY, count: 2 },
|
||||
],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
@ -171,11 +156,11 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
sortedParty.forEach((pokemon, index) => {
|
||||
if (index < 2) {
|
||||
// -15 to the two highest BST mons
|
||||
modifyPlayerPokemonBST(pokemon, false);
|
||||
pokemon.heldItemManager.add(HeldItemId.SHUCKLE_JUICE_BAD);
|
||||
encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender());
|
||||
} else {
|
||||
// +10 for the rest
|
||||
modifyPlayerPokemonBST(pokemon, true);
|
||||
pokemon.heldItemManager.add(HeldItemId.SHUCKLE_JUICE_GOOD);
|
||||
}
|
||||
});
|
||||
|
||||
@ -207,7 +192,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW],
|
||||
guaranteedRewardSpecs: [HeldItemId.SOUL_DEW],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push(
|
||||
|
||||
@ -1,27 +1,24 @@
|
||||
import { applyAbAttrs } from "#abilities/apply-ab-attrs";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { SpeciesFormChangeAbilityTrigger } from "#data/form-change-triggers";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import type { Reward } from "#items/reward";
|
||||
import { generateRewardOptionFromId } from "#items/reward-utils";
|
||||
import { showEncounterDialogue, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
@ -32,6 +29,8 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
// TODO: make all items unstealable
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/theWinstrateChallenge";
|
||||
|
||||
@ -142,7 +141,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter
|
||||
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
|
||||
globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY],
|
||||
guaranteedRewardSpecs: [RewardId.RARER_CANDY],
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
@ -158,17 +157,17 @@ async function spawnNextTrainerOrEndEncounter() {
|
||||
await showEncounterDialogue(`${namespace}:victory`, `${namespace}:speaker`);
|
||||
|
||||
// Give 10x Voucher
|
||||
const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier();
|
||||
globalScene.addModifier(newModifier);
|
||||
const reward = generateRewardOptionFromId(RewardId.VOUCHER_PREMIUM).type;
|
||||
globalScene.applyReward(reward as Reward, {});
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }));
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: (reward as Reward).name }));
|
||||
|
||||
await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`);
|
||||
globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
|
||||
const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
|
||||
machoBrace.type.tier = ModifierTier.MASTER;
|
||||
const machoBrace = generateRewardOptionFromId(HeldItemId.MACHO_BRACE)!;
|
||||
machoBrace.type.tier = RarityTier.MASTER;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [machoBrace],
|
||||
guaranteedRewardOptions: [machoBrace],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.doContinueEncounter = undefined;
|
||||
@ -258,16 +257,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 0, // Guts
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [MoveId.FACADE, MoveId.BRAVE_BIRD, MoveId.PROTECT, MoveId.QUICK_ATTACK],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.FLAME_ORB, count: 1 },
|
||||
{ entry: HeldItemId.FOCUS_BAND, count: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -276,16 +268,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 1, // Guts
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [MoveId.FACADE, MoveId.OBSTRUCT, MoveId.NIGHT_SLASH, MoveId.FIRE_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.FLAME_ORB, count: 1 },
|
||||
{ entry: HeldItemId.LEFTOVERS, count: 2 },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -302,16 +287,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 0, // Natural Cure
|
||||
nature: Nature.CALM,
|
||||
moveSet: [MoveId.SYNTHESIS, MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.SLEEP_POWDER],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.SOUL_DEW, count: 1 },
|
||||
{ entry: HeldItemId.QUICK_CLAW, count: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -320,21 +298,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
|
||||
formIndex: 1,
|
||||
nature: Nature.TIMID,
|
||||
moveSet: [MoveId.PSYSHOCK, MoveId.MOONBLAST, MoveId.SHADOW_BALL, MoveId.WILL_O_WISP],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.PSYCHIC,
|
||||
]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.FAIRY,
|
||||
]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.TWISTED_SPOON, count: 1 },
|
||||
{ entry: HeldItemId.FAIRY_FEATHER, count: 1 },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -351,17 +317,9 @@ function getViviTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 3, // Lightning Rod
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [MoveId.WATERFALL, MoveId.MEGAHORN, MoveId.KNOCK_OFF, MoveId.REST],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
stackCount: 4,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.LUM_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.HP_UP, count: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -370,16 +328,9 @@ function getViviTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 1, // Poison Heal
|
||||
nature: Nature.JOLLY,
|
||||
moveSet: [MoveId.SPORE, MoveId.SWORDS_DANCE, MoveId.SEED_BOMB, MoveId.DRAIN_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
stackCount: 4,
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.HP_UP, count: 4 },
|
||||
{ entry: HeldItemId.TOXIC_ORB, count: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -388,13 +339,7 @@ function getViviTrainerConfig(): EnemyPartyConfig {
|
||||
formIndex: 1,
|
||||
nature: Nature.CALM,
|
||||
moveSet: [MoveId.EARTH_POWER, MoveId.FIRE_BLAST, MoveId.YAWN, MoveId.PROTECT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 3,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 3 }],
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -410,12 +355,7 @@ function getVickyTrainerConfig(): EnemyPartyConfig {
|
||||
formIndex: 1,
|
||||
nature: Nature.IMPISH,
|
||||
moveSet: [MoveId.AXE_KICK, MoveId.ICE_PUNCH, MoveId.ZEN_HEADBUTT, MoveId.BULLET_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.SHELL_BELL, count: 1 }],
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -431,13 +371,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 0, // Soundproof
|
||||
nature: Nature.MODEST,
|
||||
moveSet: [MoveId.THUNDERBOLT, MoveId.GIGA_DRAIN, MoveId.FOUL_PLAY, MoveId.THUNDER_WAVE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.ZINC, count: 2 }],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(SpeciesId.SWALOT),
|
||||
@ -445,51 +379,18 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 2, // Gluttony
|
||||
nature: Nature.QUIET,
|
||||
moveSet: [MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.ICE_BEAM, MoveId.EARTHQUAKE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemId.SITRUS_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.APICOT_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.GANLON_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.STARF_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.SALAC_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.LUM_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.LANSAT_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.LIECHI_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.PETAYA_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.ENIGMA_BERRY, count: 2 },
|
||||
{ entry: HeldItemId.LEPPA_BERRY, count: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -498,13 +399,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 2, // Tangled Feet
|
||||
nature: Nature.JOLLY,
|
||||
moveSet: [MoveId.DRILL_PECK, MoveId.QUICK_ATTACK, MoveId.THRASH, MoveId.KNOCK_OFF],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.KINGS_ROCK, count: 2 }],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(SpeciesId.ALAKAZAM),
|
||||
@ -512,13 +407,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
formIndex: 1,
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [MoveId.PSYCHIC, MoveId.SHADOW_BALL, MoveId.FOCUS_BLAST, MoveId.THUNDERBOLT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.WIDE_LENS, count: 2 }],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(SpeciesId.DARMANITAN),
|
||||
@ -526,13 +415,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
||||
abilityIndex: 0, // Sheer Force
|
||||
nature: Nature.IMPISH,
|
||||
moveSet: [MoveId.EARTHQUAKE, MoveId.U_TURN, MoveId.FLARE_BLITZ, MoveId.ROCK_SLIDE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 2 }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -12,7 +12,6 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { getStatKey } from "#enums/stat";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
@ -26,7 +25,6 @@ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
|
||||
import { isNullOrUndefined, randSeedShuffle } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
@ -102,8 +100,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
// Spawn light training session with chosen pokemon
|
||||
// Every 50 waves, add +1 boss segment, capping at 5
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 50), 5);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
const config = getEnemyConfig(playerPokemon, segments);
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
@ -152,13 +149,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
globalScene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
}
|
||||
|
||||
// Add pokemon and mods back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
// Make held items show up again
|
||||
globalScene.updateItems(true);
|
||||
queueEncounterMessage(`${namespace}:option.1.finished`);
|
||||
};
|
||||
|
||||
@ -217,8 +209,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
// Spawn medium training session with chosen pokemon
|
||||
// Every 40 waves, add +1 boss segment, capping at 6
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
const config = getEnemyConfig(playerPokemon, segments);
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
@ -227,13 +218,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
playerPokemon.setCustomNature(encounter.misc.chosenNature);
|
||||
globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature);
|
||||
|
||||
// Add pokemon and modifiers back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
// Make held items show up again
|
||||
globalScene.updateItems(true);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
@ -308,8 +294,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
// Every 30 waves, add +1 boss segment, capping at 6
|
||||
// Also starts with +1 to all stats
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
const config = getEnemyConfig(playerPokemon, segments);
|
||||
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
@ -340,13 +325,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
playerPokemon.calculateStats();
|
||||
globalScene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
|
||||
// Add pokemon and mods back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
// Make held items show up again
|
||||
globalScene.updateItems(true);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
@ -373,18 +353,12 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
)
|
||||
.build();
|
||||
|
||||
function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig {
|
||||
function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number): EnemyPartyConfig {
|
||||
playerPokemon.resetSummonData();
|
||||
|
||||
// Passes modifiers by reference
|
||||
modifiers.value = playerPokemon.getHeldItems();
|
||||
const modifierConfigs = modifiers.value.map(mod => {
|
||||
return {
|
||||
modifier: mod.clone(),
|
||||
isTransferable: false,
|
||||
stackCount: mod.stackCount,
|
||||
};
|
||||
}) as HeldModifierConfig[];
|
||||
// TODO: fix various things, like make enemy items untransferable, make sure form change items can come back
|
||||
const config = playerPokemon.heldItemManager.generateHeldItemConfiguration();
|
||||
|
||||
const data = new PokemonData(playerPokemon);
|
||||
return {
|
||||
@ -396,12 +370,8 @@ function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifier
|
||||
formIndex: playerPokemon.formIndex,
|
||||
level: playerPokemon.level,
|
||||
dataSource: data,
|
||||
modifierConfigs: modifierConfigs,
|
||||
heldItemConfig: config,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
class ModifiersHolder {
|
||||
public value: PokemonHeldItemModifier[] = [];
|
||||
}
|
||||
|
||||
@ -1,29 +1,28 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { allHeldItems, allTrainerItems } from "#data/data-lists";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import { assignItemToFirstFreePokemon } from "#items/item-utility";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import {
|
||||
type EnemyPartyConfig,
|
||||
type EnemyPokemonConfig,
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#mystery-encounters/encounter-phase-utils";
|
||||
import { applyModifierTypeToPlayerPokemon } from "#mystery-encounters/encounter-pokemon-utils";
|
||||
import { type MysteryEncounter, MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import i18next from "#plugins/i18n";
|
||||
@ -83,41 +82,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
formIndex: 1, // Gmax
|
||||
bossSegmentModifier: 1, // +1 Segment from normal
|
||||
moveSet: [MoveId.GUNK_SHOT, MoveId.STOMPING_TANTRUM, MoveId.HAMMER_ARM, MoveId.PAYBACK],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 0),
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 1),
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(3, 1),
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType,
|
||||
stackCount: randSeedInt(2, 0),
|
||||
},
|
||||
heldItemConfig: [
|
||||
{ entry: HeldItemCategoryId.BERRY, count: 4 },
|
||||
{ entry: HeldItemCategoryId.BASE_STAT_BOOST, count: 2 },
|
||||
{ entry: HeldItemId.TOXIC_ORB, count: randSeedInt(2, 0) },
|
||||
{ entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 1) },
|
||||
{ entry: HeldItemId.LUCKY_EGG, count: randSeedInt(3, 1) },
|
||||
{ entry: HeldItemId.GOLDEN_EGG, count: randSeedInt(2, 0) },
|
||||
],
|
||||
};
|
||||
const config: EnemyPartyConfig = {
|
||||
@ -157,18 +128,14 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
await tryApplyDigRewardItems();
|
||||
|
||||
const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [
|
||||
SHOP_ITEM_COST_MULTIPLIER,
|
||||
]);
|
||||
const modifier = blackSludge?.newModifier();
|
||||
if (modifier) {
|
||||
await globalScene.addModifier(modifier, false, false, false, true);
|
||||
const blackSludge = globalScene.trainerItems.add(TrainerItemId.BLACK_SLUDGE);
|
||||
if (blackSludge) {
|
||||
globalScene.playSound("battle_anims/PRSFX- Venom Drench", {
|
||||
volume: 2,
|
||||
});
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: modifier.type.name,
|
||||
modifierName: allTrainerItems[TrainerItemId.BLACK_SLUDGE].name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
@ -200,8 +167,8 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
|
||||
guaranteedRewardSpecs: [HeldItemId.LEFTOVERS],
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push(
|
||||
@ -225,44 +192,18 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.build();
|
||||
|
||||
async function tryApplyDigRewardItems() {
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
const leftovers = generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
|
||||
// Iterate over the party until an item was successfully given
|
||||
// First leftovers
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
|
||||
|
||||
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
|
||||
await applyModifierTypeToPlayerPokemon(pokemon, leftovers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party);
|
||||
|
||||
// Second leftovers
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
|
||||
|
||||
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
|
||||
await applyModifierTypeToPlayerPokemon(pokemon, leftovers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: leftovers.name,
|
||||
modifierName: allHeldItems[HeldItemId.LEFTOVERS].name,
|
||||
count: 2,
|
||||
}),
|
||||
null,
|
||||
@ -271,23 +212,12 @@ async function tryApplyDigRewardItems() {
|
||||
);
|
||||
|
||||
// Only Shell bell
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
|
||||
|
||||
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
|
||||
await applyModifierTypeToPlayerPokemon(pokemon, shellBell);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assignItemToFirstFreePokemon(HeldItemId.SHELL_BELL, party);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: shellBell.name,
|
||||
modifierName: allHeldItems[HeldItemId.SHELL_BELL].name,
|
||||
count: 1,
|
||||
}),
|
||||
null,
|
||||
|
||||
@ -2,6 +2,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { HeldItemCategoryId, type HeldItemId } from "#enums/held-item-id";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -10,7 +11,8 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { Stat } from "#enums/stat";
|
||||
import type { EnemyPokemon, Pokemon } from "#field/pokemon";
|
||||
import { BerryModifier } from "#modifiers/modifier";
|
||||
import type { HeldItemSpecs } from "#items/held-item-data-types";
|
||||
import { getPartyBerries } from "#items/item-utility";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
@ -29,10 +31,10 @@ import {
|
||||
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
||||
import { MoveRequirement, PersistentModifierRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { HeldItemRequirement, MoveRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { CHARMING_MOVES } from "#mystery-encounters/requirement-groups";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import { isNullOrUndefined, randSeedInt } from "#utils/common";
|
||||
import { isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/uncommonBreed";
|
||||
@ -187,7 +189,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
@ -202,20 +204,16 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
||||
// Give it some food
|
||||
|
||||
// Remove 4 random berries from player's party
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems: BerryModifier[] = globalScene.findModifiers(
|
||||
m => m instanceof BerryModifier,
|
||||
) as BerryModifier[];
|
||||
const berryMap = getPartyBerries();
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const index = randSeedInt(berryItems.length);
|
||||
const randBerry = berryItems[index];
|
||||
randBerry.stackCount--;
|
||||
if (randBerry.stackCount === 0) {
|
||||
globalScene.removeModifier(randBerry);
|
||||
berryItems.splice(index, 1);
|
||||
}
|
||||
const berryWeights = berryMap.map(b => (b.item as HeldItemSpecs).stack);
|
||||
const index = pickWeightedIndex(berryWeights) ?? 0;
|
||||
const randBerry = berryMap[index];
|
||||
globalScene.getPokemonById(randBerry.pokemonId)?.heldItemManager.remove(randBerry.item.id as HeldItemId);
|
||||
(randBerry.item as HeldItemSpecs).stack -= 1;
|
||||
}
|
||||
await globalScene.updateModifiers(true, true);
|
||||
await globalScene.updateItems(true);
|
||||
|
||||
// Pokemon joins the team, with 2 egg moves
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allSpecies, modifierTypes } from "#data/data-lists";
|
||||
import { allSpecies } from "#data/data-lists";
|
||||
import { getLevelTotalExp } from "#data/exp";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -11,17 +11,17 @@ import { Nature } from "#enums/nature";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import type { HeldItemConfiguration } from "#items/held-item-data-types";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
@ -38,7 +38,6 @@ import { achvs } from "#system/achv";
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import { trainerConfigs } from "#trainers/trainer-config";
|
||||
import { TrainerPartyTemplate } from "#trainers/trainer-party-template";
|
||||
import type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedShuffle } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
|
||||
@ -220,13 +219,13 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
|
||||
await doNewTeamPostProcess(transformations);
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [
|
||||
modifierTypes.MEMORY_MUSHROOM,
|
||||
modifierTypes.ROGUE_BALL,
|
||||
modifierTypes.MINT,
|
||||
modifierTypes.MINT,
|
||||
modifierTypes.MINT,
|
||||
modifierTypes.MINT,
|
||||
guaranteedRewardSpecs: [
|
||||
RewardId.MEMORY_MUSHROOM,
|
||||
RewardId.ROGUE_BALL,
|
||||
RewardId.MINT,
|
||||
RewardId.MINT,
|
||||
RewardId.MINT,
|
||||
RewardId.MINT,
|
||||
],
|
||||
fillRemaining: false,
|
||||
});
|
||||
@ -245,7 +244,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Battle your "future" team for some item rewards
|
||||
// Battle your "future" team for some item RewardId
|
||||
const transformations: PokemonTransformation[] =
|
||||
globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
|
||||
|
||||
@ -261,20 +260,14 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
dataSource.player = false;
|
||||
|
||||
// Copy held items to new pokemon
|
||||
const newPokemonHeldItemConfigs: HeldModifierConfig[] = [];
|
||||
for (const item of transformation.heldItems) {
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: item.clone() as PokemonHeldItemModifier,
|
||||
stackCount: item.getStackCount(),
|
||||
isTransferable: false,
|
||||
});
|
||||
}
|
||||
// TODO: Make items untransferable
|
||||
const newPokemonHeldItemConfig = transformation.heldItems;
|
||||
|
||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false,
|
||||
newPokemonHeldItemConfig.push({
|
||||
entry: HeldItemId.OLD_GATEAU,
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
|
||||
@ -283,7 +276,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD,
|
||||
level: previousPokemon.level,
|
||||
dataSource: dataSource,
|
||||
modifierConfigs: newPokemonHeldItemConfigs,
|
||||
heldItemConfig: newPokemonHeldItemConfig,
|
||||
};
|
||||
|
||||
enemyPokemonConfigs.push(enemyConfig);
|
||||
@ -302,7 +295,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
};
|
||||
|
||||
const onBeforeRewards = () => {
|
||||
// Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently)
|
||||
// Before battle RewardId, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently)
|
||||
// One random pokemon will get its passive unlocked
|
||||
const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive);
|
||||
if (passiveDisabledPokemon?.length > 0) {
|
||||
@ -315,13 +308,13 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.GREAT,
|
||||
ModifierTier.GREAT,
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.GREAT,
|
||||
RarityTier.GREAT,
|
||||
],
|
||||
fillRemaining: false,
|
||||
},
|
||||
@ -365,7 +358,7 @@ interface PokemonTransformation {
|
||||
previousPokemon: PlayerPokemon;
|
||||
newSpecies: PokemonSpecies;
|
||||
newPokemon: PlayerPokemon;
|
||||
heldItems: PokemonHeldItemModifier[];
|
||||
heldItems: HeldItemConfiguration;
|
||||
}
|
||||
|
||||
function getTeamTransformations(): PokemonTransformation[] {
|
||||
@ -390,9 +383,7 @@ function getTeamTransformations(): PokemonTransformation[] {
|
||||
for (let i = 0; i < numPokemon; i++) {
|
||||
const removed = removedPokemon[i];
|
||||
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
|
||||
pokemonTransformations[index].heldItems = removed
|
||||
.getHeldItems()
|
||||
.filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
pokemonTransformations[index].heldItems = removed.heldItemManager.generateHeldItemConfiguration();
|
||||
|
||||
const bst = removed.getSpeciesForm().getBaseStatTotal();
|
||||
let newBstRange: [number, number];
|
||||
@ -448,17 +439,14 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
|
||||
}
|
||||
|
||||
// Copy old items to new pokemon
|
||||
for (const item of transformation.heldItems) {
|
||||
item.pokemonId = newPokemon.id;
|
||||
globalScene.addModifier(item, false, false, false, true);
|
||||
}
|
||||
const heldItemConfiguration = transformation.heldItems;
|
||||
|
||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU();
|
||||
const modifier = modType?.newModifier(newPokemon);
|
||||
if (modifier) {
|
||||
globalScene.addModifier(modifier, false, false, false, true);
|
||||
}
|
||||
heldItemConfiguration.push({
|
||||
entry: HeldItemId.OLD_GATEAU,
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
|
||||
newPokemon.calculateStats();
|
||||
@ -500,7 +488,9 @@ async function postProcessTransformedPokemon(
|
||||
const hiddenIndex = newPokemon.species.ability2 ? 2 : 1;
|
||||
if (newPokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new NumberHolder(256);
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { allAbilities } from "#data/data-lists";
|
||||
import { allAbilities, allHeldItems } from "#data/data-lists";
|
||||
import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers";
|
||||
import { pokemonFormChanges } from "#data/pokemon-forms";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import { FormChangeItem } from "#enums/form-change-item";
|
||||
import { getHeldItemCategory, type HeldItemCategoryId, type HeldItemId } from "#enums/held-item-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
@ -13,8 +14,6 @@ import { StatusEffect } from "#enums/status-effect";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { PlayerPokemon } from "#field/pokemon";
|
||||
import { AttackTypeBoosterModifier } from "#modifiers/modifier";
|
||||
import type { AttackTypeBoosterModifierType } from "#modifiers/modifier-type";
|
||||
import { coerceArray, isNullOrUndefined } from "#utils/common";
|
||||
|
||||
export interface EncounterRequirement {
|
||||
@ -351,39 +350,6 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
|
||||
}
|
||||
}
|
||||
|
||||
export class PersistentModifierRequirement extends EncounterSceneRequirement {
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfItems: number;
|
||||
|
||||
constructor(heldItem: string | string[], minNumberOfItems = 1) {
|
||||
super();
|
||||
this.minNumberOfItems = minNumberOfItems;
|
||||
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
const partyPokemon = globalScene.getPlayerParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
let modifierCount = 0;
|
||||
for (const modifier of this.requiredHeldItemModifiers) {
|
||||
const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier);
|
||||
if (matchingMods?.length > 0) {
|
||||
for (const matchingMod of matchingMods) {
|
||||
modifierCount += matchingMod.stackCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modifierCount >= this.minNumberOfItems;
|
||||
}
|
||||
|
||||
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["requiredItem", this.requiredHeldItemModifiers[0]];
|
||||
}
|
||||
}
|
||||
|
||||
export class MoneyRequirement extends EncounterSceneRequirement {
|
||||
requiredMoney: number; // Static value
|
||||
scalingMultiplier: number; // Calculates required money based off wave index
|
||||
@ -832,73 +798,14 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
|
||||
}
|
||||
}
|
||||
|
||||
export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(heldItem: string | string[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
const partyPokemon = globalScene.getPlayerParty();
|
||||
if (isNullOrUndefined(partyPokemon)) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter(pokemon =>
|
||||
this.requiredHeldItemModifiers.some(heldItem => {
|
||||
return pokemon.getHeldItems().some(it => {
|
||||
return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter(
|
||||
pokemon =>
|
||||
pokemon.getHeldItems().filter(it => {
|
||||
return (
|
||||
!this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
|
||||
(!this.requireTransferable || it.isTransferable)
|
||||
);
|
||||
}).length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter(it => {
|
||||
return (
|
||||
this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
|
||||
(!this.requireTransferable || it.isTransferable)
|
||||
);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
}
|
||||
return ["heldItem", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItemTypes: PokemonType[];
|
||||
export class HoldingItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItems: (HeldItemId | HeldItemCategoryId)[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(
|
||||
heldItemTypes: PokemonType | PokemonType[],
|
||||
heldItem: HeldItemId | HeldItemCategoryId | (HeldItemId | HeldItemCategoryId)[],
|
||||
minNumberOfPokemon = 1,
|
||||
invertQuery = false,
|
||||
requireTransferable = true,
|
||||
@ -906,7 +813,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemTypes = coerceArray(heldItemTypes);
|
||||
this.requiredHeldItems = coerceArray(heldItem);
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
@ -921,45 +828,92 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter(pokemon =>
|
||||
this.requiredHeldItemTypes.some(heldItemType => {
|
||||
return pokemon.getHeldItems().some(it => {
|
||||
return (
|
||||
it instanceof AttackTypeBoosterModifier &&
|
||||
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
|
||||
(!this.requireTransferable || it.isTransferable)
|
||||
);
|
||||
});
|
||||
this.requiredHeldItems.some(heldItem => {
|
||||
return this.requireTransferable
|
||||
? pokemon.heldItemManager.hasTransferableItem(heldItem)
|
||||
: pokemon.heldItemManager.hasItem(heldItem);
|
||||
}),
|
||||
);
|
||||
}
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter(
|
||||
pokemon =>
|
||||
pokemon.getHeldItems().filter(it => {
|
||||
return !this.requiredHeldItemTypes.some(
|
||||
heldItemType =>
|
||||
it instanceof AttackTypeBoosterModifier &&
|
||||
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
|
||||
(!this.requireTransferable || it.isTransferable),
|
||||
);
|
||||
}).length > 0,
|
||||
return partyPokemon.filter(pokemon =>
|
||||
pokemon.getHeldItems().some(item => {
|
||||
return (
|
||||
!this.requiredHeldItems.some(heldItem => item === heldItem || getHeldItemCategory(item) === heldItem) &&
|
||||
(!this.requireTransferable || allHeldItems[item].isTransferable)
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter(it => {
|
||||
const requiredItems = pokemon?.getHeldItems().filter(item => {
|
||||
return (
|
||||
this.requiredHeldItemTypes.some(
|
||||
heldItemType =>
|
||||
it instanceof AttackTypeBoosterModifier &&
|
||||
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType,
|
||||
) &&
|
||||
(!this.requireTransferable || it.isTransferable)
|
||||
this.requiredHeldItems.some(heldItem => item === heldItem) &&
|
||||
(!this.requireTransferable || allHeldItems[item].isTransferable)
|
||||
);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
return ["heldItem", allHeldItems[requiredItems[0]].name];
|
||||
}
|
||||
return ["heldItem", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class HeldItemRequirement extends EncounterSceneRequirement {
|
||||
requiredHeldItems: (HeldItemId | HeldItemCategoryId)[];
|
||||
minNumberOfItems: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(
|
||||
heldItem: HeldItemId | HeldItemCategoryId | (HeldItemId | HeldItemCategoryId)[],
|
||||
minNumberOfItems = 1,
|
||||
invertQuery = false,
|
||||
requireTransferable = true,
|
||||
) {
|
||||
super();
|
||||
this.minNumberOfItems = minNumberOfItems;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItems = coerceArray(heldItem);
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
const partyPokemon = globalScene.getPlayerParty();
|
||||
if (isNullOrUndefined(partyPokemon)) {
|
||||
return false;
|
||||
}
|
||||
console.log("COUNTED:", this.queryPartyForItems(partyPokemon), this.minNumberOfItems);
|
||||
return this.queryPartyForItems(partyPokemon) >= this.minNumberOfItems;
|
||||
}
|
||||
|
||||
queryPartyForItems(partyPokemon: PlayerPokemon[]): number {
|
||||
let count = 0;
|
||||
for (const pokemon of partyPokemon) {
|
||||
for (const item of pokemon.getHeldItems()) {
|
||||
const itemInList = this.requiredHeldItems.some(
|
||||
heldItem => item === heldItem || getHeldItemCategory(item) === heldItem,
|
||||
);
|
||||
const requiredItem = this.invertQuery ? !itemInList : itemInList;
|
||||
if (requiredItem && (!this.requireTransferable || allHeldItems[item].isTransferable)) {
|
||||
count += pokemon.heldItemManager.getStack(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter(item => {
|
||||
return (
|
||||
this.requiredHeldItems.some(heldItem => item === heldItem) &&
|
||||
(!this.requireTransferable || allHeldItems[item].isTransferable)
|
||||
);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", allHeldItems[requiredItems[0]].name];
|
||||
}
|
||||
return ["heldItem", ""];
|
||||
}
|
||||
|
||||
@ -174,11 +174,11 @@ export class MysteryEncounter implements IMysteryEncounter {
|
||||
onVisualsStart?: () => boolean;
|
||||
/** Event triggered prior to {@linkcode CommandPhase}, during {@linkcode TurnInitPhase} */
|
||||
onTurnStart?: () => boolean;
|
||||
/** Event prior to any rewards logic in {@linkcode MysteryEncounterRewardsPhase} */
|
||||
/** Event prior to any reward logic in {@linkcode MysteryEncounterRewardsPhase} */
|
||||
onRewards?: () => Promise<void>;
|
||||
/** Will provide the player party EXP before rewards are displayed for that wave */
|
||||
doEncounterExp?: () => boolean;
|
||||
/** Will provide the player a rewards shop for that wave */
|
||||
/** Will provide the player a reward shop for that wave */
|
||||
doEncounterRewards?: () => boolean;
|
||||
/** Will execute callback during VictoryPhase of a continuousEncounter */
|
||||
doContinueEncounter?: () => Promise<void>;
|
||||
@ -241,7 +241,7 @@ export class MysteryEncounter implements IMysteryEncounter {
|
||||
* Defaults to true so that the first shop does not override the specified rewards.
|
||||
* Will be set to false after a shop is shown (so can't reroll same rarity items for free)
|
||||
*/
|
||||
lockEncounterRewardTiers: boolean;
|
||||
lockEncounterRarityTiers: boolean;
|
||||
/**
|
||||
* Will be set automatically, indicates special moves in startOfBattleEffects are complete (so will not repeat)
|
||||
*/
|
||||
@ -296,7 +296,7 @@ export class MysteryEncounter implements IMysteryEncounter {
|
||||
|
||||
// Reset any dirty flags or encounter data
|
||||
this.startOfBattleEffectsComplete = false;
|
||||
this.lockEncounterRewardTiers = true;
|
||||
this.lockEncounterRarityTiers = true;
|
||||
this.dialogueTokens = {};
|
||||
this.enemyPartyConfigs = [];
|
||||
this.startOfBattleEffects = [];
|
||||
@ -562,7 +562,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
continuousEncounter = false;
|
||||
catchAllowed = false;
|
||||
fleeAllowed = true;
|
||||
lockEncounterRewardTiers = false;
|
||||
lockEncounterRarityTiers = false;
|
||||
startOfBattleEffectsComplete = false;
|
||||
hasBattleAnimationsWithoutTargets = false;
|
||||
skipEnemyBattleTurns = false;
|
||||
@ -928,34 +928,6 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Can set custom encounter rewards via this callback function
|
||||
* If rewards are always deterministic for an encounter, this is a good way to set them
|
||||
*
|
||||
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
|
||||
* It may be better to programmatically set doEncounterRewards elsewhere.
|
||||
* There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set rewards
|
||||
* @param doEncounterRewards Synchronous callback function to perform during rewards phase of the encounter
|
||||
* @returns
|
||||
*/
|
||||
withRewards(doEncounterRewards: () => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterRewards">> {
|
||||
return Object.assign(this, { doEncounterRewards: doEncounterRewards });
|
||||
}
|
||||
|
||||
/**
|
||||
* Can set custom encounter exp via this callback function
|
||||
* If exp always deterministic for an encounter, this is a good way to set them
|
||||
*
|
||||
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
|
||||
* It may be better to programmatically set doEncounterExp elsewhere.
|
||||
* There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set rewards
|
||||
* @param doEncounterExp Synchronous callback function to perform during rewards phase of the encounter
|
||||
* @returns
|
||||
*/
|
||||
withExp(doEncounterExp: () => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterExp">> {
|
||||
return Object.assign(this, { doEncounterExp: doEncounterExp });
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to perform init logic before intro visuals are shown and before the MysteryEncounterPhase begins
|
||||
* Useful for performing things like procedural generation of intro sprites, etc.
|
||||
|
||||
@ -5,7 +5,6 @@ import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { BiomePoolTier, biomeLinks } from "#balance/biomes";
|
||||
import { initMoveAnim, loadMoveAnimAssets } from "#data/battle-anims";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import type { IEggOptions } from "#data/egg";
|
||||
import { Egg } from "#data/egg";
|
||||
import type { Gender } from "#data/gender";
|
||||
@ -14,11 +13,9 @@ import type { CustomPokemonData } from "#data/pokemon-data";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { Status } from "#data/status-effect";
|
||||
import type { AiType } from "#enums/ai-type";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import type { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import type { Nature } from "#enums/nature";
|
||||
@ -31,13 +28,8 @@ import { UiMode } from "#enums/ui-mode";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { EnemyPokemon } from "#field/pokemon";
|
||||
import { Trainer } from "#field/trainer";
|
||||
import type { CustomModifierSettings, ModifierType } from "#modifiers/modifier-type";
|
||||
import {
|
||||
getPartyLuckValue,
|
||||
ModifierTypeGenerator,
|
||||
ModifierTypeOption,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#modifiers/modifier-type";
|
||||
import type { HeldItemConfiguration } from "#items/held-item-data-types";
|
||||
import type { CustomRewardSettings } from "#items/reward-pool-utils";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
|
||||
import type { MysteryEncounterOption } from "#mystery-encounters/mystery-encounter-option";
|
||||
@ -45,11 +37,11 @@ import type { Variant } from "#sprites/variant";
|
||||
import type { PokemonData } from "#system/pokemon-data";
|
||||
import type { TrainerConfig } from "#trainers/trainer-config";
|
||||
import { trainerConfigs } from "#trainers/trainer-config";
|
||||
import type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
|
||||
import type { PartyOption, PokemonSelectFilter } from "#ui/party-ui-handler";
|
||||
import { PartyUiMode } from "#ui/party-ui-handler";
|
||||
import { coerceArray, isNullOrUndefined, randomString, randSeedInt, randSeedItem } from "#utils/common";
|
||||
import { getPartyLuckValue } from "#utils/party";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
@ -101,7 +93,7 @@ export interface EnemyPokemonConfig {
|
||||
/** Can set just the status, or pass a timer on the status turns */
|
||||
status?: StatusEffect | [StatusEffect, number];
|
||||
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
|
||||
modifierConfigs?: HeldModifierConfig[];
|
||||
heldItemConfig?: HeldItemConfiguration;
|
||||
tags?: BattlerTagType[];
|
||||
dataSource?: PokemonData;
|
||||
tera?: PokemonType;
|
||||
@ -199,6 +191,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
|
||||
battle.enemyLevels.forEach((level, e) => {
|
||||
let enemySpecies: PokemonSpecies | undefined;
|
||||
let heldItemConfig: HeldItemConfiguration = [];
|
||||
let dataSource: PokemonData | undefined;
|
||||
let isBoss = false;
|
||||
if (!loaded) {
|
||||
@ -210,12 +203,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
dataSource = config.dataSource;
|
||||
enemySpecies = config.species;
|
||||
isBoss = config.isBoss;
|
||||
heldItemConfig = config.heldItemConfig ?? [];
|
||||
battle.enemyParty[e] = globalScene.addEnemyPokemon(
|
||||
enemySpecies,
|
||||
level,
|
||||
TrainerSlot.TRAINER,
|
||||
isBoss,
|
||||
false,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
} else {
|
||||
@ -225,6 +220,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) {
|
||||
const config = partyConfig.pokemonConfigs[e];
|
||||
level = config.level ? config.level : level;
|
||||
heldItemConfig = config.heldItemConfig ?? [];
|
||||
dataSource = config.dataSource;
|
||||
enemySpecies = config.species;
|
||||
isBoss = config.isBoss;
|
||||
@ -241,6 +237,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
TrainerSlot.NONE,
|
||||
isBoss,
|
||||
false,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
}
|
||||
@ -427,16 +424,6 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
enemyPokemon_2.x += 300;
|
||||
}
|
||||
});
|
||||
if (!loaded) {
|
||||
regenerateModifierPoolThresholds(
|
||||
globalScene.getEnemyField(),
|
||||
battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD,
|
||||
);
|
||||
const customModifierTypes = partyConfig?.pokemonConfigs
|
||||
?.filter(config => config?.modifierConfigs)
|
||||
.map(config => config.modifierConfigs!);
|
||||
globalScene.generateEnemyModifiers(customModifierTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -485,45 +472,6 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts modifier bullshit to an actual item
|
||||
* @param modifier
|
||||
* @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
|
||||
*/
|
||||
export function generateModifierType(modifier: () => ModifierType, pregenArgs?: any[]): ModifierType | null {
|
||||
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier);
|
||||
if (!modifierId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result: ModifierType = modifierTypes[modifierId]();
|
||||
|
||||
// Populates item id and tier (order matters)
|
||||
result = result
|
||||
.withIdFromFunc(modifierTypes[modifierId])
|
||||
.withTierFromPool(ModifierPoolType.PLAYER, globalScene.getPlayerParty());
|
||||
|
||||
return result instanceof ModifierTypeGenerator
|
||||
? result.generateType(globalScene.getPlayerParty(), pregenArgs)
|
||||
: result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts modifier bullshit to an actual item
|
||||
* @param modifier
|
||||
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
|
||||
*/
|
||||
export function generateModifierTypeOption(
|
||||
modifier: () => ModifierType,
|
||||
pregenArgs?: any[],
|
||||
): ModifierTypeOption | null {
|
||||
const result = generateModifierType(modifier, pregenArgs);
|
||||
if (result) {
|
||||
return new ModifierTypeOption(result, 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is intended for use inside onPreOptionPhase() of an encounter option
|
||||
* @param onPokemonSelected - Any logic that needs to be performed when Pokemon is chosen
|
||||
@ -738,12 +686,12 @@ export function selectOptionThenPokemon(
|
||||
/**
|
||||
* Will initialize reward phases to follow the mystery encounter
|
||||
* Can have shop displayed or skipped
|
||||
* @param customShopRewards - adds a shop phase with the specified rewards / reward tiers
|
||||
* @param customShopRewards - adds a shop phase with the specified reward tiers
|
||||
* @param eggRewards
|
||||
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before {@linkcode MysteryEncounterRewardsPhase})
|
||||
*/
|
||||
export function setEncounterRewards(
|
||||
customShopRewards?: CustomModifierSettings,
|
||||
customShopRewards?: CustomRewardSettings,
|
||||
eggRewards?: IEggOptions[],
|
||||
preRewardsCallback?: Function,
|
||||
) {
|
||||
@ -753,7 +701,7 @@ export function setEncounterRewards(
|
||||
}
|
||||
|
||||
if (customShopRewards) {
|
||||
globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, customShopRewards);
|
||||
globalScene.phaseManager.unshiftNew("SelectRewardPhase", 0, undefined, customShopRewards);
|
||||
} else {
|
||||
globalScene.phaseManager.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase"));
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { Gender } from "#data/gender";
|
||||
import {
|
||||
doPokeballBounceAnim,
|
||||
@ -14,6 +13,7 @@ import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { getStatusEffectCatchRateMultiplier } from "#data/status-effect";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import { ChallengeType } from "#enums/challenge-type";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
@ -23,8 +23,6 @@ import { StatusEffect } from "#enums/status-effect";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#field/anims";
|
||||
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type";
|
||||
import {
|
||||
getEncounterText,
|
||||
queueEncounterMessage,
|
||||
@ -374,60 +372,13 @@ export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
|
||||
applyHpChangeToPokemon(pokemon, heal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will modify all of a Pokemon's base stats by a flat value
|
||||
* Base stats can never go below 1
|
||||
* @param pokemon
|
||||
* @param value
|
||||
*/
|
||||
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, good: boolean) {
|
||||
const modType = modifierTypes
|
||||
.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
|
||||
.generateType(globalScene.getPlayerParty(), [good ? 10 : -15])
|
||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
|
||||
const modifier = modType?.newModifier(pokemon);
|
||||
if (modifier) {
|
||||
globalScene.addModifier(modifier, false, false, false, true);
|
||||
pokemon.calculateStats();
|
||||
export function applyHeldItemWithFallback(pokemon: Pokemon, item: HeldItemId, fallbackItem?: HeldItemId) {
|
||||
const added = pokemon.heldItemManager.add(item);
|
||||
if (!added && fallbackItem) {
|
||||
pokemon.heldItemManager.add(fallbackItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to add a new modifier to a Pokemon.
|
||||
* If the Pokemon already has max stacks of that item, it will instead apply 'fallbackModifierType', if specified.
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
* @param modType
|
||||
* @param fallbackModifierType
|
||||
*/
|
||||
export async function applyModifierTypeToPlayerPokemon(
|
||||
pokemon: PlayerPokemon,
|
||||
modType: PokemonHeldItemModifierType,
|
||||
fallbackModifierType?: PokemonHeldItemModifierType,
|
||||
) {
|
||||
// Check if the Pokemon has max stacks of that item already
|
||||
const modifier = modType.newModifier(pokemon);
|
||||
const existing = globalScene.findModifier(
|
||||
(m): m is PokemonHeldItemModifier =>
|
||||
m instanceof PokemonHeldItemModifier &&
|
||||
m.type.id === modType.id &&
|
||||
m.pokemonId === pokemon.id &&
|
||||
m.matchType(modifier),
|
||||
) as PokemonHeldItemModifier | undefined;
|
||||
|
||||
// At max stacks
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
if (!fallbackModifierType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply fallback
|
||||
return applyModifierTypeToPlayerPokemon(pokemon, fallbackModifierType);
|
||||
}
|
||||
|
||||
globalScene.addModifier(modifier, false, false, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative to using AttemptCapturePhase
|
||||
* Assumes player sprite is visible on the screen (this is intended for non-combat uses)
|
||||
@ -693,19 +644,16 @@ export async function catchPokemon(
|
||||
};
|
||||
const addToParty = (slotIndex?: number) => {
|
||||
const newPokemon = pokemon.addToParty(pokeballType, slotIndex);
|
||||
const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier, false);
|
||||
if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === 6) {
|
||||
globalScene.validateAchv(achvs.SHINY_PARTY);
|
||||
}
|
||||
Promise.all(modifiers.map(m => globalScene.addModifier(m, true))).then(() => {
|
||||
globalScene.updateModifiers(true);
|
||||
removePokemon();
|
||||
if (newPokemon) {
|
||||
newPokemon.loadAssets().then(end);
|
||||
} else {
|
||||
end();
|
||||
}
|
||||
});
|
||||
globalScene.updateItems(true);
|
||||
removePokemon();
|
||||
if (newPokemon) {
|
||||
newPokemon.loadAssets().then(end);
|
||||
} else {
|
||||
end();
|
||||
}
|
||||
};
|
||||
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||
const addStatus = new BooleanHolder(true);
|
||||
@ -737,6 +685,7 @@ export async function catchPokemon(
|
||||
pokemon.variant,
|
||||
pokemon.ivs,
|
||||
pokemon.nature,
|
||||
pokemon.heldItemManager.generateHeldItemConfiguration(),
|
||||
pokemon,
|
||||
);
|
||||
globalScene.ui.setMode(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
@ -93,7 +94,9 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number {
|
||||
}
|
||||
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
|
||||
const catchingCharmMultiplier = new NumberHolder(1);
|
||||
globalScene.findModifier(m => m.is("CriticalCatchChanceBoosterModifier"))?.apply(catchingCharmMultiplier);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.CRITICAL_CATCH_CHANCE_BOOSTER, {
|
||||
numberHolder: catchingCharmMultiplier,
|
||||
});
|
||||
const dexMultiplier =
|
||||
globalScene.gameMode.isDaily || dexCount > 800
|
||||
? 2.5
|
||||
|
||||
@ -10,8 +10,8 @@ import { StatusEffect } from "#enums/status-effect";
|
||||
import type { TimeOfDay } from "#enums/time-of-day";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { PokemonFormChangeItemModifier } from "#modifiers/modifier";
|
||||
import { type Constructor, coerceArray } from "#utils/common";
|
||||
import { toCamelCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
export abstract class SpeciesFormChangeTrigger {
|
||||
@ -77,16 +77,12 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
|
||||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
return !!globalScene.findModifier(r => {
|
||||
// Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier
|
||||
const m = r as PokemonFormChangeItemModifier;
|
||||
return (
|
||||
"formChangeItem" in m &&
|
||||
m.pokemonId === pokemon.id &&
|
||||
m.formChangeItem === this.item &&
|
||||
m.active === this.active
|
||||
);
|
||||
});
|
||||
const matchItem = pokemon.heldItemManager.formChangeItems[this.item];
|
||||
if (!matchItem) {
|
||||
return false;
|
||||
}
|
||||
console.log("CAN CHANGE FORMS:", matchItem.active === this.active);
|
||||
return matchItem.active === this.active;
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,11 +139,7 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
|
||||
super();
|
||||
this.move = move;
|
||||
this.known = known;
|
||||
const moveKey = MoveId[this.move]
|
||||
.split("_")
|
||||
.filter(f => f)
|
||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||
.join("") as unknown as string;
|
||||
const moveKey = toCamelCase(MoveId[this.move]);
|
||||
this.description = known
|
||||
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
|
||||
move: i18next.t(`move:${moveKey}.name`),
|
||||
|
||||
@ -4,8 +4,8 @@ import { globalScene } from "#app/global-scene";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
|
||||
@ -45,8 +45,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig()
|
||||
@ -77,8 +77,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig()
|
||||
@ -150,8 +150,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig()
|
||||
@ -212,14 +212,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
TrainerType.PENNY,
|
||||
]),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig()
|
||||
@ -231,14 +225,8 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.ULTRA],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig()
|
||||
@ -258,14 +246,14 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
TrainerType.PENNY_2,
|
||||
]),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
@ -362,14 +350,14 @@ export const classicFixedBattles: FixedBattleConfigs = {
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.GREAT,
|
||||
ModifierTier.GREAT,
|
||||
.setCustomRewards({
|
||||
guaranteedRarityTiers: [
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ROGUE,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.ULTRA,
|
||||
RarityTier.GREAT,
|
||||
RarityTier.GREAT,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
|
||||
@ -3,7 +3,6 @@ import { globalScene } from "#app/global-scene";
|
||||
import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions";
|
||||
import { signatureSpecies } from "#balance/signature-species";
|
||||
import { tmSpecies } from "#balance/tms";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { doubleBattleDialogue } from "#data/double-battle-dialogue";
|
||||
import { Gender } from "#data/gender";
|
||||
import type { PokemonSpecies, PokemonSpeciesFilter } from "#data/pokemon-species";
|
||||
@ -14,6 +13,7 @@ import { PokeballType } from "#enums/pokeball";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { TeraAIMode } from "#enums/tera-ai-mode";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import { TrainerPoolTier } from "#enums/trainer-pool-tier";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
@ -31,10 +31,10 @@ import {
|
||||
TrainerPartyTemplate,
|
||||
trainerPartyTemplates,
|
||||
} from "#trainers/trainer-party-template";
|
||||
import type { ModifierTypeFunc } from "#types/modifier-types";
|
||||
import type { SilentReward } from "#types/rewards";
|
||||
import type {
|
||||
GenAIFunc,
|
||||
GenModifiersFunc,
|
||||
GenTrainerItemsFunc,
|
||||
PartyMemberFunc,
|
||||
PartyMemberFuncs,
|
||||
PartyTemplateFunc,
|
||||
@ -107,9 +107,9 @@ export class TrainerConfig {
|
||||
public femaleEncounterBgm: string;
|
||||
public doubleEncounterBgm: string;
|
||||
public victoryBgm: string;
|
||||
public genModifiersFunc: GenModifiersFunc;
|
||||
public genTrainerItemsFunc: GenTrainerItemsFunc;
|
||||
public genAIFuncs: GenAIFunc[] = [];
|
||||
public modifierRewardFuncs: ModifierTypeFunc[] = [];
|
||||
public silentRewards: SilentReward[] = [];
|
||||
public partyTemplates: TrainerPartyTemplate[];
|
||||
public partyTemplateFunc: PartyTemplateFunc;
|
||||
public partyMemberFuncs: PartyMemberFuncs = {};
|
||||
@ -458,8 +458,8 @@ export class TrainerConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
setGenModifiersFunc(genModifiersFunc: GenModifiersFunc): TrainerConfig {
|
||||
this.genModifiersFunc = genModifiersFunc;
|
||||
setGenTrainerItemsFunc(genTrainerItemsFunc: GenTrainerItemsFunc): TrainerConfig {
|
||||
this.genTrainerItemsFunc = genTrainerItemsFunc;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -469,7 +469,7 @@ export class TrainerConfig {
|
||||
* @param slot Optional, a specified slot that should be terastallized. Wraps to match party size (-1 will get the last slot and so on).
|
||||
* @returns this
|
||||
*/
|
||||
setRandomTeraModifiers(count: () => number, slot?: number): TrainerConfig {
|
||||
setRandomTeraType(count: () => number, slot?: number): TrainerConfig {
|
||||
this.genAIFuncs.push((party: EnemyPokemon[]) => {
|
||||
const shedinjaCanTera = !this.hasSpecialtyType() || this.specialtyType === PokemonType.BUG; // Better to check one time than 6
|
||||
const partyMemberIndexes = new Array(party.length)
|
||||
@ -500,24 +500,8 @@ export class TrainerConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
// function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: Type[]): PersistentModifier[] {
|
||||
// const ret: PersistentModifier[] = [];
|
||||
// const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
|
||||
// for (let t = 0; t < Math.min(count, party.length); t++) {
|
||||
// const randomIndex = Utils.randSeedItem(partyMemberIndexes);
|
||||
// partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
|
||||
// ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct?
|
||||
// }
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
|
||||
this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => {
|
||||
const modifierTypeFunc = func();
|
||||
const modifierType = modifierTypeFunc();
|
||||
modifierType.withIdFromFunc(modifierTypeFunc);
|
||||
return modifierType;
|
||||
});
|
||||
setSilentReward(...silentRewards: SilentReward[]): TrainerConfig {
|
||||
this.silentRewards = silentRewards;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -682,7 +666,7 @@ export class TrainerConfig {
|
||||
this.setHasVoucher(true);
|
||||
this.setBattleBgm("battle_unova_gym");
|
||||
this.setVictoryBgm("victory_gym");
|
||||
this.setRandomTeraModifiers(
|
||||
this.setRandomTeraType(
|
||||
() => (ignoreMinTeraWave || globalScene.currentBattle.waveIndex >= GYM_LEADER_TERA_WAVE ? 1 : 0),
|
||||
teraSlot,
|
||||
);
|
||||
@ -743,7 +727,7 @@ export class TrainerConfig {
|
||||
this.setHasVoucher(true);
|
||||
this.setBattleBgm("battle_unova_elite");
|
||||
this.setVictoryBgm("victory_gym");
|
||||
this.setRandomTeraModifiers(() => 1, teraSlot);
|
||||
this.setRandomTeraType(() => 1, teraSlot);
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -920,11 +904,11 @@ export class TrainerConfig {
|
||||
clone = this.battleBgm ? clone.setBattleBgm(this.battleBgm) : clone;
|
||||
clone = this.encounterBgm ? clone.setEncounterBgm(this.encounterBgm) : clone;
|
||||
clone = this.victoryBgm ? clone.setVictoryBgm(this.victoryBgm) : clone;
|
||||
clone = this.genModifiersFunc ? clone.setGenModifiersFunc(this.genModifiersFunc) : clone;
|
||||
clone = this.genTrainerItemsFunc ? clone.setGenTrainerItemsFunc(this.genTrainerItemsFunc) : clone;
|
||||
|
||||
if (this.modifierRewardFuncs) {
|
||||
if (this.silentRewards) {
|
||||
// Clones array instead of passing ref
|
||||
clone.modifierRewardFuncs = this.modifierRewardFuncs.slice(0);
|
||||
clone.silentRewards = this.silentRewards.slice(0);
|
||||
}
|
||||
|
||||
if (this.partyTemplates) {
|
||||
@ -992,6 +976,7 @@ export function getRandomPartyMemberFunc(
|
||||
undefined,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
postProcess,
|
||||
);
|
||||
};
|
||||
@ -1016,7 +1001,16 @@ function getSpeciesFilterRandomPartyMemberFunc(
|
||||
.getTrainerSpeciesForLevel(level, true, strength, waveIndex),
|
||||
);
|
||||
|
||||
return globalScene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess);
|
||||
return globalScene.addEnemyPokemon(
|
||||
species,
|
||||
level,
|
||||
trainerSlot,
|
||||
undefined,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
postProcess,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@ -1865,27 +1859,43 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc([
|
||||
SpeciesId.METAPOD,
|
||||
SpeciesId.LEDYBA,
|
||||
SpeciesId.CLEFFA,
|
||||
SpeciesId.WOOPER,
|
||||
SpeciesId.TEDDIURSA,
|
||||
SpeciesId.REMORAID,
|
||||
SpeciesId.HOUNDOUR,
|
||||
SpeciesId.SILCOON,
|
||||
SpeciesId.PLUSLE,
|
||||
SpeciesId.VOLBEAT,
|
||||
SpeciesId.PACHIRISU,
|
||||
SpeciesId.SILCOON,
|
||||
SpeciesId.METAPOD,
|
||||
SpeciesId.IGGLYBUFF,
|
||||
SpeciesId.SPINDA,
|
||||
SpeciesId.BONSLY,
|
||||
SpeciesId.PETILIL,
|
||||
SpeciesId.EEVEE,
|
||||
SpeciesId.SPRITZEE,
|
||||
SpeciesId.MILCERY,
|
||||
SpeciesId.PICHU,
|
||||
]),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
1,
|
||||
getRandomPartyMemberFunc(
|
||||
[
|
||||
SpeciesId.KAKUNA,
|
||||
SpeciesId.SPINARAK,
|
||||
SpeciesId.IGGLYBUFF,
|
||||
SpeciesId.PALDEA_WOOPER,
|
||||
SpeciesId.PHANPY,
|
||||
SpeciesId.MANTYKE,
|
||||
SpeciesId.ELECTRIKE,
|
||||
SpeciesId.CASCOON,
|
||||
SpeciesId.MINUN,
|
||||
SpeciesId.ILLUMISE,
|
||||
SpeciesId.EMOLGA,
|
||||
SpeciesId.CASCOON,
|
||||
SpeciesId.KAKUNA,
|
||||
SpeciesId.CLEFFA,
|
||||
SpeciesId.SPINDA,
|
||||
SpeciesId.MIME_JR,
|
||||
SpeciesId.COTTONEE,
|
||||
SpeciesId.SWIRLIX,
|
||||
SpeciesId.FIDOUGH,
|
||||
SpeciesId.EEVEE,
|
||||
],
|
||||
TrainerSlot.TRAINER_PARTNER,
|
||||
@ -4512,10 +4522,7 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setBattleBgm("battle_rival")
|
||||
.setMixedBattleBgm("battle_rival")
|
||||
.setPartyTemplates(trainerPartyTemplates.RIVAL)
|
||||
.setModifierRewardFuncs(
|
||||
() => modifierTypes.SUPER_EXP_CHARM,
|
||||
() => modifierTypes.EXP_SHARE,
|
||||
)
|
||||
.setSilentReward(TrainerItemId.SUPER_EXP_CHARM, TrainerItemId.EXP_SHARE)
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc(
|
||||
@ -4582,7 +4589,7 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setBattleBgm("battle_rival")
|
||||
.setMixedBattleBgm("battle_rival")
|
||||
.setPartyTemplates(trainerPartyTemplates.RIVAL_2)
|
||||
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
|
||||
.setSilentReward(TrainerItemId.EXP_SHARE)
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc(
|
||||
@ -4735,7 +4742,7 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setBattleBgm("battle_rival_2")
|
||||
.setMixedBattleBgm("battle_rival_2")
|
||||
.setPartyTemplates(trainerPartyTemplates.RIVAL_4)
|
||||
.setModifierRewardFuncs(() => modifierTypes.TERA_ORB)
|
||||
.setSilentReward(TrainerItemId.TERA_ORB)
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc(
|
||||
|
||||
37
src/enums/held-item-effect.ts
Normal file
37
src/enums/held-item-effect.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
/**
|
||||
* Enum representing the various "classes" of item effects that can be applied.
|
||||
*/
|
||||
export const HeldItemEffect = {
|
||||
ATTACK_TYPE_BOOST: 1,
|
||||
TURN_END_HEAL: 2,
|
||||
HIT_HEAL: 3,
|
||||
RESET_NEGATIVE_STAT_STAGE: 4,
|
||||
EXP_BOOSTER: 5,
|
||||
// Should we actually distinguish different berry effects?
|
||||
BERRY: 6,
|
||||
BASE_STAT_BOOSTER: 7,
|
||||
INSTANT_REVIVE: 8,
|
||||
STAT_BOOST: 9,
|
||||
CRIT_BOOST: 10,
|
||||
TURN_END_STATUS: 11,
|
||||
SURVIVE_CHANCE: 12,
|
||||
BYPASS_SPEED_CHANCE: 13,
|
||||
FLINCH_CHANCE: 14,
|
||||
FIELD_EFFECT: 15,
|
||||
FRIENDSHIP_BOOSTER: 16,
|
||||
NATURE_WEIGHT_BOOSTER: 17,
|
||||
ACCURACY_BOOSTER: 18,
|
||||
MULTI_HIT: 19,
|
||||
DAMAGE_MONEY_REWARD: 20,
|
||||
BATON: 21,
|
||||
TURN_END_ITEM_STEAL: 22,
|
||||
CONTACT_ITEM_STEAL_CHANCE: 23,
|
||||
EVO_TRACKER: 40,
|
||||
BASE_STAT_TOTAL: 50,
|
||||
BASE_STAT_FLAT: 51,
|
||||
INCREMENTING_STAT: 52,
|
||||
} as const;
|
||||
|
||||
export type HeldItemEffect = ObjectValues<typeof HeldItemEffect>;
|
||||
151
src/enums/held-item-id.ts
Normal file
151
src/enums/held-item-id.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
// TODO: make category the lower 2 bytes
|
||||
export const HeldItemId = {
|
||||
NONE: 0x0000,
|
||||
|
||||
// Berries
|
||||
SITRUS_BERRY: 0x0101,
|
||||
LUM_BERRY: 0x0102,
|
||||
ENIGMA_BERRY: 0x0103,
|
||||
LIECHI_BERRY: 0x0104,
|
||||
GANLON_BERRY: 0x0105,
|
||||
PETAYA_BERRY: 0x0106,
|
||||
APICOT_BERRY: 0x0107,
|
||||
SALAC_BERRY: 0x0108,
|
||||
LANSAT_BERRY: 0x0109,
|
||||
STARF_BERRY: 0x010A,
|
||||
LEPPA_BERRY: 0x010B,
|
||||
|
||||
// Other items that are consumed
|
||||
REVIVER_SEED: 0x0201,
|
||||
WHITE_HERB: 0x0202,
|
||||
|
||||
// Type Boosters
|
||||
SILK_SCARF: 0x0301,
|
||||
BLACK_BELT: 0x0302,
|
||||
SHARP_BEAK: 0x0303,
|
||||
POISON_BARB: 0x0304,
|
||||
SOFT_SAND: 0x0305,
|
||||
HARD_STONE: 0x0306,
|
||||
SILVER_POWDER: 0x0307,
|
||||
SPELL_TAG: 0x0308,
|
||||
METAL_COAT: 0x0309,
|
||||
CHARCOAL: 0x030A,
|
||||
MYSTIC_WATER: 0x030B,
|
||||
MIRACLE_SEED: 0x030C,
|
||||
MAGNET: 0x030D,
|
||||
TWISTED_SPOON: 0x030E,
|
||||
NEVER_MELT_ICE: 0x030F,
|
||||
DRAGON_FANG: 0x0310,
|
||||
BLACK_GLASSES: 0x0311,
|
||||
FAIRY_FEATHER: 0x0312,
|
||||
|
||||
// Species Stat Boosters
|
||||
LIGHT_BALL: 0x0401,
|
||||
THICK_CLUB: 0x0402,
|
||||
METAL_POWDER: 0x0403,
|
||||
QUICK_POWDER: 0x0404,
|
||||
DEEP_SEA_SCALE: 0x0405,
|
||||
DEEP_SEA_TOOTH: 0x0406,
|
||||
|
||||
// Crit Boosters
|
||||
SCOPE_LENS: 0x0501,
|
||||
LEEK: 0x0502,
|
||||
|
||||
// Items increasing gains
|
||||
LUCKY_EGG: 0x0601,
|
||||
GOLDEN_EGG: 0x0602,
|
||||
SOOTHE_BELL: 0x0603,
|
||||
|
||||
// Unique items
|
||||
FOCUS_BAND: 0x0701,
|
||||
QUICK_CLAW: 0x0702,
|
||||
KINGS_ROCK: 0x0703,
|
||||
LEFTOVERS: 0x0704,
|
||||
SHELL_BELL: 0x0705,
|
||||
MYSTICAL_ROCK: 0x0706,
|
||||
WIDE_LENS: 0x0707,
|
||||
MULTI_LENS: 0x0708,
|
||||
GOLDEN_PUNCH: 0x0709,
|
||||
GRIP_CLAW: 0x070A,
|
||||
TOXIC_ORB: 0x070B,
|
||||
FLAME_ORB: 0x070C,
|
||||
SOUL_DEW: 0x070D,
|
||||
BATON: 0x070E,
|
||||
MINI_BLACK_HOLE: 0x070F,
|
||||
EVIOLITE: 0x0710,
|
||||
|
||||
// Vitamins
|
||||
HP_UP: 0x0801,
|
||||
PROTEIN: 0x0802,
|
||||
IRON: 0x0803,
|
||||
CALCIUM: 0x0804,
|
||||
ZINC: 0x0805,
|
||||
CARBOS: 0x0806,
|
||||
|
||||
// Other stat boosting items
|
||||
SHUCKLE_JUICE_GOOD: 0x0901,
|
||||
SHUCKLE_JUICE_BAD: 0x0902,
|
||||
OLD_GATEAU: 0x0903,
|
||||
MACHO_BRACE: 0x0904,
|
||||
|
||||
// Evo trackers
|
||||
GIMMIGHOUL_EVO_TRACKER: 0x0A01,
|
||||
} as const;
|
||||
|
||||
export type HeldItemId = ObjectValues<typeof HeldItemId>;
|
||||
|
||||
type HeldItemNameMap = {
|
||||
[k in HeldItemName as (typeof HeldItemId)[k]]: k
|
||||
}
|
||||
|
||||
type HeldItemName = keyof typeof HeldItemId;
|
||||
|
||||
/** `const object` mapping all held item IDs to their respective names. */
|
||||
// TODO: This stores names as UPPER_SNAKE_CASE, but the locales are in PascalCase...
|
||||
export const HeldItemNames = Object.freeze(Object.entries(HeldItemId).reduce(
|
||||
// Use a type-safe reducer to force number keys and values
|
||||
(acc, [key, value]) => {
|
||||
acc[value] = key;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
)) as HeldItemNameMap;
|
||||
|
||||
export const HeldItemCategoryId = {
|
||||
NONE: 0x0000,
|
||||
BERRY: 0x0100,
|
||||
CONSUMABLE: 0x0200,
|
||||
TYPE_ATTACK_BOOSTER: 0x0300,
|
||||
SPECIES_STAT_BOOSTER: 0x0400,
|
||||
CRIT_BOOSTER: 0x0500,
|
||||
GAIN_INCREASE: 0x0600,
|
||||
UNIQUE: 0x0700,
|
||||
VITAMIN: 0x0800,
|
||||
BASE_STAT_BOOST: 0x0900,
|
||||
EVO_TRACKER: 0x0A00,
|
||||
} as const;
|
||||
|
||||
export type HeldItemCategoryId = ObjectValues<typeof HeldItemCategoryId>;
|
||||
|
||||
const ITEM_CATEGORY_MASK = 0xFF00
|
||||
|
||||
export function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId {
|
||||
return (itemId & ITEM_CATEGORY_MASK) as HeldItemCategoryId;
|
||||
}
|
||||
|
||||
export function isCategoryId(id: number): id is HeldItemCategoryId {
|
||||
return (Object.values(HeldItemCategoryId) as number[]).includes(id);
|
||||
}
|
||||
|
||||
export function isItemInCategory(itemId: HeldItemId, category: HeldItemCategoryId): boolean {
|
||||
return getHeldItemCategory(itemId) === category;
|
||||
}
|
||||
|
||||
export function isItemInRequested(
|
||||
itemId: HeldItemId,
|
||||
requestedItems: (HeldItemCategoryId | HeldItemId)[]
|
||||
): boolean {
|
||||
return requestedItems.some(entry => itemId === entry || (itemId & ITEM_CATEGORY_MASK) === entry);
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
export enum ModifierPoolType {
|
||||
PLAYER,
|
||||
WILD,
|
||||
TRAINER,
|
||||
ENEMY_BUFF,
|
||||
DAILY_STARTER
|
||||
}
|
||||
98
src/enums/reward-id.ts
Normal file
98
src/enums/reward-id.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
export const RewardId = {
|
||||
NONE: 0x0000,
|
||||
|
||||
POKEBALL: 0x2001,
|
||||
GREAT_BALL: 0x2002,
|
||||
ULTRA_BALL: 0x2003,
|
||||
ROGUE_BALL: 0x2004,
|
||||
MASTER_BALL: 0x2005,
|
||||
|
||||
VOUCHER: 0x2101,
|
||||
VOUCHER_PLUS: 0x2102,
|
||||
VOUCHER_PREMIUM: 0x2103,
|
||||
|
||||
NUGGET: 0x2201,
|
||||
BIG_NUGGET: 0x2202,
|
||||
RELIC_GOLD: 0x2203,
|
||||
|
||||
RARE_CANDY: 0x2301,
|
||||
RARER_CANDY: 0x2302,
|
||||
|
||||
EVOLUTION_ITEM: 0x2401,
|
||||
RARE_EVOLUTION_ITEM: 0x2402,
|
||||
|
||||
POTION: 0x2501,
|
||||
SUPER_POTION: 0x2502,
|
||||
HYPER_POTION: 0x2503,
|
||||
MAX_POTION: 0x2504,
|
||||
FULL_HEAL: 0x2505,
|
||||
FULL_RESTORE: 0x2506,
|
||||
|
||||
REVIVE: 0x2601,
|
||||
MAX_REVIVE: 0x2602,
|
||||
SACRED_ASH: 0x2603,
|
||||
|
||||
ETHER: 0x2701,
|
||||
MAX_ETHER: 0x2702,
|
||||
|
||||
ELIXIR: 0x2801,
|
||||
MAX_ELIXIR: 0x2802,
|
||||
|
||||
PP_UP: 0x2901,
|
||||
PP_MAX: 0x2902,
|
||||
|
||||
TM_COMMON: 0x2A01,
|
||||
TM_GREAT: 0x2A02,
|
||||
TM_ULTRA: 0x2A03,
|
||||
|
||||
MINT: 0x2B01,
|
||||
TERA_SHARD: 0x2B02,
|
||||
MEMORY_MUSHROOM: 0x2B03,
|
||||
DNA_SPLICERS: 0x2B04,
|
||||
|
||||
SPECIES_STAT_BOOSTER: 0x2C01,
|
||||
RARE_SPECIES_STAT_BOOSTER: 0x2C02,
|
||||
BASE_STAT_BOOSTER: 0x2C03,
|
||||
ATTACK_TYPE_BOOSTER: 0x2C04,
|
||||
BERRY: 0x2C05,
|
||||
|
||||
TEMP_STAT_STAGE_BOOSTER: 0x2D01,
|
||||
DIRE_HIT: 0x2D02,
|
||||
LURE: 0x2D03,
|
||||
SUPER_LURE: 0x2D04,
|
||||
MAX_LURE: 0x2D05,
|
||||
|
||||
FORM_CHANGE_ITEM: 0x2E01,
|
||||
RARE_FORM_CHANGE_ITEM: 0x2E02,
|
||||
} as const;
|
||||
|
||||
export type RewardId = ObjectValues<typeof RewardId>;
|
||||
|
||||
export const RewardCategoryId = {
|
||||
NONE: 0x0000,
|
||||
POKEBALL: 0x0100,
|
||||
VOUCHER: 0x0200,
|
||||
MONEY: 0x0300,
|
||||
CANDY: 0x0400,
|
||||
EVOLUTION_ITEM: 0x0500,
|
||||
HEALING: 0x0600,
|
||||
REVIVE: 0x0700,
|
||||
ETHER: 0x0800,
|
||||
ELIXIR: 0x0900,
|
||||
PP_UP: 0x0A00,
|
||||
TM: 0x0B00,
|
||||
OTHER: 0x0C00,
|
||||
HELD_ITEM: 0x0D00,
|
||||
TRAINER_ITEM: 0x0E00,
|
||||
FORM_CHANGE_ITEM: 0x0F00,
|
||||
} as const;
|
||||
|
||||
export type RewardCategoryId = ObjectValues<typeof RewardCategoryId>;
|
||||
|
||||
const ITEM_CATEGORY_MASK = 0xFF00
|
||||
|
||||
export function getRewardCategory(itemId: RewardId): RewardCategoryId {
|
||||
return (itemId & ITEM_CATEGORY_MASK) as RewardCategoryId;
|
||||
}
|
||||
13
src/enums/reward-pool-type.ts
Normal file
13
src/enums/reward-pool-type.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export enum RewardPoolType {
|
||||
PLAYER,
|
||||
}
|
||||
|
||||
export enum HeldItemPoolType {
|
||||
WILD,
|
||||
TRAINER,
|
||||
DAILY_STARTER,
|
||||
}
|
||||
|
||||
export enum TrainerItemPoolType {
|
||||
ENEMY_BUFF,
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
export enum ModifierTier {
|
||||
export enum RarityTier {
|
||||
COMMON,
|
||||
GREAT,
|
||||
ULTRA,
|
||||
68
src/enums/trainer-item-id.ts
Normal file
68
src/enums/trainer-item-id.ts
Normal file
@ -0,0 +1,68 @@
|
||||
export const TrainerItemId = {
|
||||
NONE: 0x0000,
|
||||
|
||||
MAP: 0x1001,
|
||||
IV_SCANNER: 0x1002,
|
||||
LOCK_CAPSULE: 0x1003,
|
||||
MEGA_BRACELET: 0x1004,
|
||||
DYNAMAX_BAND: 0x1005,
|
||||
TERA_ORB: 0x1006,
|
||||
|
||||
GOLDEN_POKEBALL: 0x1007,
|
||||
|
||||
OVAL_CHARM: 0x1008,
|
||||
EXP_SHARE: 0x1009,
|
||||
EXP_BALANCE: 0x100A,
|
||||
|
||||
CANDY_JAR: 0x100B,
|
||||
BERRY_POUCH: 0x100C,
|
||||
|
||||
HEALING_CHARM: 0x100D,
|
||||
EXP_CHARM: 0x100E,
|
||||
SUPER_EXP_CHARM: 0x100F,
|
||||
GOLDEN_EXP_CHARM: 0x1010,
|
||||
AMULET_COIN: 0x1011,
|
||||
|
||||
ABILITY_CHARM: 0x1012,
|
||||
SHINY_CHARM: 0x1013,
|
||||
CATCHING_CHARM: 0x1014,
|
||||
|
||||
BLACK_SLUDGE: 0x1015,
|
||||
GOLDEN_BUG_NET: 0x1016,
|
||||
|
||||
LURE: 0x1101,
|
||||
SUPER_LURE: 0x1102,
|
||||
MAX_LURE: 0x1103,
|
||||
|
||||
X_ATTACK: 0x1201,
|
||||
X_DEFENSE: 0x1202,
|
||||
X_SP_ATK: 0x1203,
|
||||
X_SP_DEF: 0x1204,
|
||||
X_SPEED: 0x1205,
|
||||
X_ACCURACY: 0x1206,
|
||||
DIRE_HIT: 0x1207,
|
||||
|
||||
ENEMY_DAMAGE_BOOSTER: 0x1301,
|
||||
ENEMY_DAMAGE_REDUCTION: 0x1302,
|
||||
ENEMY_HEAL: 0x1303,
|
||||
ENEMY_ATTACK_POISON_CHANCE: 0x1304,
|
||||
ENEMY_ATTACK_PARALYZE_CHANCE: 0x1305,
|
||||
ENEMY_ATTACK_BURN_CHANCE: 0x1306,
|
||||
ENEMY_STATUS_EFFECT_HEAL_CHANCE: 0x1307,
|
||||
ENEMY_ENDURE_CHANCE: 0x1308,
|
||||
ENEMY_FUSED_CHANCE: 0x1309,
|
||||
} as const;
|
||||
|
||||
export type TrainerItemId = (typeof TrainerItemId)[keyof typeof TrainerItemId];
|
||||
|
||||
type TrainerItemName = keyof typeof TrainerItemId;
|
||||
type TrainerItemValue = typeof TrainerItemId[TrainerItemName];
|
||||
|
||||
// Use a type-safe reducer to force number keys and values
|
||||
export const TrainerItemNames: Record<TrainerItemValue, TrainerItemName> = Object.entries(TrainerItemId).reduce(
|
||||
(acc, [key, value]) => {
|
||||
acc[value as TrainerItemValue] = key as TrainerItemName;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<TrainerItemValue, TrainerItemName>
|
||||
);
|
||||
@ -5,7 +5,7 @@ export enum UiMode {
|
||||
FIGHT,
|
||||
BALL,
|
||||
TARGET_SELECT,
|
||||
MODIFIER_SELECT,
|
||||
REWARD_SELECT,
|
||||
SAVE_SLOT,
|
||||
PARTY,
|
||||
SUMMARY,
|
||||
@ -38,6 +38,7 @@ export enum UiMode {
|
||||
UNAVAILABLE,
|
||||
CHALLENGE_SELECT,
|
||||
RENAME_POKEMON,
|
||||
RENAME_RUN,
|
||||
RUN_HISTORY,
|
||||
RUN_INFO,
|
||||
TEST_DIALOGUE,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { BerryModifier } from "#modifiers/modifier";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { Move } from "#moves/move";
|
||||
|
||||
/** Alias for all {@linkcode BattleScene} events */
|
||||
@ -81,12 +82,13 @@ export class MoveUsedEvent extends Event {
|
||||
* @extends Event
|
||||
*/
|
||||
export class BerryUsedEvent extends Event {
|
||||
/** The {@linkcode BerryModifier} being used */
|
||||
public berryModifier: BerryModifier;
|
||||
constructor(berry: BerryModifier) {
|
||||
/** The {@linkcode BerryType} being used */
|
||||
public pokemon: Pokemon;
|
||||
public berryType: BerryType;
|
||||
constructor(pokemon: Pokemon, berryType: BerryType) {
|
||||
super(BattleSceneEventType.BERRY_USED);
|
||||
|
||||
this.berryModifier = berry;
|
||||
this.pokemon = pokemon;
|
||||
this.berryType = berryType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
import type { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { CommonAnim } from "#enums/move-anims-common";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
@ -33,7 +34,7 @@ import { TrainerType } from "#enums/trainer-type";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { FieldEffectModifier } from "#modifiers/modifier";
|
||||
import { applyHeldItems } from "#items/all-held-items";
|
||||
import type { Move } from "#moves/move";
|
||||
import type { AbstractConstructor } from "#types/type-helpers";
|
||||
import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common";
|
||||
@ -54,7 +55,7 @@ export class Arena {
|
||||
public bgm: string;
|
||||
public ignoreAbilities: boolean;
|
||||
public ignoringEffectSource: BattlerIndex | null;
|
||||
public playerTerasUsed: number;
|
||||
public playerTerasUsed = 0;
|
||||
/**
|
||||
* Saves the number of times a party pokemon faints during a arena encounter.
|
||||
* {@linkcode globalScene.currentBattle.enemyFaints} is the corresponding faint counter for the enemy (this resets every wave).
|
||||
@ -68,12 +69,11 @@ export class Arena {
|
||||
|
||||
public readonly eventTarget: EventTarget = new EventTarget();
|
||||
|
||||
constructor(biome: BiomeId, bgm: string, playerFaints = 0) {
|
||||
constructor(biome: BiomeId, playerFaints = 0) {
|
||||
this.biomeType = biome;
|
||||
this.bgm = bgm;
|
||||
this.bgm = BiomeId[biome].toLowerCase();
|
||||
this.trainerPool = biomeTrainerPools[biome];
|
||||
this.updatePoolsForTimeOfDay();
|
||||
this.playerTerasUsed = 0;
|
||||
this.playerFaints = playerFaints;
|
||||
}
|
||||
|
||||
@ -341,7 +341,7 @@ export class Arena {
|
||||
|
||||
if (!isNullOrUndefined(user)) {
|
||||
weatherDuration.value = 5;
|
||||
globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, weatherDuration);
|
||||
applyHeldItems(HeldItemEffect.FIELD_EFFECT, { pokemon: user, fieldDuration: weatherDuration });
|
||||
}
|
||||
|
||||
this.weather = weather ? new Weather(weather, weatherDuration.value) : null;
|
||||
@ -428,7 +428,7 @@ export class Arena {
|
||||
|
||||
if (!isNullOrUndefined(user)) {
|
||||
terrainDuration.value = 5;
|
||||
globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, terrainDuration);
|
||||
applyHeldItems(HeldItemEffect.FIELD_EFFECT, { pokemon: user, fieldDuration: terrainDuration });
|
||||
}
|
||||
|
||||
this.terrain = terrain ? new Terrain(terrain, terrainDuration.value) : null;
|
||||
@ -895,7 +895,7 @@ export class Arena {
|
||||
case BiomeId.CAVE:
|
||||
return 14.24;
|
||||
case BiomeId.DESERT:
|
||||
return 1.143;
|
||||
return 9.02;
|
||||
case BiomeId.ICE_CAVE:
|
||||
return 0.0;
|
||||
case BiomeId.MEADOW:
|
||||
@ -923,7 +923,7 @@ export class Arena {
|
||||
case BiomeId.JUNGLE:
|
||||
return 0.0;
|
||||
case BiomeId.FAIRY_CAVE:
|
||||
return 4.542;
|
||||
return 0.0;
|
||||
case BiomeId.TEMPLE:
|
||||
return 2.547;
|
||||
case BiomeId.ISLAND:
|
||||
|
||||
@ -336,7 +336,7 @@ export class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container {
|
||||
tryPlaySprite(
|
||||
sprite: Phaser.GameObjects.Sprite,
|
||||
tintSprite: Phaser.GameObjects.Sprite,
|
||||
animConfig: Phaser.Types.Animations.PlayAnimationConfig,
|
||||
animConfig: PlayAnimationConfig,
|
||||
): boolean {
|
||||
// Show an error in the console if there isn't a texture loaded
|
||||
if (sprite.texture.key === "__MISSING") {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Ability, PreAttackModifyDamageAbAttrParams } from "#abilities/ability";
|
||||
import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#abilities/apply-ab-attrs";
|
||||
import type { AnySound, BattleScene } from "#app/battle-scene";
|
||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||
import { PLAYER_PARTY_MAX_SIZE, RARE_CANDY_FRIENDSHIP_CAP } from "#app/constants";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
@ -39,6 +39,7 @@ import {
|
||||
TrappedTag,
|
||||
TypeImmuneTag,
|
||||
} from "#data/battler-tags";
|
||||
import { getDailyEventSeedBoss } from "#data/daily-run";
|
||||
import { allAbilities, allMoves } from "#data/data-lists";
|
||||
import { getLevelTotalExp } from "#data/exp";
|
||||
import {
|
||||
@ -78,9 +79,10 @@ import { ChallengeType } from "#enums/challenge-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { DexAttr } from "#enums/dex-attr";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { LearnMoveSituation } from "#enums/learn-move-situation";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
@ -90,6 +92,7 @@ import { Nature } from "#enums/nature";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import {
|
||||
@ -107,27 +110,11 @@ import type { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { doShinySparkleAnim } from "#field/anims";
|
||||
import {
|
||||
BaseStatModifier,
|
||||
CritBoosterModifier,
|
||||
EnemyDamageBoosterModifier,
|
||||
EnemyDamageReducerModifier,
|
||||
EnemyFusionChanceModifier,
|
||||
EvoTrackerModifier,
|
||||
HiddenAbilityRateBoosterModifier,
|
||||
PokemonBaseStatFlatModifier,
|
||||
PokemonBaseStatTotalModifier,
|
||||
PokemonFriendshipBoosterModifier,
|
||||
PokemonHeldItemModifier,
|
||||
PokemonIncrementingStatModifier,
|
||||
PokemonMultiHitModifier,
|
||||
PokemonNatureWeightModifier,
|
||||
ShinyRateBoosterModifier,
|
||||
StatBoosterModifier,
|
||||
SurviveDamageModifier,
|
||||
TempCritBoosterModifier,
|
||||
TempStatStageBoosterModifier,
|
||||
} from "#modifiers/modifier";
|
||||
import { applyHeldItems } from "#items/all-held-items";
|
||||
import type { HeldItemConfiguration } from "#items/held-item-data-types";
|
||||
import { HeldItemManager } from "#items/held-item-manager";
|
||||
import { assignItemsFromConfiguration } from "#items/held-item-pool";
|
||||
import { TrainerItemEffect } from "#items/trainer-item";
|
||||
import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
import type { Move } from "#moves/move";
|
||||
import { getMoveTargets } from "#moves/move-utils";
|
||||
@ -138,6 +125,8 @@ import { populateVariantColors, variantColorCache, variantData } from "#sprites/
|
||||
import { achvs } from "#system/achv";
|
||||
import type { StarterDataEntry, StarterMoveset } from "#system/game-data";
|
||||
import type { PokemonData } from "#system/pokemon-data";
|
||||
import { RibbonData } from "#system/ribbons/ribbon-data";
|
||||
import { awardRibbonsToSpeciesLine } from "#system/ribbons/ribbon-methods";
|
||||
import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#types/ability-types";
|
||||
import type { DamageCalculationResult, DamageResult } from "#types/damage-result";
|
||||
import type { IllusionData } from "#types/illusion-data";
|
||||
@ -289,6 +278,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
private shinySparkle: Phaser.GameObjects.Sprite;
|
||||
|
||||
public readonly heldItemManager: HeldItemManager = new HeldItemManager();
|
||||
|
||||
// TODO: Rework this eventually
|
||||
constructor(
|
||||
x: number,
|
||||
@ -302,6 +293,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
variant?: Variant,
|
||||
ivs?: number[],
|
||||
nature?: Nature,
|
||||
heldItemConfig?: HeldItemConfiguration,
|
||||
dataSource?: Pokemon | PokemonData,
|
||||
) {
|
||||
super(globalScene, x, y);
|
||||
@ -331,6 +323,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate);
|
||||
this.levelExp = dataSource?.levelExp || 0;
|
||||
|
||||
this.heldItemManager = new HeldItemManager();
|
||||
if (heldItemConfig) {
|
||||
assignItemsFromConfiguration(heldItemConfig, this);
|
||||
}
|
||||
|
||||
if (dataSource) {
|
||||
this.id = dataSource.id;
|
||||
this.hp = dataSource.hp;
|
||||
@ -408,7 +405,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (level > 1) {
|
||||
const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly);
|
||||
if (!fused.value && this.isEnemy() && !this.hasTrainer()) {
|
||||
globalScene.applyModifier(EnemyFusionChanceModifier, false, fused);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_FUSED_CHANCE, { booleanHolder: fused });
|
||||
}
|
||||
|
||||
if (fused.value) {
|
||||
@ -596,7 +593,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// Roll for hidden ability chance, applying any ability charms for enemy mons
|
||||
const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE);
|
||||
if (!this.hasTrainer()) {
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
}
|
||||
|
||||
// If the roll succeeded and we have one, use HA; otherwise pick a random ability
|
||||
@ -1152,14 +1151,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.setScale(this.getSpriteScale());
|
||||
}
|
||||
|
||||
getHeldItems(): PokemonHeldItemModifier[] {
|
||||
if (!globalScene) {
|
||||
return [];
|
||||
}
|
||||
return globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id,
|
||||
this.isPlayer(),
|
||||
) as PokemonHeldItemModifier[];
|
||||
getHeldItems(): HeldItemId[] {
|
||||
return this.heldItemManager.getHeldItems();
|
||||
}
|
||||
|
||||
updateScale(): void {
|
||||
@ -1383,8 +1376,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
getCritStage(source: Pokemon, move: Move): number {
|
||||
const critStage = new NumberHolder(0);
|
||||
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
|
||||
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
||||
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
||||
applyHeldItems(HeldItemEffect.CRIT_BOOST, { pokemon: source, critStage: critStage });
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.TEMP_CRIT_BOOSTER, { numberHolder: critStage });
|
||||
applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage });
|
||||
const critBoostTag = source.getTag(CritBoostTag);
|
||||
if (critBoostTag) {
|
||||
@ -1439,7 +1432,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
): number {
|
||||
const statVal = new NumberHolder(this.getStat(stat, false));
|
||||
if (!ignoreHeldItems) {
|
||||
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statVal);
|
||||
applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: this, stat: stat, statValue: statVal });
|
||||
}
|
||||
|
||||
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
||||
@ -1549,7 +1542,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
const statHolder = new NumberHolder(Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01));
|
||||
if (s === Stat.HP) {
|
||||
statHolder.value = statHolder.value + this.level + 10;
|
||||
globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
|
||||
applyHeldItems(HeldItemEffect.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder });
|
||||
if (this.hasAbility(AbilityId.WONDER_GUARD, false, true)) {
|
||||
statHolder.value = 1;
|
||||
}
|
||||
@ -1564,14 +1557,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
} else {
|
||||
statHolder.value += 5;
|
||||
const natureStatMultiplier = new NumberHolder(getNatureStatMultiplier(this.getNature(), s));
|
||||
globalScene.applyModifier(PokemonNatureWeightModifier, this.isPlayer(), this, natureStatMultiplier);
|
||||
applyHeldItems(HeldItemEffect.NATURE_WEIGHT_BOOSTER, { pokemon: this, multiplier: natureStatMultiplier });
|
||||
if (natureStatMultiplier.value !== 1) {
|
||||
statHolder.value = Math.max(
|
||||
Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](statHolder.value * natureStatMultiplier.value),
|
||||
1,
|
||||
);
|
||||
}
|
||||
globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
|
||||
applyHeldItems(HeldItemEffect.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder });
|
||||
}
|
||||
|
||||
statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
|
||||
@ -1584,9 +1577,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
const baseStats = this.getSpeciesForm(true).baseStats.slice(0);
|
||||
applyChallenges(ChallengeType.FLIP_STAT, this, baseStats);
|
||||
// Shuckle Juice
|
||||
globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats);
|
||||
applyHeldItems(HeldItemEffect.BASE_STAT_TOTAL, { pokemon: this, baseStats: baseStats });
|
||||
// Old Gateau
|
||||
globalScene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats);
|
||||
applyHeldItems(HeldItemEffect.BASE_STAT_FLAT, { pokemon: this, baseStats: baseStats });
|
||||
if (this.isFusion()) {
|
||||
const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats;
|
||||
applyChallenges(ChallengeType.FLIP_STAT, this, fusionBaseStats);
|
||||
@ -1600,7 +1593,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
// Vitamins
|
||||
globalScene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats);
|
||||
applyHeldItems(HeldItemEffect.BASE_STAT_BOOSTER, { pokemon: this, baseStats: baseStats });
|
||||
|
||||
return baseStats;
|
||||
}
|
||||
@ -1824,7 +1817,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// Overrides moveset based on arrays specified in overrides.ts
|
||||
let overrideArray: MoveId | Array<MoveId> = this.isPlayer()
|
||||
? Overrides.MOVESET_OVERRIDE
|
||||
: Overrides.OPP_MOVESET_OVERRIDE;
|
||||
: Overrides.ENEMY_MOVESET_OVERRIDE;
|
||||
overrideArray = coerceArray(overrideArray);
|
||||
if (overrideArray.length > 0) {
|
||||
if (!this.isPlayer()) {
|
||||
@ -2029,8 +2022,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) {
|
||||
return allAbilities[Overrides.ABILITY_OVERRIDE];
|
||||
}
|
||||
if (Overrides.OPP_ABILITY_OVERRIDE && this.isEnemy()) {
|
||||
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
||||
if (Overrides.ENEMY_ABILITY_OVERRIDE && this.isEnemy()) {
|
||||
return allAbilities[Overrides.ENEMY_ABILITY_OVERRIDE];
|
||||
}
|
||||
if (this.isFusion()) {
|
||||
if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) {
|
||||
@ -2059,8 +2052,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) {
|
||||
return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE];
|
||||
}
|
||||
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && this.isEnemy()) {
|
||||
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
|
||||
if (Overrides.ENEMY_PASSIVE_ABILITY_OVERRIDE && this.isEnemy()) {
|
||||
return allAbilities[Overrides.ENEMY_PASSIVE_ABILITY_OVERRIDE];
|
||||
}
|
||||
if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) {
|
||||
return allAbilities[this.customPokemonData.passive];
|
||||
@ -2127,14 +2120,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// returns override if valid for current case
|
||||
if (
|
||||
(Overrides.HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isPlayer()) ||
|
||||
(Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isEnemy())
|
||||
(Overrides.ENEMY_HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isEnemy())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
((Overrides.PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) &&
|
||||
this.isPlayer()) ||
|
||||
((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) &&
|
||||
((Overrides.ENEMY_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.ENEMY_HAS_PASSIVE_ABILITY_OVERRIDE) &&
|
||||
this.isEnemy())
|
||||
) {
|
||||
return true;
|
||||
@ -2231,8 +2224,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first
|
||||
* and then multiplicative modifiers happening after (Heavy Metal and Light Metal)
|
||||
* Gets the weight of the Pokemon with subtractive abilities (Autotomize) happening first
|
||||
* and then multiplicative abilities happening after (Heavy Metal and Light Metal)
|
||||
* @returns the kg of the Pokemon (minimum of 0.1)
|
||||
*/
|
||||
public getWeight(): number {
|
||||
@ -2857,7 +2850,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
if (!this.hasTrainer()) {
|
||||
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold });
|
||||
}
|
||||
} else {
|
||||
shinyThreshold.value = thresholdOverride;
|
||||
@ -2879,17 +2872,17 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*
|
||||
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
|
||||
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
|
||||
* @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}
|
||||
* @param applyItemsToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}
|
||||
* @returns `true` if the Pokemon has been set as a shiny, `false` otherwise
|
||||
*/
|
||||
public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean {
|
||||
public trySetShinySeed(thresholdOverride?: number, applyItemsToOverride?: boolean): boolean {
|
||||
if (!this.shiny) {
|
||||
const shinyThreshold = new NumberHolder(thresholdOverride ?? BASE_SHINY_CHANCE);
|
||||
if (applyModifiersToOverride) {
|
||||
if (applyItemsToOverride) {
|
||||
if (timedEventManager.isEventActive()) {
|
||||
shinyThreshold.value *= timedEventManager.getShinyMultiplier();
|
||||
}
|
||||
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold });
|
||||
}
|
||||
|
||||
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
||||
@ -2951,17 +2944,17 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*
|
||||
* The base hidden ability odds are {@linkcode BASE_HIDDEN_ABILITY_CHANCE} / `65536`
|
||||
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the HA chance, overrides {@linkcode haThreshold} if set (bypassing HA rate modifiers such as Ability Charm)
|
||||
* @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride}
|
||||
* @param applyItemsToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride}
|
||||
* @returns `true` if the Pokemon has been set to have its hidden ability, `false` otherwise
|
||||
*/
|
||||
public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean {
|
||||
public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyItemsToOverride?: boolean): boolean {
|
||||
if (!this.species.abilityHidden) {
|
||||
return false;
|
||||
}
|
||||
const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE);
|
||||
if (applyModifiersToOverride) {
|
||||
if (applyItemsToOverride) {
|
||||
if (!this.hasTrainer()) {
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: haThreshold });
|
||||
}
|
||||
}
|
||||
|
||||
@ -2975,7 +2968,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
public generateFusionSpecies(forStarter?: boolean): void {
|
||||
const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE);
|
||||
if (!this.hasTrainer()) {
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, {
|
||||
numberHolder: hiddenAbilityChance,
|
||||
});
|
||||
}
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
@ -3000,8 +2995,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
if (forStarter && this.isPlayer() && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) {
|
||||
fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE);
|
||||
} else if (this.isEnemy() && Overrides.OPP_FUSION_SPECIES_OVERRIDE) {
|
||||
fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE);
|
||||
} else if (this.isEnemy() && Overrides.ENEMY_FUSION_SPECIES_OVERRIDE) {
|
||||
fusionOverride = getPokemonSpecies(Overrides.ENEMY_FUSION_SPECIES_OVERRIDE);
|
||||
}
|
||||
|
||||
this.fusionSpecies =
|
||||
@ -3103,11 +3098,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
||||
if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) {
|
||||
if (tmPoolTiers[moveId] === RarityTier.COMMON && this.level >= 15) {
|
||||
movePool.push([moveId, 4]);
|
||||
} else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) {
|
||||
} else if (tmPoolTiers[moveId] === RarityTier.GREAT && this.level >= 30) {
|
||||
movePool.push([moveId, 8]);
|
||||
} else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) {
|
||||
} else if (tmPoolTiers[moveId] === RarityTier.ULTRA && this.level >= 50) {
|
||||
movePool.push([moveId, 14]);
|
||||
}
|
||||
}
|
||||
@ -3485,7 +3480,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (!ignoreStatStage.value) {
|
||||
const statStageMultiplier = new NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value));
|
||||
if (!ignoreHeldItems) {
|
||||
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER, {
|
||||
numberHolder: statStageMultiplier,
|
||||
});
|
||||
}
|
||||
return Math.min(statStageMultiplier.value, 4);
|
||||
}
|
||||
@ -3519,7 +3516,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: this, stat: Stat.EVA, ignored: ignoreEvaStatStage });
|
||||
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
|
||||
|
||||
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.TEMP_ACCURACY_BOOSTER, { numberHolder: userAccStage });
|
||||
|
||||
userAccStage.value = ignoreAccStatStage.value ? 0 : Math.min(userAccStage.value, 6);
|
||||
targetEvaStage.value = ignoreEvaStatStage.value ? 0 : targetEvaStage.value;
|
||||
@ -3772,14 +3769,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
applyMoveAttrs("FixedDamageAttr", source, this, move, fixedDamage);
|
||||
if (fixedDamage.value) {
|
||||
const multiLensMultiplier = new NumberHolder(1);
|
||||
globalScene.applyModifiers(
|
||||
PokemonMultiHitModifier,
|
||||
source.isPlayer(),
|
||||
source,
|
||||
move.id,
|
||||
null,
|
||||
multiLensMultiplier,
|
||||
);
|
||||
applyHeldItems(HeldItemEffect.MULTI_HIT, {
|
||||
pokemon: source,
|
||||
moveId: move.id,
|
||||
damageMultiplier: multiLensMultiplier,
|
||||
});
|
||||
fixedDamage.value = toDmgValue(fixedDamage.value * multiLensMultiplier.value);
|
||||
|
||||
return {
|
||||
@ -3823,14 +3817,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
/** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */
|
||||
const multiStrikeEnhancementMultiplier = new NumberHolder(1);
|
||||
globalScene.applyModifiers(
|
||||
PokemonMultiHitModifier,
|
||||
source.isPlayer(),
|
||||
source,
|
||||
move.id,
|
||||
null,
|
||||
multiStrikeEnhancementMultiplier,
|
||||
);
|
||||
applyHeldItems(HeldItemEffect.MULTI_HIT, {
|
||||
pokemon: source,
|
||||
moveId: move.id,
|
||||
damageMultiplier: multiStrikeEnhancementMultiplier,
|
||||
});
|
||||
|
||||
if (!ignoreSourceAbility) {
|
||||
applyAbAttrs("AddSecondStrikeAbAttr", {
|
||||
@ -3949,10 +3940,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
/** Apply the enemy's Damage and Resistance tokens */
|
||||
if (!source.isPlayer()) {
|
||||
globalScene.applyModifiers(EnemyDamageBoosterModifier, false, damage);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_DAMAGE_BOOSTER, { numberHolder: damage });
|
||||
}
|
||||
if (!this.isPlayer()) {
|
||||
globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage);
|
||||
globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_DAMAGE_REDUCER, { numberHolder: damage });
|
||||
}
|
||||
|
||||
const abAttrParams: PreAttackModifyDamageAbAttrParams = {
|
||||
@ -3962,7 +3953,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
simulated,
|
||||
damage,
|
||||
};
|
||||
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
|
||||
/** Apply this Pokemon's post-calc defensive attributes (e.g. Fur Coat) */
|
||||
if (!ignoreAbility) {
|
||||
applyAbAttrs("ReceivedMoveDamageMultiplierAbAttr", abAttrParams);
|
||||
|
||||
@ -4064,7 +4055,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN);
|
||||
}
|
||||
if (!surviveDamage.value) {
|
||||
globalScene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage);
|
||||
applyHeldItems(HeldItemEffect.SURVIVE_CHANCE, { pokemon: this, surviveDamage: surviveDamage });
|
||||
}
|
||||
if (surviveDamage.value) {
|
||||
damage = this.hp - 1;
|
||||
@ -4513,7 +4504,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.setScale(this.getSpriteScale());
|
||||
this.loadAssets().then(() => {
|
||||
this.calculateStats();
|
||||
globalScene.updateModifiers(this.isPlayer(), true);
|
||||
globalScene.updateItems(this.isPlayer());
|
||||
Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve());
|
||||
});
|
||||
});
|
||||
@ -5631,16 +5622,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* Should be `false` for all item loss occurring outside of battle (MEs, etc.).
|
||||
* @returns Whether the item was removed successfully.
|
||||
*/
|
||||
public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle = true): boolean {
|
||||
public loseHeldItem(heldItemId: HeldItemId, forBattle = true): boolean {
|
||||
// TODO: What does a -1 pokemon id mean?
|
||||
if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) {
|
||||
if (!this.heldItemManager.hasItem(heldItemId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
heldItem.stackCount--;
|
||||
if (heldItem.stackCount <= 0) {
|
||||
globalScene.removeModifier(heldItem, this.isEnemy());
|
||||
}
|
||||
this.heldItemManager.remove(heldItemId);
|
||||
|
||||
if (forBattle) {
|
||||
applyAbAttrs("PostItemLostAbAttr", { pokemon: this });
|
||||
}
|
||||
@ -5662,13 +5651,6 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
this.turnData.berriesEaten.push(berryType);
|
||||
}
|
||||
|
||||
getPersistentTreasureCount(): number {
|
||||
return (
|
||||
this.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length +
|
||||
globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") || m.is("ExtraModifierModifier")).length
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayerPokemon extends Pokemon {
|
||||
@ -5685,9 +5667,24 @@ export class PlayerPokemon extends Pokemon {
|
||||
variant?: Variant,
|
||||
ivs?: number[],
|
||||
nature?: Nature,
|
||||
heldItemConfig?: HeldItemConfiguration,
|
||||
dataSource?: Pokemon | PokemonData,
|
||||
) {
|
||||
super(106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
|
||||
super(
|
||||
106,
|
||||
148,
|
||||
species,
|
||||
level,
|
||||
abilityIndex,
|
||||
formIndex,
|
||||
gender,
|
||||
shiny,
|
||||
variant,
|
||||
ivs,
|
||||
nature,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
|
||||
if (Overrides.STATUS_OVERRIDE) {
|
||||
this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4);
|
||||
@ -5821,45 +5818,59 @@ export class PlayerPokemon extends Pokemon {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
addFriendship(friendship: number): void {
|
||||
if (friendship > 0) {
|
||||
const starterSpeciesId = this.species.getRootSpeciesId();
|
||||
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
|
||||
const starterData = [
|
||||
globalScene.gameData.starterData[starterSpeciesId],
|
||||
fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null,
|
||||
].filter(d => !!d);
|
||||
const amount = new NumberHolder(friendship);
|
||||
globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
||||
const candyFriendshipMultiplier = globalScene.gameMode.isClassic
|
||||
? timedEventManager.getClassicFriendshipMultiplier()
|
||||
: 1;
|
||||
const fusionReduction = fusionStarterSpeciesId
|
||||
? timedEventManager.areFusionsBoosted()
|
||||
? 1.5 // Divide candy gain for fusions by 1.5 during events
|
||||
: 2 // 2 for fusions outside events
|
||||
: 1; // 1 for non-fused mons
|
||||
const starterAmount = new NumberHolder(Math.floor((amount.value * candyFriendshipMultiplier) / fusionReduction));
|
||||
|
||||
// Add friendship to this PlayerPokemon
|
||||
this.friendship = Math.min(this.friendship + amount.value, 255);
|
||||
if (this.friendship === 255) {
|
||||
globalScene.validateAchv(achvs.MAX_FRIENDSHIP);
|
||||
}
|
||||
// Add to candy progress for this mon's starter species and its fused species (if it has one)
|
||||
starterData.forEach((sd: StarterDataEntry, i: number) => {
|
||||
const speciesId = !i ? starterSpeciesId : (fusionStarterSpeciesId as SpeciesId);
|
||||
sd.friendship = (sd.friendship || 0) + starterAmount.value;
|
||||
if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) {
|
||||
globalScene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1);
|
||||
sd.friendship = 0;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Lose friendship upon fainting
|
||||
/**
|
||||
* Add friendship to this Pokemon
|
||||
*
|
||||
* @remarks
|
||||
* This adds friendship to the pokemon's friendship stat (used for evolution, return, etc.) and candy progress.
|
||||
* For fusions, candy progress for each species in the fusion is halved.
|
||||
*
|
||||
* @param friendship - The amount of friendship to add. Negative values will reduce friendship, though not below 0.
|
||||
* @param capped - If true, don't allow the friendship gain to exceed 200. Used to cap friendship gains from rare candies.
|
||||
*/
|
||||
addFriendship(friendship: number, capped = false): void {
|
||||
// Short-circuit friendship loss, which doesn't impact candy friendship
|
||||
if (friendship <= 0) {
|
||||
this.friendship = Math.max(this.friendship + friendship, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const starterSpeciesId = this.species.getRootSpeciesId();
|
||||
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
|
||||
const starterGameData = globalScene.gameData.starterData;
|
||||
const starterData: [StarterDataEntry, SpeciesId][] = [[starterGameData[starterSpeciesId], starterSpeciesId]];
|
||||
if (fusionStarterSpeciesId) {
|
||||
starterData.push([starterGameData[fusionStarterSpeciesId], fusionStarterSpeciesId]);
|
||||
}
|
||||
const amount = new NumberHolder(friendship);
|
||||
applyHeldItems(HeldItemEffect.FRIENDSHIP_BOOSTER, { pokemon: this, friendship: amount });
|
||||
friendship = amount.value;
|
||||
|
||||
const newFriendship = this.friendship + friendship;
|
||||
// If capped is true, only adjust friendship if the new friendship is less than or equal to 200.
|
||||
if (!capped || newFriendship <= RARE_CANDY_FRIENDSHIP_CAP) {
|
||||
this.friendship = Math.min(newFriendship, 255);
|
||||
if (newFriendship >= 255) {
|
||||
globalScene.validateAchv(achvs.MAX_FRIENDSHIP);
|
||||
awardRibbonsToSpeciesLine(this.species.speciesId, RibbonData.FRIENDSHIP);
|
||||
}
|
||||
}
|
||||
|
||||
let candyFriendshipMultiplier = globalScene.gameMode.isClassic
|
||||
? timedEventManager.getClassicFriendshipMultiplier()
|
||||
: 1;
|
||||
if (fusionStarterSpeciesId) {
|
||||
candyFriendshipMultiplier /= timedEventManager.areFusionsBoosted() ? 1.5 : 2;
|
||||
}
|
||||
const candyFriendshipAmount = Math.floor(friendship * candyFriendshipMultiplier);
|
||||
// Add to candy progress for this mon's starter species and its fused species (if it has one)
|
||||
starterData.forEach(([sd, id]: [StarterDataEntry, SpeciesId]) => {
|
||||
sd.friendship = (sd.friendship || 0) + candyFriendshipAmount;
|
||||
if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[id])) {
|
||||
globalScene.gameData.addStarterCandy(getPokemonSpecies(id), 1);
|
||||
sd.friendship = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise<Pokemon> {
|
||||
@ -5891,6 +5902,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.variant,
|
||||
this.ivs,
|
||||
this.nature,
|
||||
this.heldItemManager.generateHeldItemConfiguration(),
|
||||
this,
|
||||
);
|
||||
this.fusionSpecies = originalFusionSpecies;
|
||||
@ -5913,6 +5925,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.variant,
|
||||
this.ivs,
|
||||
this.nature,
|
||||
this.heldItemManager.generateHeldItemConfiguration(),
|
||||
this,
|
||||
);
|
||||
}
|
||||
@ -5985,9 +5998,9 @@ export class PlayerPokemon extends Pokemon {
|
||||
});
|
||||
};
|
||||
if (preEvolution.speciesId === SpeciesId.GIMMIGHOUL) {
|
||||
const evotracker = this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? null;
|
||||
const evotracker = this.heldItemManager.hasItem(HeldItemId.GIMMIGHOUL_EVO_TRACKER);
|
||||
if (evotracker) {
|
||||
globalScene.removeModifier(evotracker);
|
||||
this.heldItemManager.remove(HeldItemId.GIMMIGHOUL_EVO_TRACKER, 0, true);
|
||||
}
|
||||
}
|
||||
if (!globalScene.gameMode.isDaily || this.metBiome > -1) {
|
||||
@ -6040,16 +6053,12 @@ export class PlayerPokemon extends Pokemon {
|
||||
|
||||
globalScene.getPlayerParty().push(newPokemon);
|
||||
newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies);
|
||||
const modifiers = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
modifiers.forEach(m => {
|
||||
const clonedModifier = m.clone() as PokemonHeldItemModifier;
|
||||
clonedModifier.pokemonId = newPokemon.id;
|
||||
globalScene.addModifier(clonedModifier, true);
|
||||
//TODO: This currently does not consider any values associated with the items e.g. disabled
|
||||
const heldItems = this.getHeldItems();
|
||||
heldItems.forEach(item => {
|
||||
newPokemon.heldItemManager.add(item, this.heldItemManager.getStack(item));
|
||||
});
|
||||
globalScene.updateModifiers(true);
|
||||
globalScene.updateItems(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6070,6 +6079,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.variant,
|
||||
this.ivs,
|
||||
this.nature,
|
||||
this.heldItemManager.generateHeldItemConfiguration(),
|
||||
this,
|
||||
);
|
||||
ret.loadAssets().then(() => resolve(ret));
|
||||
@ -6094,7 +6104,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
const updateAndResolve = () => {
|
||||
this.loadAssets().then(() => {
|
||||
this.calculateStats();
|
||||
globalScene.updateModifiers(true, true);
|
||||
globalScene.updateItems(true);
|
||||
this.updateInfo(true).then(() => resolve());
|
||||
});
|
||||
};
|
||||
@ -6160,15 +6170,11 @@ export class PlayerPokemon extends Pokemon {
|
||||
}
|
||||
|
||||
// combine the two mons' held items
|
||||
const fusedPartyMemberHeldModifiers = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
for (const modifier of fusedPartyMemberHeldModifiers) {
|
||||
globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false);
|
||||
const fusedPartyMemberHeldItems = pokemon.getHeldItems();
|
||||
for (const item of fusedPartyMemberHeldItems) {
|
||||
globalScene.tryTransferHeldItem(item, pokemon, this, false, pokemon.heldItemManager.getStack(item), true, false);
|
||||
}
|
||||
globalScene.updateModifiers(true, true);
|
||||
globalScene.removePartyMemberModifiers(fusedPartyMemberIndex);
|
||||
globalScene.updateItems(true);
|
||||
globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0];
|
||||
const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this);
|
||||
pokemon
|
||||
@ -6216,6 +6222,7 @@ export class EnemyPokemon extends Pokemon {
|
||||
trainerSlot: TrainerSlot,
|
||||
boss: boolean,
|
||||
shinyLock = false,
|
||||
heldItemConfig?: HeldItemConfiguration,
|
||||
dataSource?: PokemonData,
|
||||
) {
|
||||
super(
|
||||
@ -6230,6 +6237,7 @@ export class EnemyPokemon extends Pokemon {
|
||||
!shinyLock && dataSource ? dataSource.variant : undefined,
|
||||
undefined,
|
||||
dataSource ? dataSource.nature : undefined,
|
||||
heldItemConfig,
|
||||
dataSource,
|
||||
);
|
||||
|
||||
@ -6240,41 +6248,46 @@ export class EnemyPokemon extends Pokemon {
|
||||
this.setBoss(boss, dataSource?.bossSegments);
|
||||
}
|
||||
|
||||
if (Overrides.OPP_STATUS_OVERRIDE) {
|
||||
this.status = new Status(Overrides.OPP_STATUS_OVERRIDE, 0, 4);
|
||||
if (Overrides.ENEMY_STATUS_OVERRIDE) {
|
||||
this.status = new Status(Overrides.ENEMY_STATUS_OVERRIDE, 0, 4);
|
||||
}
|
||||
|
||||
if (Overrides.OPP_GENDER_OVERRIDE !== null) {
|
||||
this.gender = Overrides.OPP_GENDER_OVERRIDE;
|
||||
if (Overrides.ENEMY_GENDER_OVERRIDE !== null) {
|
||||
this.gender = Overrides.ENEMY_GENDER_OVERRIDE;
|
||||
}
|
||||
|
||||
const speciesId = this.species.speciesId;
|
||||
|
||||
if (
|
||||
speciesId in Overrides.OPP_FORM_OVERRIDES &&
|
||||
!isNullOrUndefined(Overrides.OPP_FORM_OVERRIDES[speciesId]) &&
|
||||
this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]]
|
||||
speciesId in Overrides.ENEMY_FORM_OVERRIDES &&
|
||||
!isNullOrUndefined(Overrides.ENEMY_FORM_OVERRIDES[speciesId]) &&
|
||||
this.species.forms[Overrides.ENEMY_FORM_OVERRIDES[speciesId]]
|
||||
) {
|
||||
this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId];
|
||||
this.formIndex = Overrides.ENEMY_FORM_OVERRIDES[speciesId];
|
||||
} else if (globalScene.gameMode.isDaily && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) {
|
||||
const eventBoss = getDailyEventSeedBoss(globalScene.seed);
|
||||
if (!isNullOrUndefined(eventBoss)) {
|
||||
this.formIndex = eventBoss.formIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dataSource) {
|
||||
this.generateAndPopulateMoveset();
|
||||
if (shinyLock || Overrides.OPP_SHINY_OVERRIDE === false) {
|
||||
if (shinyLock || Overrides.ENEMY_SHINY_OVERRIDE === false) {
|
||||
this.shiny = false;
|
||||
} else {
|
||||
this.trySetShiny();
|
||||
}
|
||||
|
||||
if (!this.shiny && Overrides.OPP_SHINY_OVERRIDE) {
|
||||
if (!this.shiny && Overrides.ENEMY_SHINY_OVERRIDE) {
|
||||
this.shiny = true;
|
||||
this.initShinySparkle();
|
||||
}
|
||||
|
||||
if (this.shiny) {
|
||||
this.variant = this.generateShinyVariant();
|
||||
if (Overrides.OPP_VARIANT_OVERRIDE !== null) {
|
||||
this.variant = Overrides.OPP_VARIANT_OVERRIDE;
|
||||
if (Overrides.ENEMY_VARIANT_OVERRIDE !== null) {
|
||||
this.variant = Overrides.ENEMY_VARIANT_OVERRIDE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6867,6 +6880,7 @@ export class EnemyPokemon extends Pokemon {
|
||||
this.variant,
|
||||
this.ivs,
|
||||
this.nature,
|
||||
this.heldItemManager.generateHeldItemConfiguration(),
|
||||
this,
|
||||
);
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
import type { EnemyPokemon } from "#field/pokemon";
|
||||
import type { PersistentModifier } from "#modifiers/modifier";
|
||||
import type { TrainerItemConfiguration } from "#items/trainer-item-data-types";
|
||||
import { getIsInitialized, initI18n } from "#plugins/i18n";
|
||||
import type { TrainerConfig } from "#trainers/trainer-config";
|
||||
import { trainerConfigs } from "#trainers/trainer-config";
|
||||
@ -634,7 +634,7 @@ export class Trainer extends Phaser.GameObjects.Container {
|
||||
return maxScorePartyMemberIndexes[0];
|
||||
}
|
||||
|
||||
getPartyMemberModifierChanceMultiplier(index: number): number {
|
||||
getPartyMemberItemChanceMultiplier(index: number): number {
|
||||
switch (this.getPartyTemplate().getStrength(index)) {
|
||||
case PartyMemberStrength.WEAKER:
|
||||
return 0.75;
|
||||
@ -647,14 +647,14 @@ export class Trainer extends Phaser.GameObjects.Container {
|
||||
case PartyMemberStrength.STRONGER:
|
||||
return 0.375;
|
||||
default:
|
||||
console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0");
|
||||
console.warn("getPartyMemberItemChanceMultiplier not defined. Using default 0");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
genModifiers(party: EnemyPokemon[]): PersistentModifier[] {
|
||||
if (this.config.genModifiersFunc) {
|
||||
return this.config.genModifiersFunc(party);
|
||||
genTrainerItems(party: EnemyPokemon[]): TrainerItemConfiguration {
|
||||
if (this.config.genTrainerItemsFunc) {
|
||||
return this.config.genTrainerItemsFunc(party);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES, CLASSIC_MODE_MYSTERY_ENCOUNTER_
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import Overrides from "#app/overrides";
|
||||
import { allChallenges, type Challenge, copyChallenge } from "#data/challenge";
|
||||
import { getDailyStartingBiome } from "#data/daily-run";
|
||||
import { getDailyEventSeedBoss, getDailyStartingBiome } from "#data/daily-run";
|
||||
import { allSpecies } from "#data/data-lists";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
@ -15,6 +15,7 @@ import type { Arena } from "#field/arena";
|
||||
import { classicFixedBattles, type FixedBattleConfigs } from "#trainers/fixed-battle-configs";
|
||||
import { applyChallenges } from "#utils/challenge-utils";
|
||||
import { BooleanHolder, isNullOrUndefined, randSeedInt, randSeedItem } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
interface GameModeConfig {
|
||||
@ -211,6 +212,12 @@ export class GameMode implements GameModeConfig {
|
||||
|
||||
getOverrideSpecies(waveIndex: number): PokemonSpecies | null {
|
||||
if (this.isDaily && this.isWaveFinal(waveIndex)) {
|
||||
const eventBoss = getDailyEventSeedBoss(globalScene.seed);
|
||||
if (!isNullOrUndefined(eventBoss)) {
|
||||
// Cannot set form index here, it will be overriden when adding it as enemy pokemon.
|
||||
return getPokemonSpecies(eventBoss.speciesId);
|
||||
}
|
||||
|
||||
const allFinalBossSpecies = allSpecies.filter(
|
||||
s =>
|
||||
(s.subLegendary || s.legendary || s.mythical) &&
|
||||
@ -333,7 +340,7 @@ export class GameMode implements GameModeConfig {
|
||||
}
|
||||
}
|
||||
|
||||
getEnemyModifierChance(isBoss: boolean): number {
|
||||
getEnemyItemChance(isBoss: boolean): number {
|
||||
switch (this.modeId) {
|
||||
case GameModes.CLASSIC:
|
||||
case GameModes.CHALLENGE:
|
||||
|
||||
@ -6,8 +6,11 @@ import { initSpecies } from "#balance/pokemon-species";
|
||||
import { initChallenges } from "#data/challenge";
|
||||
import { initTrainerTypeDialogue } from "#data/dialogue";
|
||||
import { initPokemonForms } from "#data/pokemon-forms";
|
||||
import { initModifierPools } from "#modifiers/init-modifier-pools";
|
||||
import { initModifierTypes } from "#modifiers/modifier-type";
|
||||
import { initHeldItems } from "#items/all-held-items";
|
||||
import { initTrainerItems } from "#items/all-trainer-items";
|
||||
import { initHeldItemPools } from "#items/init-held-item-pools";
|
||||
import { initRewardPools } from "#items/init-reward-pools";
|
||||
import { initTrainerItemPools } from "#items/init-trainer-item-pools";
|
||||
import { initMoves } from "#moves/move";
|
||||
import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters";
|
||||
import { initAchievements } from "#system/achv";
|
||||
@ -16,10 +19,9 @@ import { initStatsKeys } from "#ui/game-stats-ui-handler";
|
||||
|
||||
/** Initialize the game. */
|
||||
export function initializeGame() {
|
||||
initModifierTypes();
|
||||
initModifierPools();
|
||||
initAchievements();
|
||||
initItems();
|
||||
initVouchers();
|
||||
initAchievements();
|
||||
initStatsKeys();
|
||||
initPokemonPrevolutions();
|
||||
initPokemonStarters();
|
||||
@ -33,3 +35,14 @@ export function initializeGame() {
|
||||
initChallenges();
|
||||
initMysteryEncounters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method to initialize all the item-related code.
|
||||
*/
|
||||
function initItems() {
|
||||
initHeldItems();
|
||||
initHeldItemPools();
|
||||
initTrainerItems();
|
||||
initTrainerItemPools();
|
||||
initRewardPools();
|
||||
}
|
||||
|
||||
195
src/items/all-held-items.ts
Normal file
195
src/items/all-held-items.ts
Normal file
@ -0,0 +1,195 @@
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { type PermanentStat, Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { AccuracyBoosterHeldItem, type AccuracyBoostParams } from "#items/accuracy-booster";
|
||||
import {
|
||||
AttackTypeBoosterHeldItem,
|
||||
type AttackTypeBoostParams,
|
||||
attackTypeToHeldItem,
|
||||
} from "#items/attack-type-booster";
|
||||
import { BaseStatBoosterHeldItem, type BaseStatBoosterParams, permanentStatToHeldItem } from "#items/base-stat-booster";
|
||||
import { BaseStatFlatHeldItem, type BaseStatFlatParams } from "#items/base-stat-flat";
|
||||
import { BaseStatTotalHeldItem, type BaseStatTotalParams } from "#items/base-stat-total";
|
||||
import { BatonHeldItem, type BatonParams } from "#items/baton";
|
||||
import { BerryHeldItem, type BerryParams, berryTypeToHeldItem } from "#items/berry";
|
||||
import { BypassSpeedChanceHeldItem, type BypassSpeedChanceParams } from "#items/bypass-speed-chance";
|
||||
import { CritBoostHeldItem, type CritBoostParams, SpeciesCritBoostHeldItem } from "#items/crit-booster";
|
||||
import { DamageMoneyRewardHeldItem, type DamageMoneyRewardParams } from "#items/damage-money-reward";
|
||||
import { type EvoTrackerParams, GimmighoulEvoTrackerHeldItem } from "#items/evo-tracker";
|
||||
import { ExpBoosterHeldItem, type ExpBoostParams } from "#items/exp-booster";
|
||||
import { FieldEffectHeldItem, type FieldEffectParams } from "#items/field-effect";
|
||||
import { FlinchChanceHeldItem, type FlinchChanceParams } from "#items/flinch-chance";
|
||||
import { FriendshipBoosterHeldItem, type FriendshipBoostParams } from "#items/friendship-booster";
|
||||
import { HitHealHeldItem, type HitHealParams } from "#items/hit-heal";
|
||||
import { IncrementingStatHeldItem, type IncrementingStatParams } from "#items/incrementing-stat";
|
||||
import { InstantReviveHeldItem, type InstantReviveParams } from "#items/instant-revive";
|
||||
import { ContactItemStealChanceHeldItem, type ItemStealParams, TurnEndItemStealHeldItem } from "#items/item-steal";
|
||||
import { MultiHitHeldItem, type MultiHitParams } from "#items/multi-hit";
|
||||
import { NatureWeightBoosterHeldItem, type NatureWeightBoostParams } from "#items/nature-weight-booster";
|
||||
import { ResetNegativeStatStageHeldItem, type ResetNegativeStatStageParams } from "#items/reset-negative-stat-stage";
|
||||
import { EvolutionStatBoostHeldItem, SpeciesStatBoostHeldItem, type StatBoostParams } from "#items/stat-booster";
|
||||
import { SurviveChanceHeldItem, type SurviveChanceParams } from "#items/survive-chance";
|
||||
import { TurnEndHealHeldItem, type TurnEndHealParams } from "#items/turn-end-heal";
|
||||
import { TurnEndStatusHeldItem, type TurnEndStatusParams } from "#items/turn-end-status";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
|
||||
export function initHeldItems() {
|
||||
for (const berry of getEnumValues(BerryType)) {
|
||||
const maxStackCount = [BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(berry) ? 2 : 3;
|
||||
const berryId = berryTypeToHeldItem[berry];
|
||||
allHeldItems[berryId] = new BerryHeldItem(berry, maxStackCount);
|
||||
}
|
||||
|
||||
allHeldItems[HeldItemId.REVIVER_SEED] = new InstantReviveHeldItem(HeldItemId.REVIVER_SEED, 1);
|
||||
allHeldItems[HeldItemId.WHITE_HERB] = new ResetNegativeStatStageHeldItem(HeldItemId.WHITE_HERB, 2);
|
||||
|
||||
// SILK_SCARF, BLACK_BELT, etc...
|
||||
for (const [typeKey, heldItemType] of Object.entries(attackTypeToHeldItem)) {
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114957526
|
||||
const pokemonType = Number(typeKey) as PokemonType;
|
||||
allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2);
|
||||
}
|
||||
|
||||
// Items that boost specific stats
|
||||
allHeldItems[HeldItemId.EVIOLITE] = new EvolutionStatBoostHeldItem(
|
||||
HeldItemId.EVIOLITE,
|
||||
1,
|
||||
[Stat.DEF, Stat.SPDEF],
|
||||
1.5,
|
||||
);
|
||||
allHeldItems[HeldItemId.LIGHT_BALL] = new SpeciesStatBoostHeldItem(
|
||||
HeldItemId.LIGHT_BALL,
|
||||
1,
|
||||
[Stat.ATK, Stat.SPATK],
|
||||
2,
|
||||
[SpeciesId.PIKACHU],
|
||||
);
|
||||
allHeldItems[HeldItemId.THICK_CLUB] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.ATK], 2, [
|
||||
SpeciesId.CUBONE,
|
||||
SpeciesId.MAROWAK,
|
||||
SpeciesId.ALOLA_MAROWAK,
|
||||
]);
|
||||
allHeldItems[HeldItemId.METAL_POWDER] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.DEF], 2, [
|
||||
SpeciesId.DITTO,
|
||||
]);
|
||||
allHeldItems[HeldItemId.QUICK_POWDER] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPD], 2, [
|
||||
SpeciesId.DITTO,
|
||||
]);
|
||||
allHeldItems[HeldItemId.DEEP_SEA_SCALE] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPDEF], 2, [
|
||||
SpeciesId.CLAMPERL,
|
||||
]);
|
||||
allHeldItems[HeldItemId.DEEP_SEA_TOOTH] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPATK], 2, [
|
||||
SpeciesId.CLAMPERL,
|
||||
]);
|
||||
|
||||
// Items that boost the crit rate
|
||||
allHeldItems[HeldItemId.SCOPE_LENS] = new CritBoostHeldItem(HeldItemId.SCOPE_LENS, 1, 1);
|
||||
allHeldItems[HeldItemId.LEEK] = new SpeciesCritBoostHeldItem(HeldItemId.LEEK, 1, 2, [
|
||||
SpeciesId.FARFETCHD,
|
||||
SpeciesId.GALAR_FARFETCHD,
|
||||
SpeciesId.SIRFETCHD,
|
||||
]);
|
||||
|
||||
allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40);
|
||||
allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100);
|
||||
allHeldItems[HeldItemId.SOOTHE_BELL] = new FriendshipBoosterHeldItem(HeldItemId.SOOTHE_BELL, 3);
|
||||
|
||||
allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4);
|
||||
allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4);
|
||||
|
||||
allHeldItems[HeldItemId.FOCUS_BAND] = new SurviveChanceHeldItem(HeldItemId.FOCUS_BAND, 5);
|
||||
allHeldItems[HeldItemId.QUICK_CLAW] = new BypassSpeedChanceHeldItem(HeldItemId.QUICK_CLAW, 3);
|
||||
allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10);
|
||||
allHeldItems[HeldItemId.MYSTICAL_ROCK] = new FieldEffectHeldItem(HeldItemId.MYSTICAL_ROCK, 2);
|
||||
allHeldItems[HeldItemId.SOUL_DEW] = new NatureWeightBoosterHeldItem(HeldItemId.SOUL_DEW, 10);
|
||||
allHeldItems[HeldItemId.WIDE_LENS] = new AccuracyBoosterHeldItem(HeldItemId.WIDE_LENS, 3, 5);
|
||||
allHeldItems[HeldItemId.MULTI_LENS] = new MultiHitHeldItem(HeldItemId.MULTI_LENS, 2);
|
||||
allHeldItems[HeldItemId.GOLDEN_PUNCH] = new DamageMoneyRewardHeldItem(HeldItemId.GOLDEN_PUNCH, 5);
|
||||
allHeldItems[HeldItemId.BATON] = new BatonHeldItem(HeldItemId.BATON, 1);
|
||||
allHeldItems[HeldItemId.GRIP_CLAW] = new ContactItemStealChanceHeldItem(HeldItemId.GRIP_CLAW, 5, 10);
|
||||
allHeldItems[HeldItemId.MINI_BLACK_HOLE] = new TurnEndItemStealHeldItem(HeldItemId.MINI_BLACK_HOLE, 1)
|
||||
.unstealable()
|
||||
.untransferable();
|
||||
|
||||
allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN);
|
||||
allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC);
|
||||
|
||||
// vitamins
|
||||
for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) {
|
||||
const stat = Number(statKey) as PermanentStat;
|
||||
allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 30, stat)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
}
|
||||
|
||||
allHeldItems[HeldItemId.SHUCKLE_JUICE_GOOD] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE_GOOD, 1, 10)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
allHeldItems[HeldItemId.SHUCKLE_JUICE_BAD] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE_BAD, 1, -15)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
allHeldItems[HeldItemId.OLD_GATEAU] = new BaseStatFlatHeldItem(HeldItemId.OLD_GATEAU, 1)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
allHeldItems[HeldItemId.MACHO_BRACE] = new IncrementingStatHeldItem(HeldItemId.MACHO_BRACE, 50)
|
||||
.unstealable()
|
||||
.untransferable()
|
||||
.unsuppressable();
|
||||
allHeldItems[HeldItemId.GIMMIGHOUL_EVO_TRACKER] = new GimmighoulEvoTrackerHeldItem(
|
||||
HeldItemId.GIMMIGHOUL_EVO_TRACKER,
|
||||
999,
|
||||
SpeciesId.GIMMIGHOUL,
|
||||
10,
|
||||
);
|
||||
}
|
||||
|
||||
type ApplyHeldItemsParams = {
|
||||
[HeldItemEffect.ATTACK_TYPE_BOOST]: AttackTypeBoostParams;
|
||||
[HeldItemEffect.TURN_END_HEAL]: TurnEndHealParams;
|
||||
[HeldItemEffect.HIT_HEAL]: HitHealParams;
|
||||
[HeldItemEffect.RESET_NEGATIVE_STAT_STAGE]: ResetNegativeStatStageParams;
|
||||
[HeldItemEffect.EXP_BOOSTER]: ExpBoostParams;
|
||||
[HeldItemEffect.BERRY]: BerryParams;
|
||||
[HeldItemEffect.BASE_STAT_BOOSTER]: BaseStatBoosterParams;
|
||||
[HeldItemEffect.INSTANT_REVIVE]: InstantReviveParams;
|
||||
[HeldItemEffect.STAT_BOOST]: StatBoostParams;
|
||||
[HeldItemEffect.CRIT_BOOST]: CritBoostParams;
|
||||
[HeldItemEffect.TURN_END_STATUS]: TurnEndStatusParams;
|
||||
[HeldItemEffect.SURVIVE_CHANCE]: SurviveChanceParams;
|
||||
[HeldItemEffect.BYPASS_SPEED_CHANCE]: BypassSpeedChanceParams;
|
||||
[HeldItemEffect.FLINCH_CHANCE]: FlinchChanceParams;
|
||||
[HeldItemEffect.FIELD_EFFECT]: FieldEffectParams;
|
||||
[HeldItemEffect.FRIENDSHIP_BOOSTER]: FriendshipBoostParams;
|
||||
[HeldItemEffect.NATURE_WEIGHT_BOOSTER]: NatureWeightBoostParams;
|
||||
[HeldItemEffect.ACCURACY_BOOSTER]: AccuracyBoostParams;
|
||||
[HeldItemEffect.MULTI_HIT]: MultiHitParams;
|
||||
[HeldItemEffect.DAMAGE_MONEY_REWARD]: DamageMoneyRewardParams;
|
||||
[HeldItemEffect.BATON]: BatonParams;
|
||||
[HeldItemEffect.CONTACT_ITEM_STEAL_CHANCE]: ItemStealParams;
|
||||
[HeldItemEffect.TURN_END_ITEM_STEAL]: ItemStealParams;
|
||||
[HeldItemEffect.BASE_STAT_TOTAL]: BaseStatTotalParams;
|
||||
[HeldItemEffect.BASE_STAT_FLAT]: BaseStatFlatParams;
|
||||
[HeldItemEffect.INCREMENTING_STAT]: IncrementingStatParams;
|
||||
[HeldItemEffect.EVO_TRACKER]: EvoTrackerParams;
|
||||
};
|
||||
|
||||
export function applyHeldItems<T extends HeldItemEffect>(effect: T, params: ApplyHeldItemsParams[T]) {
|
||||
const pokemon = params.pokemon;
|
||||
if (pokemon) {
|
||||
// TODO: Make this use `getHeldItems` and make `heldItems` array private
|
||||
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
|
||||
if (allHeldItems[item].effects.includes(effect)) {
|
||||
allHeldItems[item].apply(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
191
src/items/all-rewards.ts
Normal file
191
src/items/all-rewards.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { RewardId } from "#enums/reward-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import { VoucherType } from "#system/voucher";
|
||||
import {
|
||||
AddMoneyReward,
|
||||
AddPokeballReward,
|
||||
AddVoucherReward,
|
||||
AllPokemonFullReviveReward,
|
||||
AllPokemonLevelIncrementReward,
|
||||
AttackTypeBoosterRewardGenerator,
|
||||
BaseStatBoosterRewardGenerator,
|
||||
BerryRewardGenerator,
|
||||
EvolutionItemRewardGenerator,
|
||||
FormChangeItemRewardGenerator,
|
||||
FusePokemonReward,
|
||||
LapsingTrainerItemReward,
|
||||
MintRewardGenerator,
|
||||
NoneReward,
|
||||
PokemonAllMovePpRestoreReward,
|
||||
PokemonHpRestoreReward,
|
||||
PokemonLevelIncrementReward,
|
||||
PokemonPpRestoreReward,
|
||||
PokemonPpUpReward,
|
||||
PokemonReviveReward,
|
||||
PokemonStatusHealReward,
|
||||
RememberMoveReward,
|
||||
type Reward,
|
||||
type RewardGenerator,
|
||||
SpeciesStatBoosterRewardGenerator,
|
||||
TempStatStageBoosterRewardGenerator,
|
||||
TeraTypeRewardGenerator,
|
||||
TmRewardGenerator,
|
||||
} from "./reward";
|
||||
|
||||
// TODO: Move to `reward-utils.ts` and un-exportt
|
||||
export const allRewards = {
|
||||
[RewardId.NONE]: new NoneReward(),
|
||||
|
||||
// Pokeball rewards
|
||||
[RewardId.POKEBALL]: new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL),
|
||||
[RewardId.GREAT_BALL]: new AddPokeballReward("gb", PokeballType.GREAT_BALL, 5, RewardId.GREAT_BALL),
|
||||
[RewardId.ULTRA_BALL]: new AddPokeballReward("ub", PokeballType.ULTRA_BALL, 5, RewardId.ULTRA_BALL),
|
||||
[RewardId.ROGUE_BALL]: new AddPokeballReward("rb", PokeballType.ROGUE_BALL, 5, RewardId.ROGUE_BALL),
|
||||
[RewardId.MASTER_BALL]: new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL),
|
||||
|
||||
// Voucher rewards
|
||||
[RewardId.VOUCHER]: new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER),
|
||||
[RewardId.VOUCHER_PLUS]: new AddVoucherReward(VoucherType.PLUS, 1, RewardId.VOUCHER_PLUS),
|
||||
[RewardId.VOUCHER_PREMIUM]: new AddVoucherReward(VoucherType.PREMIUM, 1, RewardId.VOUCHER_PREMIUM),
|
||||
|
||||
// Money rewards
|
||||
[RewardId.NUGGET]: new AddMoneyReward(
|
||||
"modifierType:ModifierType.NUGGET",
|
||||
"nugget",
|
||||
1,
|
||||
"modifierType:ModifierType.MoneyRewardModifierType.extra.small",
|
||||
RewardId.NUGGET,
|
||||
),
|
||||
[RewardId.BIG_NUGGET]: new AddMoneyReward(
|
||||
"modifierType:ModifierType.BIG_NUGGET",
|
||||
"big_nugget",
|
||||
2.5,
|
||||
"modifierType:ModifierType.MoneyRewardModifierType.extra.moderate",
|
||||
RewardId.BIG_NUGGET,
|
||||
),
|
||||
[RewardId.RELIC_GOLD]: new AddMoneyReward(
|
||||
"modifierType:ModifierType.RELIC_GOLD",
|
||||
"relic_gold",
|
||||
10,
|
||||
"modifierType:ModifierType.MoneyRewardModifierType.extra.large",
|
||||
RewardId.RELIC_GOLD,
|
||||
),
|
||||
|
||||
// Party-wide consumables
|
||||
[RewardId.RARER_CANDY]: new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy"),
|
||||
[RewardId.SACRED_ASH]: new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash"),
|
||||
|
||||
// Pokemon consumables
|
||||
[RewardId.RARE_CANDY]: new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy"),
|
||||
|
||||
[RewardId.EVOLUTION_ITEM]: new EvolutionItemRewardGenerator(false),
|
||||
[RewardId.RARE_EVOLUTION_ITEM]: new EvolutionItemRewardGenerator(true),
|
||||
|
||||
[RewardId.POTION]: new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10),
|
||||
[RewardId.SUPER_POTION]: new PokemonHpRestoreReward(
|
||||
"modifierType:ModifierType.SUPER_POTION",
|
||||
"super_potion",
|
||||
RewardId.SUPER_POTION,
|
||||
50,
|
||||
25,
|
||||
),
|
||||
[RewardId.HYPER_POTION]: new PokemonHpRestoreReward(
|
||||
"modifierType:ModifierType.HYPER_POTION",
|
||||
"hyper_potion",
|
||||
RewardId.HYPER_POTION,
|
||||
200,
|
||||
50,
|
||||
),
|
||||
[RewardId.MAX_POTION]: new PokemonHpRestoreReward(
|
||||
"modifierType:ModifierType.MAX_POTION",
|
||||
"max_potion",
|
||||
RewardId.MAX_POTION,
|
||||
0,
|
||||
100,
|
||||
),
|
||||
[RewardId.FULL_RESTORE]: new PokemonHpRestoreReward(
|
||||
"modifierType:ModifierType.FULL_RESTORE",
|
||||
"full_restore",
|
||||
RewardId.FULL_RESTORE,
|
||||
0,
|
||||
100,
|
||||
true,
|
||||
),
|
||||
|
||||
[RewardId.REVIVE]: new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50),
|
||||
[RewardId.MAX_REVIVE]: new PokemonReviveReward(
|
||||
"modifierType:ModifierType.MAX_REVIVE",
|
||||
"max_revive",
|
||||
RewardId.MAX_REVIVE,
|
||||
100,
|
||||
),
|
||||
|
||||
[RewardId.FULL_HEAL]: new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal"),
|
||||
|
||||
[RewardId.ETHER]: new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10),
|
||||
[RewardId.MAX_ETHER]: new PokemonPpRestoreReward(
|
||||
"modifierType:ModifierType.MAX_ETHER",
|
||||
"max_ether",
|
||||
RewardId.MAX_ETHER,
|
||||
-1,
|
||||
),
|
||||
|
||||
[RewardId.ELIXIR]: new PokemonAllMovePpRestoreReward(
|
||||
"modifierType:ModifierType.ELIXIR",
|
||||
"elixir",
|
||||
RewardId.ELIXIR,
|
||||
10,
|
||||
),
|
||||
[RewardId.MAX_ELIXIR]: new PokemonAllMovePpRestoreReward(
|
||||
"modifierType:ModifierType.MAX_ELIXIR",
|
||||
"max_elixir",
|
||||
RewardId.MAX_ELIXIR,
|
||||
-1,
|
||||
),
|
||||
|
||||
[RewardId.PP_UP]: new PokemonPpUpReward("modifierType:ModifierType.PP_UP", "pp_up", RewardId.PP_UP, 1),
|
||||
[RewardId.PP_MAX]: new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3),
|
||||
|
||||
[RewardId.MINT]: new MintRewardGenerator(),
|
||||
|
||||
[RewardId.TERA_SHARD]: new TeraTypeRewardGenerator(),
|
||||
|
||||
[RewardId.TM_COMMON]: new TmRewardGenerator(RarityTier.COMMON),
|
||||
[RewardId.TM_GREAT]: new TmRewardGenerator(RarityTier.GREAT),
|
||||
[RewardId.TM_ULTRA]: new TmRewardGenerator(RarityTier.ULTRA),
|
||||
|
||||
[RewardId.MEMORY_MUSHROOM]: new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"),
|
||||
|
||||
[RewardId.DNA_SPLICERS]: new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"),
|
||||
|
||||
// Form change items
|
||||
[RewardId.FORM_CHANGE_ITEM]: new FormChangeItemRewardGenerator(false),
|
||||
[RewardId.RARE_FORM_CHANGE_ITEM]: new FormChangeItemRewardGenerator(true),
|
||||
|
||||
// Held items
|
||||
|
||||
[RewardId.SPECIES_STAT_BOOSTER]: new SpeciesStatBoosterRewardGenerator(false),
|
||||
[RewardId.RARE_SPECIES_STAT_BOOSTER]: new SpeciesStatBoosterRewardGenerator(true),
|
||||
|
||||
[RewardId.BASE_STAT_BOOSTER]: new BaseStatBoosterRewardGenerator(),
|
||||
|
||||
[RewardId.ATTACK_TYPE_BOOSTER]: new AttackTypeBoosterRewardGenerator(),
|
||||
|
||||
[RewardId.BERRY]: new BerryRewardGenerator(),
|
||||
|
||||
// Trainer items
|
||||
|
||||
[RewardId.LURE]: new LapsingTrainerItemReward(TrainerItemId.LURE),
|
||||
[RewardId.SUPER_LURE]: new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE),
|
||||
[RewardId.MAX_LURE]: new LapsingTrainerItemReward(TrainerItemId.MAX_LURE),
|
||||
|
||||
[RewardId.TEMP_STAT_STAGE_BOOSTER]: new TempStatStageBoosterRewardGenerator(),
|
||||
|
||||
[RewardId.DIRE_HIT]: new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT),
|
||||
} as const satisfies {
|
||||
[k in RewardId]: Reward | RewardGenerator;
|
||||
};
|
||||
|
||||
export type allRewardsType = typeof allRewards;
|
||||
114
src/items/all-trainer-items.ts
Normal file
114
src/items/all-trainer-items.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { allTrainerItems } from "#data/data-lists";
|
||||
import { Stat, type TempBattleStat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { TrainerItemId } from "#enums/trainer-item-id";
|
||||
import {
|
||||
CriticalCatchChanceBoosterTrainerItem,
|
||||
DoubleBattleChanceBoosterTrainerItem,
|
||||
EnemyAttackStatusEffectChanceTrainerItem,
|
||||
EnemyDamageBoosterTrainerItem,
|
||||
EnemyDamageReducerTrainerItem,
|
||||
EnemyEndureChanceTrainerItem,
|
||||
EnemyFusionChanceTrainerItem,
|
||||
EnemyStatusEffectHealChanceTrainerItem,
|
||||
EnemyTurnHealTrainerItem,
|
||||
ExpBoosterTrainerItem,
|
||||
ExtraRewardTrainerItem,
|
||||
HealingBoosterTrainerItem,
|
||||
HealShopCostTrainerItem,
|
||||
HiddenAbilityChanceBoosterTrainerItem,
|
||||
LevelIncrementBoosterTrainerItem,
|
||||
MoneyMultiplierTrainerItem,
|
||||
PreserveBerryTrainerItem,
|
||||
ShinyRateBoosterTrainerItem,
|
||||
TempAccuracyBoosterTrainerItem,
|
||||
TempCritBoosterTrainerItem,
|
||||
TempStatStageBoosterTrainerItem,
|
||||
TrainerItem,
|
||||
tempStatToTrainerItem,
|
||||
} from "#items/trainer-item";
|
||||
|
||||
export function initTrainerItems() {
|
||||
allTrainerItems[TrainerItemId.MAP] = new TrainerItem(TrainerItemId.MAP, 1);
|
||||
allTrainerItems[TrainerItemId.IV_SCANNER] = new TrainerItem(TrainerItemId.IV_SCANNER, 1);
|
||||
allTrainerItems[TrainerItemId.LOCK_CAPSULE] = new TrainerItem(TrainerItemId.LOCK_CAPSULE, 1);
|
||||
allTrainerItems[TrainerItemId.MEGA_BRACELET] = new TrainerItem(TrainerItemId.MEGA_BRACELET, 1);
|
||||
allTrainerItems[TrainerItemId.DYNAMAX_BAND] = new TrainerItem(TrainerItemId.DYNAMAX_BAND, 1);
|
||||
allTrainerItems[TrainerItemId.TERA_ORB] = new TrainerItem(TrainerItemId.TERA_ORB, 1);
|
||||
|
||||
allTrainerItems[TrainerItemId.OVAL_CHARM] = new TrainerItem(TrainerItemId.OVAL_CHARM, 5);
|
||||
allTrainerItems[TrainerItemId.EXP_SHARE] = new TrainerItem(TrainerItemId.EXP_SHARE, 5);
|
||||
allTrainerItems[TrainerItemId.EXP_BALANCE] = new TrainerItem(TrainerItemId.EXP_BALANCE, 4);
|
||||
|
||||
allTrainerItems[TrainerItemId.CANDY_JAR] = new LevelIncrementBoosterTrainerItem(TrainerItemId.CANDY_JAR, 99);
|
||||
allTrainerItems[TrainerItemId.BERRY_POUCH] = new PreserveBerryTrainerItem(TrainerItemId.BERRY_POUCH, 3);
|
||||
|
||||
allTrainerItems[TrainerItemId.HEALING_CHARM] = new HealingBoosterTrainerItem(TrainerItemId.HEALING_CHARM, 0.1, 5);
|
||||
allTrainerItems[TrainerItemId.EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.EXP_CHARM, 25, 99);
|
||||
allTrainerItems[TrainerItemId.SUPER_EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.SUPER_EXP_CHARM, 60, 30);
|
||||
allTrainerItems[TrainerItemId.GOLDEN_EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.GOLDEN_EXP_CHARM, 100, 10);
|
||||
allTrainerItems[TrainerItemId.AMULET_COIN] = new MoneyMultiplierTrainerItem(TrainerItemId.AMULET_COIN, 5);
|
||||
|
||||
allTrainerItems[TrainerItemId.ABILITY_CHARM] = new HiddenAbilityChanceBoosterTrainerItem(
|
||||
TrainerItemId.ABILITY_CHARM,
|
||||
4,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.GOLDEN_POKEBALL] = new ExtraRewardTrainerItem(TrainerItemId.GOLDEN_POKEBALL, 3);
|
||||
allTrainerItems[TrainerItemId.SHINY_CHARM] = new ShinyRateBoosterTrainerItem(TrainerItemId.SHINY_CHARM, 4);
|
||||
allTrainerItems[TrainerItemId.CATCHING_CHARM] = new CriticalCatchChanceBoosterTrainerItem(
|
||||
TrainerItemId.CATCHING_CHARM,
|
||||
3,
|
||||
);
|
||||
|
||||
allTrainerItems[TrainerItemId.BLACK_SLUDGE] = new HealShopCostTrainerItem(TrainerItemId.BLACK_SLUDGE, 2.5, 1);
|
||||
allTrainerItems[TrainerItemId.GOLDEN_BUG_NET] = new TrainerItem(TrainerItemId.GOLDEN_BUG_NET, 1);
|
||||
|
||||
allTrainerItems[TrainerItemId.LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.LURE, 10);
|
||||
allTrainerItems[TrainerItemId.SUPER_LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.SUPER_LURE, 15);
|
||||
allTrainerItems[TrainerItemId.MAX_LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.MAX_LURE, 30);
|
||||
|
||||
for (const [statKey, trainerItemType] of Object.entries(tempStatToTrainerItem)) {
|
||||
const stat = Number(statKey) as TempBattleStat;
|
||||
if (stat === Stat.ACC) {
|
||||
allTrainerItems[trainerItemType] = new TempAccuracyBoosterTrainerItem(trainerItemType, 5);
|
||||
} else {
|
||||
allTrainerItems[trainerItemType] = new TempStatStageBoosterTrainerItem(trainerItemType, stat, 5);
|
||||
}
|
||||
}
|
||||
allTrainerItems[TrainerItemId.DIRE_HIT] = new TempCritBoosterTrainerItem(TrainerItemId.DIRE_HIT, 5);
|
||||
|
||||
allTrainerItems[TrainerItemId.ENEMY_DAMAGE_BOOSTER] = new EnemyDamageBoosterTrainerItem(
|
||||
TrainerItemId.ENEMY_DAMAGE_BOOSTER,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_DAMAGE_REDUCTION] = new EnemyDamageReducerTrainerItem(
|
||||
TrainerItemId.ENEMY_DAMAGE_REDUCTION,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_HEAL] = new EnemyTurnHealTrainerItem(TrainerItemId.ENEMY_HEAL, 10);
|
||||
allTrainerItems[TrainerItemId.ENEMY_ATTACK_POISON_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_ATTACK_POISON_CHANCE,
|
||||
StatusEffect.POISON,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE,
|
||||
StatusEffect.PARALYSIS,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_ATTACK_BURN_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_ATTACK_BURN_CHANCE,
|
||||
StatusEffect.BURN,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE] = new EnemyStatusEffectHealChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_ENDURE_CHANCE] = new EnemyEndureChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_ENDURE_CHANCE,
|
||||
10,
|
||||
);
|
||||
allTrainerItems[TrainerItemId.ENEMY_FUSED_CHANCE] = new EnemyFusionChanceTrainerItem(
|
||||
TrainerItemId.ENEMY_FUSED_CHANCE,
|
||||
10,
|
||||
);
|
||||
}
|
||||
47
src/items/apply-trainer-items.ts
Normal file
47
src/items/apply-trainer-items.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { allTrainerItems } from "#data/data-lists";
|
||||
import {
|
||||
type BooleanHolderParams,
|
||||
type NumberHolderParams,
|
||||
type PokemonParams,
|
||||
type PreserveBerryParams,
|
||||
TrainerItemEffect,
|
||||
} from "#items/trainer-item";
|
||||
import type { TrainerItemManager } from "#items/trainer-item-manager";
|
||||
|
||||
export type ApplyTrainerItemsParams = {
|
||||
[TrainerItemEffect.LEVEL_INCREMENT_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.PRESERVE_BERRY]: PreserveBerryParams;
|
||||
[TrainerItemEffect.HEALING_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.EXP_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.MONEY_MULTIPLIER]: NumberHolderParams;
|
||||
[TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.SHINY_RATE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.CRITICAL_CATCH_CHANCE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.EXTRA_REWARD]: NumberHolderParams;
|
||||
[TrainerItemEffect.HEAL_SHOP_COST]: NumberHolderParams;
|
||||
[TrainerItemEffect.DOUBLE_BATTLE_CHANCE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.TEMP_ACCURACY_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.TEMP_CRIT_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.ENEMY_DAMAGE_BOOSTER]: NumberHolderParams;
|
||||
[TrainerItemEffect.ENEMY_DAMAGE_REDUCER]: NumberHolderParams;
|
||||
[TrainerItemEffect.ENEMY_HEAL]: PokemonParams;
|
||||
[TrainerItemEffect.ENEMY_ATTACK_STATUS_CHANCE]: PokemonParams;
|
||||
[TrainerItemEffect.ENEMY_STATUS_HEAL_CHANCE]: PokemonParams;
|
||||
[TrainerItemEffect.ENEMY_ENDURE_CHANCE]: PokemonParams;
|
||||
[TrainerItemEffect.ENEMY_FUSED_CHANCE]: BooleanHolderParams;
|
||||
};
|
||||
|
||||
export function applyTrainerItems<T extends TrainerItemEffect>(
|
||||
effect: T,
|
||||
manager: TrainerItemManager,
|
||||
params: ApplyTrainerItemsParams[T],
|
||||
) {
|
||||
if (manager) {
|
||||
for (const item of Object.keys(manager.trainerItems)) {
|
||||
if (allTrainerItems[item].effects.includes(effect)) {
|
||||
allTrainerItems[item].apply(manager, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/items/held-item-data-types.ts
Normal file
93
src/items/held-item-data-types.ts
Normal file
@ -0,0 +1,93 @@
|
||||
// TODO: move all types to `src/@types/` and all functions to a utility place
|
||||
|
||||
import type { FormChangeItem } from "#enums/form-change-item";
|
||||
import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import type { RarityTier } from "#enums/reward-tier";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
|
||||
export type HeldItemData = {
|
||||
stack: number;
|
||||
/**
|
||||
* Whether this item is currently disabled.
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* The item's current cooldown.
|
||||
* @defaultValue `0`
|
||||
*/
|
||||
cooldown?: number;
|
||||
};
|
||||
|
||||
export type HeldItemDataMap = {
|
||||
[key in HeldItemId]?: HeldItemData;
|
||||
};
|
||||
|
||||
export type HeldItemSpecs = HeldItemData & {
|
||||
id: HeldItemId;
|
||||
};
|
||||
|
||||
export function isHeldItemSpecs(entry: any): entry is HeldItemSpecs {
|
||||
return typeof entry.id === "number" && "stack" in entry;
|
||||
}
|
||||
|
||||
// Types used for form change items
|
||||
export interface FormChangeItemData {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export type FormChangeItemPropertyMap = {
|
||||
[key in FormChangeItem]?: FormChangeItemData;
|
||||
};
|
||||
|
||||
export type FormChangeItemSpecs = FormChangeItemData & {
|
||||
id: FormChangeItem;
|
||||
};
|
||||
|
||||
export function isFormChangeItemSpecs(entry: any): entry is FormChangeItemSpecs {
|
||||
return typeof entry.id === "number" && "active" in entry;
|
||||
}
|
||||
|
||||
export type HeldItemWeights = {
|
||||
[key in HeldItemId]?: number;
|
||||
};
|
||||
|
||||
export type HeldItemWeightFunc = (party: Pokemon[]) => number;
|
||||
|
||||
export type HeldItemCategoryEntry = HeldItemData & {
|
||||
id: HeldItemCategoryId;
|
||||
customWeights?: HeldItemWeights;
|
||||
};
|
||||
|
||||
export function isHeldItemCategoryEntry(entry: any): entry is HeldItemCategoryEntry {
|
||||
return entry?.id && isHeldItemCategoryEntry(entry.id) && "customWeights" in entry;
|
||||
}
|
||||
|
||||
type HeldItemPoolEntry = {
|
||||
entry: HeldItemId | HeldItemCategoryId | HeldItemCategoryEntry | HeldItemSpecs;
|
||||
weight: number | HeldItemWeightFunc;
|
||||
};
|
||||
|
||||
export type HeldItemPool = HeldItemPoolEntry[];
|
||||
|
||||
export function isHeldItemPool(value: any): value is HeldItemPool {
|
||||
return Array.isArray(value) && value.every(entry => "entry" in entry && "weight" in entry);
|
||||
}
|
||||
|
||||
export type HeldItemTieredPool = {
|
||||
[key in RarityTier]?: HeldItemPool;
|
||||
};
|
||||
|
||||
type HeldItemConfigurationEntry = {
|
||||
entry: HeldItemId | HeldItemCategoryId | HeldItemCategoryEntry | HeldItemSpecs | HeldItemPool | FormChangeItemSpecs;
|
||||
count?: number | (() => number);
|
||||
};
|
||||
|
||||
export type HeldItemConfiguration = HeldItemConfigurationEntry[];
|
||||
|
||||
export type PokemonItemMap = {
|
||||
item: HeldItemSpecs | FormChangeItemSpecs;
|
||||
pokemonId: number;
|
||||
};
|
||||
|
||||
export type HeldItemSaveData = (HeldItemSpecs | FormChangeItemSpecs)[];
|
||||
47
src/items/held-item-default-tiers.ts
Normal file
47
src/items/held-item-default-tiers.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { getHeldItemCategory, HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
|
||||
export const heldItemRarities = {
|
||||
[HeldItemCategoryId.BERRY]: RarityTier.COMMON,
|
||||
|
||||
[HeldItemCategoryId.BASE_STAT_BOOST]: RarityTier.GREAT,
|
||||
[HeldItemId.WHITE_HERB]: RarityTier.GREAT,
|
||||
[HeldItemId.METAL_POWDER]: RarityTier.GREAT,
|
||||
[HeldItemId.QUICK_POWDER]: RarityTier.GREAT,
|
||||
[HeldItemId.DEEP_SEA_SCALE]: RarityTier.GREAT,
|
||||
[HeldItemId.DEEP_SEA_TOOTH]: RarityTier.GREAT,
|
||||
[HeldItemId.SOOTHE_BELL]: RarityTier.GREAT,
|
||||
|
||||
[HeldItemCategoryId.TYPE_ATTACK_BOOSTER]: RarityTier.ULTRA,
|
||||
[HeldItemId.REVIVER_SEED]: RarityTier.ULTRA,
|
||||
[HeldItemId.LIGHT_BALL]: RarityTier.ULTRA,
|
||||
[HeldItemId.EVIOLITE]: RarityTier.ULTRA,
|
||||
[HeldItemId.QUICK_CLAW]: RarityTier.ULTRA,
|
||||
[HeldItemId.MYSTICAL_ROCK]: RarityTier.ULTRA,
|
||||
[HeldItemId.WIDE_LENS]: RarityTier.ULTRA,
|
||||
[HeldItemId.GOLDEN_PUNCH]: RarityTier.ULTRA,
|
||||
[HeldItemId.TOXIC_ORB]: RarityTier.ULTRA,
|
||||
[HeldItemId.FLAME_ORB]: RarityTier.ULTRA,
|
||||
[HeldItemId.LUCKY_EGG]: RarityTier.ULTRA,
|
||||
|
||||
[HeldItemId.FOCUS_BAND]: RarityTier.ROGUE,
|
||||
[HeldItemId.KINGS_ROCK]: RarityTier.ROGUE,
|
||||
[HeldItemId.LEFTOVERS]: RarityTier.ROGUE,
|
||||
[HeldItemId.SHELL_BELL]: RarityTier.ROGUE,
|
||||
[HeldItemId.GRIP_CLAW]: RarityTier.ROGUE,
|
||||
[HeldItemId.SOUL_DEW]: RarityTier.ROGUE,
|
||||
[HeldItemId.BATON]: RarityTier.ROGUE,
|
||||
[HeldItemId.GOLDEN_EGG]: RarityTier.ULTRA,
|
||||
|
||||
[HeldItemId.MINI_BLACK_HOLE]: RarityTier.MASTER,
|
||||
[HeldItemId.MULTI_LENS]: RarityTier.MASTER,
|
||||
};
|
||||
|
||||
export function getHeldItemTier(item: HeldItemId): RarityTier {
|
||||
let tier = heldItemRarities[item];
|
||||
if (!tier) {
|
||||
const category = getHeldItemCategory(item);
|
||||
tier = heldItemRarities[category];
|
||||
}
|
||||
return tier ?? RarityTier.LUXURY;
|
||||
}
|
||||
252
src/items/held-item-manager.ts
Normal file
252
src/items/held-item-manager.ts
Normal file
@ -0,0 +1,252 @@
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import type { FormChangeItem } from "#enums/form-change-item";
|
||||
import {
|
||||
type HeldItemCategoryId,
|
||||
type HeldItemId,
|
||||
isCategoryId,
|
||||
isItemInCategory,
|
||||
isItemInRequested,
|
||||
} from "#enums/held-item-id";
|
||||
import {
|
||||
type FormChangeItemData,
|
||||
type FormChangeItemPropertyMap,
|
||||
type FormChangeItemSpecs,
|
||||
type HeldItemConfiguration,
|
||||
type HeldItemDataMap,
|
||||
type HeldItemSaveData,
|
||||
type HeldItemSpecs,
|
||||
isHeldItemSpecs,
|
||||
} from "#items/held-item-data-types";
|
||||
import { getTypedKeys } from "#utils/common";
|
||||
|
||||
export class HeldItemManager {
|
||||
// TODO: There should be a way of making these private...
|
||||
public heldItems: HeldItemDataMap;
|
||||
public formChangeItems: FormChangeItemPropertyMap;
|
||||
|
||||
constructor() {
|
||||
this.heldItems = {};
|
||||
this.formChangeItems = {};
|
||||
}
|
||||
|
||||
getItemSpecs(id: HeldItemId): HeldItemSpecs | undefined {
|
||||
const item = this.heldItems[id];
|
||||
if (item) {
|
||||
const itemSpecs: HeldItemSpecs = {
|
||||
...item,
|
||||
id,
|
||||
};
|
||||
return itemSpecs;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
generateHeldItemConfiguration(restrictedIds?: HeldItemId[]): HeldItemConfiguration {
|
||||
const config: HeldItemConfiguration = [];
|
||||
for (const [id, item] of this.getHeldItemEntries()) {
|
||||
// TODO: `in` breaks with arrays
|
||||
if (item && (!restrictedIds || id in restrictedIds)) {
|
||||
const specs: HeldItemSpecs = { ...item, id };
|
||||
config.push({ entry: specs, count: 1 });
|
||||
}
|
||||
}
|
||||
for (const [id, item] of this.getFormChangeItemEntries()) {
|
||||
if (item) {
|
||||
const specs: FormChangeItemSpecs = { ...item, id };
|
||||
config.push({ entry: specs, count: 1 });
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
generateSaveData(): HeldItemSaveData {
|
||||
const saveData: HeldItemSaveData = [];
|
||||
for (const [id, item] of this.getHeldItemEntries()) {
|
||||
if (item) {
|
||||
const specs: HeldItemSpecs = { ...item, id };
|
||||
saveData.push(specs);
|
||||
}
|
||||
}
|
||||
for (const [id, item] of this.getFormChangeItemEntries()) {
|
||||
if (item) {
|
||||
const specs: FormChangeItemSpecs = { ...item, id };
|
||||
saveData.push(specs);
|
||||
}
|
||||
}
|
||||
return saveData;
|
||||
}
|
||||
|
||||
getHeldItems(): HeldItemId[] {
|
||||
return getTypedKeys(this.heldItems);
|
||||
}
|
||||
|
||||
private getHeldItemEntries(): [HeldItemId, HeldItemSpecs][] {
|
||||
return Object.entries(this.heldItems) as unknown as [HeldItemId, HeldItemSpecs][];
|
||||
}
|
||||
|
||||
getTransferableHeldItems(): HeldItemId[] {
|
||||
return this.getHeldItems().filter(k => allHeldItems[k].isTransferable);
|
||||
}
|
||||
|
||||
getStealableHeldItems(): HeldItemId[] {
|
||||
return this.getHeldItems().filter(k => allHeldItems[k].isStealable);
|
||||
}
|
||||
|
||||
getSuppressableHeldItems(): HeldItemId[] {
|
||||
return this.getHeldItems().filter(k => allHeldItems[k].isSuppressable);
|
||||
}
|
||||
|
||||
hasItem(itemType: HeldItemId | HeldItemCategoryId): boolean {
|
||||
if (isCategoryId(itemType)) {
|
||||
return this.getHeldItems().some(id => isItemInCategory(id, itemType as HeldItemCategoryId));
|
||||
}
|
||||
return itemType in this.heldItems;
|
||||
}
|
||||
|
||||
hasTransferableItem(itemType: HeldItemId | HeldItemCategoryId): boolean {
|
||||
if (isCategoryId(itemType)) {
|
||||
return this.getHeldItems().some(
|
||||
id => isItemInCategory(id, itemType as HeldItemCategoryId) && allHeldItems[id].isTransferable,
|
||||
);
|
||||
}
|
||||
return itemType in this.heldItems && allHeldItems[itemType].isTransferable;
|
||||
}
|
||||
|
||||
// TODO: Consider renaming?
|
||||
getStack(itemType: HeldItemId): number {
|
||||
const item = this.heldItems[itemType];
|
||||
return item?.stack ?? 0;
|
||||
}
|
||||
|
||||
// Use for tests if necessary to go over stack limit
|
||||
// TODO: Do we need this? We can just use overrides
|
||||
setStack(itemType: HeldItemId, stack: number): void {
|
||||
const item = this.heldItems[itemType];
|
||||
if (item) {
|
||||
item.stack = stack;
|
||||
}
|
||||
}
|
||||
|
||||
isMaxStack(itemType: HeldItemId): boolean {
|
||||
const item = this.heldItems[itemType];
|
||||
return item ? item.stack >= allHeldItems[itemType].getMaxStackCount() : false;
|
||||
}
|
||||
|
||||
overrideItems(newItems: HeldItemDataMap) {
|
||||
this.heldItems = newItems;
|
||||
// The following is to allow randomly generated item configs to have stack 0
|
||||
for (const [item, properties] of this.getHeldItemEntries()) {
|
||||
if (!properties || properties.stack <= 0) {
|
||||
delete this.heldItems[item];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(itemType: HeldItemId | HeldItemSpecs, addStack = 1): boolean {
|
||||
if (isHeldItemSpecs(itemType)) {
|
||||
return this.addItemWithSpecs(itemType);
|
||||
}
|
||||
|
||||
const maxStack = allHeldItems[itemType].getMaxStackCount();
|
||||
const item = this.heldItems[itemType];
|
||||
|
||||
if (item) {
|
||||
// TODO: We may want an error message of some kind instead
|
||||
if (item.stack < maxStack) {
|
||||
item.stack = Math.min(item.stack + addStack, maxStack);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
this.heldItems[itemType] = { stack: Math.min(addStack, maxStack) };
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addItemWithSpecs(itemSpecs: HeldItemSpecs): boolean {
|
||||
const id = itemSpecs.id;
|
||||
const maxStack = allHeldItems[id].getMaxStackCount();
|
||||
const item = this.heldItems[id];
|
||||
|
||||
const tempStack = item?.stack ?? 0;
|
||||
|
||||
this.heldItems[id] = itemSpecs;
|
||||
this.heldItems[id].stack = Math.min(itemSpecs.stack + tempStack, maxStack);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
remove(itemType: HeldItemId, removeStack = 1, all = false) {
|
||||
const item = this.heldItems[itemType];
|
||||
|
||||
if (item) {
|
||||
item.stack -= removeStack;
|
||||
|
||||
if (all || item.stack <= 0) {
|
||||
// TODO: Delete is bad for performance
|
||||
delete this.heldItems[itemType];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterRequestedItems(requestedItems: (HeldItemCategoryId | HeldItemId)[], transferableOnly = true, exclude = false) {
|
||||
const currentItems = transferableOnly ? this.getTransferableHeldItems() : this.getHeldItems();
|
||||
return currentItems.filter(it => !exclude && isItemInRequested(it, requestedItems));
|
||||
}
|
||||
|
||||
getHeldItemCount(): number {
|
||||
let total = 0;
|
||||
for (const properties of Object.values(this.heldItems)) {
|
||||
total += properties?.stack ?? 0;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
addFormChangeItem(id: FormChangeItem) {
|
||||
if (!(id in this.formChangeItems)) {
|
||||
this.formChangeItems[id] = { active: false };
|
||||
}
|
||||
}
|
||||
|
||||
addFormChangeItemWithSpecs(item: FormChangeItemSpecs) {
|
||||
if (!(item.id in this.formChangeItems)) {
|
||||
this.formChangeItems[item.id] = { active: item.active };
|
||||
}
|
||||
}
|
||||
|
||||
hasFormChangeItem(id: FormChangeItem): boolean {
|
||||
return id in this.formChangeItems;
|
||||
}
|
||||
|
||||
hasActiveFormChangeItem(id: FormChangeItem): boolean {
|
||||
const item = this.formChangeItems[id];
|
||||
if (item) {
|
||||
return item.active;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getFormChangeItems(): FormChangeItem[] {
|
||||
return getTypedKeys(this.formChangeItems);
|
||||
}
|
||||
|
||||
private getFormChangeItemEntries(): [FormChangeItem, FormChangeItemData | undefined][] {
|
||||
return Object.entries(this.formChangeItems) as unknown as [FormChangeItem, FormChangeItemData | undefined][];
|
||||
}
|
||||
|
||||
getActiveFormChangeItems(): FormChangeItem[] {
|
||||
return this.getFormChangeItems().filter(m => this.formChangeItems[m]?.active);
|
||||
}
|
||||
|
||||
toggleActive(id: FormChangeItem) {
|
||||
const item = this.formChangeItems[id];
|
||||
if (item) {
|
||||
item.active = !item.active;
|
||||
}
|
||||
}
|
||||
|
||||
clearItems() {
|
||||
this.heldItems = {};
|
||||
this.formChangeItems = {};
|
||||
}
|
||||
}
|
||||
324
src/items/held-item-pool.ts
Normal file
324
src/items/held-item-pool.ts
Normal file
@ -0,0 +1,324 @@
|
||||
import { allHeldItems } from "#data/data-lists";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { HeldItemCategoryId, HeldItemId, HeldItemNames, isCategoryId } from "#enums/held-item-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import { HeldItemPoolType } from "#enums/reward-pool-type";
|
||||
import { RarityTier } from "#enums/reward-tier";
|
||||
import { PERMANENT_STATS } from "#enums/stat";
|
||||
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import { attackTypeToHeldItem } from "#items/attack-type-booster";
|
||||
import { permanentStatToHeldItem } from "#items/base-stat-booster";
|
||||
import { berryTypeToHeldItem } from "#items/berry";
|
||||
import {
|
||||
type HeldItemConfiguration,
|
||||
type HeldItemPool,
|
||||
type HeldItemSaveData,
|
||||
type HeldItemSpecs,
|
||||
type HeldItemTieredPool,
|
||||
type HeldItemWeights,
|
||||
isFormChangeItemSpecs,
|
||||
isHeldItemCategoryEntry,
|
||||
isHeldItemPool,
|
||||
isHeldItemSpecs,
|
||||
} from "#items/held-item-data-types";
|
||||
import { coerceArray, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
|
||||
export const wildHeldItemPool: HeldItemTieredPool = {};
|
||||
|
||||
export const trainerHeldItemPool: HeldItemTieredPool = {};
|
||||
|
||||
export const dailyStarterHeldItemPool: HeldItemTieredPool = {};
|
||||
|
||||
export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) {
|
||||
for (const p of party) {
|
||||
for (let m = 0; m < 3; m++) {
|
||||
const tierValue = randSeedInt(64);
|
||||
|
||||
const tier = getDailyRarityTier(tierValue);
|
||||
|
||||
const item = getNewHeldItemFromPool(
|
||||
getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool,
|
||||
p,
|
||||
party,
|
||||
);
|
||||
p.heldItemManager.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDailyRarityTier(tierValue: number): RarityTier {
|
||||
if (tierValue > 25) {
|
||||
return RarityTier.COMMON;
|
||||
}
|
||||
if (tierValue > 12) {
|
||||
return RarityTier.GREAT;
|
||||
}
|
||||
if (tierValue > 4) {
|
||||
return RarityTier.ULTRA;
|
||||
}
|
||||
if (tierValue > 0) {
|
||||
return RarityTier.ROGUE;
|
||||
}
|
||||
return RarityTier.MASTER;
|
||||
}
|
||||
|
||||
function getHeldItemPool(poolType: HeldItemPoolType): HeldItemTieredPool {
|
||||
let pool: HeldItemTieredPool;
|
||||
switch (poolType) {
|
||||
case HeldItemPoolType.WILD:
|
||||
pool = wildHeldItemPool;
|
||||
break;
|
||||
case HeldItemPoolType.TRAINER:
|
||||
pool = trainerHeldItemPool;
|
||||
break;
|
||||
case HeldItemPoolType.DAILY_STARTER:
|
||||
pool = dailyStarterHeldItemPool;
|
||||
break;
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
// TODO: Add proper documentation to this function (once it fully works...)
|
||||
export function assignEnemyHeldItemsForWave(
|
||||
waveIndex: number,
|
||||
count: number,
|
||||
enemy: EnemyPokemon,
|
||||
poolType: HeldItemPoolType.WILD | HeldItemPoolType.TRAINER,
|
||||
upgradeChance = 0,
|
||||
): void {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const item = getNewHeldItemFromTieredPool(
|
||||
getHeldItemPool(poolType),
|
||||
enemy,
|
||||
upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0,
|
||||
);
|
||||
if (item) {
|
||||
enemy.heldItemManager.add(item);
|
||||
}
|
||||
}
|
||||
if (!(waveIndex % 1000)) {
|
||||
enemy.heldItemManager.add(HeldItemId.MINI_BLACK_HOLE);
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomTier(): RarityTier {
|
||||
const tierValue = randSeedInt(1024);
|
||||
|
||||
if (tierValue > 255) {
|
||||
return RarityTier.COMMON;
|
||||
}
|
||||
if (tierValue > 60) {
|
||||
return RarityTier.GREAT;
|
||||
}
|
||||
if (tierValue > 12) {
|
||||
return RarityTier.ULTRA;
|
||||
}
|
||||
if (tierValue) {
|
||||
return RarityTier.ROGUE;
|
||||
}
|
||||
return RarityTier.MASTER;
|
||||
}
|
||||
|
||||
function determineItemPoolTier(pool: HeldItemTieredPool, upgradeCount?: number): RarityTier {
|
||||
let tier = getRandomTier();
|
||||
|
||||
if (!upgradeCount) {
|
||||
upgradeCount = 0;
|
||||
}
|
||||
|
||||
tier += upgradeCount;
|
||||
while (tier && !pool[tier]?.length) {
|
||||
tier--;
|
||||
if (upgradeCount) {
|
||||
upgradeCount--;
|
||||
}
|
||||
}
|
||||
|
||||
return tier;
|
||||
}
|
||||
|
||||
function getNewHeldItemFromTieredPool(
|
||||
pool: HeldItemTieredPool,
|
||||
pokemon: Pokemon,
|
||||
upgradeCount: number,
|
||||
): HeldItemId | HeldItemSpecs {
|
||||
const tier = determineItemPoolTier(pool, upgradeCount);
|
||||
const tierPool = pool[tier];
|
||||
|
||||
return getNewHeldItemFromPool(tierPool!, pokemon);
|
||||
}
|
||||
|
||||
export function getNewVitaminHeldItem(customWeights: HeldItemWeights = {}, target?: Pokemon): HeldItemId {
|
||||
const items = PERMANENT_STATS.map(s => permanentStatToHeldItem[s]);
|
||||
const weights = items.map(t => (target?.heldItemManager.isMaxStack(t) ? 0 : (customWeights[t] ?? 1)));
|
||||
const pickedIndex = pickWeightedIndex(weights);
|
||||
return !isNullOrUndefined(pickedIndex) ? items[pickedIndex] : 0;
|
||||
}
|
||||
|
||||
export function getNewBerryHeldItem(customWeights: HeldItemWeights = {}, target?: Pokemon): HeldItemId {
|
||||
const berryTypes = getEnumValues(BerryType);
|
||||
const items = berryTypes.map(b => berryTypeToHeldItem[b]);
|
||||
|
||||
const weights = items.map(t =>
|
||||
target?.heldItemManager.isMaxStack(t)
|
||||
? 0
|
||||
: (customWeights[t] ??
|
||||
(t === HeldItemId.SITRUS_BERRY || t === HeldItemId.LUM_BERRY || t === HeldItemId.LEPPA_BERRY))
|
||||
? 2
|
||||
: 1,
|
||||
);
|
||||
|
||||
const pickedIndex = pickWeightedIndex(weights);
|
||||
return !isNullOrUndefined(pickedIndex) ? items[pickedIndex] : 0;
|
||||
}
|
||||
|
||||
export function getNewAttackTypeBoosterHeldItem(
|
||||
pokemon: Pokemon | Pokemon[],
|
||||
customWeights: HeldItemWeights = {},
|
||||
target?: Pokemon,
|
||||
): HeldItemId | null {
|
||||
const party = coerceArray(pokemon);
|
||||
|
||||
// TODO: make this consider moves or abilities that change types
|
||||
const attackMoveTypes = party.flatMap(p =>
|
||||
p
|
||||
.getMoveset()
|
||||
.filter(m => m.getMove().is("AttackMove"))
|
||||
.map(m => p.getMoveType(m.getMove(), true)),
|
||||
);
|
||||
if (!attackMoveTypes.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const attackMoveTypeWeights = attackMoveTypes.reduce((map, type) => {
|
||||
const current = map.get(type) ?? 0;
|
||||
if (current < 3) {
|
||||
map.set(type, current + 1);
|
||||
}
|
||||
return map;
|
||||
}, new Map<PokemonType, number>());
|
||||
|
||||
const types = Array.from(attackMoveTypeWeights.keys());
|
||||
|
||||
const weights = types.map(type =>
|
||||
target?.heldItemManager.isMaxStack(attackTypeToHeldItem[type])
|
||||
? 0
|
||||
: (customWeights[attackTypeToHeldItem[type]] ?? attackMoveTypeWeights.get(type)!),
|
||||
);
|
||||
|
||||
const pickedIndex = pickWeightedIndex(weights);
|
||||
return !isNullOrUndefined(pickedIndex) ? attackTypeToHeldItem[types[pickedIndex]] : 0;
|
||||
}
|
||||
|
||||
export function getNewHeldItemFromCategory(
|
||||
id: HeldItemCategoryId,
|
||||
pokemon: Pokemon | Pokemon[],
|
||||
customWeights: HeldItemWeights = {},
|
||||
target?: Pokemon,
|
||||
): HeldItemId | null {
|
||||
if (id === HeldItemCategoryId.BERRY) {
|
||||
return getNewBerryHeldItem(customWeights, target);
|
||||
}
|
||||
if (id === HeldItemCategoryId.VITAMIN) {
|
||||
return getNewVitaminHeldItem(customWeights, target);
|
||||
}
|
||||
if (id === HeldItemCategoryId.TYPE_ATTACK_BOOSTER) {
|
||||
return getNewAttackTypeBoosterHeldItem(pokemon, customWeights, target);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPoolWeights(pool: HeldItemPool, pokemon: Pokemon): number[] {
|
||||
return pool.map(p => {
|
||||
let weight = typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight;
|
||||
|
||||
if (typeof p.entry === "number" && !isCategoryId(p.entry)) {
|
||||
const itemId = p.entry as HeldItemId;
|
||||
console.log("ITEM ID: ", itemId, HeldItemNames[itemId]);
|
||||
console.log(allHeldItems[itemId]);
|
||||
|
||||
if (pokemon.heldItemManager.getStack(itemId) >= allHeldItems[itemId].getMaxStackCount()) {
|
||||
weight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return weight;
|
||||
});
|
||||
}
|
||||
|
||||
function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon, party?: Pokemon[]): HeldItemId | HeldItemSpecs {
|
||||
const weights = getPoolWeights(pool, pokemon);
|
||||
|
||||
const pickedIndex = pickWeightedIndex(weights);
|
||||
if (isNullOrUndefined(pickedIndex)) {
|
||||
return 0;
|
||||
}
|
||||
const entry = pool[pickedIndex].entry;
|
||||
|
||||
if (typeof entry === "number") {
|
||||
if (isCategoryId(entry)) {
|
||||
return getNewHeldItemFromCategory(entry, party ?? pokemon, {}, pokemon) as HeldItemId;
|
||||
}
|
||||
return entry as HeldItemId;
|
||||
}
|
||||
|
||||
if (isHeldItemCategoryEntry(entry)) {
|
||||
return getNewHeldItemFromCategory(entry.id, party ?? pokemon, entry?.customWeights, pokemon) as HeldItemId;
|
||||
}
|
||||
|
||||
return entry as HeldItemSpecs;
|
||||
}
|
||||
|
||||
function assignItemsFromCategory(id: HeldItemCategoryId, pokemon: Pokemon, count: number) {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const newItem = getNewHeldItemFromCategory(id, pokemon, {}, pokemon);
|
||||
if (newItem) {
|
||||
pokemon.heldItemManager.add(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function assignItemsFromConfiguration(config: HeldItemConfiguration, pokemon: Pokemon) {
|
||||
config.forEach(item => {
|
||||
const { entry, count } = item;
|
||||
const actualCount = typeof count === "function" ? count() : (count ?? 1);
|
||||
|
||||
if (typeof entry === "number") {
|
||||
if (isCategoryId(entry)) {
|
||||
assignItemsFromCategory(entry, pokemon, actualCount);
|
||||
}
|
||||
pokemon.heldItemManager.add(entry as HeldItemId, actualCount);
|
||||
}
|
||||
|
||||
if (isHeldItemSpecs(entry)) {
|
||||
pokemon.heldItemManager.add(entry);
|
||||
}
|
||||
|
||||
if (isFormChangeItemSpecs(entry)) {
|
||||
pokemon.heldItemManager.addFormChangeItemWithSpecs(entry);
|
||||
}
|
||||
|
||||
if (isHeldItemCategoryEntry(entry)) {
|
||||
assignItemsFromCategory(entry.id, pokemon, actualCount);
|
||||
}
|
||||
|
||||
if (isHeldItemPool(entry)) {
|
||||
for (let i = 1; i <= actualCount; i++) {
|
||||
const newItem = getNewHeldItemFromPool(entry, pokemon);
|
||||
if (newItem) {
|
||||
pokemon.heldItemManager.add(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Handle form change items
|
||||
export function saveDataToConfig(saveData: HeldItemSaveData): HeldItemConfiguration {
|
||||
const config: HeldItemConfiguration = [];
|
||||
for (const specs of saveData) {
|
||||
config.push({ entry: specs, count: 1 });
|
||||
}
|
||||
return config;
|
||||
}
|
||||
139
src/items/held-item.ts
Normal file
139
src/items/held-item.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { applyAbAttrs } from "#abilities/apply-ab-attrs";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { type HeldItemId, HeldItemNames } from "#enums/held-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class HeldItem {
|
||||
// public pokemonId: number;
|
||||
// TODO: Should this be readonly?
|
||||
public type: HeldItemId;
|
||||
public readonly maxStackCount: number;
|
||||
public isTransferable = true;
|
||||
public isStealable = true;
|
||||
public isSuppressable = true;
|
||||
|
||||
//TODO: If this is actually never changed by any subclass, perhaps it should not be here
|
||||
public soundName = "se/restore";
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount = 1) {
|
||||
this.type = type;
|
||||
this.maxStackCount = maxStackCount;
|
||||
|
||||
this.isTransferable = true;
|
||||
this.isStealable = true;
|
||||
this.isSuppressable = true;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.name`);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.description`);
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return `${HeldItemNames[this.type]?.toLowerCase()}`;
|
||||
}
|
||||
|
||||
// TODO: Aren't these fine as just properties to set in the subclass definition?
|
||||
untransferable(): HeldItem {
|
||||
this.isTransferable = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
unstealable(): HeldItem {
|
||||
this.isStealable = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
unsuppressable(): HeldItem {
|
||||
this.isSuppressable = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114950716
|
||||
getMaxStackCount(): number {
|
||||
return this.maxStackCount;
|
||||
}
|
||||
|
||||
createSummaryIcon(pokemon?: Pokemon, overrideStackCount?: number): Phaser.GameObjects.Container {
|
||||
const stackCount = overrideStackCount ?? (pokemon ? this.getStackCount(pokemon) : 0);
|
||||
|
||||
const container = globalScene.add.container(0, 0);
|
||||
|
||||
const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5);
|
||||
container.add(item);
|
||||
|
||||
const stackText = this.getIconStackText(stackCount);
|
||||
if (stackText) {
|
||||
container.add(stackText);
|
||||
}
|
||||
|
||||
container.setScale(0.5);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
createPokemonIcon(pokemon: Pokemon): Phaser.GameObjects.Container {
|
||||
const container = globalScene.add.container(0, 0);
|
||||
|
||||
const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true);
|
||||
container.add(pokemonIcon);
|
||||
container.setName(pokemon.id.toString());
|
||||
|
||||
const item = globalScene.add
|
||||
.sprite(16, 16, "items")
|
||||
.setScale(0.5)
|
||||
.setOrigin(0, 0.5)
|
||||
.setTexture("items", this.iconName);
|
||||
container.add(item);
|
||||
|
||||
const stackText = this.getIconStackText(this.getStackCount(pokemon));
|
||||
if (stackText) {
|
||||
container.add(stackText);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
getIconStackText(stackCount: number): Phaser.GameObjects.BitmapText | null {
|
||||
if (this.getMaxStackCount() === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11);
|
||||
text.letterSpacing = -0.5;
|
||||
if (stackCount >= this.getMaxStackCount()) {
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114955458
|
||||
text.setTint(0xf89890);
|
||||
}
|
||||
text.setOrigin(0);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
getStackCount(pokemon: Pokemon): number {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
return stackCount;
|
||||
}
|
||||
|
||||
getScoreMultiplier(): number {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConsumableHeldItem extends HeldItem {
|
||||
// Sometimes berries are not eaten, some stuff may not proc unburden...
|
||||
consume(pokemon: Pokemon, isPlayer: boolean, remove = true, unburden = true): void {
|
||||
if (remove) {
|
||||
pokemon.heldItemManager.remove(this.type, 1);
|
||||
// TODO: Turn this into updateItemBar or something
|
||||
globalScene.updateItems(isPlayer);
|
||||
}
|
||||
if (unburden) {
|
||||
applyAbAttrs("PostItemLostAbAttr", { pokemon: pokemon });
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/items/held-items/accuracy-booster.ts
Normal file
46
src/items/held-items/accuracy-booster.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import type { HeldItemId } from "#enums/held-item-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
|
||||
export interface AccuracyBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** Holds the move's accuracy, which may be modified after item application */
|
||||
moveAccuracy: NumberHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @sealed
|
||||
*/
|
||||
export class AccuracyBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.ACCURACY_BOOSTER];
|
||||
|
||||
private accuracyAmount: number;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, accuracy: number) {
|
||||
super(type, maxStackCount);
|
||||
this.accuracyAmount = accuracy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@linkcode PokemonMoveAccuracyBoosterHeldItem} should be applied
|
||||
* @param pokemon - The {@linkcode Pokemon} to apply the move accuracy boost to
|
||||
* @param moveAccuracy - {@linkcode NumberHolder} holding the move accuracy boost
|
||||
* @returns `true` if {@linkcode PokemonMoveAccuracyBoosterHeldItem} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, moveAccuracy?: NumberHolder): boolean {
|
||||
// return super.shouldApply(pokemon, moveAccuracy) && !!moveAccuracy;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies {@linkcode PokemonMoveAccuracyBoosterHeldItem}
|
||||
*/
|
||||
apply({ pokemon, moveAccuracy }: AccuracyBoostParams): true {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
moveAccuracy.value += this.accuracyAmount * stackCount;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
76
src/items/held-items/attack-type-booster.ts
Normal file
76
src/items/held-items/attack-type-booster.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId, HeldItemNames } from "#enums/held-item-id";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import type { NumberHolder } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface AttackTypeBoostParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The resolved type of the move */
|
||||
moveType: PokemonType;
|
||||
/** Holder for the damage value */
|
||||
// TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2119660807
|
||||
movePower: NumberHolder;
|
||||
}
|
||||
|
||||
interface AttackTypeToHeldItemMap {
|
||||
[key: number]: HeldItemId;
|
||||
}
|
||||
|
||||
export const attackTypeToHeldItem: AttackTypeToHeldItemMap = {
|
||||
[PokemonType.NORMAL]: HeldItemId.SILK_SCARF,
|
||||
[PokemonType.FIGHTING]: HeldItemId.BLACK_BELT,
|
||||
[PokemonType.FLYING]: HeldItemId.SHARP_BEAK,
|
||||
[PokemonType.POISON]: HeldItemId.POISON_BARB,
|
||||
[PokemonType.GROUND]: HeldItemId.SOFT_SAND,
|
||||
[PokemonType.ROCK]: HeldItemId.HARD_STONE,
|
||||
[PokemonType.BUG]: HeldItemId.SILVER_POWDER,
|
||||
[PokemonType.GHOST]: HeldItemId.SPELL_TAG,
|
||||
[PokemonType.STEEL]: HeldItemId.METAL_COAT,
|
||||
[PokemonType.FIRE]: HeldItemId.CHARCOAL,
|
||||
[PokemonType.WATER]: HeldItemId.MYSTIC_WATER,
|
||||
[PokemonType.GRASS]: HeldItemId.MIRACLE_SEED,
|
||||
[PokemonType.ELECTRIC]: HeldItemId.MAGNET,
|
||||
[PokemonType.PSYCHIC]: HeldItemId.TWISTED_SPOON,
|
||||
[PokemonType.ICE]: HeldItemId.NEVER_MELT_ICE,
|
||||
[PokemonType.DRAGON]: HeldItemId.DRAGON_FANG,
|
||||
[PokemonType.DARK]: HeldItemId.BLACK_GLASSES,
|
||||
[PokemonType.FAIRY]: HeldItemId.FAIRY_FEATHER,
|
||||
};
|
||||
|
||||
export class AttackTypeBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL];
|
||||
public moveType: PokemonType;
|
||||
public powerBoost: number;
|
||||
|
||||
// This constructor may need a revision
|
||||
constructor(type: HeldItemId, maxStackCount: number, moveType: PokemonType, powerBoost: number) {
|
||||
super(type, maxStackCount);
|
||||
this.moveType = moveType;
|
||||
this.powerBoost = powerBoost;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItemNames[this.type]?.toLowerCase()}`);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", {
|
||||
moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`),
|
||||
});
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return `${HeldItemNames[this.type]?.toLowerCase()}`;
|
||||
}
|
||||
|
||||
apply({ pokemon, moveType, movePower }: AttackTypeBoostParams): void {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
if (moveType === this.moveType && movePower.value >= 1) {
|
||||
movePower.value = Math.floor(movePower.value * (1 + stackCount * this.powerBoost));
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/items/held-items/base-stat-booster.ts
Normal file
78
src/items/held-items/base-stat-booster.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { HeldItemId } from "#enums/held-item-id";
|
||||
import { getStatKey, type PermanentStat, Stat } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface BaseStatBoosterParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The base stats of the {@linkcode pokemon} */
|
||||
baseStats: number[];
|
||||
}
|
||||
|
||||
type PermanentStatToHeldItemMap = {
|
||||
[key in PermanentStat]: HeldItemId;
|
||||
};
|
||||
|
||||
export const permanentStatToHeldItem: PermanentStatToHeldItemMap = {
|
||||
[Stat.HP]: HeldItemId.HP_UP,
|
||||
[Stat.ATK]: HeldItemId.PROTEIN,
|
||||
[Stat.DEF]: HeldItemId.IRON,
|
||||
[Stat.SPATK]: HeldItemId.CALCIUM,
|
||||
[Stat.SPDEF]: HeldItemId.ZINC,
|
||||
[Stat.SPD]: HeldItemId.CARBOS,
|
||||
};
|
||||
|
||||
export const statBoostItems: Record<PermanentStat, string> = {
|
||||
[Stat.HP]: "hp_up",
|
||||
[Stat.ATK]: "protein",
|
||||
[Stat.DEF]: "iron",
|
||||
[Stat.SPATK]: "calcium",
|
||||
[Stat.SPDEF]: "zinc",
|
||||
[Stat.SPD]: "carbos",
|
||||
};
|
||||
|
||||
export class BaseStatBoosterHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_BOOSTER];
|
||||
public stat: PermanentStat;
|
||||
|
||||
constructor(type: HeldItemId, maxStackCount: number, stat: PermanentStat) {
|
||||
super(type, maxStackCount);
|
||||
this.stat = stat;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return i18next.t(`modifierType:BaseStatBoosterItem.${statBoostItems[this.stat]}`);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", {
|
||||
stat: i18next.t(getStatKey(this.stat)),
|
||||
});
|
||||
}
|
||||
|
||||
get iconName(): string {
|
||||
return statBoostItems[this.stat];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@linkcode BaseStatModifier} should be applied to the specified {@linkcode Pokemon}.
|
||||
* @param _pokemon - The {@linkcode Pokemon} to be modified
|
||||
* @param baseStats - The base stats of the {@linkcode Pokemon}
|
||||
* @returns `true` if the {@linkcode Pokemon} should be modified
|
||||
*/
|
||||
// override shouldApply(_pokemon?: Pokemon, baseStats?: number[]): boolean {
|
||||
// return super.shouldApply(_pokemon, baseStats) && Array.isArray(baseStats);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies the {@linkcode BaseStatModifier} to the specified {@linkcode Pokemon}.
|
||||
*/
|
||||
apply({ pokemon, baseStats }: BaseStatBoosterParams): boolean {
|
||||
const stackCount = pokemon.heldItemManager.getStack(this.type);
|
||||
baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + stackCount * 0.1));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
64
src/items/held-items/base-stat-flat.ts
Normal file
64
src/items/held-items/base-stat-flat.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { HeldItemEffect } from "#enums/held-item-effect";
|
||||
import { Stat } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { HeldItem } from "#items/held-item";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface BaseStatFlatParams {
|
||||
/** The pokemon with the item */
|
||||
pokemon: Pokemon;
|
||||
/** The amount of exp to gain */
|
||||
baseStats: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently used by Old Gateau item
|
||||
*/
|
||||
export class BaseStatFlatHeldItem extends HeldItem {
|
||||
public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_FLAT];
|
||||
public isTransferable = false;
|
||||
|
||||
get description(): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@linkcode PokemonBaseStatFlatModifier} should be applied to the {@linkcode Pokemon}.
|
||||
* @param pokemon The {@linkcode Pokemon} that holds the item
|
||||
* @param baseStats The base stats of the {@linkcode Pokemon}
|
||||
* @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied
|
||||
*/
|
||||
// override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean {
|
||||
// return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Applies the {@linkcode PokemonBaseStatFlatModifier}
|
||||
*/
|
||||
apply({ pokemon, baseStats }: BaseStatFlatParams): true {
|
||||
const stats = this.getStats(pokemon);
|
||||
const statModifier = 20;
|
||||
// Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats
|
||||
baseStats.forEach((v, i) => {
|
||||
if (stats.includes(i)) {
|
||||
const newVal = Math.floor(v + statModifier);
|
||||
baseStats[i] = Math.min(Math.max(newVal, 1), 999999);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef
|
||||
* @returns Array of 3 {@linkcode Stat}s to boost
|
||||
*/
|
||||
getStats(pokemon: Pokemon): [HpOrSpeed: Stat, AtkOrSpAtk: Stat, DefOrSpDef: Stat] {
|
||||
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
||||
return [
|
||||
baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD,
|
||||
baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK,
|
||||
baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF,
|
||||
];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user