Merge pull request #112 from AsdarDevelops/pokemon-salesman
Pokemon salesman
This commit is contained in:
commit
014022bd0d
|
@ -0,0 +1,115 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: "[Bug] "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: session-file
|
||||||
|
attributes:
|
||||||
|
label: User data export file
|
||||||
|
description: Open Menu → ManageData → Export Session → Select slot. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
|
||||||
|
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: data-file
|
||||||
|
attributes:
|
||||||
|
label: Session export file
|
||||||
|
description: Open Menu → ManageData → Export Data. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
|
||||||
|
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: A clear and concise description of what you expected to happen. If it is an existing move or ability
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: media
|
||||||
|
attributes:
|
||||||
|
label: Screenshots / Videos
|
||||||
|
description: If applicable, add screenshots or videos to help explain your problem.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: dropdown
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: What OS did you observe the bug on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- PC/Windows
|
||||||
|
- Mac/OSX
|
||||||
|
- Linux
|
||||||
|
- iOS
|
||||||
|
- Android
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: os-other
|
||||||
|
attributes:
|
||||||
|
label: If other please specify
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: dropdown
|
||||||
|
id: browser
|
||||||
|
attributes:
|
||||||
|
label: Which browser do you use?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Chrome
|
||||||
|
- Firefox
|
||||||
|
- Safari
|
||||||
|
- Edge
|
||||||
|
- Opera
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: browser-other
|
||||||
|
attributes:
|
||||||
|
label: If other please specify
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context about the problem here.
|
|
@ -28,7 +28,8 @@
|
||||||
- [ ] There is no overlap with another PR?
|
- [ ] There is no overlap with another PR?
|
||||||
- [ ] The PR is self-contained and cannot be split into smaller PRs?
|
- [ ] The PR is self-contained and cannot be split into smaller PRs?
|
||||||
- [ ] Have I provided a clear explanation of the changes?
|
- [ ] Have I provided a clear explanation of the changes?
|
||||||
|
- [ ] Have I considered writing automated tests for the issue?
|
||||||
- [ ] Have I tested the changes (manually)?
|
- [ ] Have I tested the changes (manually)?
|
||||||
- [ ] Are all unit tests still passing? (`npm run test`)
|
- [ ] Are all unit tests still passing? (`npm run test`)
|
||||||
- [ ] Are the changes visual?
|
- [ ] Are the changes visual?
|
||||||
- [ ] Have I provided screenshots/videos of the changes?
|
- [ ] Have I provided screenshots/videos of the changes?
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 318 B |
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"image": "encounter_radar.png",
|
||||||
|
"format": "RGBA8888",
|
||||||
|
"size": {
|
||||||
|
"w": 17,
|
||||||
|
"h": 16
|
||||||
|
},
|
||||||
|
"scale": 1,
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"filename": "0001.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 1,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"app": "https://www.codeandweb.com/texturepacker",
|
||||||
|
"version": "3.0",
|
||||||
|
"smartupdate": "$TexturePacker:SmartUpdate:eb3445f19546ab36edb2909c89b8aa86:c8de156a28ef70ee4ddf70cffe1ba3ba:e7008b81ccf0cb0325145a809afa6165$"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 255 B |
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "berry_juice.png",
|
"image": "pokemon_salesman.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 24,
|
"w": 40,
|
||||||
"h": 23
|
"h": 80
|
||||||
},
|
},
|
||||||
"scale": 1,
|
"scale": 1,
|
||||||
"frames": [
|
"frames": [
|
||||||
|
@ -14,20 +14,20 @@
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": true,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 24,
|
"w": 80,
|
||||||
"h": 24
|
"h": 80
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 1,
|
"x": 21,
|
||||||
"y": 2,
|
"y": 2,
|
||||||
"w": 22,
|
"w": 38,
|
||||||
"h": 21
|
"h": 78
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 1,
|
"x": 1,
|
||||||
"y": 1,
|
"y": 1,
|
||||||
"w": 22,
|
"w": 38,
|
||||||
"h": 21
|
"h": 78
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -36,6 +36,6 @@
|
||||||
"meta": {
|
"meta": {
|
||||||
"app": "https://www.codeandweb.com/texturepacker",
|
"app": "https://www.codeandweb.com/texturepacker",
|
||||||
"version": "3.0",
|
"version": "3.0",
|
||||||
"smartupdate": "$TexturePacker:SmartUpdate:04685a0eb6ef9095824b65408ec1b38f:9891674d538df100fcddde29330c21ae:927f117bdb1c2a27226a5540ce00ee8b$"
|
"smartupdate": "$TexturePacker:SmartUpdate:dd57e3db21f3933c15be65bec261f4c1:05c7ef32252a5c2d3ad007b7e26fabd7:ae82f52e471ed81e2558206f05476cd7$"
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
After Width: | Height: | Size: 839 B |
|
@ -67,7 +67,7 @@ import { UiTheme } from "#enums/ui-theme";
|
||||||
import { TimedEventManager } from "#app/timed-event-manager.js";
|
import { TimedEventManager } from "#app/timed-event-manager.js";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import IMysteryEncounter, { MysteryEncounterTier, MysteryEncounterVariant } from "./data/mystery-encounters/mystery-encounter";
|
import IMysteryEncounter, { MysteryEncounterTier, MysteryEncounterVariant } from "./data/mystery-encounters/mystery-encounter";
|
||||||
import { allMysteryEncounters, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
|
import { allMysteryEncounters, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
|
||||||
import { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data";
|
import { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
|
||||||
|
@ -1074,7 +1074,7 @@ export default class BattleScene extends SceneBase {
|
||||||
// let testStartingWeight = 0;
|
// let testStartingWeight = 0;
|
||||||
// while (testStartingWeight < 3) {
|
// while (testStartingWeight < 3) {
|
||||||
// calculateMEAggregateStats(this, testStartingWeight);
|
// calculateMEAggregateStats(this, testStartingWeight);
|
||||||
// testStartingWeight += 1;
|
// testStartingWeight += 2;
|
||||||
// }
|
// }
|
||||||
// Check for mystery encounter
|
// Check for mystery encounter
|
||||||
// Can only occur in place of a standard wild battle, waves 10-180
|
// Can only occur in place of a standard wild battle, waves 10-180
|
||||||
|
@ -1098,7 +1098,7 @@ export default class BattleScene extends SceneBase {
|
||||||
// Reset base spawn weight
|
// Reset base spawn weight
|
||||||
this.mysteryEncounterData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
this.mysteryEncounterData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||||
} else {
|
} else {
|
||||||
this.mysteryEncounterData.encounterSpawnChance = sessionEncounterRate + WIGHT_INCREMENT_ON_SPAWN_MISS;
|
this.mysteryEncounterData.encounterSpawnChance = sessionEncounterRate + WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,11 +87,17 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
if (!correctMove) {
|
if (!correctMove) {
|
||||||
encounter.options[0].dialogue.selected = [
|
encounter.options[0].dialogue.selected = [
|
||||||
{
|
{
|
||||||
text: `${namespace}:incorrect`,
|
text: `${namespace}:option:incorrect`,
|
||||||
speaker: `${namespace}:speaker`,
|
speaker: `${namespace}:option:speaker`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: `${namespace}:lesson_learned`,
|
text: `${namespace}:option:lesson_learned`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
encounter.dialogue.outro = [
|
||||||
|
{
|
||||||
|
text: `${namespace}:outro_bad`,
|
||||||
|
speaker: `${namespace}:speaker`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||||
|
@ -103,6 +109,12 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
text: `${namespace}:option:selected`,
|
text: `${namespace}:option:selected`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
encounter.dialogue.outro = [
|
||||||
|
{
|
||||||
|
text: `${namespace}:outro_good`,
|
||||||
|
speaker: `${namespace}:speaker`,
|
||||||
|
},
|
||||||
|
];
|
||||||
setEncounterExp(scene, [pokemon.id], 100);
|
setEncounterExp(scene, [pokemon.id], 100);
|
||||||
}
|
}
|
||||||
encounter.misc = {
|
encounter.misc = {
|
||||||
|
@ -161,11 +173,23 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
if (!correctMove) {
|
if (!correctMove) {
|
||||||
encounter.options[1].dialogue.selected = [
|
encounter.options[1].dialogue.selected = [
|
||||||
{
|
{
|
||||||
text: `${namespace}:incorrect`,
|
text: `${namespace}:option:incorrect`,
|
||||||
speaker: `${namespace}:speaker`,
|
speaker: `${namespace}:option:speaker`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: `${namespace}:lesson_learned`,
|
text: `${namespace}:option:lesson_learned`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
encounter.dialogue.outro = [
|
||||||
|
{
|
||||||
|
text: `${namespace}:outro_bad`,
|
||||||
|
speaker: `${namespace}:speaker`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
encounter.dialogue.outro = [
|
||||||
|
{
|
||||||
|
text: `${namespace}:outro_bad`,
|
||||||
|
speaker: `${namespace}:speaker`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||||
|
@ -177,6 +201,12 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
text: `${namespace}:option:selected`,
|
text: `${namespace}:option:selected`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
encounter.dialogue.outro = [
|
||||||
|
{
|
||||||
|
text: `${namespace}:outro_good`,
|
||||||
|
speaker: `${namespace}:speaker`,
|
||||||
|
},
|
||||||
|
];
|
||||||
setEncounterExp(scene, [pokemon.id], 100);
|
setEncounterExp(scene, [pokemon.id], 100);
|
||||||
}
|
}
|
||||||
encounter.misc = {
|
encounter.misc = {
|
||||||
|
@ -235,18 +265,20 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
if (!correctMove) {
|
if (!correctMove) {
|
||||||
encounter.options[2].dialogue.selected = [
|
encounter.options[2].dialogue.selected = [
|
||||||
{
|
{
|
||||||
text: `${namespace}:incorrect`,
|
text: `${namespace}:option:incorrect`,
|
||||||
speaker: `${namespace}:speaker`,
|
speaker: `${namespace}:option:speaker`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: `${namespace}:lesson_learned`,
|
text: `${namespace}:option:lesson_learned`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
setEncounterExp(
|
encounter.dialogue.outro = [
|
||||||
scene,
|
{
|
||||||
scene.getParty().map((p) => p.id),
|
text: `${namespace}:outro_bad`,
|
||||||
50
|
speaker: `${namespace}:speaker`,
|
||||||
);
|
},
|
||||||
|
];
|
||||||
|
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||||
} else {
|
} else {
|
||||||
encounter.setDialogueToken("pokeName", pokemon.name);
|
encounter.setDialogueToken("pokeName", pokemon.name);
|
||||||
encounter.setDialogueToken("move", move.getName());
|
encounter.setDialogueToken("move", move.getName());
|
||||||
|
@ -255,6 +287,12 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
text: `${namespace}:option:selected`,
|
text: `${namespace}:option:selected`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
encounter.dialogue.outro = [
|
||||||
|
{
|
||||||
|
text: `${namespace}:outro_good`,
|
||||||
|
speaker: `${namespace}:speaker`,
|
||||||
|
},
|
||||||
|
];
|
||||||
setEncounterExp(scene, [pokemon.id], 100);
|
setEncounterExp(scene, [pokemon.id], 100);
|
||||||
}
|
}
|
||||||
encounter.misc = {
|
encounter.misc = {
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const FightOrFlightEncounter: IMysteryEncounter =
|
||||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
{
|
{
|
||||||
text: `${namespace}_intro_message`,
|
text: `${namespace}:intro`,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.withOnInit((scene: BattleScene) => {
|
.withOnInit((scene: BattleScene) => {
|
||||||
|
@ -87,6 +87,7 @@ export const FightOrFlightEncounter: IMysteryEncounter =
|
||||||
y: -5,
|
y: -5,
|
||||||
scale: 0.75,
|
scale: 0.75,
|
||||||
isItem: true,
|
isItem: true,
|
||||||
|
disableAnimation: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spriteKey: bossSpriteKey,
|
spriteKey: bossSpriteKey,
|
||||||
|
@ -149,7 +150,7 @@ export const FightOrFlightEncounter: IMysteryEncounter =
|
||||||
const primaryPokemon = encounter.options[1].primaryPokemon;
|
const primaryPokemon = encounter.options[1].primaryPokemon;
|
||||||
if (primaryPokemon) {
|
if (primaryPokemon) {
|
||||||
// Use primaryPokemon to execute the thievery
|
// Use primaryPokemon to execute the thievery
|
||||||
await showEncounterText(scene, `${namespace}:option:2:steal_result`);
|
await showEncounterText(scene, `${namespace}:option:2:special_result`);
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,7 +169,7 @@ export const MysteriousChallengersEncounter: IMysteryEncounter =
|
||||||
// Spawn hard fight with ULTRA/GREAT reward (can improve with luck)
|
// Spawn hard fight with ULTRA/GREAT reward (can improve with luck)
|
||||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
|
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
|
||||||
|
|
||||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true });
|
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true });
|
||||||
|
|
||||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||||
let ret;
|
let ret;
|
||||||
|
@ -197,7 +197,7 @@ export const MysteriousChallengersEncounter: IMysteryEncounter =
|
||||||
// To avoid player level snowballing from picking this option
|
// To avoid player level snowballing from picking this option
|
||||||
encounter.expMultiplier = 0.9;
|
encounter.expMultiplier = 0.9;
|
||||||
|
|
||||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
|
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
|
||||||
|
|
||||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||||
let ret;
|
let ret;
|
||||||
|
|
|
@ -34,8 +34,8 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
])
|
])
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
{
|
{
|
||||||
text: "${namespace}:intro:message",
|
text: `${namespace}:intro`,
|
||||||
},
|
}
|
||||||
])
|
])
|
||||||
.withTitle(`${namespace}:title`)
|
.withTitle(`${namespace}:title`)
|
||||||
.withDescription(`${namespace}:description`)
|
.withDescription(`${namespace}:description`)
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||||
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
import BattleScene from "../../../battle-scene";
|
||||||
|
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
||||||
|
import { MoneyRequirement } from "../mystery-encounter-requirements";
|
||||||
|
import { catchPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { PokeballType } from "#app/data/pokeball";
|
||||||
|
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
|
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
|
|
||||||
|
/** the i18n namespace for this encounter */
|
||||||
|
const namespace = "mysteryEncounter:pokemonSalesman";
|
||||||
|
|
||||||
|
const MAX_POKEMON_PRICE_MULTIPLIER = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pokemon Salesman encounter.
|
||||||
|
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/36 | GitHub Issue #36}
|
||||||
|
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||||
|
*/
|
||||||
|
export const PokemonSalesmanEncounter: IMysteryEncounter =
|
||||||
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.POKEMON_SALESMAN)
|
||||||
|
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||||
|
.withSceneWaveRangeRequirement(10, 180)
|
||||||
|
.withSceneRequirement(new MoneyRequirement(null, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
|
||||||
|
.withAutoHideIntroVisuals(false)
|
||||||
|
.withIntroSpriteConfigs([
|
||||||
|
{
|
||||||
|
spriteKey: "pokemon_salesman",
|
||||||
|
fileRoot: "mystery-encounters",
|
||||||
|
hasShadow: true
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.withIntroDialogue([
|
||||||
|
{
|
||||||
|
text: `${namespace}:intro`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}:intro_dialogue`,
|
||||||
|
speaker: `${namespace}:speaker`,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withTitle(`${namespace}:title`)
|
||||||
|
.withDescription(`${namespace}:description`)
|
||||||
|
.withQuery(`${namespace}:query`)
|
||||||
|
.withOnInit((scene: BattleScene) => {
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
|
||||||
|
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||||
|
const tries = 0;
|
||||||
|
|
||||||
|
// Reroll any species that don't have HAs
|
||||||
|
while (isNullOrUndefined(species.abilityHidden) && tries < 5) {
|
||||||
|
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pokemon: PlayerPokemon;
|
||||||
|
if (isNullOrUndefined(species.abilityHidden) || randSeedInt(100) === 0) {
|
||||||
|
// If no HA mon found or you roll 1%, give shiny Magikarp
|
||||||
|
species = getPokemonSpecies(Species.MAGIKARP);
|
||||||
|
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||||
|
pokemon = scene.addPlayerPokemon(species, 5, hiddenIndex, species.formIndex, null, true);
|
||||||
|
} else {
|
||||||
|
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||||
|
pokemon = scene.addPlayerPokemon(species, 5, hiddenIndex, species.formIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const spriteKey = pokemon.getSpriteId();
|
||||||
|
const spriteRoot = pokemon.getSpriteAtlasPath();
|
||||||
|
encounter.spriteConfigs.push({
|
||||||
|
spriteKey: spriteKey,
|
||||||
|
fileRoot: spriteRoot,
|
||||||
|
hasShadow: true,
|
||||||
|
repeat: true,
|
||||||
|
isPokemon: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const starterTier = speciesStarters[species.speciesId];
|
||||||
|
// Prices decrease by starter tier less than 5, but only reduces cost by half at max
|
||||||
|
let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5);
|
||||||
|
if (pokemon.shiny) {
|
||||||
|
// Always max price for shiny (flip HA back to normal), and add special messaging
|
||||||
|
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
|
||||||
|
pokemon.abilityIndex = 0;
|
||||||
|
encounter.dialogue.encounterOptionsDialogue.description = `${namespace}:description_shiny`;
|
||||||
|
encounter.options[0].dialogue.buttonTooltip = `${namespace}:option:1:tooltip_shiny`;
|
||||||
|
}
|
||||||
|
const price = scene.getWaveMoneyAmount(priceMultiplier);
|
||||||
|
encounter.setDialogueToken("purchasePokemon", pokemon.name);
|
||||||
|
encounter.setDialogueToken("price", price.toString());
|
||||||
|
encounter.misc = {
|
||||||
|
price: price,
|
||||||
|
pokemon: pokemon
|
||||||
|
};
|
||||||
|
|
||||||
|
pokemon.calculateStats();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.withOption(
|
||||||
|
new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(EncounterOptionMode.DEFAULT_OR_SPECIAL)
|
||||||
|
.withHasDexProgress(true)
|
||||||
|
.withSceneMoneyRequirement(null, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}:option:1:label`,
|
||||||
|
buttonTooltip: `${namespace}:option:1:tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}:option:1:selected_message`,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
const price = encounter.misc.price;
|
||||||
|
const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon;
|
||||||
|
|
||||||
|
// Update money
|
||||||
|
updatePlayerMoney(scene, -price, true, false);
|
||||||
|
|
||||||
|
// Show dialogue
|
||||||
|
await showEncounterDialogue(scene, `${namespace}:option:1:selected_dialogue`, `${namespace}:speaker`);
|
||||||
|
await transitionMysteryEncounterIntroVisuals(scene);
|
||||||
|
|
||||||
|
// "Catch" purchased pokemon
|
||||||
|
const data = new PokemonData(purchasedPokemon);
|
||||||
|
data.player = false;
|
||||||
|
await catchPokemon(scene, data.toPokemon(scene) as EnemyPokemon, null, PokeballType.POKEBALL, true);
|
||||||
|
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withSimpleOption(
|
||||||
|
{
|
||||||
|
buttonLabel: `${namespace}:option:2:label`,
|
||||||
|
buttonTooltip: `${namespace}:option:2:tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}:option:2:selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
async (scene: BattleScene) => {
|
||||||
|
// Leave encounter with no rewards or exp
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build();
|
|
@ -19,6 +19,11 @@ import { BerryType } from "#enums/berry-type";
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:theStrongStuff";
|
const namespace = "mysteryEncounter:theStrongStuff";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Strong Stuff encounter.
|
||||||
|
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/54 | GitHub Issue #54}
|
||||||
|
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||||
|
*/
|
||||||
export const TheStrongStuffEncounter: IMysteryEncounter =
|
export const TheStrongStuffEncounter: IMysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
||||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||||
|
@ -28,12 +33,13 @@ export const TheStrongStuffEncounter: IMysteryEncounter =
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "berry_juice",
|
spriteKey: "berry_juice",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "items",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
|
isItem: true,
|
||||||
scale: 1.5,
|
scale: 1.5,
|
||||||
x: -15,
|
x: -15,
|
||||||
y: 3,
|
y: 3,
|
||||||
yShadow: 0
|
disableAnimation: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spriteKey: Species.SHUCKLE.toString(),
|
spriteKey: Species.SHUCKLE.toString(),
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, selectPokemonForOption, se
|
||||||
import { getNatureName, Nature } from "#app/data/nature";
|
import { getNatureName, Nature } from "#app/data/nature";
|
||||||
import { speciesStarters } from "#app/data/pokemon-species";
|
import { speciesStarters } from "#app/data/pokemon-species";
|
||||||
import { Stat } from "#app/data/pokemon-stat";
|
import { Stat } from "#app/data/pokemon-stat";
|
||||||
import { PlayerPokemon } from "#app/field/pokemon";
|
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { pokemonInfo } from "#app/locales/en/pokemon-info";
|
import { pokemonInfo } from "#app/locales/en/pokemon-info";
|
||||||
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
import { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
|
@ -16,7 +16,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import BattleScene from "../../../battle-scene";
|
import BattleScene from "../../../battle-scene";
|
||||||
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
||||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
/** The i18n namespace for the encounter */
|
/** The i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:trainingSession";
|
const namespace = "mysteryEncounter:trainingSession";
|
||||||
|
@ -27,11 +27,10 @@ const namespace = "mysteryEncounter:trainingSession";
|
||||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||||
*/
|
*/
|
||||||
export const TrainingSessionEncounter: IMysteryEncounter =
|
export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
|
||||||
MysteryEncounterType.TRAINING_SESSION
|
|
||||||
)
|
|
||||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||||
|
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
|
||||||
.withHideWildIntroMessage(true)
|
.withHideWildIntroMessage(true)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
|
@ -46,7 +45,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
{
|
{
|
||||||
text: `${namespace}:intro`,
|
text: `${namespace}:intro`,
|
||||||
},
|
}
|
||||||
])
|
])
|
||||||
.withTitle(`${namespace}:title`)
|
.withTitle(`${namespace}:title`)
|
||||||
.withDescription(`${namespace}:description`)
|
.withDescription(`${namespace}:description`)
|
||||||
|
@ -54,6 +53,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
.withOption(
|
.withOption(
|
||||||
new MysteryEncounterOptionBuilder()
|
new MysteryEncounterOptionBuilder()
|
||||||
.withOptionMode(EncounterOptionMode.DEFAULT)
|
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||||
|
.withHasDexProgress(true)
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option:1:label`,
|
buttonLabel: `${namespace}:option:1:label`,
|
||||||
buttonTooltip: `${namespace}:option:1:tooltip`,
|
buttonTooltip: `${namespace}:option:1:tooltip`,
|
||||||
|
@ -71,7 +71,17 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected);
|
// Only Pokemon that are not KOed/legal can be trained
|
||||||
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
|
const meetsReqs = pokemon.isAllowedInBattle();
|
||||||
|
if (!meetsReqs) {
|
||||||
|
return getEncounterText(scene, `${namespace}:invalid_selection`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
|
||||||
})
|
})
|
||||||
.withOptionPhase(async (scene: BattleScene) => {
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter;
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
@ -187,6 +197,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
.withOption(
|
.withOption(
|
||||||
new MysteryEncounterOptionBuilder()
|
new MysteryEncounterOptionBuilder()
|
||||||
.withOptionMode(EncounterOptionMode.DEFAULT)
|
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||||
|
.withHasDexProgress(true)
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option:2:label`,
|
buttonLabel: `${namespace}:option:2:label`,
|
||||||
buttonTooltip: `${namespace}:option:2:tooltip`,
|
buttonTooltip: `${namespace}:option:2:tooltip`,
|
||||||
|
@ -220,7 +231,17 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected);
|
// Only Pokemon that are not KOed/legal can be trained
|
||||||
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
|
const meetsReqs = pokemon.isAllowedInBattle();
|
||||||
|
if (!meetsReqs) {
|
||||||
|
return getEncounterText(scene, `${namespace}:invalid_selection`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
|
||||||
})
|
})
|
||||||
.withOptionPhase(async (scene: BattleScene) => {
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter;
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
@ -269,6 +290,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
.withOption(
|
.withOption(
|
||||||
new MysteryEncounterOptionBuilder()
|
new MysteryEncounterOptionBuilder()
|
||||||
.withOptionMode(EncounterOptionMode.DEFAULT)
|
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||||
|
.withHasDexProgress(true)
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}:option:3:label`,
|
buttonLabel: `${namespace}:option:3:label`,
|
||||||
buttonTooltip: `${namespace}:option:3:tooltip`,
|
buttonTooltip: `${namespace}:option:3:tooltip`,
|
||||||
|
@ -311,7 +333,17 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected);
|
// Only Pokemon that are not KOed/legal can be trained
|
||||||
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
|
const meetsReqs = pokemon.isAllowedInBattle();
|
||||||
|
if (!meetsReqs) {
|
||||||
|
return getEncounterText(scene, `${namespace}:invalid_selection`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
|
||||||
})
|
})
|
||||||
.withOptionPhase(async (scene: BattleScene) => {
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter;
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
@ -393,23 +425,12 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
function getEnemyConfig(
|
function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon,segments: number,modifiers: ModifiersHolder): EnemyPartyConfig {
|
||||||
scene: BattleScene,
|
|
||||||
playerPokemon: PlayerPokemon,
|
|
||||||
segments: number,
|
|
||||||
modifiers: ModifiersHolder
|
|
||||||
): EnemyPartyConfig {
|
|
||||||
playerPokemon.resetSummonData();
|
playerPokemon.resetSummonData();
|
||||||
|
|
||||||
// Passes modifiers by reference
|
// Passes modifiers by reference
|
||||||
modifiers.value = scene.findModifiers(
|
modifiers.value = scene.findModifiers((m) => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === playerPokemon.id) as PokemonHeldItemModifier[];
|
||||||
(m) =>
|
const modifierTypes = modifiers.value.map((mod) => mod.type) as PokemonHeldItemModifierType[];
|
||||||
m instanceof PokemonHeldItemModifier &&
|
|
||||||
(m as PokemonHeldItemModifier).pokemonId === playerPokemon.id
|
|
||||||
) as PokemonHeldItemModifier[];
|
|
||||||
const modifierTypes = modifiers.value.map(
|
|
||||||
(mod) => mod.type
|
|
||||||
) as PokemonHeldItemModifierType[];
|
|
||||||
|
|
||||||
const data = new PokemonData(playerPokemon);
|
const data = new PokemonData(playerPokemon);
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import * as Utils from "../../utils";
|
||||||
import { Type } from "../type";
|
import { Type } from "../type";
|
||||||
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
|
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
|
||||||
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
|
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
|
||||||
|
import { isNullOrUndefined } from "../../utils";
|
||||||
|
|
||||||
export enum EncounterOptionMode {
|
export enum EncounterOptionMode {
|
||||||
/** Default style */
|
/** Default style */
|
||||||
|
@ -23,6 +24,9 @@ export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean
|
||||||
|
|
||||||
export default interface MysteryEncounterOption {
|
export default interface MysteryEncounterOption {
|
||||||
optionMode: EncounterOptionMode;
|
optionMode: EncounterOptionMode;
|
||||||
|
|
||||||
|
hasDexProgress?: boolean;
|
||||||
|
|
||||||
requirements?: EncounterSceneRequirement[];
|
requirements?: EncounterSceneRequirement[];
|
||||||
primaryPokemonRequirements?: EncounterPokemonRequirement[];
|
primaryPokemonRequirements?: EncounterPokemonRequirement[];
|
||||||
secondaryPokemonRequirements?: EncounterPokemonRequirement[];
|
secondaryPokemonRequirements?: EncounterPokemonRequirement[];
|
||||||
|
@ -47,6 +51,7 @@ export default interface MysteryEncounterOption {
|
||||||
export default class MysteryEncounterOption implements MysteryEncounterOption {
|
export default class MysteryEncounterOption implements MysteryEncounterOption {
|
||||||
constructor(option: MysteryEncounterOption) {
|
constructor(option: MysteryEncounterOption) {
|
||||||
Object.assign(this, option);
|
Object.assign(this, option);
|
||||||
|
this.hasDexProgress = !isNullOrUndefined(this.hasDexProgress) ? this.hasDexProgress : false;
|
||||||
this.requirements = this.requirements ? this.requirements : [];
|
this.requirements = this.requirements ? this.requirements : [];
|
||||||
this.primaryPokemonRequirements = this.primaryPokemonRequirements ? this.primaryPokemonRequirements : [];
|
this.primaryPokemonRequirements = this.primaryPokemonRequirements ? this.primaryPokemonRequirements : [];
|
||||||
this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ? this.secondaryPokemonRequirements : [];
|
this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ? this.secondaryPokemonRequirements : [];
|
||||||
|
@ -155,6 +160,10 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
|
||||||
return Object.assign(this, { optionMode });
|
return Object.assign(this, { optionMode });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withHasDexProgress(hasDexProgress: boolean): this & Required<Pick<MysteryEncounterOption, "hasDexProgress">> {
|
||||||
|
return Object.assign(this, { hasDexProgress: hasDexProgress });
|
||||||
|
}
|
||||||
|
|
||||||
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<MysteryEncounterOption, "requirements">> {
|
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<MysteryEncounterOption, "requirements">> {
|
||||||
this.requirements.push(requirement);
|
this.requirements.push(requirement);
|
||||||
return Object.assign(this, { requirements: this.requirements });
|
return Object.assign(this, { requirements: this.requirements });
|
||||||
|
|
|
@ -144,20 +144,23 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
||||||
|
|
||||||
export class PartySizeRequirement extends EncounterSceneRequirement {
|
export class PartySizeRequirement extends EncounterSceneRequirement {
|
||||||
partySizeRange: [number, number];
|
partySizeRange: [number, number];
|
||||||
|
excludeFainted: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for specifying a party size requirement
|
* Used for specifying a party size requirement
|
||||||
* If min and max are equivalent, will check for exact size
|
* If min and max are equivalent, will check for exact size
|
||||||
* @param partySizeRange - [min, max]
|
* @param partySizeRange - [min, max]
|
||||||
|
* @param excludeFainted
|
||||||
*/
|
*/
|
||||||
constructor(partySizeRange: [number, number]) {
|
constructor(partySizeRange: [number, number], excludeFainted: boolean) {
|
||||||
super();
|
super();
|
||||||
this.partySizeRange = partySizeRange;
|
this.partySizeRange = partySizeRange;
|
||||||
|
this.excludeFainted = excludeFainted;
|
||||||
}
|
}
|
||||||
|
|
||||||
meetsRequirement(scene: BattleScene): boolean {
|
meetsRequirement(scene: BattleScene): boolean {
|
||||||
if (!isNullOrUndefined(this?.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
|
if (!isNullOrUndefined(this?.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
|
||||||
const partySize = scene.getParty().length;
|
const partySize = this.excludeFainted ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
|
||||||
if (partySize >= 0 && (this?.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this?.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) {
|
if (partySize >= 0 && (this?.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this?.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -403,7 +403,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
options?: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]] = [null, null];
|
options?: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]] = [null, null];
|
||||||
spriteConfigs?: MysteryEncounterSpriteConfig[];
|
spriteConfigs?: MysteryEncounterSpriteConfig[];
|
||||||
|
|
||||||
dialogue?: MysteryEncounterDialogue;
|
dialogue?: MysteryEncounterDialogue = {};
|
||||||
encounterTier?: MysteryEncounterTier;
|
encounterTier?: MysteryEncounterTier;
|
||||||
encounterAnimations?: EncounterAnim[];
|
encounterAnimations?: EncounterAnim[];
|
||||||
requirements?: EncounterSceneRequirement[] = [];
|
requirements?: EncounterSceneRequirement[] = [];
|
||||||
|
@ -460,6 +460,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
* There should be at least 2 options defined and no more than 4.
|
* There should be at least 2 options defined and no more than 4.
|
||||||
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
|
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
|
||||||
*
|
*
|
||||||
|
* @param hasDexProgress -
|
||||||
* @param dialogue - {@linkcode OptionTextDisplay}
|
* @param dialogue - {@linkcode OptionTextDisplay}
|
||||||
* @param callback - {@linkcode OptionPhaseCallback}
|
* @param callback - {@linkcode OptionPhaseCallback}
|
||||||
* @returns
|
* @returns
|
||||||
|
@ -468,6 +469,24 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
return this.withOption(new MysteryEncounterOptionBuilder().withOptionMode(EncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
|
return this.withOption(new MysteryEncounterOptionBuilder().withOptionMode(EncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an option + phasefor the encounter.
|
||||||
|
* Use for easy/streamlined options.
|
||||||
|
* There should be at least 2 options defined and no more than 4.
|
||||||
|
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
|
||||||
|
*
|
||||||
|
* @param dialogue - {@linkcode OptionTextDisplay}
|
||||||
|
* @param callback - {@linkcode OptionPhaseCallback}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
|
||||||
|
return this.withOption(new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||||
|
.withHasDexProgress(true)
|
||||||
|
.withDialogue(dialogue)
|
||||||
|
.withOptionPhase(callback).build());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the sprites that will be shown on the enemy field when the encounter spawns
|
* Defines the sprites that will be shown on the enemy field when the encounter spawns
|
||||||
* Can be one or more sprites, recommended not to exceed 4
|
* Can be one or more sprites, recommended not to exceed 4
|
||||||
|
@ -478,7 +497,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
return Object.assign(this, { spriteConfigs: spriteConfigs });
|
return Object.assign(this, { spriteConfigs: spriteConfigs });
|
||||||
}
|
}
|
||||||
|
|
||||||
withIntroDialogue(dialogue: MysteryEncounterDialogue["intro"] = []) {
|
withIntroDialogue(dialogue: MysteryEncounterDialogue["intro"] = []): this {
|
||||||
this.dialogue = {...this.dialogue, intro: dialogue };
|
this.dialogue = {...this.dialogue, intro: dialogue };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -550,7 +569,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
* @param max optional max wave. If not given, defaults to min => exact wave
|
* @param max optional max wave. If not given, defaults to min => exact wave
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
withSceneWaveRangeRequirement(min: number, max?: number) {
|
withSceneWaveRangeRequirement(min: number, max?: number): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||||
return this.withSceneRequirement(new WaveRangeRequirement([min, max ?? min]));
|
return this.withSceneRequirement(new WaveRangeRequirement([min, max ?? min]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,10 +578,11 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
*
|
*
|
||||||
* @param min min wave (or exact size if only min is given)
|
* @param min min wave (or exact size if only min is given)
|
||||||
* @param max optional max size. If not given, defaults to min => exact wave
|
* @param max optional max size. If not given, defaults to min => exact wave
|
||||||
|
* @param excludeFainted - if true, only counts unfainted mons
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
withScenePartySizeRequirement(min: number, max?: number) {
|
withScenePartySizeRequirement(min: number, max?: number, excludeFainted?: boolean): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||||
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min]));
|
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -700,7 +720,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
* @param title - title of the encounter
|
* @param title - title of the encounter
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
withTitle(title: string) {
|
withTitle(title: string): this {
|
||||||
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
||||||
|
|
||||||
this.dialogue = {
|
this.dialogue = {
|
||||||
|
@ -720,7 +740,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
* @param description - description of the encounter
|
* @param description - description of the encounter
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
withDescription(description: string) {
|
withDescription(description: string): this {
|
||||||
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
||||||
|
|
||||||
this.dialogue = {
|
this.dialogue = {
|
||||||
|
@ -740,7 +760,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
* @param query - query to use for the encounter
|
* @param query - query to use for the encounter
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
withQuery(query: string) {
|
withQuery(query: string): this {
|
||||||
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
||||||
|
|
||||||
this.dialogue = {
|
this.dialogue = {
|
||||||
|
@ -760,7 +780,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
* @param dialogue - outro dialogue/s
|
* @param dialogue - outro dialogue/s
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
withOutroDialogue(dialogue: MysteryEncounterDialogue["outro"] = []) {
|
withOutroDialogue(dialogue: MysteryEncounterDialogue["outro"] = []): this {
|
||||||
this.dialogue = {...this.dialogue, outro: dialogue };
|
this.dialogue = {...this.dialogue, outro: dialogue };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -768,10 +788,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||||
/**
|
/**
|
||||||
* Builds the mystery encounter
|
* Builds the mystery encounter
|
||||||
*
|
*
|
||||||
* @param this - MysteryEncounter
|
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
build(this: IMysteryEncounter) {
|
build(this: IMysteryEncounter): IMysteryEncounter {
|
||||||
return new IMysteryEncounter(this);
|
return new IMysteryEncounter(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,11 @@ import IMysteryEncounter from "./mystery-encounter";
|
||||||
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
|
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
|
||||||
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
|
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
|
||||||
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
|
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
|
||||||
|
import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/pokemon-salesman-encounter";
|
||||||
|
|
||||||
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
||||||
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
||||||
export const WIGHT_INCREMENT_ON_SPAWN_MISS = 5;
|
export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 5;
|
||||||
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 15;
|
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 15;
|
||||||
|
|
||||||
export const EXTREME_ENCOUNTER_BIOMES = [
|
export const EXTREME_ENCOUNTER_BIOMES = [
|
||||||
|
@ -132,14 +133,14 @@ const nonExtremeBiomeEncounters: MysteryEncounterType[] = [
|
||||||
|
|
||||||
const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
|
const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
|
||||||
MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
|
MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
|
||||||
MysteryEncounterType.SHADY_VITAMIN_DEALER
|
MysteryEncounterType.SHADY_VITAMIN_DEALER,
|
||||||
|
MysteryEncounterType.POKEMON_SALESMAN
|
||||||
];
|
];
|
||||||
|
|
||||||
const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
||||||
MysteryEncounterType.DEPARTMENT_STORE_SALE
|
MysteryEncounterType.DEPARTMENT_STORE_SALE
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To add an encounter to every biome possible, use this array
|
* To add an encounter to every biome possible, use this array
|
||||||
*/
|
*/
|
||||||
|
@ -225,6 +226,7 @@ export function initMysteryEncounters() {
|
||||||
allMysteryEncounters[MysteryEncounterType.LOST_AT_SEA] = LostAtSeaEncounter;
|
allMysteryEncounters[MysteryEncounterType.LOST_AT_SEA] = LostAtSeaEncounter;
|
||||||
allMysteryEncounters[MysteryEncounterType.FIERY_FALLOUT] = FieryFalloutEncounter;
|
allMysteryEncounters[MysteryEncounterType.FIERY_FALLOUT] = FieryFalloutEncounter;
|
||||||
allMysteryEncounters[MysteryEncounterType.THE_STRONG_STUFF] = TheStrongStuffEncounter;
|
allMysteryEncounters[MysteryEncounterType.THE_STRONG_STUFF] = TheStrongStuffEncounter;
|
||||||
|
allMysteryEncounters[MysteryEncounterType.POKEMON_SALESMAN] = PokemonSalesmanEncounter;
|
||||||
|
|
||||||
// Add extreme encounters to biome map
|
// Add extreme encounters to biome map
|
||||||
extremeBiomeEncounters.forEach(encounter => {
|
extremeBiomeEncounters.forEach(encounter => {
|
||||||
|
|
|
@ -63,10 +63,12 @@ export function showEncounterText(scene: BattleScene, contentKey: string, callba
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param textContentKey
|
* @param textContentKey
|
||||||
* @param speakerContentKey
|
* @param speakerContentKey
|
||||||
* @param callback
|
* @param callbackDelay
|
||||||
*/
|
*/
|
||||||
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, callback?: Function) {
|
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, callbackDelay: number = 0): Promise<void> {
|
||||||
const text: string = getEncounterText(scene, textContentKey);
|
return new Promise<void>(resolve => {
|
||||||
const speaker: string = getEncounterText(scene, speakerContentKey);
|
const text: string = getEncounterText(scene, textContentKey);
|
||||||
scene.ui.showDialogue(text, speaker, null, callback, 0, 0);
|
const speaker: string = getEncounterText(scene, speakerContentKey);
|
||||||
|
scene.ui.showDialogue(text, speaker, null, () => resolve(), callbackDelay);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { BattlerIndex, BattleType } from "#app/battle";
|
import { BattlerIndex, BattleType } from "#app/battle";
|
||||||
import { biomeLinks } from "#app/data/biomes";
|
import { biomeLinks } from "#app/data/biomes";
|
||||||
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
|
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
|
||||||
|
@ -316,18 +316,21 @@ export function initCustomMovesForEncounter(scene: BattleScene, moves: Moves | M
|
||||||
* @param scene - Battle Scene
|
* @param scene - Battle Scene
|
||||||
* @param changeValue
|
* @param changeValue
|
||||||
* @param playSound
|
* @param playSound
|
||||||
|
* @param showMessage
|
||||||
*/
|
*/
|
||||||
export function updatePlayerMoney(scene: BattleScene, changeValue: number, playSound: boolean = true) {
|
export function updatePlayerMoney(scene: BattleScene, changeValue: number, playSound: boolean = true, showMessage: boolean = true) {
|
||||||
scene.money += changeValue;
|
scene.money = Math.min(Math.max(scene.money + changeValue, 0), Number.MAX_SAFE_INTEGER);
|
||||||
scene.updateMoneyText();
|
scene.updateMoneyText();
|
||||||
scene.animateMoneyChanged(false);
|
scene.animateMoneyChanged(false);
|
||||||
if (playSound) {
|
if (playSound) {
|
||||||
scene.playSound("buy");
|
scene.playSound("buy");
|
||||||
}
|
}
|
||||||
if (changeValue < 0) {
|
if (showMessage) {
|
||||||
scene.queueMessage(i18next.t("mysteryEncounter:paid_money", { amount: -changeValue }), null, true);
|
if (changeValue < 0) {
|
||||||
} else {
|
scene.queueMessage(i18next.t("mysteryEncounter:paid_money", { amount: -changeValue }), null, true);
|
||||||
scene.queueMessage(i18next.t("mysteryEncounter:receive_money", { amount: changeValue }), null, true);
|
} else {
|
||||||
|
scene.queueMessage(i18next.t("mysteryEncounter:receive_money", { amount: changeValue }), null, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,6 +402,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
||||||
}).concat({
|
}).concat({
|
||||||
label: i18next.t("menu:cancel"),
|
label: i18next.t("menu:cancel"),
|
||||||
handler: () => {
|
handler: () => {
|
||||||
|
scene.ui.clearText();
|
||||||
scene.ui.setMode(Mode.MYSTERY_ENCOUNTER);
|
scene.ui.setMode(Mode.MYSTERY_ENCOUNTER);
|
||||||
resolve(false);
|
resolve(false);
|
||||||
return true;
|
return true;
|
||||||
|
@ -730,12 +734,18 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
||||||
const numRuns = 1000;
|
const numRuns = 1000;
|
||||||
let run = 0;
|
let run = 0;
|
||||||
const targetEncountersPerRun = 15; // AVERAGE_ENCOUNTERS_PER_RUN_TARGET
|
const targetEncountersPerRun = 15; // AVERAGE_ENCOUNTERS_PER_RUN_TARGET
|
||||||
|
const biomes = Object.keys(Biome).filter(key => isNaN(Number(key)));
|
||||||
|
const alwaysPickTheseBiomes = [Biome.ISLAND, Biome.ABYSS, Biome.WASTELAND, Biome.FAIRY_CAVE, Biome.TEMPLE, Biome.LABORATORY, Biome.SPACE, Biome.WASTELAND];
|
||||||
|
|
||||||
const calculateNumEncounters = (): number[] => {
|
const calculateNumEncounters = (): any[] => {
|
||||||
let encounterRate = baseSpawnWeight; // BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
|
let encounterRate = baseSpawnWeight; // BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
|
||||||
const numEncounters = [0, 0, 0, 0];
|
const numEncounters = [0, 0, 0, 0];
|
||||||
|
const encountersByBiome = new Map<string, number>(biomes.map(b => [b, 0]));
|
||||||
|
const validMEfloorsByBiome = new Map<string, number>(biomes.map(b => [b, 0]));
|
||||||
let currentBiome = Biome.TOWN;
|
let currentBiome = Biome.TOWN;
|
||||||
let currentArena = scene.newArena(currentBiome);
|
let currentArena = scene.newArena(currentBiome);
|
||||||
|
scene.setSeed(Utils.randomString(24));
|
||||||
|
scene.resetSeed();
|
||||||
for (let i = 10; i < 180; i++) {
|
for (let i = 10; i < 180; i++) {
|
||||||
// Boss
|
// Boss
|
||||||
if (i % 10 === 0) {
|
if (i % 10 === 0) {
|
||||||
|
@ -748,10 +758,17 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
||||||
let biomes: Biome[];
|
let biomes: Biome[];
|
||||||
scene.executeWithSeedOffset(() => {
|
scene.executeWithSeedOffset(() => {
|
||||||
biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[])
|
biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[])
|
||||||
.filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1]))
|
.filter(b => {
|
||||||
|
return !Array.isArray(b) || !Utils.randSeedInt(b[1]);
|
||||||
|
})
|
||||||
.map(b => !Array.isArray(b) ? b : b[0]);
|
.map(b => !Array.isArray(b) ? b : b[0]);
|
||||||
}, i);
|
}, i * 100);
|
||||||
currentBiome = biomes[Utils.randSeedInt(biomes.length)];
|
const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b));
|
||||||
|
if (specialBiomes.length > 0) {
|
||||||
|
currentBiome = specialBiomes[Utils.randSeedInt(specialBiomes.length)];
|
||||||
|
} else {
|
||||||
|
currentBiome = biomes[Utils.randSeedInt(biomes.length)];
|
||||||
|
}
|
||||||
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
|
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
|
||||||
currentBiome = (biomeLinks[currentBiome] as Biome);
|
currentBiome = (biomeLinks[currentBiome] as Biome);
|
||||||
} else {
|
} else {
|
||||||
|
@ -778,6 +795,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
||||||
// Otherwise, roll encounter
|
// Otherwise, roll encounter
|
||||||
|
|
||||||
const roll = Utils.randSeedInt(256);
|
const roll = Utils.randSeedInt(256);
|
||||||
|
validMEfloorsByBiome.set(Biome[currentBiome], validMEfloorsByBiome.get(Biome[currentBiome]) + 1);
|
||||||
|
|
||||||
// If total number of encounters is lower than expected for the run, slightly favor a new encounter
|
// If total number of encounters is lower than expected for the run, slightly favor a new encounter
|
||||||
// Do the reverse as well
|
// Do the reverse as well
|
||||||
|
@ -803,31 +821,63 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
||||||
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; // 64 - 32 - 16 - 10 = 6
|
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; // 64 - 32 - 16 - 10 = 6
|
||||||
|
|
||||||
tierValue > commonThreshold ? ++numEncounters[0] : tierValue > uncommonThreshold ? ++numEncounters[1] : tierValue > rareThreshold ? ++numEncounters[2] : ++numEncounters[3];
|
tierValue > commonThreshold ? ++numEncounters[0] : tierValue > uncommonThreshold ? ++numEncounters[1] : tierValue > rareThreshold ? ++numEncounters[2] : ++numEncounters[3];
|
||||||
|
encountersByBiome.set(Biome[currentBiome], encountersByBiome.get(Biome[currentBiome]) + 1);
|
||||||
} else {
|
} else {
|
||||||
encounterRate += WIGHT_INCREMENT_ON_SPAWN_MISS;
|
encounterRate += WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return numEncounters;
|
return [numEncounters, encountersByBiome, validMEfloorsByBiome];
|
||||||
};
|
};
|
||||||
|
|
||||||
const runs = [];
|
const encounterRuns: number[][] = [];
|
||||||
|
const encountersByBiomeRuns: Map<string, number>[] = [];
|
||||||
|
const validFloorsByBiome: Map<string, number>[] = [];
|
||||||
while (run < numRuns) {
|
while (run < numRuns) {
|
||||||
scene.executeWithSeedOffset(() => {
|
scene.executeWithSeedOffset(() => {
|
||||||
const numEncounters = calculateNumEncounters();
|
const [numEncounters, encountersByBiome, validMEfloorsByBiome] = calculateNumEncounters();
|
||||||
runs.push(numEncounters);
|
encounterRuns.push(numEncounters);
|
||||||
|
encountersByBiomeRuns.push(encountersByBiome);
|
||||||
|
validFloorsByBiome.push(validMEfloorsByBiome);
|
||||||
}, 1000 * run);
|
}, 1000 * run);
|
||||||
run++;
|
run++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const n = runs.length;
|
const n = encounterRuns.length;
|
||||||
const totalEncountersInRun = runs.map(run => run.reduce((a, b) => a + b));
|
const totalEncountersInRun = encounterRuns.map(run => run.reduce((a, b) => a + b));
|
||||||
const totalMean = totalEncountersInRun.reduce((a, b) => a + b) / n;
|
const totalMean = totalEncountersInRun.reduce((a, b) => a + b) / n;
|
||||||
const totalStd = Math.sqrt(totalEncountersInRun.map(x => Math.pow(x - totalMean, 2)).reduce((a, b) => a + b) / n);
|
const totalStd = Math.sqrt(totalEncountersInRun.map(x => Math.pow(x - totalMean, 2)).reduce((a, b) => a + b) / n);
|
||||||
const commonMean = runs.reduce((a, b) => a + b[0], 0) / n;
|
const commonMean = encounterRuns.reduce((a, b) => a + b[0], 0) / n;
|
||||||
const uncommonMean = runs.reduce((a, b) => a + b[1], 0) / n;
|
const uncommonMean = encounterRuns.reduce((a, b) => a + b[1], 0) / n;
|
||||||
const rareMean = runs.reduce((a, b) => a + b[2], 0) / n;
|
const rareMean = encounterRuns.reduce((a, b) => a + b[2], 0) / n;
|
||||||
const superRareMean = runs.reduce((a, b) => a + b[3], 0) / n;
|
const superRareMean = encounterRuns.reduce((a, b) => a + b[3], 0) / n;
|
||||||
|
|
||||||
console.log(`Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Uncommons: ${uncommonMean}\nAvg Rares: ${rareMean}\nAvg Super Rares: ${superRareMean}`);
|
const encountersPerRunPerBiome = encountersByBiomeRuns.reduce((a, b) => {
|
||||||
|
for (const biome of a.keys()) {
|
||||||
|
a.set(biome, a.get(biome) + b.get(biome));
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
const meanEncountersPerRunPerBiome: Map<string, number> = new Map<string, number>();
|
||||||
|
encountersPerRunPerBiome.forEach((value, key) => {
|
||||||
|
meanEncountersPerRunPerBiome.set(key, value / n);
|
||||||
|
});
|
||||||
|
|
||||||
|
const validMEFloorsPerRunPerBiome = validFloorsByBiome.reduce((a, b) => {
|
||||||
|
for (const biome of a.keys()) {
|
||||||
|
a.set(biome, a.get(biome) + b.get(biome));
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
const meanMEFloorsPerRunPerBiome: Map<string, number> = new Map<string, number>();
|
||||||
|
validMEFloorsPerRunPerBiome.forEach((value, key) => {
|
||||||
|
meanMEFloorsPerRunPerBiome.set(key, value / n);
|
||||||
|
});
|
||||||
|
|
||||||
|
let stats = `Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Greats: ${uncommonMean}\nAvg Ultras: ${rareMean}\nAvg Rogues: ${superRareMean}\n`;
|
||||||
|
|
||||||
|
const meanEncountersPerRunPerBiomeSorted = [...meanEncountersPerRunPerBiome.entries()].sort((e1, e2) => e2[1] - e1[1]);
|
||||||
|
meanEncountersPerRunPerBiomeSorted.forEach(value => stats = stats + `${value[0]}: avg valid floors ${meanMEFloorsPerRunPerBiome.get(value[0])}, avg MEs ${value[1]},\n`);
|
||||||
|
|
||||||
|
console.log(stats);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { Type } from "#app/data/type";
|
||||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
|
|
||||||
export interface MysteryEncounterPokemonData {
|
export interface MysteryEncounterPokemonData {
|
||||||
spriteScale?: number
|
spriteScale?: number
|
||||||
|
@ -204,13 +205,11 @@ export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, h
|
||||||
* @param pokemon
|
* @param pokemon
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
export function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
|
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
|
||||||
pokemon.getSpeciesForm().baseStats = [...pokemon.getSpeciesForm().baseStats].map(v => {
|
const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE().generateType(null, [value]);
|
||||||
const newVal = Math.floor(v + value);
|
const modifier = modType.newModifier(pokemon);
|
||||||
return Math.max(newVal, 1);
|
await pokemon.scene.addModifier(modifier, false, false, false, true);
|
||||||
});
|
|
||||||
pokemon.calculateStats();
|
pokemon.calculateStats();
|
||||||
pokemon.updateInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -387,7 +386,7 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType): Promise<void> {
|
export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType, isObtain: boolean = false): Promise<void> {
|
||||||
scene.unshiftPhase(new VictoryPhase(scene, BattlerIndex.ENEMY));
|
scene.unshiftPhase(new VictoryPhase(scene, BattlerIndex.ENEMY));
|
||||||
|
|
||||||
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
|
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
|
||||||
|
@ -413,14 +412,16 @@ function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phase
|
||||||
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => {
|
scene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => {
|
||||||
const end = () => {
|
const end = () => {
|
||||||
scene.pokemonInfoContainer.hide();
|
scene.pokemonInfoContainer.hide();
|
||||||
removePb(scene, pokeball);
|
removePb(scene, pokeball);
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
const removePokemon = () => {
|
const removePokemon = () => {
|
||||||
scene.field.remove(pokemon, true);
|
if (pokemon) {
|
||||||
|
scene.field.remove(pokemon, true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const addToParty = () => {
|
const addToParty = () => {
|
||||||
const newPokemon = pokemon.addToParty(pokeballType);
|
const newPokemon = pokemon.addToParty(pokeballType);
|
||||||
|
@ -471,14 +472,18 @@ function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phase
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) {
|
function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) {
|
||||||
scene.tweens.add({
|
if (pokeball) {
|
||||||
targets: pokeball,
|
scene.tweens.add({
|
||||||
duration: 250,
|
targets: pokeball,
|
||||||
delay: 250,
|
duration: 250,
|
||||||
ease: "Sine.easeIn",
|
delay: 250,
|
||||||
alpha: 0,
|
ease: "Sine.easeIn",
|
||||||
onComplete: () => pokeball.destroy()
|
alpha: 0,
|
||||||
});
|
onComplete: () => {
|
||||||
|
pokeball.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||||
|
|
|
@ -11,5 +11,6 @@ export enum MysteryEncounterType {
|
||||||
SAFARI_ZONE,
|
SAFARI_ZONE,
|
||||||
LOST_AT_SEA, //might be generalized later on
|
LOST_AT_SEA, //might be generalized later on
|
||||||
FIERY_FALLOUT,
|
FIERY_FALLOUT,
|
||||||
THE_STRONG_STUFF
|
THE_STRONG_STUFF,
|
||||||
|
POKEMON_SALESMAN
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,13 @@ type KnownFileRoot =
|
||||||
| "mystery-encounters"
|
| "mystery-encounters"
|
||||||
| "pokeball"
|
| "pokeball"
|
||||||
| "pokemon"
|
| "pokemon"
|
||||||
|
| "pokemon/back"
|
||||||
|
| "pokemon/exp"
|
||||||
|
| "pokemon/female"
|
||||||
|
| "pokemon/icons"
|
||||||
|
| "pokemon/input"
|
||||||
|
| "pokemon/shiny"
|
||||||
|
| "pokemon/variant"
|
||||||
| "statuses"
|
| "statuses"
|
||||||
| "trainer"
|
| "trainer"
|
||||||
| "ui";
|
| "ui";
|
||||||
|
@ -25,7 +32,7 @@ export class MysteryEncounterSpriteConfig {
|
||||||
/** The sprite key (which is the image file name). e.g. "ace_trainer_f" */
|
/** The sprite key (which is the image file name). e.g. "ace_trainer_f" */
|
||||||
spriteKey: string;
|
spriteKey: string;
|
||||||
/** Refer to [/public/images](../../public/images) directorty for all folder names */
|
/** Refer to [/public/images](../../public/images) directorty for all folder names */
|
||||||
fileRoot: KnownFileRoot & string;
|
fileRoot: KnownFileRoot & string | string;
|
||||||
/** Enable shadow. Defaults to `false` */
|
/** Enable shadow. Defaults to `false` */
|
||||||
hasShadow?: boolean = false;
|
hasShadow?: boolean = false;
|
||||||
/** Disable animation. Defaults to `false` */
|
/** Disable animation. Defaults to `false` */
|
||||||
|
@ -44,6 +51,8 @@ export class MysteryEncounterSpriteConfig {
|
||||||
yShadow?: number;
|
yShadow?: number;
|
||||||
/** Sprite scale. `0` - `n` */
|
/** Sprite scale. `0` - `n` */
|
||||||
scale?: number;
|
scale?: number;
|
||||||
|
/** If you are using a Pokemon sprite, set to `true`. This will ensure variant, form, gender, shiny sprites are loaded properly */
|
||||||
|
isPokemon?: boolean;
|
||||||
/** If you are using an item sprite, set to `true` */
|
/** If you are using an item sprite, set to `true` */
|
||||||
isItem?: boolean;
|
isItem?: boolean;
|
||||||
/** The sprites alpha. `0` - `1` The lower the number, the more transparent */
|
/** The sprites alpha. `0` - `1` The lower the number, the more transparent */
|
||||||
|
@ -155,13 +164,18 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
}
|
}
|
||||||
|
|
||||||
this.spriteConfigs.forEach((config) => {
|
this.spriteConfigs.forEach((config) => {
|
||||||
if (!config.isItem) {
|
if (config.isPokemon) {
|
||||||
this.scene.loadAtlas(config.spriteKey, config.fileRoot);
|
this.scene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
|
||||||
} else {
|
} else if (config.isItem) {
|
||||||
this.scene.loadAtlas("items", "");
|
this.scene.loadAtlas("items", "");
|
||||||
|
} else {
|
||||||
|
this.scene.loadAtlas(config.spriteKey, config.fileRoot);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load dex progress icon
|
||||||
|
this.scene.loadAtlas("encounter_radar", "mystery-encounters");
|
||||||
|
|
||||||
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
||||||
this.spriteConfigs.every((config) => {
|
this.spriteConfigs.every((config) => {
|
||||||
if (config.isItem) {
|
if (config.isItem) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import * as Utils from "../utils";
|
||||||
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
|
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
|
||||||
import { getLevelTotalExp } from "../data/exp";
|
import { getLevelTotalExp } from "../data/exp";
|
||||||
import { Stat } from "../data/pokemon-stat";
|
import { Stat } from "../data/pokemon-stat";
|
||||||
import { AttackTypeBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonMultiHitModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, TerastallizeModifier } from "../modifier/modifier";
|
import { AttackTypeBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonMultiHitModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, TerastallizeModifier, PokemonBaseStatTotalModifier } from "../modifier/modifier";
|
||||||
import { PokeballType } from "../data/pokeball";
|
import { PokeballType } from "../data/pokeball";
|
||||||
import { Gender } from "../data/gender";
|
import { Gender } from "../data/gender";
|
||||||
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
|
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
|
||||||
|
@ -731,6 +731,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
this.stats = [ 0, 0, 0, 0, 0, 0 ];
|
this.stats = [ 0, 0, 0, 0, 0, 0 ];
|
||||||
}
|
}
|
||||||
const baseStats = this.getSpeciesForm().baseStats.slice(0);
|
const baseStats = this.getSpeciesForm().baseStats.slice(0);
|
||||||
|
this.scene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats);
|
||||||
if (this.fusionSpecies) {
|
if (this.fusionSpecies) {
|
||||||
const fusionBaseStats = this.getFusionSpeciesForm().baseStats;
|
const fusionBaseStats = this.getFusionSpeciesForm().baseStats;
|
||||||
for (let s = 0; s < this.stats.length; s++) {
|
for (let s = 0; s < this.stats.length; s++) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ export const battle: SimpleTranslationEntries = {
|
||||||
"moneyWon": "You got\n₽{{moneyAmount}} for winning!",
|
"moneyWon": "You got\n₽{{moneyAmount}} for winning!",
|
||||||
"moneyPickedUp": "You picked up ₽{{moneyAmount}}!",
|
"moneyPickedUp": "You picked up ₽{{moneyAmount}}!",
|
||||||
"pokemonCaught": "{{pokemonName}} was caught!",
|
"pokemonCaught": "{{pokemonName}} was caught!",
|
||||||
|
"pokemonObtained": "You got {{pokemonName}}!",
|
||||||
"pokemonBrokeFree": "Oh no!\nThe Pokémon broke free!",
|
"pokemonBrokeFree": "Oh no!\nThe Pokémon broke free!",
|
||||||
"pokemonFled": "The wild {{pokemonName}} fled!",
|
"pokemonFled": "The wild {{pokemonName}} fled!",
|
||||||
"playerFled": "You fled from the {{pokemonName}}!",
|
"playerFled": "You fled from the {{pokemonName}}!",
|
||||||
|
|
|
@ -66,6 +66,16 @@ export const modifierType: ModifierTypeTranslationEntries = {
|
||||||
"PokemonBaseStatBoosterModifierType": {
|
"PokemonBaseStatBoosterModifierType": {
|
||||||
description: "Increases the holder's base {{statName}} by 10%. The higher your IVs, the higher the stack limit.",
|
description: "Increases the holder's base {{statName}} by 10%. The higher your IVs, the higher the stack limit.",
|
||||||
},
|
},
|
||||||
|
"PokemonBaseStatTotalModifierType": {
|
||||||
|
name: "Shuckle Juice",
|
||||||
|
description: "{{increaseDecrease}} all of the holder's base stats by {{statValue}}. You were {{blessCurse}} by the Shuckle.",
|
||||||
|
extra: {
|
||||||
|
"increase": "Increases",
|
||||||
|
"decrease": "Decreases",
|
||||||
|
"blessed": "blessed",
|
||||||
|
"cursed": "cursed"
|
||||||
|
},
|
||||||
|
},
|
||||||
"AllPokemonFullHpRestoreModifierType": {
|
"AllPokemonFullHpRestoreModifierType": {
|
||||||
description: "Restores 100% HP for all Pokémon.",
|
description: "Restores 100% HP for all Pokémon.",
|
||||||
},
|
},
|
||||||
|
@ -240,6 +250,8 @@ export const modifierType: ModifierTypeTranslationEntries = {
|
||||||
"ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Full Heal Token", description: "Adds a 2.5% chance every turn to heal a status condition." },
|
"ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Full Heal Token", description: "Adds a 2.5% chance every turn to heal a status condition." },
|
||||||
"ENEMY_ENDURE_CHANCE": { name: "Endure Token" },
|
"ENEMY_ENDURE_CHANCE": { name: "Endure Token" },
|
||||||
"ENEMY_FUSED_CHANCE": { name: "Fusion Token", description: "Adds a 1% chance that a wild Pokémon will be a fusion." },
|
"ENEMY_FUSED_CHANCE": { name: "Fusion Token", description: "Adds a 1% chance that a wild Pokémon will be a fusion." },
|
||||||
|
|
||||||
|
"MYSTERY_ENCOUNTER_SHUCKLE_JUICE": { name: "Shuckle Juice" },
|
||||||
},
|
},
|
||||||
SpeciesBoosterItem: {
|
SpeciesBoosterItem: {
|
||||||
"LIGHT_BALL": { name: "Light Ball", description: "It's a mysterious orb that boosts Pikachu's Attack and Sp. Atk stats." },
|
"LIGHT_BALL": { name: "Light Ball", description: "It's a mysterious orb that boosts Pikachu's Attack and Sp. Atk stats." },
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { shadyVitaminDealerDialogue } from "#app/locales/en/mystery-encounters/s
|
||||||
import { slumberingSnorlaxDialogue } from "#app/locales/en/mystery-encounters/slumbering-snorlax-dialogue";
|
import { slumberingSnorlaxDialogue } from "#app/locales/en/mystery-encounters/slumbering-snorlax-dialogue";
|
||||||
import { trainingSessionDialogue } from "#app/locales/en/mystery-encounters/training-session-dialogue";
|
import { trainingSessionDialogue } from "#app/locales/en/mystery-encounters/training-session-dialogue";
|
||||||
import { theStrongStuffDialogue } from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue";
|
import { theStrongStuffDialogue } from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue";
|
||||||
|
import { pokemonSalesmanDialogue } from "#app/locales/en/mystery-encounters/pokemon-salesman-dialogue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patterns that can be used:
|
* Patterns that can be used:
|
||||||
|
@ -32,6 +33,7 @@ export const mysteryEncounter = {
|
||||||
// General use content
|
// General use content
|
||||||
"paid_money": "You paid ₽{{amount, number}}.",
|
"paid_money": "You paid ₽{{amount, number}}.",
|
||||||
"receive_money": "You received ₽{{amount, number}}!",
|
"receive_money": "You received ₽{{amount, number}}!",
|
||||||
|
"affects_pokedex": "Affects Pokédex Data",
|
||||||
|
|
||||||
mysteriousChallengers: mysteriousChallengersDialogue,
|
mysteriousChallengers: mysteriousChallengersDialogue,
|
||||||
mysteriousChest: mysteriousChestDialogue,
|
mysteriousChest: mysteriousChestDialogue,
|
||||||
|
@ -46,4 +48,5 @@ export const mysteryEncounter = {
|
||||||
lostAtSea: lostAtSeaDialogue,
|
lostAtSea: lostAtSeaDialogue,
|
||||||
fieryFallout: fieryFalloutDialogue,
|
fieryFallout: fieryFalloutDialogue,
|
||||||
theStrongStuff: theStrongStuffDialogue,
|
theStrongStuff: theStrongStuffDialogue,
|
||||||
|
pokemonSalesman: pokemonSalesmanDialogue
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const fightOrFlightDialogue = {
|
||||||
selected: "You approach the\nPokémon without fear.",
|
selected: "You approach the\nPokémon without fear.",
|
||||||
},
|
},
|
||||||
2: {
|
2: {
|
||||||
label: "{{option2PrimaryName}} can help",
|
label: "Steal the item",
|
||||||
tooltip: "@[SUMMARY_GREEN]{(35%) Steal Item}\n@[SUMMARY_BLUE]{(65%) Harder Battle}",
|
tooltip: "@[SUMMARY_GREEN]{(35%) Steal Item}\n@[SUMMARY_BLUE]{(65%) Harder Battle}",
|
||||||
tooltip_special: "(+) {{option2PrimaryName}} uses {{option2PrimaryMove}}",
|
tooltip_special: "(+) {{option2PrimaryName}} uses {{option2PrimaryMove}}",
|
||||||
good_result: `.@d{32}.@d{32}.@d{32}
|
good_result: `.@d{32}.@d{32}.@d{32}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
export const pokemonSalesmanDialogue = {
|
||||||
|
intro: "A chipper elderly man approaches you.",
|
||||||
|
speaker: "Gentleman",
|
||||||
|
intro_dialogue: "Hello there! Have I got a deal just for YOU!",
|
||||||
|
title: "The Pokémon Salesman",
|
||||||
|
description: "\"This {{purchasePokemon}} is extremely unique and carries an ability not normally found in its species! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"",
|
||||||
|
description_shiny: "\"This {{purchasePokemon}} is extremely unique and has a pigment not normally found in its species! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"",
|
||||||
|
query: "What will you do?",
|
||||||
|
option: {
|
||||||
|
1: {
|
||||||
|
label: "Accept",
|
||||||
|
tooltip: "(-) Pay {{price, money}}\n(+) Gain a {{purchasePokemon}} with its Hidden Ability",
|
||||||
|
tooltip_shiny: "(-) Pay {{price, money}}\n(+) Gain a shiny {{purchasePokemon}}",
|
||||||
|
selected_message: "You paid an outrageous sum and bought the {{purchasePokemon}}.",
|
||||||
|
selected_dialogue: `Excellent choice!
|
||||||
|
$I can see you've a keen eye for business.
|
||||||
|
$Oh, yeah...@d{64} Returns not accepted, got that?`,
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
label: "Refuse",
|
||||||
|
tooltip: "(-) No Rewards",
|
||||||
|
selected: `No?@d{32} You say no?
|
||||||
|
$I'm only doing this as a favor to you!`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -3,6 +3,7 @@ export const trainingSessionDialogue = {
|
||||||
title: "Training Session",
|
title: "Training Session",
|
||||||
description: "These supplies look like they could be used to train a member of your party! There are a few ways you could train your Pokémon, by battling against it with the rest of your team.",
|
description: "These supplies look like they could be used to train a member of your party! There are a few ways you could train your Pokémon, by battling against it with the rest of your team.",
|
||||||
query: "How should you train?",
|
query: "How should you train?",
|
||||||
|
invalid_selection: "Pokémon must be healthy enough.",
|
||||||
option: {
|
option: {
|
||||||
1: {
|
1: {
|
||||||
label: "Light Training",
|
label: "Light Training",
|
||||||
|
@ -19,7 +20,7 @@ export const trainingSessionDialogue = {
|
||||||
},
|
},
|
||||||
3: {
|
3: {
|
||||||
label: "Heavy Training",
|
label: "Heavy Training",
|
||||||
tooltip: "(-) Each of your Pokémon lose {{damagePercentage}}% of their total HP",
|
tooltip: "(-) Harsh Battle\n(+) Change Pokémon's Ability",
|
||||||
select_prompt: "Select a new ability\nto train your Pokémon in.",
|
select_prompt: "Select a new ability\nto train your Pokémon in.",
|
||||||
finished: `{{selectedPokemon}} returns, feeling\nworn out but accomplished!
|
finished: `{{selectedPokemon}} returns, feeling\nworn out but accomplished!
|
||||||
$Its ability was changed to {{ability}}!`,
|
$Its ability was changed to {{ability}}!`,
|
||||||
|
|
|
@ -621,6 +621,27 @@ export class PokemonBaseStatBoosterModifierType extends PokemonHeldItemModifierT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
|
||||||
|
private readonly statModifier: integer;
|
||||||
|
|
||||||
|
constructor(statModifier: integer) {
|
||||||
|
super("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE", "berry_juice", (_type, args) => new Modifiers.PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, this.statModifier));
|
||||||
|
this.statModifier = statModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDescription(scene: BattleScene): string {
|
||||||
|
return i18next.t("modifierType:ModifierType.PokemonBaseStatTotalModifierType.description", {
|
||||||
|
increaseDecrease: i18next.t(this.statModifier >= 0 ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.increase" : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.decrease"),
|
||||||
|
blessCurse: i18next.t(this.statModifier >= 0 ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.blessed" : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.cursed"),
|
||||||
|
statValue: this.statModifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPregenArgs(): any[] {
|
||||||
|
return [ this.statModifier ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AllPokemonFullHpRestoreModifierType extends ModifierType {
|
class AllPokemonFullHpRestoreModifierType extends ModifierType {
|
||||||
private descriptionKey: string;
|
private descriptionKey: string;
|
||||||
|
|
||||||
|
@ -1354,6 +1375,13 @@ export const modifierTypes = {
|
||||||
ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_STATUS_EFFECT_HEAL_CHANCE", "wl_full_heal", (type, _args) => new Modifiers.EnemyStatusEffectHealChanceModifier(type, 2.5, 10)),
|
ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_STATUS_EFFECT_HEAL_CHANCE", "wl_full_heal", (type, _args) => new Modifiers.EnemyStatusEffectHealChanceModifier(type, 2.5, 10)),
|
||||||
ENEMY_ENDURE_CHANCE: () => new EnemyEndureChanceModifierType("modifierType:ModifierType.ENEMY_ENDURE_CHANCE", "wl_reset_urge", 2),
|
ENEMY_ENDURE_CHANCE: () => new EnemyEndureChanceModifierType("modifierType:ModifierType.ENEMY_ENDURE_CHANCE", "wl_reset_urge", 2),
|
||||||
ENEMY_FUSED_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_FUSED_CHANCE", "wl_custom_spliced", (type, _args) => new Modifiers.EnemyFusionChanceModifier(type, 1)),
|
ENEMY_FUSED_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_FUSED_CHANCE", "wl_custom_spliced", (type, _args) => new Modifiers.EnemyFusionChanceModifier(type, 1)),
|
||||||
|
|
||||||
|
MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
|
||||||
|
if (pregenArgs) {
|
||||||
|
return new PokemonBaseStatTotalModifierType(pregenArgs[0] as integer);
|
||||||
|
}
|
||||||
|
return new PokemonBaseStatTotalModifierType(Utils.randSeedInt(20));
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ModifierPool {
|
interface ModifierPool {
|
||||||
|
|
|
@ -708,6 +708,55 @@ export class PokemonBaseStatModifier extends PokemonHeldItemModifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||||
|
private statModifier: integer;
|
||||||
|
|
||||||
|
constructor(type: ModifierTypes.PokemonBaseStatTotalModifierType, pokemonId: integer, statModifier: integer, stackCount?: integer) {
|
||||||
|
super(type, pokemonId, stackCount);
|
||||||
|
this.statModifier = statModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchType(modifier: Modifier): boolean {
|
||||||
|
if (modifier instanceof PokemonBaseStatTotalModifier) {
|
||||||
|
return (modifier as PokemonBaseStatTotalModifier).statModifier === this.statModifier;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone(): PersistentModifier {
|
||||||
|
return new PokemonBaseStatTotalModifier(this.type as ModifierTypes.PokemonBaseStatTotalModifierType, this.pokemonId, this.statModifier, this.stackCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
getArgs(): any[] {
|
||||||
|
return super.getArgs().concat(this.statModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldApply(args: any[]): boolean {
|
||||||
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(args: any[]): boolean {
|
||||||
|
args[1].forEach((v, i) => {
|
||||||
|
const newVal = Math.floor(v + this.statModifier);
|
||||||
|
args[1][i] = Math.min(Math.max(newVal, 1), 999999);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTransferrable(_withinParty: boolean): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getScoreMultiplier(): number {
|
||||||
|
return 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifier used for held items that apply {@linkcode Stat} boost(s)
|
* Modifier used for held items that apply {@linkcode Stat} boost(s)
|
||||||
* using a multiplier.
|
* using a multiplier.
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
import { Biome } from "#app/enums/biome";
|
||||||
|
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||||
|
import { Species } from "#app/enums/species";
|
||||||
|
import GameManager from "#app/test/utils/gameManager";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import Battle from "#app/battle";
|
||||||
|
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
|
import { runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
|
import { PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/pokemon-salesman-encounter";
|
||||||
|
|
||||||
|
const namespace = "mysteryEncounter:pokemonSalesman";
|
||||||
|
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||||
|
const defaultBiome = Biome.CAVE;
|
||||||
|
const defaultWave = 45;
|
||||||
|
|
||||||
|
describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
let scene: BattleScene;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
scene = game.scene;
|
||||||
|
game.override.mysteryEncounterChance(100);
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||||
|
game.override.startingWave(defaultWave);
|
||||||
|
game.override.startingBiome(defaultBiome);
|
||||||
|
|
||||||
|
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||||
|
]);
|
||||||
|
HUMAN_TRANSITABLE_BIOMES.forEach(biome => {
|
||||||
|
biomeMap.set(biome, [MysteryEncounterType.POKEMON_SALESMAN]);
|
||||||
|
});
|
||||||
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the correct properties", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
|
||||||
|
|
||||||
|
expect(PokemonSalesmanEncounter.encounterType).toBe(MysteryEncounterType.POKEMON_SALESMAN);
|
||||||
|
expect(PokemonSalesmanEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA);
|
||||||
|
expect(PokemonSalesmanEncounter.dialogue).toBeDefined();
|
||||||
|
expect(PokemonSalesmanEncounter.dialogue.intro).toStrictEqual([
|
||||||
|
{ text: `${namespace}:intro` },
|
||||||
|
{ speaker: "mysteryEncounter:pokemonSalesman:speaker", text: "mysteryEncounter:pokemonSalesman:intro_dialogue" }
|
||||||
|
]);
|
||||||
|
expect(PokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
|
||||||
|
expect(PokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
|
||||||
|
expect(PokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}:query`);
|
||||||
|
expect(PokemonSalesmanEncounter.options.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
|
||||||
|
game.override.startingBiome(Biome.VOLCANO);
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.POKEMON_SALESMAN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not run below wave 10", async () => {
|
||||||
|
game.override.startingWave(9);
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.POKEMON_SALESMAN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not run above wave 179", async () => {
|
||||||
|
game.override.startingWave(181);
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize fully ", async () => {
|
||||||
|
vi.spyOn(scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: PokemonSalesmanEncounter } as Battle);
|
||||||
|
|
||||||
|
const { onInit } = PokemonSalesmanEncounter;
|
||||||
|
|
||||||
|
expect(PokemonSalesmanEncounter.onInit).toBeDefined();
|
||||||
|
|
||||||
|
const onInitResult = onInit(scene);
|
||||||
|
|
||||||
|
expect(PokemonSalesmanEncounter.dialogueTokens?.purchasePokemon).toBeDefined();
|
||||||
|
expect(PokemonSalesmanEncounter.dialogueTokens?.price).toBeDefined();
|
||||||
|
expect(PokemonSalesmanEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy();
|
||||||
|
expect(PokemonSalesmanEncounter.misc?.price?.toString()).toBe(PokemonSalesmanEncounter.dialogueTokens?.price);
|
||||||
|
expect(onInitResult).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 1 - Purchase the pokemon", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option1 = PokemonSalesmanEncounter.options[0];
|
||||||
|
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT_OR_SPECIAL);
|
||||||
|
expect(option1.dialogue).toBeDefined();
|
||||||
|
expect(option1.dialogue).toStrictEqual({
|
||||||
|
buttonLabel: `${namespace}:option:1:label`,
|
||||||
|
buttonTooltip: `${namespace}:option:1:tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}:option:1:selected_message`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should update the player's money properly", async () => {
|
||||||
|
const initialMoney = 20000;
|
||||||
|
scene.money = initialMoney;
|
||||||
|
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
|
||||||
|
await runSelectMysteryEncounterOption(game, 1);
|
||||||
|
|
||||||
|
const price = scene.currentBattle.mysteryEncounter.misc.price;
|
||||||
|
|
||||||
|
expect(updateMoneySpy).toHaveBeenCalledWith(scene, -price, true, false);
|
||||||
|
expect(scene.money).toBe(initialMoney - price);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should add the Pokemon to the party", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
|
||||||
|
|
||||||
|
const initialPartySize = scene.getParty().length;
|
||||||
|
const pokemonName = scene.currentBattle.mysteryEncounter.misc.pokemon.name;
|
||||||
|
|
||||||
|
await runSelectMysteryEncounterOption(game, 1);
|
||||||
|
|
||||||
|
expect(scene.getParty().length).toBe(initialPartySize + 1);
|
||||||
|
expect(scene.getParty().find(p => p.name === pokemonName) instanceof PlayerPokemon).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave encounter without battle", async () => {
|
||||||
|
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
|
||||||
|
await runSelectMysteryEncounterOption(game, 1);
|
||||||
|
|
||||||
|
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 2 - Leave", () => {
|
||||||
|
it("should leave encounter without battle", async () => {
|
||||||
|
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
|
||||||
|
await runSelectMysteryEncounterOption(game, 2);
|
||||||
|
|
||||||
|
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -22,6 +22,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { PokemonMove } from "#app/field/pokemon";
|
import { PokemonMove } from "#app/field/pokemon";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||||
|
import { PokemonBaseStatTotalModifier } from "#app/modifier/modifier";
|
||||||
|
|
||||||
const namespace = "mysteryEncounter:theStrongStuff";
|
const namespace = "mysteryEncounter:theStrongStuff";
|
||||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||||
|
@ -153,7 +154,9 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||||
await runSelectMysteryEncounterOption(game, 1);
|
await runSelectMysteryEncounterOption(game, 1);
|
||||||
|
|
||||||
const bstsAfter = scene.getParty().map(p => {
|
const bstsAfter = scene.getParty().map(p => {
|
||||||
return p.getSpeciesForm().getBaseStatTotal();
|
const baseStats = p.getSpeciesForm().baseStats.slice(0);
|
||||||
|
scene.applyModifiers(PokemonBaseStatTotalModifier, true, p, baseStats);
|
||||||
|
return baseStats.reduce((a, b) => a + b);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(bstsAfter[0]).toEqual(bstsPrior[0] - 20 * 6);
|
expect(bstsAfter[0]).toEqual(bstsPrior[0] - 20 * 6);
|
||||||
|
|
|
@ -298,7 +298,7 @@ describe("Mystery Encounter Utils", () => {
|
||||||
const spy = vi.spyOn(game.scene.ui, "showDialogue");
|
const spy = vi.spyOn(game.scene.ui, "showDialogue");
|
||||||
|
|
||||||
showEncounterDialogue(scene, "mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue");
|
showEncounterDialogue(scene, "mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue");
|
||||||
expect(spy).toHaveBeenCalledWith("valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", "valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", null, undefined, 0, 0);
|
expect(spy).toHaveBeenCalledWith("valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", "valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", null, expect.any(Function), 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -248,6 +248,14 @@ export default class MockText {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableInteractive() {
|
||||||
|
// Disables interaction with this Game Object.
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTint() {
|
||||||
|
// Clears tint on this Game Object.
|
||||||
|
}
|
||||||
|
|
||||||
add(obj) {
|
add(obj) {
|
||||||
// Adds a child to this Game Object.
|
// Adds a child to this Game Object.
|
||||||
this.list.push(obj);
|
this.list.push(obj);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { getPokeballAtlasKey } from "../data/pokeball";
|
||||||
import { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
import { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
|
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
export default class MysteryEncounterUiHandler extends UiHandler {
|
export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
private cursorContainer: Phaser.GameObjects.Container;
|
private cursorContainer: Phaser.GameObjects.Container;
|
||||||
|
@ -29,6 +30,10 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
private descriptionScrollTween: Phaser.Tweens.Tween;
|
private descriptionScrollTween: Phaser.Tweens.Tween;
|
||||||
private rarityBall: Phaser.GameObjects.Sprite;
|
private rarityBall: Phaser.GameObjects.Sprite;
|
||||||
|
|
||||||
|
private dexProgressWindow: Phaser.GameObjects.NineSlice;
|
||||||
|
private dexProgressContainer: Phaser.GameObjects.Container;
|
||||||
|
private showDexProgress: boolean = false;
|
||||||
|
|
||||||
private overrideSettings: OptionSelectSettings;
|
private overrideSettings: OptionSelectSettings;
|
||||||
private encounterOptions: MysteryEncounterOption[] = [];
|
private encounterOptions: MysteryEncounterOption[] = [];
|
||||||
private optionsMeetsReqs: boolean[];
|
private optionsMeetsReqs: boolean[];
|
||||||
|
@ -50,6 +55,9 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
this.optionsContainer = this.scene.add.container(12, -38.7);
|
this.optionsContainer = this.scene.add.container(12, -38.7);
|
||||||
this.optionsContainer.setVisible(false);
|
this.optionsContainer.setVisible(false);
|
||||||
ui.add(this.optionsContainer);
|
ui.add(this.optionsContainer);
|
||||||
|
this.dexProgressContainer = this.scene.add.container(214, -43);
|
||||||
|
this.dexProgressContainer.setVisible(false);
|
||||||
|
ui.add(this.dexProgressContainer);
|
||||||
this.descriptionContainer = this.scene.add.container(0, -152);
|
this.descriptionContainer = this.scene.add.container(0, -152);
|
||||||
this.descriptionContainer.setVisible(false);
|
this.descriptionContainer.setVisible(false);
|
||||||
ui.add(this.descriptionContainer);
|
ui.add(this.descriptionContainer);
|
||||||
|
@ -65,9 +73,23 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
this.tooltipWindow = addWindow(this.scene, 0, 0, 110, 48, false, false, 0, 0, WindowVariant.THIN);
|
this.tooltipWindow = addWindow(this.scene, 0, 0, 110, 48, false, false, 0, 0, WindowVariant.THIN);
|
||||||
this.tooltipContainer.add(this.tooltipWindow);
|
this.tooltipContainer.add(this.tooltipWindow);
|
||||||
|
|
||||||
|
this.dexProgressWindow = addWindow(this.scene, 0, 0, 24, 28, false, false, 0, 0, WindowVariant.THIN);
|
||||||
|
this.dexProgressContainer.add(this.dexProgressWindow);
|
||||||
|
|
||||||
this.rarityBall = this.scene.add.sprite(141, 9, "pb");
|
this.rarityBall = this.scene.add.sprite(141, 9, "pb");
|
||||||
this.rarityBall.setScale(0.75);
|
this.rarityBall.setScale(0.75);
|
||||||
this.descriptionContainer.add(this.rarityBall);
|
this.descriptionContainer.add(this.rarityBall);
|
||||||
|
|
||||||
|
const dexProgressIndicator = this.scene.add.sprite(12, 9, "encounter_radar");
|
||||||
|
dexProgressIndicator.setScale(0.85);
|
||||||
|
this.dexProgressContainer.add(dexProgressIndicator);
|
||||||
|
this.dexProgressContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 24, 28), Phaser.Geom.Rectangle.Contains);
|
||||||
|
this.dexProgressContainer.on("pointerover", () => {
|
||||||
|
(this.scene as BattleScene).ui.showTooltip(null, i18next.t("mysteryEncounter:affects_pokedex"), true);
|
||||||
|
});
|
||||||
|
this.dexProgressContainer.on("pointerout", () => {
|
||||||
|
(this.scene as BattleScene).ui.hideTooltip();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
show(args: any[]): boolean {
|
show(args: any[]): boolean {
|
||||||
|
@ -81,6 +103,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
this.cursorContainer.setVisible(true);
|
this.cursorContainer.setVisible(true);
|
||||||
this.descriptionContainer.setVisible(showDescriptionContainer);
|
this.descriptionContainer.setVisible(showDescriptionContainer);
|
||||||
this.optionsContainer.setVisible(true);
|
this.optionsContainer.setVisible(true);
|
||||||
|
this.dexProgressContainer.setVisible(true);
|
||||||
this.displayEncounterOptions(slideInDescription);
|
this.displayEncounterOptions(slideInDescription);
|
||||||
const cursor = this.getCursor();
|
const cursor = this.getCursor();
|
||||||
if (cursor === (this?.optionsContainer?.length || 0) - 1) {
|
if (cursor === (this?.optionsContainer?.length || 0) - 1) {
|
||||||
|
@ -317,7 +340,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
const queryText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.query, TextStyle.TOOLTIP_CONTENT);
|
const queryText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.query, TextStyle.TOOLTIP_CONTENT);
|
||||||
|
|
||||||
// Clear options container (except cursor)
|
// Clear options container (except cursor)
|
||||||
this.optionsContainer.removeAll();
|
this.optionsContainer.removeAll(true);
|
||||||
|
|
||||||
// Options Window
|
// Options Window
|
||||||
for (let i = 0; i < this.encounterOptions.length; i++) {
|
for (let i = 0; i < this.encounterOptions.length; i++) {
|
||||||
|
@ -437,6 +460,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
|
|
||||||
if (isNullOrUndefined(cursor) || cursor > this.optionsContainer.length - 2) {
|
if (isNullOrUndefined(cursor) || cursor > this.optionsContainer.length - 2) {
|
||||||
// Ignore hovers on view party button
|
// Ignore hovers on view party button
|
||||||
|
// Hide dex progress if visible
|
||||||
|
this.showHideDexProgress(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,6 +512,13 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dex progress indicator
|
||||||
|
if (cursorOption.hasDexProgress && !this.showDexProgress) {
|
||||||
|
this.showHideDexProgress(true);
|
||||||
|
} else if (!cursorOption.hasDexProgress) {
|
||||||
|
this.showHideDexProgress(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
|
@ -494,6 +526,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
this.overrideSettings = null;
|
this.overrideSettings = null;
|
||||||
this.optionsContainer.setVisible(false);
|
this.optionsContainer.setVisible(false);
|
||||||
this.optionsContainer.removeAll(true);
|
this.optionsContainer.removeAll(true);
|
||||||
|
this.dexProgressContainer.setVisible(false);
|
||||||
this.descriptionContainer.setVisible(false);
|
this.descriptionContainer.setVisible(false);
|
||||||
this.tooltipContainer.setVisible(false);
|
this.tooltipContainer.setVisible(false);
|
||||||
// Keeps container background and pokeball
|
// Keeps container background and pokeball
|
||||||
|
@ -508,4 +541,30 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
}
|
}
|
||||||
this.cursorObj = null;
|
this.cursorObj = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param show - if true does show, if false does hide
|
||||||
|
*/
|
||||||
|
showHideDexProgress(show: boolean) {
|
||||||
|
if (show && !this.showDexProgress) {
|
||||||
|
this.showDexProgress = true;
|
||||||
|
this.scene.tweens.killTweensOf(this.dexProgressContainer);
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: this.dexProgressContainer,
|
||||||
|
y: -63,
|
||||||
|
ease: "Sine.easeInOut",
|
||||||
|
duration: 750
|
||||||
|
});
|
||||||
|
} else if (!show && this.showDexProgress) {
|
||||||
|
this.showDexProgress = false;
|
||||||
|
this.scene.tweens.killTweensOf(this.dexProgressContainer);
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: this.dexProgressContainer,
|
||||||
|
y: -43,
|
||||||
|
ease: "Sine.easeInOut",
|
||||||
|
duration: 750,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue