clean up safari code and utils
This commit is contained in:
parent
da0aea0d1e
commit
e1bb1e2481
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "bait.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 14,
|
||||
"h": 43
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 3,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 3,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 16,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 5,
|
||||
"w": 11,
|
||||
"h": 11
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 31,
|
||||
"w": 11,
|
||||
"h": 11
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:f0ec04fcd67ac346dce973693711d032:b697e09191c4312b8faaa0a080a309b7:1af241a52e61fa01ca849aa03c112f85$"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 277 B |
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "mud.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 18,
|
||||
"h": 55
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 16,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 3,
|
||||
"w": 16,
|
||||
"h": 13
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 16,
|
||||
"h": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 16,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 4,
|
||||
"w": 16,
|
||||
"h": 12
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 16,
|
||||
"w": 16,
|
||||
"h": 12
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 16,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 7,
|
||||
"w": 16,
|
||||
"h": 9
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 30,
|
||||
"w": 16,
|
||||
"h": 9
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 16,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 3,
|
||||
"w": 14,
|
||||
"h": 13
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 41,
|
||||
"w": 14,
|
||||
"h": 13
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:a9f7ae83758a2dffaacdaba2ee9dc2e2:0ebff9db47ce74a0ec049f5d74d589fa:c64f6b8befc3d5e9f836246d2b9536be$"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 361 B |
|
@ -9,7 +9,8 @@ import { PokeballType } from "../../pokeball";
|
|||
import { getPokemonSpecies } from "../../pokemon-species";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { EnemyPartyConfig, EnemyPokemonConfig, getRandomPlayerPokemon, getRandomSpeciesByStarterTier, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../mystery-encounter-utils";
|
||||
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
|
||||
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
const namespace = "mysteryEncounter:dark_deal";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
selectPokemonForOption,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { TempBattleStat } from "#app/data/temp-battle-stat";
|
||||
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
|
|
|
@ -4,10 +4,8 @@ import {
|
|||
EnemyPartyConfig,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
queueEncounterMessage,
|
||||
setEncounterRewards,
|
||||
showEncounterText,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
setEncounterRewards
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
|
@ -28,6 +26,7 @@ import IMysteryEncounter, {
|
|||
MysteryEncounterTier,
|
||||
} from "../mystery-encounter";
|
||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:fight_or_flight";
|
||||
|
|
|
@ -2,7 +2,7 @@ import {
|
|||
EnemyPartyConfig,
|
||||
initBattleWithEnemyConfig,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
trainerConfigs,
|
||||
TrainerPartyCompoundTemplate,
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import {
|
||||
getHighestLevelPlayerPokemon,
|
||||
koPlayerPokemon,
|
||||
leaveEncounterWithoutBattle,
|
||||
queueEncounterMessage,
|
||||
setEncounterRewards,
|
||||
showEncounterText,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
setEncounterRewards
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { GameOverPhase } from "#app/phases";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
|
@ -16,6 +12,8 @@ import IMysteryEncounter, {
|
|||
MysteryEncounterTier,
|
||||
} from "../mystery-encounter";
|
||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
export const MysteriousChestEncounter: IMysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(
|
||||
|
@ -119,11 +117,8 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
|||
// Does this synchronously so that game over doesn't happen over result message
|
||||
await showEncounterText(scene, "mysteryEncounter:mysterious_chest_option_1_bad_result")
|
||||
.then(() => {
|
||||
if (
|
||||
scene.getParty().filter((p) => p.isAllowedInBattle()).length ===
|
||||
0
|
||||
) {
|
||||
// All pokemon fainted, game over
|
||||
if (scene.getParty().filter((p) => p.isAllowedInBattle()).length === 0) {
|
||||
// All pokemon fainted, game over
|
||||
scene.clearPhaseQueue();
|
||||
scene.unshiftPhase(new GameOverPhase(scene));
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getRandomSpeciesByStarterTier, initFollowupOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "../../../battle-scene";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, MysteryEncounterVariant } from "../mystery-encounter";
|
||||
|
@ -6,27 +6,95 @@ import MysteryEncounterOption, { EncounterOptionMode, MysteryEncounterOptionBuil
|
|||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { ScanIvsPhase, SummonPhase, VictoryPhase } from "#app/phases";
|
||||
import i18next from "i18next";
|
||||
import { HiddenAbilityRateBoosterModifier, IvScannerModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier";
|
||||
import { EnemyPokemon } from "#app/field/pokemon";
|
||||
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballTintColor, PokeballType } from "#app/data/pokeball";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
|
||||
import { achvs } from "#app/system/achv";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { PokeballType } from "#app/data/pokeball";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { IntegerHolder, randSeedInt } from "#app/utils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:safari_zone";
|
||||
|
||||
export const SafariZoneEncounter: IMysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "chest_blue",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 10,
|
||||
yShadowOffset: 3
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}_intro_message`,
|
||||
},
|
||||
])
|
||||
.withTitle(`${namespace}_title`)
|
||||
.withDescription(`${namespace}_description`)
|
||||
.withQuery(`${namespace}_query`)
|
||||
.withOption(new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}_option_1_label`,
|
||||
buttonTooltip: `${namespace}_option_1_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}_option_1_selected_message`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Start safari encounter
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
encounter.encounterVariant = MysteryEncounterVariant.SAFARI_BATTLE;
|
||||
encounter.misc = {
|
||||
safariPokemonRemaining: 3
|
||||
};
|
||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
|
||||
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims");
|
||||
scene.loadSe("PRSFX- Taunt2", "battle_anims");
|
||||
scene.loadAtlas("bait", "mystery-encounters");
|
||||
scene.loadAtlas("mud", "mystery-encounters");
|
||||
await summonSafariPokemon(scene);
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, hideDescription: true });
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}_option_2_label`,
|
||||
buttonTooltip: `${namespace}_option_2_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}_option_2_selected_message`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* SAFARI ZONE OPTIONS
|
||||
* SAFARI ZONE MINIGAME OPTIONS
|
||||
*
|
||||
* Catch and flee rate **multipliers** are calculated in the same way stat changes are (they range from -6/+6)
|
||||
* Catch and flee rate **stages** are calculated in the same way stat changes are (they range from -6/+6)
|
||||
* https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Great_Marsh_and_Johto_Safari_Zone
|
||||
*
|
||||
* Catch Rate calculation:
|
||||
|
@ -38,7 +106,7 @@ const namespace = "mysteryEncounter:safari_zone";
|
|||
* fleeRate = ((255^2 - speciesCatchRate^2) / 255 / 2) [0 to 127.5] * fleeStageMultiplier [2/8 to 8/2]
|
||||
* Flee chance = fleeRate / 255
|
||||
*/
|
||||
const safariZoneOptions: MysteryEncounterOption[] = [
|
||||
const safariZoneGameOptions: MysteryEncounterOption[] = [
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
|
@ -47,7 +115,7 @@ const safariZoneOptions: MysteryEncounterOption[] = [
|
|||
selected: [
|
||||
{
|
||||
text: `${namespace}_pokeball_option_selected`,
|
||||
},
|
||||
}
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
|
@ -61,7 +129,7 @@ const safariZoneOptions: MysteryEncounterOption[] = [
|
|||
// Check how many safari pokemon left
|
||||
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon(scene);
|
||||
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: 0, hideDescription: true });
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
|
||||
} else {
|
||||
// End safari mode
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
|
@ -94,12 +162,10 @@ const safariZoneOptions: MysteryEncounterOption[] = [
|
|||
// 80% chance to increase flee stage +1
|
||||
const fleeChangeResult = tryChangeFleeStage(scene, 1, 8);
|
||||
if (!fleeChangeResult) {
|
||||
scene.queueMessage(i18next.t(`${namespace}_pokemon_busy_eating`, { pokemonName: pokemon.name }), 0, null, 500);
|
||||
await showEncounterText(scene, i18next.t(`${namespace}_pokemon_busy_eating`, { pokemonName: pokemon.name }), 1500, false );
|
||||
} else {
|
||||
scene.queueMessage(i18next.t(`${namespace}_pokemon_eating`, { pokemonName: pokemon.name }), 0, null, 500);
|
||||
await showEncounterText(scene, i18next.t(`${namespace}_pokemon_eating`, { pokemonName: pokemon.name }), 1500, false);
|
||||
}
|
||||
// TODO: throw bait with eat animation
|
||||
// TODO: play bug bite sfx, maybe spike cannon?
|
||||
|
||||
await doEndTurn(scene, 1);
|
||||
return true;
|
||||
|
@ -125,9 +191,9 @@ const safariZoneOptions: MysteryEncounterOption[] = [
|
|||
// 80% chance to decrease catch stage -1
|
||||
const catchChangeResult = tryChangeCatchStage(scene, -1, 8);
|
||||
if (!catchChangeResult) {
|
||||
scene.queueMessage(i18next.t(`${namespace}_pokemon_beside_itself_angry`, { pokemonName: pokemon.name }), 0, null, 500);
|
||||
await showEncounterText(scene, i18next.t(`${namespace}_pokemon_beside_itself_angry`, { pokemonName: pokemon.name }), 1500, false );
|
||||
} else {
|
||||
scene.queueMessage(i18next.t(`${namespace}_pokemon_angry`, { pokemonName: pokemon.name }), 0, null, 500);
|
||||
await showEncounterText(scene, i18next.t(`${namespace}_pokemon_angry`, { pokemonName: pokemon.name }), 1500, false );
|
||||
}
|
||||
|
||||
await doEndTurn(scene, 2);
|
||||
|
@ -147,7 +213,7 @@ const safariZoneOptions: MysteryEncounterOption[] = [
|
|||
// Check how many safari pokemon left
|
||||
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon(scene);
|
||||
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: 3, hideDescription: true });
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
|
||||
} else {
|
||||
// End safari mode
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
|
@ -157,106 +223,6 @@ const safariZoneOptions: MysteryEncounterOption[] = [
|
|||
.build()
|
||||
];
|
||||
|
||||
export const SafariZoneEncounter: IMysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 2 to 180
|
||||
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||
.withHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "chest_blue",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 10,
|
||||
yShadowOffset: 3,
|
||||
disableAnimation: true, // Re-enabled after option select
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}_intro_message`,
|
||||
},
|
||||
])
|
||||
.withTitle(`${namespace}_title`)
|
||||
.withDescription(`${namespace}_description`)
|
||||
.withQuery(`${namespace}_query`)
|
||||
.withOption(new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
// TODO: update
|
||||
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}_option_1_label`,
|
||||
buttonTooltip: `${namespace}_option_1_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}_option_1_selected_message`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Start safari encounter
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
encounter.encounterVariant = MysteryEncounterVariant.SAFARI_BATTLE;
|
||||
encounter.misc = {
|
||||
safariPokemonRemaining: 3
|
||||
};
|
||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
|
||||
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims");
|
||||
scene.loadSe("PRSFX- Taunt2", "battle_anims");
|
||||
await hideMysteryEncounterIntroVisuals(scene);
|
||||
await summonSafariPokemon(scene);
|
||||
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, hideDescription: true });
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}_option_2_label`,
|
||||
buttonTooltip: `${namespace}_option_2_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}_option_2_selected_message`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
function hideMysteryEncounterIntroVisuals(scene: BattleScene): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const introVisuals = scene.currentBattle.mysteryEncounter.introVisuals;
|
||||
if (introVisuals) {
|
||||
// Hide
|
||||
scene.tweens.add({
|
||||
targets: introVisuals,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750,
|
||||
onComplete: () => {
|
||||
scene.field.remove(introVisuals);
|
||||
introVisuals.setVisible(false);
|
||||
introVisuals.destroy();
|
||||
scene.currentBattle.mysteryEncounter.introVisuals = null;
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function summonSafariPokemon(scene: BattleScene) {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Message pokemon remaining
|
||||
|
@ -295,7 +261,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
|||
pokemon.calculateStats();
|
||||
|
||||
scene.currentBattle.enemyParty[0] = pokemon;
|
||||
}, scene.currentBattle.waveIndex + encounter.misc.safariPokemonRemaining);
|
||||
}, scene.currentBattle.waveIndex * 1000 + encounter.misc.safariPokemonRemaining);
|
||||
|
||||
scene.gameData.setPokemonSeen(pokemon, true);
|
||||
await pokemon.loadAssets();
|
||||
|
@ -308,18 +274,16 @@ async function summonSafariPokemon(scene: BattleScene) {
|
|||
|
||||
scene.unshiftPhase(new SummonPhase(scene, 0, false));
|
||||
|
||||
scene.ui.showText(i18next.t("battle:singleWildAppeared", { pokemonName: pokemon.name }), null, () => {
|
||||
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
|
||||
}
|
||||
}, 1500);
|
||||
showEncounterText(scene, i18next.t("battle:singleWildAppeared", { pokemonName: pokemon.name }), 1500, false)
|
||||
.then(() => {
|
||||
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||
const pokeballType: PokeballType = PokeballType.POKEBALL;
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||
const baseCatchRate = pokemon.species.catchRate;
|
||||
// Catch stage ranges from -6 to +6 (like stat boost stages)
|
||||
const safariCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage;
|
||||
|
@ -329,144 +293,18 @@ async function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise
|
|||
const pokeballMultiplier = 1.5;
|
||||
const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier);
|
||||
const ballTwitchRate = Math.round(1048560 / Math.sqrt(Math.sqrt(16711680 / catchRate)));
|
||||
const fpOffset = pokemon.getFieldPositionOffset();
|
||||
const catchSuccess = (ballTwitchRate / 65536) * (ballTwitchRate / 65536) * (ballTwitchRate / 65536);
|
||||
console.log("Catch success rate: " + catchSuccess);
|
||||
|
||||
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
|
||||
const pokeball: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey);
|
||||
pokeball.setOrigin(0.5, 0.625);
|
||||
scene.field.add(pokeball);
|
||||
|
||||
scene.playSound("pb_throw");
|
||||
scene.time.delayedCall(300, () => {
|
||||
scene.field.moveBelow(pokeball as Phaser.GameObjects.GameObject, pokemon);
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
scene.time.delayedCall(512, () => {
|
||||
// Trainer throw frames
|
||||
scene.trainer.setFrame("2");
|
||||
scene.time.delayedCall(256, () => {
|
||||
scene.trainer.setFrame("3");
|
||||
scene.time.delayedCall(768, () => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
|
||||
});
|
||||
});
|
||||
|
||||
// Pokeball move and catch logic
|
||||
scene.tweens.add({
|
||||
targets: pokeball,
|
||||
x: { value: 236 + fpOffset[0], ease: "Linear" },
|
||||
y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`));
|
||||
scene.playSound("pb_rel");
|
||||
pokemon.tint(getPokeballTintColor(pokeballType));
|
||||
|
||||
addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
|
||||
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 500,
|
||||
ease: "Sine.easeIn",
|
||||
scale: 0.25,
|
||||
y: 20,
|
||||
onComplete: () => {
|
||||
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||
pokemon.setVisible(false);
|
||||
scene.playSound("pb_catch");
|
||||
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}`));
|
||||
|
||||
const doShake = () => {
|
||||
let shakeCount = 0;
|
||||
const pbX = pokeball.x;
|
||||
const shakeCounter = scene.tweens.addCounter({
|
||||
from: 0,
|
||||
to: 1,
|
||||
repeat: 4,
|
||||
yoyo: true,
|
||||
ease: "Cubic.easeOut",
|
||||
duration: 250,
|
||||
repeatDelay: 500,
|
||||
onUpdate: t => {
|
||||
if (shakeCount && shakeCount < 4) {
|
||||
const value = t.getValue();
|
||||
const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1;
|
||||
pokeball.setX(pbX + value * 4 * directionMultiplier);
|
||||
pokeball.setAngle(value * 27.5 * directionMultiplier);
|
||||
}
|
||||
},
|
||||
onRepeat: () => {
|
||||
if (!pokemon.species.isObtainable()) {
|
||||
shakeCounter.stop();
|
||||
failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false));
|
||||
} else if (shakeCount++ < 3) {
|
||||
if (randSeedInt(65536) < ballTwitchRate) {
|
||||
scene.playSound("pb_move");
|
||||
} else {
|
||||
shakeCounter.stop();
|
||||
failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false));
|
||||
}
|
||||
} else {
|
||||
scene.playSound("pb_lock");
|
||||
addPokeballCaptureStars(scene, pokeball);
|
||||
|
||||
const pbTint = scene.add.sprite(pokeball.x, pokeball.y, "pb", "pb");
|
||||
pbTint.setOrigin(pokeball.originX, pokeball.originY);
|
||||
pbTint.setTintFill(0);
|
||||
pbTint.setAlpha(0);
|
||||
scene.field.add(pbTint);
|
||||
scene.tweens.add({
|
||||
targets: pbTint,
|
||||
alpha: 0.375,
|
||||
duration: 200,
|
||||
easing: "Sine.easeOut",
|
||||
onComplete: () => {
|
||||
scene.tweens.add({
|
||||
targets: pbTint,
|
||||
alpha: 0,
|
||||
duration: 200,
|
||||
easing: "Sine.easeIn",
|
||||
onComplete: () => pbTint.destroy()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
catchPokemon(scene, pokemon, pokeball, pokeballType).then(() => resolve(true));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
scene.time.delayedCall(250, () => doPokeballBounceAnim(scene, pokeball, 16, 72, 350, doShake));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return trainerThrowPokeball(scene, pokemon, PokeballType.POKEBALL, ballTwitchRate);
|
||||
}
|
||||
|
||||
async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||
// TODO: replace with bait
|
||||
const pokeballType: PokeballType = PokeballType.POKEBALL;
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
const fpOffset = pokemon.getFieldPositionOffset();
|
||||
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
|
||||
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey);
|
||||
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "bait", "0001.png");
|
||||
bait.setOrigin(0.5, 0.625);
|
||||
scene.field.add(bait);
|
||||
|
||||
scene.playSound("pb_throw");
|
||||
// scene.time.delayedCall(300, () => {
|
||||
// scene.field.moveBelow(pokemon, pokeball as Phaser.GameObjects.GameObject);
|
||||
// });
|
||||
|
||||
return new Promise(resolve => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
|
@ -487,31 +325,34 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
|
|||
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
// Bait frame 2
|
||||
bait.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||
// Bait frame 3
|
||||
scene.time.delayedCall(17, () => bait.setTexture("pb", `${pokeballAtlasKey}_open`));
|
||||
// scene.playSound("pb_rel");
|
||||
// pokemon.tint(getPokeballTintColor(pokeballType));
|
||||
|
||||
// addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
|
||||
scene.time.delayedCall(512, () => {
|
||||
let index = 1;
|
||||
scene.time.delayedCall(768, () => {
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 200,
|
||||
duration: 150,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: originalY - 30,
|
||||
loop: 2,
|
||||
y: originalY - 5,
|
||||
loop: 6,
|
||||
onStart: () => {
|
||||
scene.playSound("PRSFX- Bug Bite");
|
||||
bait.setFrame("0002.png");
|
||||
},
|
||||
onLoop: () => {
|
||||
scene.playSound("PRSFX- Bug Bite");
|
||||
if (index % 2 === 0) {
|
||||
scene.playSound("PRSFX- Bug Bite");
|
||||
}
|
||||
if (index === 4) {
|
||||
bait.setFrame("0003.png");
|
||||
}
|
||||
index++;
|
||||
},
|
||||
onComplete: () => {
|
||||
resolve(true);
|
||||
bait.destroy();
|
||||
scene.time.delayedCall(256, () => {
|
||||
bait.destroy();
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -522,13 +363,10 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
|
|||
}
|
||||
|
||||
async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||
// TODO: replace with mud
|
||||
const pokeballType: PokeballType = PokeballType.POKEBALL;
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
const fpOffset = pokemon.getFieldPositionOffset();
|
||||
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
|
||||
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey);
|
||||
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "mud", "0001.png");
|
||||
mud.setOrigin(0.5, 0.625);
|
||||
scene.field.add(mud);
|
||||
|
||||
|
@ -546,21 +384,24 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<bool
|
|||
});
|
||||
});
|
||||
|
||||
// Pokeball move and catch logic
|
||||
// Mud throw and splat
|
||||
scene.tweens.add({
|
||||
targets: mud,
|
||||
x: { value: 230 + fpOffset[0], ease: "Linear" },
|
||||
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
// Bait frame 2
|
||||
mud.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||
// Bait frame 3
|
||||
scene.time.delayedCall(17, () => mud.setTexture("pb", `${pokeballAtlasKey}_open`));
|
||||
// Mud frame 2
|
||||
scene.playSound("PRSFX- Sludge Bomb2");
|
||||
// pokemon.tint(getPokeballTintColor(pokeballType));
|
||||
mud.setFrame("0002.png");
|
||||
// Mud splat
|
||||
scene.time.delayedCall(512, () => {
|
||||
mud.setFrame("0003.png");
|
||||
scene.time.delayedCall(512, () => {
|
||||
mud.setFrame("0004.png");
|
||||
});
|
||||
});
|
||||
|
||||
// addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
|
||||
scene.time.delayedCall(1536, () => {
|
||||
mud.destroy();
|
||||
scene.tweens.add({
|
||||
|
@ -587,129 +428,6 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<bool
|
|||
});
|
||||
}
|
||||
|
||||
async function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) {
|
||||
return new Promise<void>(resolve => {
|
||||
scene.playSound("pb_rel");
|
||||
pokemon.setY(originalY);
|
||||
if (pokemon.status?.effect !== StatusEffect.SLEEP) {
|
||||
pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 });
|
||||
}
|
||||
pokemon.tint(getPokeballTintColor(pokeballType));
|
||||
pokemon.setVisible(true);
|
||||
pokemon.untint(250, "Sine.easeOut");
|
||||
|
||||
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
|
||||
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`));
|
||||
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 250,
|
||||
ease: "Sine.easeOut",
|
||||
scale: 1
|
||||
});
|
||||
|
||||
scene.currentBattle.lastUsedPokeball = pokeballType;
|
||||
removePb(scene, pokeball);
|
||||
|
||||
scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.name }), null, () => resolve(), null, true);
|
||||
});
|
||||
}
|
||||
|
||||
async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType): Promise<void> {
|
||||
scene.unshiftPhase(new VictoryPhase(scene, BattlerIndex.ENEMY));
|
||||
|
||||
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
|
||||
|
||||
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
|
||||
scene.validateAchv(achvs.HIDDEN_ABILITY);
|
||||
}
|
||||
|
||||
if (pokemon.species.subLegendary) {
|
||||
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
|
||||
}
|
||||
|
||||
if (pokemon.species.legendary) {
|
||||
scene.validateAchv(achvs.CATCH_LEGENDARY);
|
||||
}
|
||||
|
||||
if (pokemon.species.mythical) {
|
||||
scene.validateAchv(achvs.CATCH_MYTHICAL);
|
||||
}
|
||||
|
||||
scene.pokemonInfoContainer.show(pokemon, true);
|
||||
|
||||
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||
|
||||
return new Promise(resolve => {
|
||||
scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => {
|
||||
const end = () => {
|
||||
scene.pokemonInfoContainer.hide();
|
||||
removePb(scene, pokeball);
|
||||
resolve();
|
||||
};
|
||||
const removePokemon = () => {
|
||||
scene.field.remove(pokemon, true);
|
||||
};
|
||||
const addToParty = () => {
|
||||
const newPokemon = pokemon.addToParty(pokeballType);
|
||||
const modifiers = scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false);
|
||||
if (scene.getParty().filter(p => p.isShiny()).length === 6) {
|
||||
scene.validateAchv(achvs.SHINY_PARTY);
|
||||
}
|
||||
Promise.all(modifiers.map(m => scene.addModifier(m, true))).then(() => {
|
||||
scene.updateModifiers(true);
|
||||
removePokemon();
|
||||
if (newPokemon) {
|
||||
newPokemon.loadAssets().then(end);
|
||||
} else {
|
||||
end();
|
||||
}
|
||||
});
|
||||
};
|
||||
Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||
if (scene.getParty().length === 6) {
|
||||
const promptRelease = () => {
|
||||
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => {
|
||||
scene.pokemonInfoContainer.makeRoomForConfirmUi();
|
||||
scene.ui.setMode(Mode.CONFIRM, () => {
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
if (slotIndex < 6) {
|
||||
addToParty();
|
||||
} else {
|
||||
promptRelease();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, () => {
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
removePokemon();
|
||||
end();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
promptRelease();
|
||||
} else {
|
||||
addToParty();
|
||||
}
|
||||
});
|
||||
}, 0, true);
|
||||
});
|
||||
}
|
||||
|
||||
function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) {
|
||||
scene.tweens.add({
|
||||
targets: pokeball,
|
||||
duration: 250,
|
||||
delay: 250,
|
||||
ease: "Sine.easeIn",
|
||||
alpha: 0,
|
||||
onComplete: () => pokeball.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
|
||||
const speciesCatchRate = pokemon.species.catchRate;
|
||||
const fleeModifier = (2 + Math.min(Math.max(fleeStage, 0), 6)) / (2 - Math.max(Math.min(fleeStage, 0), -6));
|
||||
|
@ -720,84 +438,26 @@ function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
|
|||
return roll < fleeRate;
|
||||
}
|
||||
|
||||
async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||
const fleeAnimation = new Promise<void>(resolve => {
|
||||
// Ease pokemon out
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
duration: 1000,
|
||||
ease: "Sine.easeIn",
|
||||
scale: pokemon.getSpriteScale(),
|
||||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
scene.field.remove(pokemon, true);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const prompt = new Promise<void>(resolve => {
|
||||
scene.ui.showText(i18next.t("battle:pokemonFled", { pokemonName: pokemon.name }), 0, () => resolve(), 500);
|
||||
});
|
||||
|
||||
await Promise.all([fleeAnimation, prompt]);
|
||||
}
|
||||
|
||||
async function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||
const fleeAnimation = new Promise<void>(resolve => {
|
||||
// Ease pokemon out
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
duration: 1000,
|
||||
ease: "Sine.easeIn",
|
||||
scale: pokemon.getSpriteScale(),
|
||||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
scene.field.remove(pokemon, true);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const prompt = new Promise<void>(resolve => {
|
||||
scene.ui.showText(i18next.t("battle:playerFled", { pokemonName: pokemon.name }), 0, () => resolve(), 500);
|
||||
});
|
||||
|
||||
await Promise.all([fleeAnimation, prompt]);
|
||||
}
|
||||
|
||||
function tryChangeFleeStage(scene: BattleScene, change: number, chance?: number): boolean {
|
||||
if (chance && randSeedInt(10) >= chance) {
|
||||
console.log("Failed to change flee stage");
|
||||
return false;
|
||||
}
|
||||
const currentFleeStage = scene.currentBattle.mysteryEncounter.misc.fleeStage ?? 0;
|
||||
// console.log("currentFleeStage: " + currentFleeStage);
|
||||
scene.currentBattle.mysteryEncounter.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6);
|
||||
return true;
|
||||
}
|
||||
|
||||
function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number): boolean {
|
||||
if (chance && randSeedInt(10) >= chance) {
|
||||
console.log("Failed to change catch stage");
|
||||
return false;
|
||||
}
|
||||
const currentCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage ?? 0;
|
||||
// console.log("currentCatchStage: " + currentCatchStage);
|
||||
scene.currentBattle.mysteryEncounter.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function doEndTurn(scene: BattleScene, cursorIndex: number, message?: string) {
|
||||
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
|
||||
console.log("fleeStage: " + scene.currentBattle.mysteryEncounter.misc.fleeStage);
|
||||
console.log("catchStage: " + scene.currentBattle.mysteryEncounter.misc.catchStage);
|
||||
const isFlee = isPokemonFlee(pokemon, scene.currentBattle.mysteryEncounter.misc.fleeStage);
|
||||
if (isFlee) {
|
||||
// Pokemon flees!
|
||||
|
@ -805,13 +465,13 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number, message?: stri
|
|||
// Check how many safari pokemon left
|
||||
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon(scene);
|
||||
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||
} else {
|
||||
// End safari mode
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
}
|
||||
} else {
|
||||
scene.queueMessage(i18next.t(`${namespace}_pokemon_watching`, { pokemonName: pokemon.name }), 0, null, 500);
|
||||
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||
scene.queueMessage(i18next.t(`${namespace}_pokemon_watching`, { pokemonName: pokemon.name }), 0, null, 1000);
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { generateModifierTypeOption, leaveEncounterWithoutBattle, queueEncounterMessage, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
|
@ -10,6 +10,7 @@ import BattleScene from "../../../battle-scene";
|
|||
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { MoneyRequirement } from "../mystery-encounter-requirements";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:shady_vitamin_dealer";
|
||||
|
|
|
@ -10,7 +10,8 @@ import { Status, StatusEffect } from "../../status-effect";
|
|||
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, queueEncounterMessage, setEncounterExp, setEncounterRewards, } from "../mystery-encounter-utils";
|
||||
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
/** i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:sleeping_snorlax";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Ability, allAbilities } from "#app/data/ability";
|
||||
import { EnemyPartyConfig, getEncounterText, initBattleWithEnemyConfig, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
import { EnemyPartyConfig, initBattleWithEnemyConfig, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getNatureName, Nature } from "#app/data/nature";
|
||||
import { speciesStarters } from "#app/data/pokemon-species";
|
||||
import { Stat } from "#app/data/pokemon-stat";
|
||||
|
@ -16,6 +16,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||
import BattleScene from "../../../battle-scene";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
/** The i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:training_session";
|
||||
|
@ -32,7 +33,9 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||
spriteKey: "training_gear",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 3,
|
||||
y: 6,
|
||||
x: 5,
|
||||
yShadowOffset: -2
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
|
@ -162,7 +165,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||
scene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
scene.updateModifiers(true);
|
||||
scene.queueMessage(getEncounterText(scene, `${namespace}_battle_finished_1`), null, true);
|
||||
queueEncounterMessage(scene, `${namespace}_battle_finished_1`);
|
||||
};
|
||||
|
||||
setEncounterRewards(
|
||||
|
@ -234,11 +237,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
scene.queueMessage(
|
||||
getEncounterText(scene, `${namespace}_battle_finished_2`),
|
||||
null,
|
||||
true
|
||||
);
|
||||
queueEncounterMessage(scene, `${namespace}_battle_finished_2`);
|
||||
// Add the pokemon back to party with Nature change
|
||||
playerPokemon.setNature(encounter.misc.chosenNature);
|
||||
scene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
|
@ -333,7 +332,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
scene.queueMessage(getEncounterText(scene, `${namespace}_battle_finished_3`), null, true);
|
||||
queueEncounterMessage(scene, `${namespace}_battle_finished_3`);
|
||||
// Add the pokemon back to party with ability change
|
||||
const abilityIndex = encounter.misc.abilityIndex;
|
||||
if (!!playerPokemon.getFusionSpeciesForm()) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EnemyPartyConfig } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
@ -58,14 +58,16 @@ export default interface IMysteryEncounter {
|
|||
* Requirements
|
||||
*/
|
||||
requirements?: EncounterSceneRequirement[];
|
||||
/** Primary Pokemon is a single pokemon randomly selected from the party that meet ALL primary pokemon requirements */
|
||||
primaryPokemonRequirements?: EncounterPokemonRequirement[];
|
||||
secondaryPokemonRequirements?: EncounterPokemonRequirement[]; // A list of requirements that must ALL be met by a subset of pokemon to trigger the event
|
||||
/**
|
||||
* Secondary Pokemon are pokemon that meet ALL secondary pokemon requirements
|
||||
* Note that an individual requirement may require multiple pokemon, but the resulting pokemon after all secondary requirements are met may be lower than expected
|
||||
* If the primary pokemon and secondary pokemon are the same and ExcludePrimaryFromSupportRequirements flag is true, primary pokemon may be promoted from secondary pool
|
||||
*/
|
||||
secondaryPokemonRequirements?: EncounterPokemonRequirement[];
|
||||
excludePrimaryFromSupportRequirements?: boolean;
|
||||
// Primary Pokemon is a single pokemon randomly selected from a set of pokemon that meet ALL primary pokemon requirements
|
||||
primaryPokemon?: PlayerPokemon;
|
||||
// Support Pokemon are pokemon that meet ALL support pokemon requirements.
|
||||
// Note that an individual requirement may require multiple pokemon, but the resulting pokemon after all secondary requirements are met may be lower than expected
|
||||
// If the primary pokemon and supporting pokemon are the same and ExcludePrimaryFromSupportRequirements flag is true, primary pokemon may be promoted from secondary pool
|
||||
secondaryPokemon?: PlayerPokemon[];
|
||||
|
||||
/**
|
||||
|
@ -118,19 +120,11 @@ export default interface IMysteryEncounter {
|
|||
* Defaults to 1
|
||||
*/
|
||||
expMultiplier?: number;
|
||||
|
||||
/**
|
||||
* When true, will never queue PostSummon phases from a SummonPhase
|
||||
* Defaults to false
|
||||
*/
|
||||
disableAllPostSummon?: boolean;
|
||||
|
||||
/**
|
||||
* Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave
|
||||
* You should never need to modify this
|
||||
*/
|
||||
seedOffset?: any;
|
||||
|
||||
/**
|
||||
* Generic property to set any custom data required for the encounter
|
||||
* Extremely useful for carrying state/data between onPreOptionPhase/onOptionPhase/onPostOptionPhase
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import BattleScene from "#app/battle-scene";
|
||||
import { getTextWithColors, TextStyle } from "#app/ui/text";
|
||||
import { UiTheme } from "#enums/ui-theme";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function getEncounterText(scene: BattleScene, keyOrString: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
|
||||
if (isNullOrUndefined(keyOrString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let textString: string = getTextWithDialogueTokens(scene, keyOrString);
|
||||
|
||||
// Can only color the text if a Primary Style is defined
|
||||
// primaryStyle is applied to all text that does not have its own specified style
|
||||
if (primaryStyle) {
|
||||
textString = getTextWithColors(textString, primaryStyle, uiTheme);
|
||||
}
|
||||
|
||||
return textString;
|
||||
}
|
||||
|
||||
function getTextWithDialogueTokens(scene: BattleScene, keyOrString: string): string {
|
||||
if (isNullOrUndefined(keyOrString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (i18next.exists(keyOrString, scene.currentBattle?.mysteryEncounter?.dialogueTokens)) {
|
||||
const stringArray = [`${keyOrString}`] as any;
|
||||
stringArray.raw = [`${keyOrString}`];
|
||||
return i18next.t(stringArray, scene.currentBattle?.mysteryEncounter?.dialogueTokens);
|
||||
}
|
||||
|
||||
return keyOrString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will queue a message in UI with injected encounter data tokens
|
||||
* @param scene
|
||||
* @param contentKey
|
||||
*/
|
||||
export function queueEncounterMessage(scene: BattleScene, contentKey: string): void {
|
||||
const text: string = getEncounterText(scene, contentKey);
|
||||
scene.queueMessage(text, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will display a message in UI with injected encounter data tokens
|
||||
* @param scene
|
||||
* @param contentKey
|
||||
* @param prompt
|
||||
* @param callbackDelay
|
||||
*/
|
||||
export function showEncounterText(scene: BattleScene, contentKey: string, callbackDelay: number = 0, prompt: boolean = true): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
const text: string = getEncounterText(scene, contentKey);
|
||||
scene.ui.showText(text, null, () => resolve(), callbackDelay, prompt);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will display a dialogue (with speaker title) in UI with injected encounter data tokens
|
||||
* @param scene
|
||||
* @param textContentKey
|
||||
* @param speakerContentKey
|
||||
* @param callback
|
||||
*/
|
||||
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, callback?: Function) {
|
||||
const text: string = getEncounterText(scene, textContentKey);
|
||||
const speaker: string = getEncounterText(scene, speakerContentKey);
|
||||
scene.ui.showDialogue(text, speaker, null, callback, 0, 0);
|
||||
}
|
|
@ -1,21 +1,19 @@
|
|||
import i18next from "i18next";
|
||||
import { BattleType } from "#app/battle";
|
||||
import BattleScene from "../../battle-scene";
|
||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "../pokemon-species";
|
||||
import { MysteryEncounterVariant } from "./mystery-encounter";
|
||||
import { Status, StatusEffect } from "../status-effect";
|
||||
import { TrainerConfig, trainerConfigs, TrainerSlot } from "../trainer-config";
|
||||
import BattleScene from "../../../battle-scene";
|
||||
import PokemonSpecies from "../../pokemon-species";
|
||||
import { MysteryEncounterVariant } from "../mystery-encounter";
|
||||
import { Status, StatusEffect } from "../../status-effect";
|
||||
import { TrainerConfig, trainerConfigs, TrainerSlot } from "../../trainer-config";
|
||||
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
||||
import Trainer, { TrainerVariant } from "../../field/trainer";
|
||||
import Trainer, { TrainerVariant } from "../../../field/trainer";
|
||||
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
|
||||
import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||
import { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
|
||||
import { MysteryEncounterBattlePhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
|
||||
import * as Utils from "../../utils";
|
||||
import * as Utils from "../../../utils";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { Biome } from "#enums/biome";
|
||||
|
@ -24,201 +22,9 @@ import { Mode } from "#app/ui/ui";
|
|||
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { getTextWithColors, TextStyle } from "#app/ui/text";
|
||||
import * as Overrides from "#app/overrides";
|
||||
import { UiTheme } from "#enums/ui-theme";
|
||||
import { MysteryEncounterUiSettings } from "#app/ui/mystery-encounter-ui-handler";
|
||||
|
||||
/**
|
||||
*
|
||||
* Will never remove the player's last non-fainted Pokemon (if they only have 1)
|
||||
* Otherwise, picks a Pokemon completely at random and removes from the party
|
||||
* @param scene
|
||||
* @param isAllowedInBattle - default false. If true, only picks from unfainted mons. If there is only 1 unfainted mon left and doNotReturnLastAbleMon is also true, will return fainted mon
|
||||
* @param doNotReturnLastAbleMon - If true, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: boolean = false, doNotReturnLastAbleMon: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let chosenIndex: number;
|
||||
let chosenPokemon: PlayerPokemon;
|
||||
const unfaintedMons = party.filter(p => p.isAllowedInBattle());
|
||||
const faintedMons = party.filter(p => !p.isAllowedInBattle());
|
||||
|
||||
if (doNotReturnLastAbleMon && unfaintedMons.length === 1) {
|
||||
chosenIndex = Utils.randSeedInt(faintedMons.length);
|
||||
chosenPokemon = faintedMons.at(chosenIndex);
|
||||
} else if (isAllowedInBattle) {
|
||||
chosenIndex = Utils.randSeedInt(unfaintedMons.length);
|
||||
chosenPokemon = unfaintedMons.at(chosenIndex);
|
||||
} else {
|
||||
chosenIndex = Utils.randSeedInt(party.length);
|
||||
chosenPokemon = party.at(chosenIndex);
|
||||
}
|
||||
|
||||
return chosenPokemon;
|
||||
}
|
||||
|
||||
// export function getTokensFromScene(scene: BattleScene, reqs: EncounterSceneRequirement[]): Array<[RegExp, String]> {
|
||||
// const arr = [];
|
||||
// if (scene) {
|
||||
// for (const req of reqs) {
|
||||
// req.getDialogueToken(scene);
|
||||
// }
|
||||
// }
|
||||
// return arr;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon;
|
||||
party.every(p => {
|
||||
if (unfainted && p.isFainted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon?.level < p?.level ? p : pokemon : p;
|
||||
return true;
|
||||
});
|
||||
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon;
|
||||
party.every(p => {
|
||||
if (unfainted && p.isFainted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon?.level > p?.level ? p : pokemon : p;
|
||||
return true;
|
||||
});
|
||||
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* NOTE: This returns ANY random species, including those locked behind eggs, etc.
|
||||
* @param starterTiers
|
||||
* @param excludedSpecies
|
||||
* @param types
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
|
||||
let min = starterTiers instanceof Array ? starterTiers[0] : starterTiers;
|
||||
let max = starterTiers instanceof Array ? starterTiers[1] : starterTiers;
|
||||
|
||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
||||
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
||||
.filter(s => getPokemonSpecies(s[0]) && (!excludedSpecies || !excludedSpecies.includes(s[0])))
|
||||
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||
|
||||
if (!isNullOrUndefined(types) && types.length > 0) {
|
||||
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || types.includes(s[0].type2));
|
||||
}
|
||||
|
||||
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
||||
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
|
||||
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
|
||||
while (tryFilterStarterTiers.length === 0 && (min !== 0 && max !== 10)) {
|
||||
if (min > 0) {
|
||||
min--;
|
||||
} else {
|
||||
max++;
|
||||
}
|
||||
|
||||
tryFilterStarterTiers = filteredSpecies.filter(s => s[1] >= min && s[1] <= max);
|
||||
}
|
||||
|
||||
if (tryFilterStarterTiers.length > 0) {
|
||||
const index = Utils.randSeedInt(tryFilterStarterTiers.length);
|
||||
return Phaser.Math.RND.shuffle(tryFilterStarterTiers)[index][0].speciesId;
|
||||
}
|
||||
|
||||
return Species.BULBASAUR;
|
||||
}
|
||||
|
||||
export function koPlayerPokemon(pokemon: PlayerPokemon) {
|
||||
pokemon.hp = 0;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
pokemon.updateInfo();
|
||||
}
|
||||
|
||||
export function getEncounterText(scene: BattleScene, textKey: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
|
||||
if (isNullOrUndefined(textKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stringArray = [`${textKey}`] as any;
|
||||
stringArray.raw = [`${textKey}`];
|
||||
let textString: string = getTextWithDialogueTokens(scene, stringArray);
|
||||
|
||||
// Can only color the text if a Primary Style is defined
|
||||
// primaryStyle is applied to all text that does not have its own specified style
|
||||
if (primaryStyle) {
|
||||
textString = getTextWithColors(textString, primaryStyle, uiTheme);
|
||||
}
|
||||
|
||||
return textString;
|
||||
}
|
||||
|
||||
function getTextWithDialogueTokens(scene: BattleScene, textKey: TemplateStringsArray): string {
|
||||
if (isNullOrUndefined(textKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return i18next.t(textKey, scene.currentBattle?.mysteryEncounter?.dialogueTokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will queue a message in UI with injected encounter data tokens
|
||||
* @param scene
|
||||
* @param contentKey
|
||||
*/
|
||||
export function queueEncounterMessage(scene: BattleScene, contentKey: string): void {
|
||||
const text: string = getEncounterText(scene, contentKey);
|
||||
scene.queueMessage(text, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will display a message in UI with injected encounter data tokens
|
||||
* @param scene
|
||||
* @param contentKey
|
||||
*/
|
||||
export function showEncounterText(scene: BattleScene, contentKey: string): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
const text: string = getEncounterText(scene, contentKey);
|
||||
scene.ui.showText(text, null, () => resolve(), 0, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will display a dialogue (with speaker title) in UI with injected encounter data tokens
|
||||
* @param scene
|
||||
* @param textContentKey
|
||||
* @param speakerContentKey
|
||||
* @param callback
|
||||
*/
|
||||
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, callback?: Function) {
|
||||
const text: string = getEncounterText(scene, textContentKey);
|
||||
const speaker: string = getEncounterText(scene, speakerContentKey);
|
||||
scene.ui.showDialogue(text, speaker, null, callback, 0, 0);
|
||||
}
|
||||
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
export class EnemyPokemonConfig {
|
||||
species: PokemonSpecies;
|
||||
|
@ -525,8 +331,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
|||
if (!textPromptKey) {
|
||||
displayOptions();
|
||||
} else {
|
||||
const secondOptionSelectPrompt = getEncounterText(scene, textPromptKey, TextStyle.MESSAGE);
|
||||
scene.ui.showText(secondOptionSelectPrompt, null, displayOptions, null, true);
|
||||
showEncounterText(scene, textPromptKey).then(() => displayOptions());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -680,14 +485,24 @@ export function setEncounterExp(scene: BattleScene, participantIds: integer[], b
|
|||
};
|
||||
}
|
||||
|
||||
export class OptionSelectSettings {
|
||||
hideDescription?: boolean;
|
||||
slideInDescription?: boolean;
|
||||
overrideTitle?: string;
|
||||
overrideDescription?: string;
|
||||
overrideQuery?: string;
|
||||
overrideOptions?: MysteryEncounterOption[];
|
||||
startingCursorIndex?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to exit an encounter without any battles or followup
|
||||
* Will skip any shops and rewards, and queue the next encounter phase as normal
|
||||
* Can be used to queue a new series of Options to select for an Encounter
|
||||
* MUST be used only in onOptionPhase, will not work in onPreOptionPhase or onPostOptionPhase
|
||||
* @param scene
|
||||
* @param followupOptionSelectSettings
|
||||
* @param optionSelectSettings
|
||||
*/
|
||||
export function initFollowupOptionSelect(scene: BattleScene, followupOptionSelectSettings: MysteryEncounterUiSettings) {
|
||||
scene.pushPhase(new MysteryEncounterPhase(scene, followupOptionSelectSettings));
|
||||
export function initSubsequentOptionSelect(scene: BattleScene, optionSelectSettings: OptionSelectSettings) {
|
||||
scene.pushPhase(new MysteryEncounterPhase(scene, optionSelectSettings));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -721,6 +536,32 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
|
|||
}
|
||||
}
|
||||
|
||||
export function hideMysteryEncounterIntroVisuals(scene: BattleScene): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const introVisuals = scene.currentBattle.mysteryEncounter.introVisuals;
|
||||
if (introVisuals) {
|
||||
// Hide
|
||||
scene.tweens.add({
|
||||
targets: introVisuals,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750,
|
||||
onComplete: () => {
|
||||
scene.field.remove(introVisuals);
|
||||
introVisuals.setVisible(false);
|
||||
introVisuals.destroy();
|
||||
scene.currentBattle.mysteryEncounter.introVisuals = null;
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: remove once encounter spawn rate is finalized
|
||||
* Just a helper function to calculate aggregate stats for MEs in a Classic run
|
|
@ -0,0 +1,451 @@
|
|||
import BattleScene from "#app/battle-scene";
|
||||
import i18next from "i18next";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { VictoryPhase } from "#app/phases";
|
||||
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "#app/data/pokeball";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
|
||||
import { getStatusEffectCatchRateMultiplier, StatusEffect } from "#app/data/status-effect";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { achvs } from "#app/system/achv";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
/**
|
||||
*
|
||||
* Will never remove the player's last non-fainted Pokemon (if they only have 1)
|
||||
* Otherwise, picks a Pokemon completely at random and removes from the party
|
||||
* @param scene
|
||||
* @param isAllowedInBattle - default false. If true, only picks from unfainted mons. If there is only 1 unfainted mon left and doNotReturnLastAbleMon is also true, will return fainted mon
|
||||
* @param doNotReturnLastAbleMon - If true, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: boolean = false, doNotReturnLastAbleMon: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let chosenIndex: number;
|
||||
let chosenPokemon: PlayerPokemon;
|
||||
const unfaintedMons = party.filter(p => p.isAllowedInBattle());
|
||||
const faintedMons = party.filter(p => !p.isAllowedInBattle());
|
||||
|
||||
if (doNotReturnLastAbleMon && unfaintedMons.length === 1) {
|
||||
chosenIndex = randSeedInt(faintedMons.length);
|
||||
chosenPokemon = faintedMons.at(chosenIndex);
|
||||
} else if (isAllowedInBattle) {
|
||||
chosenIndex = randSeedInt(unfaintedMons.length);
|
||||
chosenPokemon = unfaintedMons.at(chosenIndex);
|
||||
} else {
|
||||
chosenIndex = randSeedInt(party.length);
|
||||
chosenPokemon = party.at(chosenIndex);
|
||||
}
|
||||
|
||||
return chosenPokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon;
|
||||
party.every(p => {
|
||||
if (unfainted && p.isFainted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon?.level < p?.level ? p : pokemon : p;
|
||||
return true;
|
||||
});
|
||||
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon;
|
||||
party.every(p => {
|
||||
if (unfainted && p.isFainted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon?.level > p?.level ? p : pokemon : p;
|
||||
return true;
|
||||
});
|
||||
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* NOTE: This returns ANY random species, including those locked behind eggs, etc.
|
||||
* @param starterTiers
|
||||
* @param excludedSpecies
|
||||
* @param types
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
|
||||
let min = starterTiers instanceof Array ? starterTiers[0] : starterTiers;
|
||||
let max = starterTiers instanceof Array ? starterTiers[1] : starterTiers;
|
||||
|
||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
||||
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
||||
.filter(s => getPokemonSpecies(s[0]) && (!excludedSpecies || !excludedSpecies.includes(s[0])))
|
||||
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||
|
||||
if (!isNullOrUndefined(types) && types.length > 0) {
|
||||
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || types.includes(s[0].type2));
|
||||
}
|
||||
|
||||
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
||||
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
|
||||
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
|
||||
while (tryFilterStarterTiers.length === 0 && (min !== 0 && max !== 10)) {
|
||||
if (min > 0) {
|
||||
min--;
|
||||
} else {
|
||||
max++;
|
||||
}
|
||||
|
||||
tryFilterStarterTiers = filteredSpecies.filter(s => s[1] >= min && s[1] <= max);
|
||||
}
|
||||
|
||||
if (tryFilterStarterTiers.length > 0) {
|
||||
const index = randSeedInt(tryFilterStarterTiers.length);
|
||||
return Phaser.Math.RND.shuffle(tryFilterStarterTiers)[index][0].speciesId;
|
||||
}
|
||||
|
||||
return Species.BULBASAUR;
|
||||
}
|
||||
|
||||
export function koPlayerPokemon(pokemon: PlayerPokemon) {
|
||||
pokemon.hp = 0;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
pokemon.updateInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative to using AttemptCapturePhase
|
||||
* Assumes player sprite is visible on the screen (this is intended for non-combat uses)
|
||||
*
|
||||
* Can await returned promise to wait for throw animation completion before continuing
|
||||
*
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
* @param pokeballType
|
||||
* @param ballTwitchRate - can pass custom ball catch rates (for special events, like safari)
|
||||
*/
|
||||
export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, pokeballType: PokeballType, ballTwitchRate?: number): Promise<boolean> {
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
if (!ballTwitchRate) {
|
||||
const _3m = 3 * pokemon.getMaxHp();
|
||||
const _2h = 2 * pokemon.hp;
|
||||
const catchRate = pokemon.species.catchRate;
|
||||
const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType);
|
||||
const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1;
|
||||
const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier);
|
||||
ballTwitchRate = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x)));
|
||||
}
|
||||
|
||||
const fpOffset = pokemon.getFieldPositionOffset();
|
||||
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
|
||||
const pokeball: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey);
|
||||
pokeball.setOrigin(0.5, 0.625);
|
||||
scene.field.add(pokeball);
|
||||
|
||||
scene.playSound("pb_throw");
|
||||
scene.time.delayedCall(300, () => {
|
||||
scene.field.moveBelow(pokeball as Phaser.GameObjects.GameObject, pokemon);
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
scene.time.delayedCall(512, () => {
|
||||
// Trainer throw frames
|
||||
scene.trainer.setFrame("2");
|
||||
scene.time.delayedCall(256, () => {
|
||||
scene.trainer.setFrame("3");
|
||||
scene.time.delayedCall(768, () => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
|
||||
});
|
||||
});
|
||||
|
||||
// Pokeball move and catch logic
|
||||
scene.tweens.add({
|
||||
targets: pokeball,
|
||||
x: { value: 236 + fpOffset[0], ease: "Linear" },
|
||||
y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`));
|
||||
scene.playSound("pb_rel");
|
||||
pokemon.tint(getPokeballTintColor(pokeballType));
|
||||
|
||||
addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
|
||||
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 500,
|
||||
ease: "Sine.easeIn",
|
||||
scale: 0.25,
|
||||
y: 20,
|
||||
onComplete: () => {
|
||||
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||
pokemon.setVisible(false);
|
||||
scene.playSound("pb_catch");
|
||||
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}`));
|
||||
|
||||
const doShake = () => {
|
||||
let shakeCount = 0;
|
||||
const pbX = pokeball.x;
|
||||
const shakeCounter = scene.tweens.addCounter({
|
||||
from: 0,
|
||||
to: 1,
|
||||
repeat: 4,
|
||||
yoyo: true,
|
||||
ease: "Cubic.easeOut",
|
||||
duration: 250,
|
||||
repeatDelay: 500,
|
||||
onUpdate: t => {
|
||||
if (shakeCount && shakeCount < 4) {
|
||||
const value = t.getValue();
|
||||
const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1;
|
||||
pokeball.setX(pbX + value * 4 * directionMultiplier);
|
||||
pokeball.setAngle(value * 27.5 * directionMultiplier);
|
||||
}
|
||||
},
|
||||
onRepeat: () => {
|
||||
if (!pokemon.species.isObtainable()) {
|
||||
shakeCounter.stop();
|
||||
failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false));
|
||||
} else if (shakeCount++ < 3) {
|
||||
if (randSeedInt(65536) < ballTwitchRate) {
|
||||
scene.playSound("pb_move");
|
||||
} else {
|
||||
shakeCounter.stop();
|
||||
failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false));
|
||||
}
|
||||
} else {
|
||||
scene.playSound("pb_lock");
|
||||
addPokeballCaptureStars(scene, pokeball);
|
||||
|
||||
const pbTint = scene.add.sprite(pokeball.x, pokeball.y, "pb", "pb");
|
||||
pbTint.setOrigin(pokeball.originX, pokeball.originY);
|
||||
pbTint.setTintFill(0);
|
||||
pbTint.setAlpha(0);
|
||||
scene.field.add(pbTint);
|
||||
scene.tweens.add({
|
||||
targets: pbTint,
|
||||
alpha: 0.375,
|
||||
duration: 200,
|
||||
easing: "Sine.easeOut",
|
||||
onComplete: () => {
|
||||
scene.tweens.add({
|
||||
targets: pbTint,
|
||||
alpha: 0,
|
||||
duration: 200,
|
||||
easing: "Sine.easeIn",
|
||||
onComplete: () => pbTint.destroy()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
catchPokemon(scene, pokemon, pokeball, pokeballType).then(() => resolve(true));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
scene.time.delayedCall(250, () => doPokeballBounceAnim(scene, pokeball, 16, 72, 350, doShake));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) {
|
||||
return new Promise<void>(resolve => {
|
||||
scene.playSound("pb_rel");
|
||||
pokemon.setY(originalY);
|
||||
if (pokemon.status?.effect !== StatusEffect.SLEEP) {
|
||||
pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 });
|
||||
}
|
||||
pokemon.tint(getPokeballTintColor(pokeballType));
|
||||
pokemon.setVisible(true);
|
||||
pokemon.untint(250, "Sine.easeOut");
|
||||
|
||||
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
|
||||
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`));
|
||||
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 250,
|
||||
ease: "Sine.easeOut",
|
||||
scale: 1
|
||||
});
|
||||
|
||||
scene.currentBattle.lastUsedPokeball = pokeballType;
|
||||
removePb(scene, pokeball);
|
||||
|
||||
scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.name }), null, () => resolve(), null, true);
|
||||
});
|
||||
}
|
||||
|
||||
function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType): Promise<void> {
|
||||
scene.unshiftPhase(new VictoryPhase(scene, BattlerIndex.ENEMY));
|
||||
|
||||
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
|
||||
|
||||
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
|
||||
scene.validateAchv(achvs.HIDDEN_ABILITY);
|
||||
}
|
||||
|
||||
if (pokemon.species.subLegendary) {
|
||||
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
|
||||
}
|
||||
|
||||
if (pokemon.species.legendary) {
|
||||
scene.validateAchv(achvs.CATCH_LEGENDARY);
|
||||
}
|
||||
|
||||
if (pokemon.species.mythical) {
|
||||
scene.validateAchv(achvs.CATCH_MYTHICAL);
|
||||
}
|
||||
|
||||
scene.pokemonInfoContainer.show(pokemon, true);
|
||||
|
||||
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||
|
||||
return new Promise(resolve => {
|
||||
scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => {
|
||||
const end = () => {
|
||||
scene.pokemonInfoContainer.hide();
|
||||
removePb(scene, pokeball);
|
||||
resolve();
|
||||
};
|
||||
const removePokemon = () => {
|
||||
scene.field.remove(pokemon, true);
|
||||
};
|
||||
const addToParty = () => {
|
||||
const newPokemon = pokemon.addToParty(pokeballType);
|
||||
const modifiers = scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false);
|
||||
if (scene.getParty().filter(p => p.isShiny()).length === 6) {
|
||||
scene.validateAchv(achvs.SHINY_PARTY);
|
||||
}
|
||||
Promise.all(modifiers.map(m => scene.addModifier(m, true))).then(() => {
|
||||
scene.updateModifiers(true);
|
||||
removePokemon();
|
||||
if (newPokemon) {
|
||||
newPokemon.loadAssets().then(end);
|
||||
} else {
|
||||
end();
|
||||
}
|
||||
});
|
||||
};
|
||||
Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||
if (scene.getParty().length === 6) {
|
||||
const promptRelease = () => {
|
||||
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => {
|
||||
scene.pokemonInfoContainer.makeRoomForConfirmUi();
|
||||
scene.ui.setMode(Mode.CONFIRM, () => {
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
if (slotIndex < 6) {
|
||||
addToParty();
|
||||
} else {
|
||||
promptRelease();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, () => {
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
removePokemon();
|
||||
end();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
promptRelease();
|
||||
} else {
|
||||
addToParty();
|
||||
}
|
||||
});
|
||||
}, 0, true);
|
||||
});
|
||||
}
|
||||
|
||||
function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) {
|
||||
scene.tweens.add({
|
||||
targets: pokeball,
|
||||
duration: 250,
|
||||
delay: 250,
|
||||
ease: "Sine.easeIn",
|
||||
alpha: 0,
|
||||
onComplete: () => pokeball.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
export function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
// Ease pokemon out
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
duration: 1000,
|
||||
ease: "Sine.easeIn",
|
||||
scale: pokemon.getSpriteScale(),
|
||||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
scene.field.remove(pokemon, true);
|
||||
showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.name }), 600, false)
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
// Ease pokemon out
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
duration: 1000,
|
||||
ease: "Sine.easeIn",
|
||||
scale: pokemon.getSpriteScale(),
|
||||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
scene.field.remove(pokemon, true);
|
||||
showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.name }), 600, false)
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -146,13 +146,13 @@ export const mysteryEncounter: SimpleTranslationEntries = {
|
|||
|
||||
"safari_zone_intro_message": "It's a safari zone!",
|
||||
"safari_zone_title": "The Safari Zone",
|
||||
"safari_zone_description": "There are all kinds of rare and special Pokémon that can be found here!\nIf you choose to enter, you'll have a time limit of 3 wild encounters where you can try to catch these special Pokémon.\nBeware, though. These Pokémon may flee before you're able to catch them!",
|
||||
"safari_zone_description": "There are all kinds of rare and special Pokémon that can be found here!\nIf you choose to enter, you'll have a time limit of 3 wild encounters where you can try to catch these special Pokémon.\n\nBeware, though. These Pokémon may flee before you're able to catch them!",
|
||||
"safari_zone_query": "Would you like to enter?",
|
||||
"safari_zone_option_1_label": "Enter",
|
||||
"safari_zone_option_1_tooltip": "(-) Pay {{option1Money, money}}\n@[SUMMARY_GREEN]{(?) Safari Zone}",
|
||||
"safari_zone_option_2_label": "Leave",
|
||||
"safari_zone_option_2_tooltip": "(-) No Rewards",
|
||||
"safari_zone_option_1_selected_message": "Time to test your luck.",
|
||||
"safari_zone_option_1_selected_message": "Time to test your luck!",
|
||||
"safari_zone_option_2_selected_message": "You hurry along your way,\nwith a slight feeling of regret.",
|
||||
"safari_zone_pokeball_option_label": "Throw a Pokéball",
|
||||
"safari_zone_pokeball_option_tooltip": "(+) Throw a Pokéball",
|
||||
|
|
|
@ -66,9 +66,10 @@ import { Species } from "#enums/species";
|
|||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { MysteryEncounterVariant } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phase";
|
||||
import { getEncounterText, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
|
||||
import { isNullOrUndefined } from "./utils";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
const { t } = i18next;
|
||||
|
||||
|
@ -1680,9 +1681,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||
|
||||
pokemon.resetTurnData();
|
||||
|
||||
const addPostSummonForEncounter = this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && !this.scene.currentBattle.mysteryEncounter?.disableAllPostSummon;
|
||||
|
||||
if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || (this.scene.currentBattle.waveIndex % 10) === 1 || addPostSummonForEncounter) {
|
||||
if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || (this.scene.currentBattle.waveIndex % 10) === 1 || this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
||||
this.queuePostSummon();
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@ import i18next from "i18next";
|
|||
import BattleScene from "../battle-scene";
|
||||
import { Phase } from "../phase";
|
||||
import { Mode } from "../ui/ui";
|
||||
import {
|
||||
getEncounterText
|
||||
} from "../data/mystery-encounters/mystery-encounter-utils";
|
||||
import { hideMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { CheckSwitchPhase, NewBattlePhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases";
|
||||
import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option";
|
||||
import { MysteryEncounterVariant } from "../data/mystery-encounters/mystery-encounter";
|
||||
|
@ -15,7 +13,7 @@ import { Tutorial, handleTutorial } from "../tutorial";
|
|||
import { IvScannerModifier } from "../modifier/modifier";
|
||||
import * as Utils from "../utils";
|
||||
import { isNullOrUndefined } from "../utils";
|
||||
import { MysteryEncounterUiSettings } from "#app/ui/mystery-encounter-ui-handler";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
/**
|
||||
* Will handle (in order):
|
||||
|
@ -27,11 +25,17 @@ import { MysteryEncounterUiSettings } from "#app/ui/mystery-encounter-ui-handler
|
|||
* - Queuing of the MysteryEncounterOptionSelectedPhase
|
||||
*/
|
||||
export class MysteryEncounterPhase extends Phase {
|
||||
followupOptionSelectSettings: MysteryEncounterUiSettings;
|
||||
optionSelectSettings: OptionSelectSettings;
|
||||
|
||||
constructor(scene: BattleScene, followupOptionSelectSettings?: MysteryEncounterUiSettings) {
|
||||
/**
|
||||
*
|
||||
* @param scene
|
||||
* @param optionSelectSettings - allows overriding the typical options of an encounter with new ones
|
||||
* Mostly useful for having repeated queries during a single encounter, where the queries and options may differ each time
|
||||
*/
|
||||
constructor(scene: BattleScene, optionSelectSettings?: OptionSelectSettings) {
|
||||
super(scene);
|
||||
this.followupOptionSelectSettings = followupOptionSelectSettings;
|
||||
this.optionSelectSettings = optionSelectSettings;
|
||||
}
|
||||
|
||||
start() {
|
||||
|
@ -45,14 +49,14 @@ export class MysteryEncounterPhase extends Phase {
|
|||
const offset = this.scene.currentBattle.mysteryEncounter.seedOffset ?? this.scene.currentBattle.waveIndex * 1000;
|
||||
this.scene.currentBattle.mysteryEncounter.seedOffset = offset + 512;
|
||||
|
||||
if (!this.followupOptionSelectSettings) {
|
||||
if (!this.optionSelectSettings) {
|
||||
// Sets flag that ME was encountered, only if this is not a followup option select phase
|
||||
// Can be used in later MEs to check for requirements to spawn, etc.
|
||||
this.scene.mysteryEncounterData.encounteredEvents.push([this.scene.currentBattle.mysteryEncounter.encounterType, this.scene.currentBattle.mysteryEncounter.encounterTier]);
|
||||
}
|
||||
|
||||
// Initiates encounter dialogue window and option select
|
||||
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, this.followupOptionSelectSettings);
|
||||
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, this.optionSelectSettings);
|
||||
}
|
||||
|
||||
handleOptionSelect(option: MysteryEncounterOption, index: number): boolean {
|
||||
|
@ -144,7 +148,7 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
|
|||
start() {
|
||||
super.start();
|
||||
if (this.scene.currentBattle.mysteryEncounter.hideIntroVisuals) {
|
||||
this.hideMysteryEncounterIntroVisuals().then(() => {
|
||||
hideMysteryEncounterIntroVisuals(this.scene).then(() => {
|
||||
this.scene.executeWithSeedOffset(() => {
|
||||
this.onOptionSelect(this.scene).finally(() => {
|
||||
this.end();
|
||||
|
@ -159,32 +163,6 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
|
|||
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
|
||||
}
|
||||
}
|
||||
|
||||
hideMysteryEncounterIntroVisuals(): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const introVisuals = this.scene.currentBattle.mysteryEncounter.introVisuals;
|
||||
if (introVisuals) {
|
||||
// Hide
|
||||
this.scene.tweens.add({
|
||||
targets: introVisuals,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750,
|
||||
onComplete: () => {
|
||||
this.scene.field.remove(introVisuals);
|
||||
introVisuals.setVisible(false);
|
||||
introVisuals.destroy();
|
||||
this.scene.currentBattle.mysteryEncounter.introVisuals = null;
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -452,7 +452,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
|
|||
this.set1f("vCutoff", v1);
|
||||
|
||||
const hasShadow = sprite.pipelineData["hasShadow"] as boolean;
|
||||
const yShadowOffset = sprite.pipelineData["yShadowOffset"] as number;
|
||||
const yShadowOffset = sprite.pipelineData["yShadowOffset"] as number ?? 0;
|
||||
if (hasShadow) {
|
||||
const isEntityObj = sprite.parentContainer instanceof Pokemon || sprite.parentContainer instanceof Trainer || sprite.parentContainer instanceof MysteryEncounterIntroVisuals;
|
||||
const field = isEntityObj ? sprite.parentContainer.parentContainer : sprite.parentContainer;
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import {
|
||||
getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon,
|
||||
getRandomPlayerPokemon, getRandomSpeciesByStarterTier, getEncounterText,
|
||||
koPlayerPokemon, queueEncounterMessage, showEncounterDialogue, showEncounterText,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
|
@ -14,6 +9,8 @@ import IMysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
|||
import { MessagePhase } from "#app/phases";
|
||||
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { Type } from "#app/data/type";
|
||||
import { getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon, getRandomPlayerPokemon, getRandomSpeciesByStarterTier, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
describe("Mystery Encounter Utils", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
|
|
|
@ -10,17 +10,8 @@ import MysteryEncounterOption, { EncounterOptionMode } from "../data/mystery-enc
|
|||
import * as Utils from "../utils";
|
||||
import { isNullOrUndefined } from "../utils";
|
||||
import { getPokeballAtlasKey } from "../data/pokeball";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
|
||||
export class MysteryEncounterUiSettings {
|
||||
hideDescription?: boolean;
|
||||
slideInDescription?: boolean;
|
||||
overrideTitle?: string;
|
||||
overrideDescription?: string;
|
||||
overrideQuery?: string;
|
||||
overrideOptions?: MysteryEncounterOption[];
|
||||
startingCursorIndex?: number;
|
||||
}
|
||||
import { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
private cursorContainer: Phaser.GameObjects.Container;
|
||||
|
@ -37,7 +28,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
private descriptionScrollTween: Phaser.Tweens.Tween;
|
||||
private rarityBall: Phaser.GameObjects.Sprite;
|
||||
|
||||
private overrideSettings: MysteryEncounterUiSettings;
|
||||
private overrideSettings: OptionSelectSettings;
|
||||
private encounterOptions: MysteryEncounterOption[] = [];
|
||||
private optionsMeetsReqs: boolean[];
|
||||
|
||||
|
@ -81,7 +72,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
show(args: any[]): boolean {
|
||||
super.show(args);
|
||||
|
||||
this.overrideSettings = args[0] as MysteryEncounterUiSettings ?? {};
|
||||
this.overrideSettings = args[0] as OptionSelectSettings ?? {};
|
||||
const showDescriptionContainer = isNullOrUndefined(this.overrideSettings?.hideDescription) ? true : !this.overrideSettings?.hideDescription;
|
||||
const slideInDescription = isNullOrUndefined(this.overrideSettings?.slideInDescription) ? true : this.overrideSettings?.slideInDescription;
|
||||
const startingCursorIndex = this.overrideSettings?.startingCursorIndex ?? 0;
|
||||
|
@ -100,7 +91,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
if (this.blockInput) {
|
||||
setTimeout(() => {
|
||||
this.unblockInput();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
}
|
||||
this.displayOptionTooltip();
|
||||
|
||||
|
@ -120,7 +111,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
if (cursor === this.viewPartyIndex) {
|
||||
// Handle view party
|
||||
success = true;
|
||||
const overrideSettings: MysteryEncounterUiSettings = {
|
||||
const overrideSettings: OptionSelectSettings = {
|
||||
...this.overrideSettings,
|
||||
slideInDescription: false
|
||||
};
|
||||
|
@ -387,7 +378,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
descriptionTextMaskRect.setScale(6);
|
||||
descriptionTextMaskRect.fillStyle(0xFFFFFF);
|
||||
descriptionTextMaskRect.beginPath();
|
||||
descriptionTextMaskRect.fillRect(6, 54, 206, 60);
|
||||
descriptionTextMaskRect.fillRect(6, 53, 206, 57);
|
||||
|
||||
const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask();
|
||||
|
||||
|
|
Loading…
Reference in New Issue