Skip to content

Commit

Permalink
Merge branch 'pagefaultgames:beta' into beta
Browse files Browse the repository at this point in the history
  • Loading branch information
pom-eranian authored Sep 24, 2024
2 parents bb414ea + 254ffcf commit 19e039a
Show file tree
Hide file tree
Showing 81 changed files with 574 additions and 341 deletions.
95 changes: 59 additions & 36 deletions src/battle-scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Phaser from "phaser";
import UI from "./ui/ui";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon";
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "./data/pokemon-species";
import { Constructor, isNullOrUndefined } from "#app/utils";
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "./utils";
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball";
Expand Down Expand Up @@ -1201,32 +1201,12 @@ export default class BattleScene extends SceneBase {

// Check for mystery encounter
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < highestMysteryEncounterWave && newWaveIndex > lowestMysteryEncounterWave) {
const roll = Utils.randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);

// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;

// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well)
// Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave) * (newWaveIndex - lowestMysteryEncounterWave);
const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length;
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER;

const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;

// If the most recent ME was 3 or fewer waves ago, can never spawn a ME
const canSpawn = encounteredEvents.length === 0 || (newWaveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex) > 3 || !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE);

if (canSpawn && roll < successRate) {
newBattleType = BattleType.MYSTERY_ENCOUNTER;
// Reset base spawn weight
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
} else {
this.mysteryEncounterSaveData.encounterSpawnChance = sessionEncounterRate + WEIGHT_INCREMENT_ON_SPAWN_MISS;
}
if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER || !isNullOrUndefined(mysteryEncounterType)) {
newBattleType = BattleType.MYSTERY_ENCOUNTER;
// Reset base spawn weight
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
} else if (newBattleType === BattleType.WILD) {
this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
}
}

Expand Down Expand Up @@ -1267,9 +1247,8 @@ export default class BattleScene extends SceneBase {
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
this.currentBattle.double = false;
this.executeWithSeedOffset(() => {
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounterType);
}, this.currentBattle.waveIndex << 4);
// Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome
this.currentBattle.mysteryEncounterType = mysteryEncounterType;
}

//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
Expand Down Expand Up @@ -2657,8 +2636,7 @@ export default class BattleScene extends SceneBase {
modifier = mt.modifier as PokemonHeldItemModifier;
modifier.pokemonId = enemyPokemon.id;
}
const stackCount = mt.stackCount ?? 1;
modifier.stackCount = stackCount;
modifier.stackCount = mt.stackCount ?? 1;
modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable;
this.addEnemyModifier(modifier, true);
});
Expand Down Expand Up @@ -3097,6 +3075,51 @@ export default class BattleScene extends SceneBase {
}
}

/**
* Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}.
* Currently, the only modes that MEs are allowed in are Classic and Challenge.
* Additionally, MEs cannot spawn outside of waves 10-180 in those modes
*
* @param newBattleType
* @param waveIndex
* @param sessionDataEncounterType
*/
private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean {
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) {
// If ME type is already defined in session data, no need to roll RNG check
if (!isNullOrUndefined(sessionDataEncounterType)) {
return true;
}

// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;

// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well)
// Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave) * (waveIndex - lowestMysteryEncounterWave);
const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length;
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER;

const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;

// If the most recent ME was 3 or fewer waves ago, can never spawn a ME
const canSpawn = encounteredEvents.length === 0 || (waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex) > 3 || !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE);

if (canSpawn) {
let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT;
// Always rolls the check on the same offset to ensure no RNG changes from reloading session
this.executeWithSeedOffset(() => {
roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);
}, waveIndex * 3 * 1000);
return roll < successRate;
}
}

return false;
}

/**
* Loads or generates a mystery encounter
* @param encounterType used to load session encounter when restarting game, etc.
Expand All @@ -3105,10 +3128,10 @@ export default class BattleScene extends SceneBase {
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
// Loading override or session encounter
let encounter: MysteryEncounter | null;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE!)) {
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE!];
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
} else {
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType!] : null;
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null;
}

// Check for queued encounters first
Expand Down Expand Up @@ -3151,7 +3174,7 @@ export default class BattleScene extends SceneBase {
let tier: MysteryEncounterTier | null = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > greatThreshold ? MysteryEncounterTier.GREAT : tierValue > ultraThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE;

if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) {
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE!;
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE;
}

let availableEncounters: MysteryEncounter[] = [];
Expand Down
2 changes: 2 additions & 0 deletions src/battle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { CustomModifierSettings } from "#app/modifier/modifier-type";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";

export enum ClassicFixedBossWaves {
// TODO: other fixed wave battles should be added here
Expand Down Expand Up @@ -88,6 +89,7 @@ export default class Battle {
public playerFaintsHistory: FaintLogEntry[] = [];
public enemyFaintsHistory: FaintLogEntry[] = [];

public mysteryEncounterType?: MysteryEncounterType;
/** If the current battle is a Mystery Encounter, this will always be defined */
public mysteryEncounter?: MysteryEncounter;

Expand Down
2 changes: 1 addition & 1 deletion src/data/battle-anims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
scene.field.add(moveAnim.bgSprite);
const fieldPokemon = scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon();
if (!isNullOrUndefined(priority)) {
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority!);
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority);
} else if (fieldPokemon?.isOnField()) {
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EggTier } from "#enums/egg-type";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";

/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:aTrainersTest";
Expand All @@ -27,7 +28,7 @@ const namespace = "mysteryEncounter:aTrainersTest";
export const ATrainersTestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(100, 180)
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PersistentModifierRequirement } from "#app/data/mystery-encounters/myst
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryModifier } from "#app/modifier/modifier";
import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { BattlerTagType } from "#enums/battler-tag-type";
Expand Down Expand Up @@ -159,12 +159,6 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
])
.withHideWildIntroMessage(true)
.withAutoHideIntroVisuals(false)
.withOnVisualsStart((scene: BattleScene) => {
doGreedentSpriteSteal(scene);
doBerrySpritePile(scene);

return true;
})
.withIntroDialogue([
{
text: `${namespace}.intro`,
Expand Down Expand Up @@ -202,10 +196,15 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
bossModifierConfigs.push({ modifier: modifierType });
}

scene.removeModifier(berryMod);
});

// Do NOT remove the real berries yet or else it will be persisted in the session data

// SpDef buff below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
[Stat.SPDEF] :
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];

// Calculate boss mon
const config: EnemyPartyConfig = {
levelAdditiveModifier: 1,
Expand All @@ -214,12 +213,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
species: getPokemonSpecies(Species.GREEDENT),
isBoss: true,
bossSegments: 3,
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF],
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH],
modifierConfigs: bossModifierConfigs,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1));
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}
}
],
Expand All @@ -230,6 +229,21 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =

return true;
})
.withOnVisualsStart((scene: BattleScene) => {
doGreedentSpriteSteal(scene);
doBerrySpritePile(scene);

// Remove the berries from the party
// Session has been safely saved at this point, so data won't be lost
const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
berryItems.forEach(berryMod => {
scene.removeModifier(berryMod);
});

scene.updateModifiers(true);

return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
Expand All @@ -251,7 +265,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
const givePartyPokemonReviverSeeds = () => {
const party = scene.getParty();
party.forEach(p => {
if (revSeed) {
const heldItems = p.getHeldItems();
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
const seedModifier = revSeed.newModifier(p);
if (seedModifier) {
encounter.setDialogueToken("foodReward", seedModifier.type.name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
Expand All @@ -17,6 +17,14 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:offerYouCantRefuse";

/**
* Money offered starts at base value of Relic Gold, increasing linearly up to 3x Relic Gold based on the starter tier of the Pokemon being purchased
* Starter value 1-3 -> Relic Gold
* Starter value 10 -> 3 * Relic Gold
*/
const MONEY_MINIMUM_MULTIPLIER = 10;
const MONEY_MAXIMUM_MULTIPLIER = 30;

/**
* An Offer You Can't Refuse encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
Expand Down Expand Up @@ -61,7 +69,11 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true);
const price = scene.getWaveMoneyAmount(10);

const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(true);
const starterValue: number = speciesStarters[baseSpecies] ?? 1;
const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER);
const price = scene.getWaveMoneyAmount(multiplier);

encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString());
Expand Down
Loading

0 comments on commit 19e039a

Please sign in to comment.