add offer you can't refuse

This commit is contained in:
ImperialSympathizer 2024-07-23 20:11:45 -04:00
parent b9a45e0a03
commit 065ac0e779
14 changed files with 246 additions and 23 deletions

View File

@ -4,8 +4,7 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "../../../battle-scene"; import BattleScene from "../../../battle-scene";
import { AddPokeballModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { PokeballType } from "../../pokeball";
import { getPokemonSpecies } from "../../pokemon-species"; import { getPokemonSpecies } from "../../pokemon-species";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
@ -138,7 +137,7 @@ export const DarkDealEncounter: IMysteryEncounter =
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Give the player 5 Rogue Balls // Give the player 5 Rogue Balls
scene.unshiftPhase(new ModifierRewardPhase(scene, () => new AddPokeballModifierType("rb", PokeballType.ROGUE_BALL, 5))); scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL));
// Start encounter with random legendary (7-10 starter strength) that has level additive // Start encounter with random legendary (7-10 starter strength) that has level additive
const bossTypes = scene.currentBattle.mysteryEncounter.misc as Type[]; const bossTypes = scene.currentBattle.mysteryEncounter.misc as Type[];

View File

@ -3,7 +3,7 @@ import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/my
import { import {
EnemyPartyConfig, EnemyPartyConfig,
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, leaveEncounterWithoutBattle, setEncounterExp,
setEncounterRewards setEncounterRewards
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
@ -38,9 +38,7 @@ const namespace = "mysteryEncounter:fightOrFlight";
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const FightOrFlightEncounter: IMysteryEncounter = export const FightOrFlightEncounter: IMysteryEncounter =
MysteryEncounterBuilder.withEncounterType( MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT)
MysteryEncounterType.FIGHT_OR_FLIGHT
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withCatchAllowed(true) .withCatchAllowed(true)
@ -151,6 +149,7 @@ export const FightOrFlightEncounter: IMysteryEncounter =
if (primaryPokemon) { if (primaryPokemon) {
// Use primaryPokemon to execute the thievery // Use primaryPokemon to execute the thievery
await showEncounterText(scene, `${namespace}:option:2:special_result`); await showEncounterText(scene, `${namespace}:option:2:special_result`);
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs[0].species.baseExp, true);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
return; return;
} }

View File

@ -29,9 +29,7 @@ const namespace = "mysteryEncounter:mysteriousChallengers";
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const MysteriousChallengersEncounter: IMysteryEncounter = export const MysteriousChallengersEncounter: IMysteryEncounter =
MysteryEncounterBuilder.withEncounterType( MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
MysteryEncounterType.MYSTERIOUS_CHALLENGERS
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()

View File

@ -0,0 +1,142 @@
import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { ModifierRewardPhase } from "#app/phases";
import { EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:offerYouCantRefuse";
/**
* An Offer You Can't Refuse encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/72 | GitHub Issue #72}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const OfferYouCantRefuseEncounter: IMysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.OFFER_YOU_CANT_REFUSE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
.withIntroSpriteConfigs([
{
spriteKey: Species.LIEPARD.toString(),
fileRoot: "pokemon",
hasShadow: true,
repeat: true,
x: 0,
y: -4,
yShadow: -4
},
{
spriteKey: "rich_kid_m",
fileRoot: "trainer",
hasShadow: true,
x: 2,
y: 5,
yShadow: 5
},
])
.withIntroDialogue([
{
text: `${namespace}:intro`,
},
{
text: `${namespace}:intro_dialogue`,
speaker: `${namespace}:speaker`,
},
])
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
const price = scene.getWaveMoneyAmount(10);
scene.currentBattle.mysteryEncounter.setDialogueToken("strongestPokemon", pokemon.name);
scene.currentBattle.mysteryEncounter.setDialogueToken("price", price.toString());
// Store pokemon and price
scene.currentBattle.mysteryEncounter.misc = {
pokemon: pokemon,
price: price
};
return true;
})
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,
selected: [
{
text: `${namespace}:option:1:selected`,
speaker: `${namespace}:speaker`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);
scene.removePokemonFromPlayerParty(encounter.misc.pokemon);
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
// Give the player a Shiny charm
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM));
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new MoveRequirement(EXTORTION_MOVES))
.withDialogue({
buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`,
disabledButtonTooltip: `${namespace}:option:2:tooltip_disabled`,
selected: [
{
text: `${namespace}:option:2:selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Extort the rich kid for money
const encounter = scene.currentBattle.mysteryEncounter;
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);
setEncounterExp(scene, encounter.options[1].primaryPokemon.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}:option:3:label`,
buttonTooltip: `${namespace}:option:3:tooltip`,
selected: [
{
speaker: `${namespace}:speaker`,
text: `${namespace}:option:2:selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();

View File

@ -21,9 +21,7 @@ const namespace = "mysteryEncounter:shadyVitaminDealer";
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ShadyVitaminDealerEncounter: IMysteryEncounter = export const ShadyVitaminDealerEncounter: IMysteryEncounter =
MysteryEncounterBuilder.withEncounterType( MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
MysteryEncounterType.SHADY_VITAMIN_DEALER
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)
.withPrimaryPokemonStatusEffectRequirement([StatusEffect.NONE]) // Pokemon must not have status .withPrimaryPokemonStatusEffectRequirement([StatusEffect.NONE]) // Pokemon must not have status
@ -230,6 +228,12 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
{ {
buttonLabel: `${namespace}:option:3:label`, buttonLabel: `${namespace}:option:3:label`,
buttonTooltip: `${namespace}:option:3:tooltip`, buttonTooltip: `${namespace}:option:3:tooltip`,
selected: [
{
text: `${namespace}:option:3:selected`,
speaker: `${namespace}:speaker`
}
]
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp

View File

@ -24,9 +24,7 @@ const namespace = "mysteryEncounter:slumberingSnorlax";
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const SlumberingSnorlaxEncounter: IMysteryEncounter = export const SlumberingSnorlaxEncounter: IMysteryEncounter =
MysteryEncounterBuilder.withEncounterType( MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX)
MysteryEncounterType.SLUMBERING_SNORLAX
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withCatchAllowed(true) .withCatchAllowed(true)

View File

@ -15,6 +15,7 @@ import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/saf
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter"; import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/pokemon-salesman-encounter"; import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/pokemon-salesman-encounter";
import { OfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/offer-you-cant-refuse-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;
@ -134,7 +135,8 @@ const nonExtremeBiomeEncounters: MysteryEncounterType[] = [
const humanTransitableBiomeEncounters: MysteryEncounterType[] = [ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.MYSTERIOUS_CHALLENGERS, MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
MysteryEncounterType.SHADY_VITAMIN_DEALER, MysteryEncounterType.SHADY_VITAMIN_DEALER,
MysteryEncounterType.POKEMON_SALESMAN MysteryEncounterType.POKEMON_SALESMAN,
MysteryEncounterType.OFFER_YOU_CANT_REFUSE
]; ];
const civilizationBiomeEncounters: MysteryEncounterType[] = [ const civilizationBiomeEncounters: MysteryEncounterType[] = [
@ -227,6 +229,7 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.FIERY_FALLOUT] = FieryFalloutEncounter; allMysteryEncounters[MysteryEncounterType.FIERY_FALLOUT] = FieryFalloutEncounter;
allMysteryEncounters[MysteryEncounterType.THE_STRONG_STUFF] = TheStrongStuffEncounter; allMysteryEncounters[MysteryEncounterType.THE_STRONG_STUFF] = TheStrongStuffEncounter;
allMysteryEncounters[MysteryEncounterType.POKEMON_SALESMAN] = PokemonSalesmanEncounter; allMysteryEncounters[MysteryEncounterType.POKEMON_SALESMAN] = PokemonSalesmanEncounter;
allMysteryEncounters[MysteryEncounterType.OFFER_YOU_CANT_REFUSE] = OfferYouCantRefuseEncounter;
// Add extreme encounters to biome map // Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => { extremeBiomeEncounters.forEach(encounter => {

View File

@ -41,6 +41,29 @@ export const PROTECTING_MOVES = [
Moves.DETECT Moves.DETECT
]; ];
export const EXTORTION_MOVES = [
Moves.BIND,
Moves.CLAMP,
Moves.INFESTATION,
Moves.SAND_TOMB,
Moves.SNAP_TRAP,
Moves.THUNDER_CAGE,
Moves.WRAP,
Moves.SPIRIT_SHACKLE,
Moves.MEAN_LOOK,
Moves.JAW_LOCK,
Moves.BLOCK,
Moves.SPIDER_WEB,
Moves.ANCHOR_SHOT,
Moves.OCTOLOCK,
Moves.PURSUIT,
Moves.CONSTRICT,
Moves.BEAT_UP,
Moves.COIL,
Moves.WRING_OUT,
Moves.STRING_SHOT
];
export const EXTORTION_ABILITIES = [ export const EXTORTION_ABILITIES = [
Abilities.INTIMIDATE, Abilities.INTIMIDATE,
Abilities.ARENA_TRAP, Abilities.ARENA_TRAP,

View File

@ -95,6 +95,27 @@ export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boole
return pokemon; return pokemon;
} }
/**
* Ties are broken by whatever mon is closer to the front of the party
* @param scene
* @param unfainted - default false. If true, only picks from unfainted mons.
* @returns
*/
export function getHighestStatTotalPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon;
party.every(p => {
if (unfainted && p.isFainted()) {
return true;
}
pokemon = pokemon ? pokemon?.stats.reduce((a, b) => a + b) < p?.stats.reduce((a, b) => a + b) ? p : pokemon : p;
return true;
});
return pokemon;
}
/** /**
* *
* NOTE: This returns ANY random species, including those locked behind eggs, etc. * NOTE: This returns ANY random species, including those locked behind eggs, etc.

View File

@ -12,5 +12,6 @@ export enum MysteryEncounterType {
LOST_AT_SEA, //might be generalized later on LOST_AT_SEA, //might be generalized later on
FIERY_FALLOUT, FIERY_FALLOUT,
THE_STRONG_STUFF, THE_STRONG_STUFF,
POKEMON_SALESMAN POKEMON_SALESMAN,
OFFER_YOU_CANT_REFUSE
} }

View File

@ -12,6 +12,7 @@ import { slumberingSnorlaxDialogue } from "#app/locales/en/mystery-encounters/sl
import { trainingSessionDialogue } from "#app/locales/en/mystery-encounters/training-session-dialogue"; import { trainingSessionDialogue } from "#app/locales/en/mystery-encounters/training-session-dialogue";
import { theStrongStuffDialogue } from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue"; import { theStrongStuffDialogue } from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue";
import { pokemonSalesmanDialogue } from "#app/locales/en/mystery-encounters/pokemon-salesman-dialogue"; import { pokemonSalesmanDialogue } from "#app/locales/en/mystery-encounters/pokemon-salesman-dialogue";
import { offerYouCantRefuseDialogue } from "#app/locales/en/mystery-encounters/offer-you-cant-refuse-dialogue";
/** /**
* Patterns that can be used: * Patterns that can be used:
@ -48,5 +49,6 @@ export const mysteryEncounter = {
lostAtSea: lostAtSeaDialogue, lostAtSea: lostAtSeaDialogue,
fieryFallout: fieryFalloutDialogue, fieryFallout: fieryFalloutDialogue,
theStrongStuff: theStrongStuffDialogue, theStrongStuff: theStrongStuffDialogue,
pokemonSalesman: pokemonSalesmanDialogue pokemonSalesman: pokemonSalesmanDialogue,
offerYouCantRefuse: offerYouCantRefuseDialogue
} as const; } as const;

View File

@ -0,0 +1,34 @@
export const offerYouCantRefuseDialogue = {
intro: "You're stopped by a rich looking boy.",
speaker: "Rich Kid",
intro_dialogue: `Good day to you.
$I can't help but notice that your\n{{strongestPokemon}} looks positively divine!
$I've always wanted to have a pet like that!
$I'd pay you handsomely, and\nI'll even give you this old bauble!`,
title: "An Offer You Can't Refuse",
description: "You're being offered a @[TOOLTIP_TITLE]{Shiny Charm} and {{price, money}} for your {{strongestPokemon}}!\n\nIt's is an extremely good deal, but can you really bear to part with such a strong team member?",
query: "What will you do?",
option: {
1: {
label: "Accept the Deal",
tooltip: "(-) Lose {{strongestPokemon}}\n(+) Gain a @[TOOLTIP_TITLE]{Shiny Charm}\n(+) Gain {{price, money}}",
selected: `Wonderful!@d{32} Come along, {{strongestPokemon}}!
$It's time to show you off to everyone at the yacht club!
$They'll be so jealous!`,
},
2: {
label: "Extort the Kid",
tooltip: "(+) Gain {{price, money}}",
tooltip_disabled: "Your Pokémon need to have certain moves or abilities to choose this",
selected: `My word, we're being robbed, Liepard!
$How wonderful!
$Now I'll have an amazing\nstory for the yacht club!`,
},
3: {
label: "Leave",
tooltip: "(-) No Rewards",
selected: `What a rotten day...
$Ah, well. Let's return to the yacht club then, Liepard.`,
}
},
};

View File

@ -24,8 +24,7 @@ export const shadyVitaminDealerDialogue = {
3: { 3: {
label: "Leave", label: "Leave",
tooltip: "(-) No Rewards", tooltip: "(-) No Rewards",
selected: `You float about in the boat, steering without direction until you finally spot a landmark you remember. selected: "Heh, wouldn't have figured you for a coward.",
$You and your Pokémon are fatigued from the whole ordeal.`,
}, },
selected: `The man hands you two bottles and quickly disappears. selected: `The man hands you two bottles and quickly disappears.
\${{selectedPokemon}} gained {{boost1}} and {{boost2}} boosts!` \${{selectedPokemon}} gained {{boost1}} and {{boost2}} boosts!`

View File

@ -117,9 +117,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0;
*/ */
// 1 to 256, set to null to ignore // 1 to 256, set to null to ignore
export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null; export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256;
export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null; export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null; export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.OFFER_YOU_CANT_REFUSE;
/** /**
* MODIFIER / ITEM OVERRIDES * MODIFIER / ITEM OVERRIDES