safari encounter and sprite offset positioning updates
This commit is contained in:
parent
43ebff59a4
commit
da0aea0d1e
|
@ -4,177 +4,198 @@
|
||||||
"image": "chest_blue.png",
|
"image": "chest_blue.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 300,
|
"w": 58,
|
||||||
"h": 75
|
"h": 528
|
||||||
},
|
},
|
||||||
"scale": 1,
|
"scale": 1,
|
||||||
"frames": [
|
"frames": [
|
||||||
{
|
{
|
||||||
"filename": "0001.png",
|
"filename": "0000.png",
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": false,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 0,
|
"x": 14,
|
||||||
"y": 0,
|
"y": 30,
|
||||||
|
"w": 48,
|
||||||
|
"h": 41
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 1,
|
||||||
|
"w": 48,
|
||||||
|
"h": 41
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "0001.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 14,
|
||||||
|
"y": 34,
|
||||||
|
"w": 49,
|
||||||
|
"h": 37
|
||||||
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": -15,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 44,
|
||||||
"w": 75,
|
"w": 49,
|
||||||
"h": 75
|
"h": 37
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "0002.png",
|
"filename": "0002.png",
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": false,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 0,
|
"x": 14,
|
||||||
"y": 0,
|
"y": 30,
|
||||||
"w": 75,
|
"w": 48,
|
||||||
"h": 75
|
"h": 41
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": -15,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 83,
|
||||||
"w": 75,
|
"w": 48,
|
||||||
"h": 75
|
"h": 41
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "0003.png",
|
"filename": "0003.png",
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": false,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 0,
|
"x": 14,
|
||||||
"y": 0,
|
"y": 23,
|
||||||
"w": 75,
|
"w": 48,
|
||||||
"h": 75
|
"h": 48
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 57,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 126,
|
||||||
"w": 75,
|
"w": 48,
|
||||||
"h": 75
|
"h": 48
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "0004.png",
|
"filename": "0004.png",
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": false,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 0,
|
"x": 13,
|
||||||
"y": 0,
|
"y": 4,
|
||||||
"w": 75,
|
"w": 55,
|
||||||
"h": 75
|
"h": 67
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 57,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 176,
|
||||||
"w": 75,
|
"w": 55,
|
||||||
"h": 75
|
"h": 67
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "0005.png",
|
"filename": "0005.png",
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": false,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 0,
|
"x": 15,
|
||||||
"y": 0,
|
"y": 2,
|
||||||
"w": 75,
|
"w": 56,
|
||||||
"h": 75
|
"h": 69
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 129,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 245,
|
||||||
"w": 75,
|
"w": 56,
|
||||||
"h": 75
|
"h": 69
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "0006.png",
|
"filename": "0006.png",
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": false,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 0,
|
"x": 15,
|
||||||
"y": 0,
|
"y": 2,
|
||||||
"w": 75,
|
"w": 56,
|
||||||
"h": 75
|
"h": 69
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 129,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 316,
|
||||||
"w": 75,
|
"w": 56,
|
||||||
"h": 75
|
"h": 69
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "0007.png",
|
"filename": "0007.png",
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": false,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 0,
|
"x": 13,
|
||||||
"y": 0,
|
"y": 2,
|
||||||
"w": 75,
|
"w": 56,
|
||||||
"h": 75
|
"h": 69
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 201,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 387,
|
||||||
"w": 75,
|
"w": 56,
|
||||||
"h": 75
|
"h": 69
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "0008.png",
|
"filename": "0008.png",
|
||||||
"rotated": false,
|
"rotated": false,
|
||||||
"trimmed": false,
|
"trimmed": true,
|
||||||
"sourceSize": {
|
"sourceSize": {
|
||||||
"w": 75,
|
"w": 75,
|
||||||
"h": 75
|
"h": 75
|
||||||
},
|
},
|
||||||
"spriteSourceSize": {
|
"spriteSourceSize": {
|
||||||
"x": 0,
|
"x": 13,
|
||||||
"y": 0,
|
"y": 2,
|
||||||
"w": 75,
|
"w": 56,
|
||||||
"h": 75
|
"h": 69
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"x": 201,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 458,
|
||||||
"w": 75,
|
"w": 56,
|
||||||
"h": 75
|
"h": 69
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -183,6 +204,6 @@
|
||||||
"meta": {
|
"meta": {
|
||||||
"app": "https://www.codeandweb.com/texturepacker",
|
"app": "https://www.codeandweb.com/texturepacker",
|
||||||
"version": "3.0",
|
"version": "3.0",
|
||||||
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
|
"smartupdate": "$TexturePacker:SmartUpdate:5f36000f6160ee6f397afe5a6fd60b73:cf6f4b08e23400447813583c322eb6c7:f4f3c064e6c93b8d1290f93bee927f60$"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -30,7 +30,8 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: 4,
|
x: 4,
|
||||||
y: 8,
|
y: 10,
|
||||||
|
yShadowOffset: 3,
|
||||||
disableAnimation: true, // Re-enabled after option select
|
disableAnimation: true, // Re-enabled after option select
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
@ -75,10 +76,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(
|
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_normal_result");
|
||||||
scene,
|
|
||||||
"mysteryEncounter:mysterious_chest_option_1_normal_result"
|
|
||||||
);
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll > 40) {
|
} else if (roll > 40) {
|
||||||
// Choose between 3 ULTRA tier items (20%)
|
// Choose between 3 ULTRA tier items (20%)
|
||||||
|
@ -90,10 +88,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(
|
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_good_result");
|
||||||
scene,
|
|
||||||
"mysteryEncounter:mysterious_chest_option_1_good_result"
|
|
||||||
);
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll > 36) {
|
} else if (roll > 36) {
|
||||||
// Choose between 2 ROGUE tier items (4%)
|
// Choose between 2 ROGUE tier items (4%)
|
||||||
|
@ -101,10 +96,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
||||||
});
|
});
|
||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(
|
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_great_result");
|
||||||
scene,
|
|
||||||
"mysteryEncounter:mysterious_chest_option_1_great_result"
|
|
||||||
);
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll > 35) {
|
} else if (roll > 35) {
|
||||||
// Choose 1 MASTER tier item (1%)
|
// Choose 1 MASTER tier item (1%)
|
||||||
|
@ -112,10 +104,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
guaranteedModifierTiers: [ModifierTier.MASTER],
|
guaranteedModifierTiers: [ModifierTier.MASTER],
|
||||||
});
|
});
|
||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(
|
queueEncounterMessage(scene, "mysteryEncounter:mysterious_chest_option_1_amazing_result");
|
||||||
scene,
|
|
||||||
"mysteryEncounter:mysterious_chest_option_1_amazing_result"
|
|
||||||
);
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else {
|
} else {
|
||||||
// Your highest level unfainted Pok<6F>mon gets OHKO. Progress with no rewards (35%)
|
// Your highest level unfainted Pok<6F>mon gets OHKO. Progress with no rewards (35%)
|
||||||
|
@ -125,27 +114,22 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
);
|
);
|
||||||
koPlayerPokemon(highestLevelPokemon);
|
koPlayerPokemon(highestLevelPokemon);
|
||||||
|
|
||||||
scene.currentBattle.mysteryEncounter.setDialogueToken(
|
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.name);
|
||||||
"pokeName",
|
|
||||||
highestLevelPokemon.name
|
|
||||||
);
|
|
||||||
// Show which Pokemon was KOed, then leave encounter with no rewards
|
// Show which Pokemon was KOed, then leave encounter with no rewards
|
||||||
// Does this synchronously so that game over doesn't happen over result message
|
// Does this synchronously so that game over doesn't happen over result message
|
||||||
await showEncounterText(
|
await showEncounterText(scene, "mysteryEncounter:mysterious_chest_option_1_bad_result")
|
||||||
scene,
|
.then(() => {
|
||||||
"mysteryEncounter:mysterious_chest_option_1_bad_result"
|
if (
|
||||||
).then(() => {
|
scene.getParty().filter((p) => p.isAllowedInBattle()).length ===
|
||||||
if (
|
|
||||||
scene.getParty().filter((p) => p.isAllowedInBattle()).length ===
|
|
||||||
0
|
0
|
||||||
) {
|
) {
|
||||||
// All pokemon fainted, game over
|
// All pokemon fainted, game over
|
||||||
scene.clearPhaseQueue();
|
scene.clearPhaseQueue();
|
||||||
scene.unshiftPhase(new GameOverPhase(scene));
|
scene.unshiftPhase(new GameOverPhase(scene));
|
||||||
} else {
|
} else {
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -1,31 +1,167 @@
|
||||||
import {
|
import { getRandomSpeciesByStarterTier, initFollowupOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||||
getHighestLevelPlayerPokemon,
|
|
||||||
koPlayerPokemon,
|
|
||||||
leaveEncounterWithoutBattle,
|
|
||||||
queueEncounterMessage,
|
|
||||||
setEncounterRewards,
|
|
||||||
showEncounterText,
|
|
||||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
|
||||||
import { GameOverPhase } from "#app/phases";
|
|
||||||
import { randSeedInt } from "#app/utils";
|
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import BattleScene from "../../../battle-scene";
|
import BattleScene from "../../../battle-scene";
|
||||||
import IMysteryEncounter, {
|
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, MysteryEncounterVariant } from "../mystery-encounter";
|
||||||
MysteryEncounterBuilder,
|
import MysteryEncounterOption, { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
MysteryEncounterTier,
|
import { TrainerSlot } from "#app/data/trainer-config";
|
||||||
} from "../mystery-encounter";
|
import { ScanIvsPhase, SummonPhase, VictoryPhase } from "#app/phases";
|
||||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
import i18next from "i18next";
|
||||||
|
import { HiddenAbilityRateBoosterModifier, IvScannerModifier, PokemonHeldItemModifier } 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 { 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";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:safari_zone";
|
const namespace = "mysteryEncounter:safari_zone";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SAFARI ZONE OPTIONS
|
||||||
|
*
|
||||||
|
* Catch and flee rate **multipliers** 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:
|
||||||
|
* catchRate = speciesCatchRate [1 to 255] * catchStageMultiplier [2/8 to 8/2] * ballCatchRate [1.5]
|
||||||
|
*
|
||||||
|
* Flee calculation:
|
||||||
|
* The harder a species is to catch, the higher its flee rate is
|
||||||
|
* (Caps at 50% base chance to flee for the hardest to catch Pokemon, before factoring in flee stage)
|
||||||
|
* fleeRate = ((255^2 - speciesCatchRate^2) / 255 / 2) [0 to 127.5] * fleeStageMultiplier [2/8 to 8/2]
|
||||||
|
* Flee chance = fleeRate / 255
|
||||||
|
*/
|
||||||
|
const safariZoneOptions: MysteryEncounterOption[] = [
|
||||||
|
new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}_pokeball_option_label`,
|
||||||
|
buttonTooltip: `${namespace}_pokeball_option_tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}_pokeball_option_selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Throw a ball option
|
||||||
|
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
|
||||||
|
const catchResult = await throwPokeball(scene, pokemon);
|
||||||
|
|
||||||
|
if (catchResult) {
|
||||||
|
// You caught pokemon
|
||||||
|
scene.unshiftPhase(new VictoryPhase(scene, 0));
|
||||||
|
// Check how many safari pokemon left
|
||||||
|
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
|
||||||
|
await summonSafariPokemon(scene);
|
||||||
|
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: 0, hideDescription: true });
|
||||||
|
} else {
|
||||||
|
// End safari mode
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pokemon failed to catch, end turn
|
||||||
|
await doEndTurn(scene, 0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build(),
|
||||||
|
new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}_bait_option_label`,
|
||||||
|
buttonTooltip: `${namespace}_bait_option_tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}_bait_option_selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Throw bait option
|
||||||
|
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
|
||||||
|
await throwBait(scene, pokemon);
|
||||||
|
|
||||||
|
// 100% chance to increase catch stage +2
|
||||||
|
tryChangeCatchStage(scene, 2);
|
||||||
|
// 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);
|
||||||
|
} else {
|
||||||
|
scene.queueMessage(i18next.t(`${namespace}_pokemon_eating`, { pokemonName: pokemon.name }), 0, null, 500);
|
||||||
|
}
|
||||||
|
// TODO: throw bait with eat animation
|
||||||
|
// TODO: play bug bite sfx, maybe spike cannon?
|
||||||
|
|
||||||
|
await doEndTurn(scene, 1);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build(),
|
||||||
|
new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}_mud_option_label`,
|
||||||
|
buttonTooltip: `${namespace}_mud_option_tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}_mud_option_selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Throw mud option
|
||||||
|
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
|
||||||
|
await throwMud(scene, pokemon);
|
||||||
|
// 100% chance to decrease flee stage -2
|
||||||
|
tryChangeFleeStage(scene, -2);
|
||||||
|
// 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);
|
||||||
|
} else {
|
||||||
|
scene.queueMessage(i18next.t(`${namespace}_pokemon_angry`, { pokemonName: pokemon.name }), 0, null, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
await doEndTurn(scene, 2);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build(),
|
||||||
|
new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(EncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}_flee_option_label`,
|
||||||
|
buttonTooltip: `${namespace}_flee_option_tooltip`,
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Flee option
|
||||||
|
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
|
||||||
|
await doPlayerFlee(scene, pokemon);
|
||||||
|
// Check how many safari pokemon left
|
||||||
|
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
|
||||||
|
await summonSafariPokemon(scene);
|
||||||
|
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, startingCursorIndex: 3, hideDescription: true });
|
||||||
|
} else {
|
||||||
|
// End safari mode
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
];
|
||||||
|
|
||||||
export const SafariZoneEncounter: IMysteryEncounter =
|
export const SafariZoneEncounter: IMysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
|
||||||
MysteryEncounterType.SAFARI_ZONE
|
|
||||||
)
|
|
||||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||||
.withSceneWaveRangeRequirement(10, 180) // waves 2 to 180
|
.withSceneWaveRangeRequirement(10, 180) // waves 2 to 180
|
||||||
|
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||||
.withHideIntroVisuals(false)
|
.withHideIntroVisuals(false)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
|
@ -33,133 +169,57 @@ export const SafariZoneEncounter: IMysteryEncounter =
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: 4,
|
x: 4,
|
||||||
y: 8,
|
y: 10,
|
||||||
|
yShadowOffset: 3,
|
||||||
disableAnimation: true, // Re-enabled after option select
|
disableAnimation: true, // Re-enabled after option select
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
{
|
{
|
||||||
text: `mysteryEncounter:${namespace}_intro_message`,
|
text: `${namespace}_intro_message`,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.withTitle(`mysteryEncounter:${namespace}_title`)
|
.withTitle(`${namespace}_title`)
|
||||||
.withDescription(`mysteryEncounter:${namespace}_description`)
|
.withDescription(`${namespace}_description`)
|
||||||
.withQuery(`mysteryEncounter:${namespace}_query`)
|
.withQuery(`${namespace}_query`)
|
||||||
.withOption(
|
.withOption(new MysteryEncounterOptionBuilder()
|
||||||
new MysteryEncounterOptionBuilder()
|
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||||
.withOptionMode(EncounterOptionMode.DEFAULT)
|
// TODO: update
|
||||||
.withDialogue({
|
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||||
buttonLabel: "mysteryEncounter:${namespace}_option_1_label",
|
.withDialogue({
|
||||||
buttonTooltip: "mysteryEncounter:${namespace}_option_1_tooltip",
|
buttonLabel: `${namespace}_option_1_label`,
|
||||||
selected: [
|
buttonTooltip: `${namespace}_option_1_tooltip`,
|
||||||
{
|
selected: [
|
||||||
text: "mysteryEncounter:${namespace}_option_1_selected_message",
|
{
|
||||||
},
|
text: `${namespace}_option_1_selected_message`,
|
||||||
],
|
},
|
||||||
})
|
],
|
||||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
})
|
||||||
// Play animation
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
const introVisuals =
|
// Start safari encounter
|
||||||
scene.currentBattle.mysteryEncounter.introVisuals;
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
introVisuals.spriteConfigs[0].disableAnimation = false;
|
encounter.encounterVariant = MysteryEncounterVariant.SAFARI_BATTLE;
|
||||||
introVisuals.playAnim();
|
encounter.misc = {
|
||||||
})
|
safariPokemonRemaining: 3
|
||||||
.withOptionPhase(async (scene: BattleScene) => {
|
};
|
||||||
// Open the chest
|
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||||
const roll = randSeedInt(100);
|
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
|
||||||
if (roll > 60) {
|
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims");
|
||||||
// Choose between 2 COMMON / 2 GREAT tier items (40%)
|
scene.loadSe("PRSFX- Taunt2", "battle_anims");
|
||||||
setEncounterRewards(scene, {
|
await hideMysteryEncounterIntroVisuals(scene);
|
||||||
guaranteedModifierTiers: [
|
await summonSafariPokemon(scene);
|
||||||
ModifierTier.COMMON,
|
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, hideDescription: true });
|
||||||
ModifierTier.COMMON,
|
return true;
|
||||||
ModifierTier.GREAT,
|
})
|
||||||
ModifierTier.GREAT,
|
.build()
|
||||||
],
|
|
||||||
});
|
|
||||||
// Display result message then proceed to rewards
|
|
||||||
queueEncounterMessage(
|
|
||||||
scene,
|
|
||||||
"mysteryEncounter:${namespace}_option_1_normal_result"
|
|
||||||
);
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
|
||||||
} else if (roll > 40) {
|
|
||||||
// Choose between 3 ULTRA tier items (20%)
|
|
||||||
setEncounterRewards(scene, {
|
|
||||||
guaranteedModifierTiers: [
|
|
||||||
ModifierTier.ULTRA,
|
|
||||||
ModifierTier.ULTRA,
|
|
||||||
ModifierTier.ULTRA,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
// Display result message then proceed to rewards
|
|
||||||
queueEncounterMessage(
|
|
||||||
scene,
|
|
||||||
"mysteryEncounter:${namespace}_option_1_good_result"
|
|
||||||
);
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
|
||||||
} else if (roll > 36) {
|
|
||||||
// Choose between 2 ROGUE tier items (4%)
|
|
||||||
setEncounterRewards(scene, {
|
|
||||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
|
||||||
});
|
|
||||||
// Display result message then proceed to rewards
|
|
||||||
queueEncounterMessage(
|
|
||||||
scene,
|
|
||||||
"mysteryEncounter:${namespace}_option_1_great_result"
|
|
||||||
);
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
|
||||||
} else if (roll > 35) {
|
|
||||||
// Choose 1 MASTER tier item (1%)
|
|
||||||
setEncounterRewards(scene, {
|
|
||||||
guaranteedModifierTiers: [ModifierTier.MASTER],
|
|
||||||
});
|
|
||||||
// Display result message then proceed to rewards
|
|
||||||
queueEncounterMessage(
|
|
||||||
scene,
|
|
||||||
"mysteryEncounter:${namespace}_option_1_amazing_result"
|
|
||||||
);
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
|
||||||
} else {
|
|
||||||
// Your highest level unfainted Pok<6F>mon gets OHKO. Progress with no rewards (35%)
|
|
||||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(
|
|
||||||
scene,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
koPlayerPokemon(highestLevelPokemon);
|
|
||||||
|
|
||||||
scene.currentBattle.mysteryEncounter.setDialogueToken(
|
|
||||||
"pokeName",
|
|
||||||
highestLevelPokemon.name
|
|
||||||
);
|
|
||||||
// Show which Pokemon was KOed, then leave encounter with no rewards
|
|
||||||
// Does this synchronously so that game over doesn't happen over result message
|
|
||||||
await showEncounterText(
|
|
||||||
scene,
|
|
||||||
"mysteryEncounter:${namespace}_option_1_bad_result"
|
|
||||||
).then(() => {
|
|
||||||
if (
|
|
||||||
scene.getParty().filter((p) => p.isAllowedInBattle()).length ===
|
|
||||||
0
|
|
||||||
) {
|
|
||||||
// All pokemon fainted, game over
|
|
||||||
scene.clearPhaseQueue();
|
|
||||||
scene.unshiftPhase(new GameOverPhase(scene));
|
|
||||||
} else {
|
|
||||||
leaveEncounterWithoutBattle(scene);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
)
|
)
|
||||||
.withSimpleOption(
|
.withSimpleOption(
|
||||||
{
|
{
|
||||||
buttonLabel: "mysteryEncounter:${namespace}_option_2_label",
|
buttonLabel: `${namespace}_option_2_label`,
|
||||||
buttonTooltip: "mysteryEncounter:${namespace}_option_2_tooltip",
|
buttonTooltip: `${namespace}_option_2_tooltip`,
|
||||||
selected: [
|
selected: [
|
||||||
{
|
{
|
||||||
text: "mysteryEncounter:${namespace}_option_2_selected_message",
|
text: `${namespace}_option_2_selected_message`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -170,3 +230,588 @@ export const SafariZoneEncounter: IMysteryEncounter =
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.build();
|
.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
|
||||||
|
scene.queueMessage(i18next.t(`${namespace}_remaining_count`, { remainingCount: encounter.misc.safariPokemonRemaining}), null, true);
|
||||||
|
|
||||||
|
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
|
||||||
|
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
|
||||||
|
let enemySpecies;
|
||||||
|
let pokemon;
|
||||||
|
scene.executeWithSeedOffset(() => {
|
||||||
|
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||||
|
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(scene.currentBattle.waveIndex, true, false, scene.gameMode));
|
||||||
|
scene.currentBattle.enemyParty = [];
|
||||||
|
pokemon = scene.addEnemyPokemon(enemySpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, false);
|
||||||
|
|
||||||
|
// Roll shiny twice
|
||||||
|
if (!pokemon.shiny) {
|
||||||
|
pokemon.trySetShiny();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roll HA twice
|
||||||
|
if (pokemon.species.abilityHidden) {
|
||||||
|
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
||||||
|
if (pokemon.abilityIndex < hiddenIndex) {
|
||||||
|
const hiddenAbilityChance = new IntegerHolder(256);
|
||||||
|
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||||
|
|
||||||
|
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||||
|
|
||||||
|
if (hasHiddenAbility) {
|
||||||
|
pokemon.abilityIndex = hiddenIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pokemon.calculateStats();
|
||||||
|
|
||||||
|
scene.currentBattle.enemyParty[0] = pokemon;
|
||||||
|
}, scene.currentBattle.waveIndex + encounter.misc.safariPokemonRemaining);
|
||||||
|
|
||||||
|
scene.gameData.setPokemonSeen(pokemon, true);
|
||||||
|
await pokemon.loadAssets();
|
||||||
|
|
||||||
|
// Reset safari catch and flee rates
|
||||||
|
encounter.misc.catchStage = 0;
|
||||||
|
encounter.misc.fleeStage = 0;
|
||||||
|
encounter.misc.pokemon = pokemon;
|
||||||
|
encounter.misc.safariPokemonRemaining -= 1;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||||
|
const pokeballType: PokeballType = PokeballType.POKEBALL;
|
||||||
|
const originalY: number = pokemon.y;
|
||||||
|
|
||||||
|
const baseCatchRate = pokemon.species.catchRate;
|
||||||
|
// Catch stage ranges from -6 to +6 (like stat boost stages)
|
||||||
|
const safariCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage;
|
||||||
|
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6)
|
||||||
|
const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
|
||||||
|
// Catch rate same as safari ball
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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`);
|
||||||
|
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: bait,
|
||||||
|
x: { value: 210 + fpOffset[0], ease: "Linear" },
|
||||||
|
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, () => {
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokemon,
|
||||||
|
duration: 200,
|
||||||
|
ease: "Cubic.easeOut",
|
||||||
|
yoyo: true,
|
||||||
|
y: originalY - 30,
|
||||||
|
loop: 2,
|
||||||
|
onStart: () => {
|
||||||
|
scene.playSound("PRSFX- Bug Bite");
|
||||||
|
},
|
||||||
|
onLoop: () => {
|
||||||
|
scene.playSound("PRSFX- Bug Bite");
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
resolve(true);
|
||||||
|
bait.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
mud.setOrigin(0.5, 0.625);
|
||||||
|
scene.field.add(mud);
|
||||||
|
|
||||||
|
scene.playSound("pb_throw");
|
||||||
|
|
||||||
|
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: 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`));
|
||||||
|
scene.playSound("PRSFX- Sludge Bomb2");
|
||||||
|
// pokemon.tint(getPokeballTintColor(pokeballType));
|
||||||
|
|
||||||
|
// addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
|
||||||
|
scene.time.delayedCall(1536, () => {
|
||||||
|
mud.destroy();
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokemon,
|
||||||
|
duration: 300,
|
||||||
|
ease: "Cubic.easeOut",
|
||||||
|
yoyo: true,
|
||||||
|
y: originalY - 20,
|
||||||
|
loop: 1,
|
||||||
|
onStart: () => {
|
||||||
|
scene.playSound("PRSFX- Taunt2");
|
||||||
|
},
|
||||||
|
onLoop: () => {
|
||||||
|
scene.playSound("PRSFX- Taunt2");
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
const fleeRate = (255 * 255 - speciesCatchRate * speciesCatchRate) / 255 / 2 * fleeModifier;
|
||||||
|
console.log("Flee rate: " + fleeRate);
|
||||||
|
const roll = randSeedInt(256);
|
||||||
|
console.log("Roll: " + roll);
|
||||||
|
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!
|
||||||
|
await doPokemonFlee(scene, pokemon);
|
||||||
|
// Check how many safari pokemon left
|
||||||
|
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
|
||||||
|
await summonSafariPokemon(scene);
|
||||||
|
initFollowupOptionSelect(scene, { overrideOptions: safariZoneOptions, 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,15 +28,17 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
|
||||||
fileRoot: "pokemon",
|
fileRoot: "pokemon",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
x: 10,
|
x: 12,
|
||||||
y: -1,
|
y: -5,
|
||||||
|
yShadowOffset: -5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spriteKey: "b2w2_veteran_m",
|
spriteKey: "b2w2_veteran_m",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: -10,
|
x: -12,
|
||||||
y: 2,
|
y: 3,
|
||||||
|
yShadowOffset: 3
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Trainer, { TrainerVariant } from "../../field/trainer";
|
||||||
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
|
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 { 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 { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
|
||||||
import { MysteryEncounterBattlePhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
|
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 { isNullOrUndefined } from "#app/utils";
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
|
@ -27,6 +27,7 @@ import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/myst
|
||||||
import { getTextWithColors, TextStyle } from "#app/ui/text";
|
import { getTextWithColors, TextStyle } from "#app/ui/text";
|
||||||
import * as Overrides from "#app/overrides";
|
import * as Overrides from "#app/overrides";
|
||||||
import { UiTheme } from "#enums/ui-theme";
|
import { UiTheme } from "#enums/ui-theme";
|
||||||
|
import { MysteryEncounterUiSettings } from "#app/ui/mystery-encounter-ui-handler";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -427,6 +428,11 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS
|
||||||
if (playSound) {
|
if (playSound) {
|
||||||
scene.playSound("buy");
|
scene.playSound("buy");
|
||||||
}
|
}
|
||||||
|
if (changeValue < 0) {
|
||||||
|
scene.queueMessage(i18next.t("mysteryEncounter:paid_money", { amount: -changeValue }), null, true);
|
||||||
|
} else {
|
||||||
|
scene.queueMessage(i18next.t("mysteryEncounter:receive_money", { amount: changeValue }), null, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -459,7 +465,7 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* This function is intended for use inside onPreOptionPhase() of an encounter option
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param onPokemonSelected - Any logic that needs to be performed when Pokemon is chosen
|
* @param onPokemonSelected - Any logic that needs to be performed when Pokemon is chosen
|
||||||
* If a second option needs to be selected, onPokemonSelected should return a OptionSelectItem[] object
|
* If a second option needs to be selected, onPokemonSelected should return a OptionSelectItem[] object
|
||||||
|
@ -674,6 +680,16 @@ export function setEncounterExp(scene: BattleScene, participantIds: integer[], b
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @param scene
|
||||||
|
* @param followupOptionSelectSettings
|
||||||
|
*/
|
||||||
|
export function initFollowupOptionSelect(scene: BattleScene, followupOptionSelectSettings: MysteryEncounterUiSettings) {
|
||||||
|
scene.pushPhase(new MysteryEncounterPhase(scene, followupOptionSelectSettings));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be used to exit an encounter without any battles or followup
|
* 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
|
* Will skip any shops and rewards, and queue the next encounter phase as normal
|
||||||
|
@ -688,7 +704,9 @@ export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: bo
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: boolean = false) {
|
export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: boolean = false) {
|
||||||
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) {
|
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.SAFARI_BATTLE) {
|
||||||
|
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
||||||
|
} else if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) {
|
||||||
scene.pushPhase(new EggLapsePhase(scene));
|
scene.pushPhase(new EggLapsePhase(scene));
|
||||||
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
||||||
} else if (!scene.getEnemyParty().find(p => scene.currentBattle.mysteryEncounter.encounterVariant !== MysteryEncounterVariant.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
|
} else if (!scene.getEnemyParty().find(p => scene.currentBattle.mysteryEncounter.encounterVariant !== MysteryEncounterVariant.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
|
||||||
|
|
|
@ -24,7 +24,8 @@ export enum MysteryEncounterVariant {
|
||||||
TRAINER_BATTLE,
|
TRAINER_BATTLE,
|
||||||
WILD_BATTLE,
|
WILD_BATTLE,
|
||||||
BOSS_BATTLE,
|
BOSS_BATTLE,
|
||||||
NO_BATTLE
|
NO_BATTLE,
|
||||||
|
SAFARI_BATTLE
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MysteryEncounterTier {
|
export enum MysteryEncounterTier {
|
||||||
|
@ -118,6 +119,18 @@ export default interface IMysteryEncounter {
|
||||||
*/
|
*/
|
||||||
expMultiplier?: number;
|
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
|
* Generic property to set any custom data required for the encounter
|
||||||
* Extremely useful for carrying state/data between onPreOptionPhase/onOptionPhase/onPostOptionPhase
|
* Extremely useful for carrying state/data between onPreOptionPhase/onOptionPhase/onPostOptionPhase
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale-encounter";
|
import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale-encounter";
|
||||||
import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
|
import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
|
||||||
import { FieldTripEncounter } from "./encounters/field-trip-encounter";
|
import { FieldTripEncounter } from "./encounters/field-trip-encounter";
|
||||||
|
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
|
||||||
|
|
||||||
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
||||||
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
||||||
|
@ -213,6 +214,7 @@ export function initMysteryEncounters() {
|
||||||
allMysteryEncounters[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleEncounter;
|
allMysteryEncounters[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleEncounter;
|
||||||
allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter;
|
allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter;
|
||||||
allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter;
|
allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter;
|
||||||
|
allMysteryEncounters[MysteryEncounterType.SAFARI_ZONE] = SafariZoneEncounter;
|
||||||
|
|
||||||
// Add extreme encounters to biome map
|
// Add extreme encounters to biome map
|
||||||
extremeBiomeEncounters.forEach(encounter => {
|
extremeBiomeEncounters.forEach(encounter => {
|
||||||
|
|
|
@ -11,6 +11,7 @@ export class MysteryEncounterSpriteConfig {
|
||||||
tint?: number;
|
tint?: number;
|
||||||
x?: number; // X offset
|
x?: number; // X offset
|
||||||
y?: number; // Y offset
|
y?: number; // Y offset
|
||||||
|
yShadowOffset?: number;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
isItem?: boolean; // For item sprites, set to true
|
isItem?: boolean; // For item sprites, set to true
|
||||||
}
|
}
|
||||||
|
@ -37,10 +38,10 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSprite = (spriteKey: string, hasShadow?: boolean) => {
|
const getSprite = (spriteKey: string, hasShadow?: boolean, yShadowOffset?: number) => {
|
||||||
const ret = this.scene.addFieldSprite(0, 0, spriteKey);
|
const ret = this.scene.addFieldSprite(0, 0, spriteKey);
|
||||||
ret.setOrigin(0.5, 1);
|
ret.setOrigin(0.5, 1);
|
||||||
ret.setPipeline(this.scene.spritePipeline, { tone: [0.0, 0.0, 0.0, 0.0], hasShadow: !!hasShadow });
|
ret.setPipeline(this.scene.spritePipeline, { tone: [0.0, 0.0, 0.0, 0.0], hasShadow: !!hasShadow, yShadowOffset: yShadowOffset ?? 0 });
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
let sprite: GameObjects.Sprite;
|
let sprite: GameObjects.Sprite;
|
||||||
let tintSprite: GameObjects.Sprite;
|
let tintSprite: GameObjects.Sprite;
|
||||||
if (!config.isItem) {
|
if (!config.isItem) {
|
||||||
sprite = getSprite(config.spriteKey, config.hasShadow);
|
sprite = getSprite(config.spriteKey, config.hasShadow, config.yShadowOffset);
|
||||||
tintSprite = getSprite(config.spriteKey);
|
tintSprite = getSprite(config.spriteKey);
|
||||||
} else {
|
} else {
|
||||||
sprite = getItemSprite(config.spriteKey);
|
sprite = getItemSprite(config.spriteKey);
|
||||||
|
@ -83,8 +84,8 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
tintSprite.setPosition(origin + config.x, tintSprite.y);
|
tintSprite.setPosition(origin + config.x, tintSprite.y);
|
||||||
}
|
}
|
||||||
if (config.y) {
|
if (config.y) {
|
||||||
sprite.setPosition(sprite.x, config.y);
|
sprite.setPosition(sprite.x, sprite.y + config.y);
|
||||||
tintSprite.setPosition(tintSprite.x, config.y);
|
tintSprite.setPosition(tintSprite.x, tintSprite.y + config.y);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Single sprite
|
// Single sprite
|
||||||
|
|
|
@ -16,6 +16,9 @@ export const battle: SimpleTranslationEntries = {
|
||||||
"moneyWon": "You got\n₽{{moneyAmount}} for winning!",
|
"moneyWon": "You got\n₽{{moneyAmount}} for winning!",
|
||||||
"moneyPickedUp": "You picked up ₽{{moneyAmount}}!",
|
"moneyPickedUp": "You picked up ₽{{moneyAmount}}!",
|
||||||
"pokemonCaught": "{{pokemonName}} was caught!",
|
"pokemonCaught": "{{pokemonName}} was caught!",
|
||||||
|
"pokemonBrokeFree": "Oh no!\nThe Pokémon broke free!",
|
||||||
|
"pokemonFled": "The wild {{pokemonName}} fled!",
|
||||||
|
"playerFled": "You fled from the {{pokemonName}}!",
|
||||||
"addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!",
|
"addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!",
|
||||||
"partyFull": "Your party is full.\nRelease a Pokémon to make room for {{pokemonName}}?",
|
"partyFull": "Your party is full.\nRelease a Pokémon to make room for {{pokemonName}}?",
|
||||||
"pokemon": "Pokémon",
|
"pokemon": "Pokémon",
|
||||||
|
|
|
@ -17,6 +17,10 @@ export const mysteryEncounter: SimpleTranslationEntries = {
|
||||||
// DO NOT REMOVE
|
// DO NOT REMOVE
|
||||||
"unit_test_dialogue": "{{test}}{{test}} {{test{{test}}}} {{test1}} {{test\}} {{test\\}} {{test\\\}} {test}}",
|
"unit_test_dialogue": "{{test}}{{test}} {{test{{test}}}} {{test1}} {{test\}} {{test\\}} {{test\\\}} {test}}",
|
||||||
|
|
||||||
|
// General use content
|
||||||
|
"paid_money": "You paid ₽{{amount, number}}.",
|
||||||
|
"receive_money": "You received ₽{{amount, number}}!",
|
||||||
|
|
||||||
// Mystery Encounters -- Common Tier
|
// Mystery Encounters -- Common Tier
|
||||||
|
|
||||||
"mysterious_chest_intro_message": "You found...@d{32} a chest?",
|
"mysterious_chest_intro_message": "You found...@d{32} a chest?",
|
||||||
|
@ -125,7 +129,7 @@ export const mysteryEncounter: SimpleTranslationEntries = {
|
||||||
"field_trip_outro_good": "Thank you so much for your kindness!\nI hope the items I had were helpful!",
|
"field_trip_outro_good": "Thank you so much for your kindness!\nI hope the items I had were helpful!",
|
||||||
"field_trip_outro_bad": "Come along children, we'll\nfind a better demonstration elsewhere.",
|
"field_trip_outro_bad": "Come along children, we'll\nfind a better demonstration elsewhere.",
|
||||||
|
|
||||||
// Mystery Encounters -- Uncommon Tier
|
// Mystery Encounters -- Great Tier
|
||||||
|
|
||||||
"mysterious_challengers_intro_message": "Mysterious challengers have appeared!",
|
"mysterious_challengers_intro_message": "Mysterious challengers have appeared!",
|
||||||
"mysterious_challengers_title": "Mysterious Challengers",
|
"mysterious_challengers_title": "Mysterious Challengers",
|
||||||
|
@ -140,7 +144,35 @@ export const mysteryEncounter: SimpleTranslationEntries = {
|
||||||
"mysterious_challengers_option_selected_message": "The trainer steps forward...",
|
"mysterious_challengers_option_selected_message": "The trainer steps forward...",
|
||||||
"mysterious_challengers_outro_win": "The mysterious challenger was defeated!",
|
"mysterious_challengers_outro_win": "The mysterious challenger was defeated!",
|
||||||
|
|
||||||
// Mystery Encounters -- Rare Tier
|
"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_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_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",
|
||||||
|
"safari_zone_pokeball_option_selected": "You throw a Pokéball!",
|
||||||
|
"safari_zone_bait_option_label": "Throw bait",
|
||||||
|
"safari_zone_bait_option_tooltip": "(+) Increases Capture Rate\n(-) Chance to Increase Flee Rate",
|
||||||
|
"safari_zone_bait_option_selected": "You throw some bait!",
|
||||||
|
"safari_zone_mud_option_label": "Throw mud",
|
||||||
|
"safari_zone_mud_option_tooltip": "(+) Decreases Flee Rate\n(-) Chance to Decrease Capture Rate",
|
||||||
|
"safari_zone_mud_option_selected": "You throw some mud!",
|
||||||
|
"safari_zone_flee_option_label": "Flee",
|
||||||
|
"safari_zone_flee_option_tooltip": "(?) Flee from this Pokémon",
|
||||||
|
"safari_zone_pokemon_watching": "{{pokemonName}} is watching carefully!",
|
||||||
|
"safari_zone_pokemon_eating": "{{pokemonName}} is eating!",
|
||||||
|
"safari_zone_pokemon_busy_eating": "{{pokemonName}} is busy eating!",
|
||||||
|
"safari_zone_pokemon_angry": "{{pokemonName}} is angry!",
|
||||||
|
"safari_zone_pokemon_beside_itself_angry": "{{pokemonName}} is beside itself with anger!",
|
||||||
|
"safari_zone_remaining_count": "{{remainingCount}} Pokémon remaining!",
|
||||||
|
|
||||||
|
// Mystery Encounters -- Ultra Tier
|
||||||
|
|
||||||
"training_session_intro_message": "You've come across some\ntraining tools and supplies.",
|
"training_session_intro_message": "You've come across some\ntraining tools and supplies.",
|
||||||
"training_session_title": "Training Session",
|
"training_session_title": "Training Session",
|
||||||
|
@ -163,7 +195,7 @@ export const mysteryEncounter: SimpleTranslationEntries = {
|
||||||
$Its ability was changed to {{ability}}!`,
|
$Its ability was changed to {{ability}}!`,
|
||||||
"training_session_outro_win": "That was a successful training session!",
|
"training_session_outro_win": "That was a successful training session!",
|
||||||
|
|
||||||
// Mystery Encounters -- Super Rare Tier
|
// Mystery Encounters -- Rogue Tier
|
||||||
|
|
||||||
"dark_deal_intro_message": "A strange man in a tattered coat\nstands in your way...",
|
"dark_deal_intro_message": "A strange man in a tattered coat\nstands in your way...",
|
||||||
"dark_deal_speaker": "Shady Guy",
|
"dark_deal_speaker": "Shady Guy",
|
||||||
|
|
|
@ -1650,7 +1650,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
||||||
pokemon.untint(250, "Sine.easeIn");
|
pokemon.untint(250, "Sine.easeIn");
|
||||||
this.scene.updateFieldScale();
|
this.scene.updateFieldScale();
|
||||||
pokemon.x += 16;
|
pokemon.x += 16;
|
||||||
pokemon.y -= 16;
|
pokemon.y -= 20;
|
||||||
pokemon.alpha = 0;
|
pokemon.alpha = 0;
|
||||||
|
|
||||||
// Ease pokemon in
|
// Ease pokemon in
|
||||||
|
@ -1680,7 +1680,9 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
||||||
|
|
||||||
pokemon.resetTurnData();
|
pokemon.resetTurnData();
|
||||||
|
|
||||||
if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER || (this.scene.currentBattle.waveIndex % 10) === 1) {
|
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) {
|
||||||
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
||||||
this.queuePostSummon();
|
this.queuePostSummon();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { Tutorial, handleTutorial } from "../tutorial";
|
||||||
import { IvScannerModifier } from "../modifier/modifier";
|
import { IvScannerModifier } from "../modifier/modifier";
|
||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { isNullOrUndefined } from "../utils";
|
import { isNullOrUndefined } from "../utils";
|
||||||
|
import { MysteryEncounterUiSettings } from "#app/ui/mystery-encounter-ui-handler";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will handle (in order):
|
* Will handle (in order):
|
||||||
|
@ -26,8 +27,11 @@ import { isNullOrUndefined } from "../utils";
|
||||||
* - Queuing of the MysteryEncounterOptionSelectedPhase
|
* - Queuing of the MysteryEncounterOptionSelectedPhase
|
||||||
*/
|
*/
|
||||||
export class MysteryEncounterPhase extends Phase {
|
export class MysteryEncounterPhase extends Phase {
|
||||||
constructor(scene: BattleScene) {
|
followupOptionSelectSettings: MysteryEncounterUiSettings;
|
||||||
|
|
||||||
|
constructor(scene: BattleScene, followupOptionSelectSettings?: MysteryEncounterUiSettings) {
|
||||||
super(scene);
|
super(scene);
|
||||||
|
this.followupOptionSelectSettings = followupOptionSelectSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -37,12 +41,18 @@ export class MysteryEncounterPhase extends Phase {
|
||||||
this.scene.clearPhaseQueue();
|
this.scene.clearPhaseQueue();
|
||||||
this.scene.clearPhaseQueueSplice();
|
this.scene.clearPhaseQueueSplice();
|
||||||
|
|
||||||
// Sets flag that ME was encountered
|
// Generates seed offset for RNG consistency, but incremented if the same MysteryEncounter has multiple option select cycles
|
||||||
// Can be used in later MEs to check for requirements to spawn, etc.
|
const offset = this.scene.currentBattle.mysteryEncounter.seedOffset ?? this.scene.currentBattle.waveIndex * 1000;
|
||||||
this.scene.mysteryEncounterData.encounteredEvents.push([this.scene.currentBattle.mysteryEncounter.encounterType, this.scene.currentBattle.mysteryEncounter.encounterTier]);
|
this.scene.currentBattle.mysteryEncounter.seedOffset = offset + 512;
|
||||||
|
|
||||||
|
if (!this.followupOptionSelectSettings) {
|
||||||
|
// 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
|
// Initiates encounter dialogue window and option select
|
||||||
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER);
|
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, this.followupOptionSelectSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOptionSelect(option: MysteryEncounterOption, index: number): boolean {
|
handleOptionSelect(option: MysteryEncounterOption, index: number): boolean {
|
||||||
|
@ -61,24 +71,24 @@ export class MysteryEncounterPhase extends Phase {
|
||||||
return await option.onPreOptionPhase(this.scene)
|
return await option.onPreOptionPhase(this.scene)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (isNullOrUndefined(result) || result) {
|
if (isNullOrUndefined(result) || result) {
|
||||||
this.continueEncounter(index);
|
this.continueEncounter();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, this.scene.currentBattle.waveIndex * 1000);
|
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
|
||||||
} else {
|
} else {
|
||||||
this.continueEncounter(index);
|
this.continueEncounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
continueEncounter(optionIndex: number) {
|
continueEncounter() {
|
||||||
const endDialogueAndContinueEncounter = () => {
|
const endDialogueAndContinueEncounter = () => {
|
||||||
this.scene.pushPhase(new MysteryEncounterOptionSelectedPhase(this.scene));
|
this.scene.pushPhase(new MysteryEncounterOptionSelectedPhase(this.scene));
|
||||||
this.end();
|
this.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
const optionSelectDialogue = this.scene.currentBattle?.mysteryEncounter?.options?.[optionIndex]?.dialogue;
|
const optionSelectDialogue = this.scene.currentBattle?.mysteryEncounter?.selectedOption?.dialogue;
|
||||||
if (optionSelectDialogue?.selected?.length > 0) {
|
if (optionSelectDialogue?.selected?.length > 0) {
|
||||||
// Handle intermediate dialogue (between player selection event and the onOptionSelect logic)
|
// Handle intermediate dialogue (between player selection event and the onOptionSelect logic)
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
this.scene.ui.setMode(Mode.MESSAGE);
|
||||||
|
@ -139,14 +149,14 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
|
||||||
this.onOptionSelect(this.scene).finally(() => {
|
this.onOptionSelect(this.scene).finally(() => {
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
}, this.scene.currentBattle.waveIndex * 1000);
|
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.scene.executeWithSeedOffset(() => {
|
this.scene.executeWithSeedOffset(() => {
|
||||||
this.onOptionSelect(this.scene).finally(() => {
|
this.onOptionSelect(this.scene).finally(() => {
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
}, this.scene.currentBattle.waveIndex * 1000);
|
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +275,7 @@ export class MysteryEncounterBattlePhase extends Phase {
|
||||||
} else {
|
} else {
|
||||||
const trainer = this.scene.currentBattle.trainer;
|
const trainer = this.scene.currentBattle.trainer;
|
||||||
let message: string;
|
let message: string;
|
||||||
scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), scene.currentBattle.waveIndex * 1000);
|
scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.mysteryEncounter.seedOffset);
|
||||||
|
|
||||||
const showDialogueAndSummon = () => {
|
const showDialogueAndSummon = () => {
|
||||||
scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE, true), null, () => {
|
scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE, true), null, () => {
|
||||||
|
@ -390,6 +400,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
|
||||||
this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
|
this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
|
||||||
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, null, { fillRemaining: false, rerollMultiplier: 0 }));
|
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, null, { fillRemaining: false, rerollMultiplier: 0 }));
|
||||||
}
|
}
|
||||||
|
// Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave)
|
||||||
}, this.scene.currentBattle.waveIndex * 1000);
|
}, this.scene.currentBattle.waveIndex * 1000);
|
||||||
|
|
||||||
this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene));
|
this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene));
|
||||||
|
@ -423,7 +434,7 @@ export class PostMysteryEncounterPhase extends Phase {
|
||||||
this.continueEncounter();
|
this.continueEncounter();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, this.scene.currentBattle.waveIndex * 1000);
|
}, this.scene.currentBattle.mysteryEncounter.seedOffset);
|
||||||
} else {
|
} else {
|
||||||
this.continueEncounter();
|
this.continueEncounter();
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ uniform vec2 texFrameUv;
|
||||||
uniform vec2 size;
|
uniform vec2 size;
|
||||||
uniform vec2 texSize;
|
uniform vec2 texSize;
|
||||||
uniform float yOffset;
|
uniform float yOffset;
|
||||||
|
uniform float yShadowOffset;
|
||||||
uniform vec4 tone;
|
uniform vec4 tone;
|
||||||
uniform ivec4 baseVariantColors[32];
|
uniform ivec4 baseVariantColors[32];
|
||||||
uniform vec4 variantColors[32];
|
uniform vec4 variantColors[32];
|
||||||
|
@ -252,7 +253,7 @@ void main() {
|
||||||
float width = size.x - (yOffset / 2.0);
|
float width = size.x - (yOffset / 2.0);
|
||||||
|
|
||||||
float spriteX = ((floor(outPosition.x / fieldScale) - relPosition.x) / width) + 0.5;
|
float spriteX = ((floor(outPosition.x / fieldScale) - relPosition.x) / width) + 0.5;
|
||||||
float spriteY = ((floor(outPosition.y / fieldScale) - relPosition.y) / size.y);
|
float spriteY = ((floor(outPosition.y / fieldScale) - relPosition.y - yShadowOffset) / size.y);
|
||||||
|
|
||||||
if (yCenter == 1) {
|
if (yCenter == 1) {
|
||||||
spriteY += 0.5;
|
spriteY += 0.5;
|
||||||
|
@ -339,6 +340,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
|
||||||
this.set2f("size", 0, 0);
|
this.set2f("size", 0, 0);
|
||||||
this.set2f("texSize", 0, 0);
|
this.set2f("texSize", 0, 0);
|
||||||
this.set1f("yOffset", 0);
|
this.set1f("yOffset", 0);
|
||||||
|
this.set1f("yShadowOffset", 0);
|
||||||
this.set4fv("tone", this._tone);
|
this.set4fv("tone", this._tone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,6 +353,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
|
||||||
const tone = data["tone"] as number[];
|
const tone = data["tone"] as number[];
|
||||||
const teraColor = data["teraColor"] as integer[] ?? [ 0, 0, 0 ];
|
const teraColor = data["teraColor"] as integer[] ?? [ 0, 0, 0 ];
|
||||||
const hasShadow = data["hasShadow"] as boolean;
|
const hasShadow = data["hasShadow"] as boolean;
|
||||||
|
const yShadowOffset = data["yShadowOffset"] as number;
|
||||||
const ignoreFieldPos = data["ignoreFieldPos"] as boolean;
|
const ignoreFieldPos = data["ignoreFieldPos"] as boolean;
|
||||||
const ignoreOverride = data["ignoreOverride"] as boolean;
|
const ignoreOverride = data["ignoreOverride"] as boolean;
|
||||||
|
|
||||||
|
@ -377,6 +380,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
|
||||||
this.set2f("size", sprite.frame.width, sprite.height);
|
this.set2f("size", sprite.frame.width, sprite.height);
|
||||||
this.set2f("texSize", sprite.texture.source[0].width, sprite.texture.source[0].height);
|
this.set2f("texSize", sprite.texture.source[0].width, sprite.texture.source[0].height);
|
||||||
this.set1f("yOffset", sprite.height - sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
|
this.set1f("yOffset", sprite.height - sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
|
||||||
|
this.set1f("yShadowOffset", yShadowOffset ?? 0);
|
||||||
this.set4fv("tone", tone);
|
this.set4fv("tone", tone);
|
||||||
this.bindTexture(this.game.textures.get("tera").source[0].glTexture, 1);
|
this.bindTexture(this.game.textures.get("tera").source[0].glTexture, 1);
|
||||||
|
|
||||||
|
@ -448,6 +452,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
|
||||||
this.set1f("vCutoff", v1);
|
this.set1f("vCutoff", v1);
|
||||||
|
|
||||||
const hasShadow = sprite.pipelineData["hasShadow"] as boolean;
|
const hasShadow = sprite.pipelineData["hasShadow"] as boolean;
|
||||||
|
const yShadowOffset = sprite.pipelineData["yShadowOffset"] as number;
|
||||||
if (hasShadow) {
|
if (hasShadow) {
|
||||||
const isEntityObj = sprite.parentContainer instanceof Pokemon || sprite.parentContainer instanceof Trainer || sprite.parentContainer instanceof MysteryEncounterIntroVisuals;
|
const isEntityObj = sprite.parentContainer instanceof Pokemon || sprite.parentContainer instanceof Trainer || sprite.parentContainer instanceof MysteryEncounterIntroVisuals;
|
||||||
const field = isEntityObj ? sprite.parentContainer.parentContainer : sprite.parentContainer;
|
const field = isEntityObj ? sprite.parentContainer.parentContainer : sprite.parentContainer;
|
||||||
|
@ -455,7 +460,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
|
||||||
const baseY = (isEntityObj
|
const baseY = (isEntityObj
|
||||||
? sprite.parentContainer.y
|
? sprite.parentContainer.y
|
||||||
: sprite.y + sprite.height) * 6 / fieldScaleRatio;
|
: sprite.y + sprite.height) * 6 / fieldScaleRatio;
|
||||||
const bottomPadding = Math.ceil(sprite.height * 0.05) * 6 / fieldScaleRatio;
|
const bottomPadding = Math.ceil(sprite.height * 0.05 + Math.max(yShadowOffset, 0)) * 6 / fieldScaleRatio;
|
||||||
const yDelta = (baseY - y1) / field.scale;
|
const yDelta = (baseY - y1) / field.scale;
|
||||||
y2 = y1 = baseY + bottomPadding;
|
y2 = y1 = baseY + bottomPadding;
|
||||||
const pixelHeight = (v1 - v0) / (sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
|
const pixelHeight = (v1 - v0) / (sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
|
||||||
|
|
|
@ -12,6 +12,16 @@ import { isNullOrUndefined } from "../utils";
|
||||||
import { getPokeballAtlasKey } from "../data/pokeball";
|
import { getPokeballAtlasKey } from "../data/pokeball";
|
||||||
import { getEncounterText } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
export default class MysteryEncounterUiHandler extends UiHandler {
|
export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
private cursorContainer: Phaser.GameObjects.Container;
|
private cursorContainer: Phaser.GameObjects.Container;
|
||||||
private cursorObj: Phaser.GameObjects.Image;
|
private cursorObj: Phaser.GameObjects.Image;
|
||||||
|
@ -27,7 +37,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
private descriptionScrollTween: Phaser.Tweens.Tween;
|
private descriptionScrollTween: Phaser.Tweens.Tween;
|
||||||
private rarityBall: Phaser.GameObjects.Sprite;
|
private rarityBall: Phaser.GameObjects.Sprite;
|
||||||
|
|
||||||
private filteredEncounterOptions: MysteryEncounterOption[] = [];
|
private overrideSettings: MysteryEncounterUiSettings;
|
||||||
|
private encounterOptions: MysteryEncounterOption[] = [];
|
||||||
private optionsMeetsReqs: boolean[];
|
private optionsMeetsReqs: boolean[];
|
||||||
|
|
||||||
protected viewPartyIndex: integer = 0;
|
protected viewPartyIndex: integer = 0;
|
||||||
|
@ -70,16 +81,21 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
show(args: any[]): boolean {
|
show(args: any[]): boolean {
|
||||||
super.show(args);
|
super.show(args);
|
||||||
|
|
||||||
|
this.overrideSettings = args[0] as MysteryEncounterUiSettings ?? {};
|
||||||
|
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;
|
||||||
|
|
||||||
this.cursorContainer.setVisible(true);
|
this.cursorContainer.setVisible(true);
|
||||||
this.descriptionContainer.setVisible(true);
|
this.descriptionContainer.setVisible(showDescriptionContainer);
|
||||||
this.optionsContainer.setVisible(true);
|
this.optionsContainer.setVisible(true);
|
||||||
this.displayEncounterOptions(!(args[0] as boolean || false));
|
this.displayEncounterOptions(slideInDescription);
|
||||||
const cursor = this.getCursor();
|
const cursor = this.getCursor();
|
||||||
if (cursor === (this?.optionsContainer?.length || 0) - 1) {
|
if (cursor === (this?.optionsContainer?.length || 0) - 1) {
|
||||||
// Always resets cursor on view party button if it was last there
|
// Always resets cursor on view party button if it was last there
|
||||||
this.setCursor(cursor);
|
this.setCursor(cursor);
|
||||||
} else {
|
} else {
|
||||||
this.setCursor(0);
|
this.setCursor(startingCursorIndex);
|
||||||
}
|
}
|
||||||
if (this.blockInput) {
|
if (this.blockInput) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -100,12 +116,16 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
|
|
||||||
if (button === Button.CANCEL || button === Button.ACTION) {
|
if (button === Button.CANCEL || button === Button.ACTION) {
|
||||||
if (button === Button.ACTION) {
|
if (button === Button.ACTION) {
|
||||||
const selected = this.filteredEncounterOptions[cursor];
|
const selected = this.encounterOptions[cursor];
|
||||||
if (cursor === this.viewPartyIndex) {
|
if (cursor === this.viewPartyIndex) {
|
||||||
// Handle view party
|
// Handle view party
|
||||||
success = true;
|
success = true;
|
||||||
|
const overrideSettings: MysteryEncounterUiSettings = {
|
||||||
|
...this.overrideSettings,
|
||||||
|
slideInDescription: false
|
||||||
|
};
|
||||||
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
|
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
|
||||||
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, true);
|
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, overrideSettings);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setCursor(this.viewPartyIndex);
|
this.setCursor(this.viewPartyIndex);
|
||||||
this.unblockInput();
|
this.unblockInput();
|
||||||
|
@ -253,7 +273,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
if (this.blockInput) {
|
if (this.blockInput) {
|
||||||
this.blockInput = false;
|
this.blockInput = false;
|
||||||
for (let i = 0; i < this.optionsContainer.length - 1; i++) {
|
for (let i = 0; i < this.optionsContainer.length - 1; i++) {
|
||||||
const optionMode = this.filteredEncounterOptions[i].optionMode;
|
const optionMode = this.encounterOptions[i].optionMode;
|
||||||
if (!this.optionsMeetsReqs[i] && (optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL)) {
|
if (!this.optionsMeetsReqs[i] && (optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -296,7 +316,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
displayEncounterOptions(slideInDescription: boolean = true): void {
|
displayEncounterOptions(slideInDescription: boolean = true): void {
|
||||||
this.getUi().clearText();
|
this.getUi().clearText();
|
||||||
const mysteryEncounter = this.scene.currentBattle.mysteryEncounter;
|
const mysteryEncounter = this.scene.currentBattle.mysteryEncounter;
|
||||||
this.filteredEncounterOptions = mysteryEncounter.options;
|
this.encounterOptions = this.overrideSettings?.overrideOptions ?? mysteryEncounter.options;
|
||||||
this.optionsMeetsReqs = [];
|
this.optionsMeetsReqs = [];
|
||||||
|
|
||||||
const titleText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.title, TextStyle.TOOLTIP_TITLE);
|
const titleText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.title, TextStyle.TOOLTIP_TITLE);
|
||||||
|
@ -307,11 +327,11 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
this.optionsContainer.removeAll();
|
this.optionsContainer.removeAll();
|
||||||
|
|
||||||
// Options Window
|
// Options Window
|
||||||
for (let i = 0; i < this.filteredEncounterOptions.length; i++) {
|
for (let i = 0; i < this.encounterOptions.length; i++) {
|
||||||
const option = this.filteredEncounterOptions[i];
|
const option = this.encounterOptions[i];
|
||||||
|
|
||||||
let optionText;
|
let optionText;
|
||||||
switch (this.filteredEncounterOptions.length) {
|
switch (this.encounterOptions.length) {
|
||||||
case 2:
|
case 2:
|
||||||
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
|
optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 });
|
||||||
break;
|
break;
|
||||||
|
@ -424,7 +444,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
let text: string;
|
let text: string;
|
||||||
const cursorOption = this.filteredEncounterOptions[cursor];
|
const cursorOption = this.encounterOptions[cursor];
|
||||||
const optionDialogue = cursorOption.dialogue;
|
const optionDialogue = cursorOption.dialogue;
|
||||||
if (!this.optionsMeetsReqs[cursor] && (cursorOption.optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || cursorOption.optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL) && optionDialogue.disabledButtonTooltip) {
|
if (!this.optionsMeetsReqs[cursor] && (cursorOption.optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || cursorOption.optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL) && optionDialogue.disabledButtonTooltip) {
|
||||||
text = getEncounterText(this.scene, optionDialogue.disabledButtonTooltip, TextStyle.TOOLTIP_CONTENT);
|
text = getEncounterText(this.scene, optionDialogue.disabledButtonTooltip, TextStyle.TOOLTIP_CONTENT);
|
||||||
|
@ -474,6 +494,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
super.clear();
|
super.clear();
|
||||||
|
this.overrideSettings = null;
|
||||||
this.optionsContainer.setVisible(false);
|
this.optionsContainer.setVisible(false);
|
||||||
this.optionsContainer.removeAll(true);
|
this.optionsContainer.removeAll(true);
|
||||||
this.descriptionContainer.setVisible(false);
|
this.descriptionContainer.setVisible(false);
|
||||||
|
|
Loading…
Reference in New Issue