diff --git a/.github/ISSUE_TEMPLATE/mystery_event.yml b/.github/ISSUE_TEMPLATE/mystery_event.yml index be7b98eda78..3791026e778 100644 --- a/.github/ISSUE_TEMPLATE/mystery_event.yml +++ b/.github/ISSUE_TEMPLATE/mystery_event.yml @@ -9,7 +9,7 @@ body: attributes: label: Event Name description: Name of the event - placeholder: e.g Fight or Flight + placeholder: e.g. "Fight or Flight" validations: required: true - type: markdown # SEPARATOR @@ -20,15 +20,24 @@ body: id: rarity attributes: label: Rarity Tier + description: Check out the [Event Proposal Guide](https://github.com/AsdarDevelops/PokeRogue-Events/blob/mystery-battle-events/MEs_Proposal_Guide.md) if you have not yet! multiple: false options: - Common - Great - Ultra - Rogue + - Part of a "Quest" - Other or unsure (please specify) + + - type: input + id: rarity-other + attributes: + label: Rarity Tier - Other. Please Specify + description: If you chose `Other` on the `Rarity Tier` please specify it here + placeholder: e.g. "I'm unsure of whether this should be Common or Great" validations: - required: true + required: false - type: markdown # SEPARATOR attributes: value: | @@ -37,8 +46,8 @@ body: id: waves attributes: label: Waves - description: Classic/Challenge is 1 -200. Currently only 11-179 is supported. - placeholder: 1-200 + description: Classic/Challenge ranges 1-200. Currently only 11-179 is supported. + placeholder: 11-179 validations: required: true - type: markdown # SEPARATOR @@ -49,8 +58,8 @@ body: id: description attributes: label: Description - description: Describe the event you are proposing - placeholder: What is it? + description: Describe the event you are proposing. Explain its theme and how it's different from others. If the Event has any requirements to even trigger, detail them here too. + placeholder: e.g. "Fight or Flight is a common event where the player can fight a boss PKMN of the biome. The PKMN is stronger than usual, but also holds an item that's better than usual." validations: required: true - type: markdown # SEPARATOR @@ -61,11 +70,13 @@ body: id: biomes attributes: label: Biomes - description: Select all biomes where the event can occur + description: Select all biomes where the event can occur. "ANY, NON-EXTREME, CIVILIZATION and HUMAN are groups of biomes. Check the [Biomes part of the guide](https://github.com/AsdarDevelops/PokeRogue-Events/blob/mystery-battle-events/MEs_Proposal_Guide.md#biomes)." multiple: true options: - - ANY (no need to select all) - - NON-EXTREME (almost all except Space, Seabed, etc...) + - ANY + - NON-EXTREME + - HUMAN + - CIVILIZATION - TOWN - PLAINS - GRASS @@ -104,6 +115,15 @@ body: - OTHER (please specify) validations: required: true + + - type: input + id: biome-other + attributes: + label: Biome - Other. Please Specify + description: If you chose `Other` on the `Biome` please specify it here + placeholder: e.g. "I would like to only trigger at Graveyard at night!" + validations: + required: false - type: markdown # SEPARATOR attributes: value: | @@ -134,13 +154,25 @@ body: attributes: label: Explanation/Notes on Design description: Explain why you think this design is right and what this Event brings to the table - placeholder: Explain why you think this design is right and what this Event brings to the table + placeholder: e.g. "We need more simple Events that mix slightly higher stakes with slightly better rewards" validations: required: true - type: markdown # SEPARATOR attributes: value: | --- + - type: textarea + id: artist-notes + attributes: + label: Notes to Artists + description: Does your Event need custom spriting? If so, please detail them here (reference screenshots are helpful) + placeholder: Ie. "We currently don't have a Cynthia sprite while dressed in a Garchomp costume. RAWR! This is highly needed for my Event!" + validations: + required: false + - type: markdown # SEPARATOR + attributes: + value: | + --- - type: textarea id: dev-notes attributes: diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 3e9176430a5..c0a3f0b090a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1071,13 +1071,14 @@ export default class BattleScene extends SceneBase { this.field.add(newTrainer); } - // Check for mystery encounter - // Can only occur in place of a standard wild battle, waves 10-180 + // TODO: remove this once spawn rates are finalized // let testStartingWeight = 0; - // while (testStartingWeight < 20) { + // while (testStartingWeight < 3) { // calculateMEAggregateStats(this, testStartingWeight); // testStartingWeight += 1; // } + // Check for mystery encounter + // Can only occur in place of a standard wild battle, waves 10-180 if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < 180 && newWaveIndex > 10) { const roll = Utils.randSeedInt(256); @@ -2651,7 +2652,7 @@ export default class BattleScene extends SceneBase { } // Common / Uncommon / Rare / Super Rare - const tierWeights = [61, 40, 21, 6]; + const tierWeights = [64, 40, 21, 3]; // Adjust tier weights by previously encountered events to lower odds of only common/uncommons in run this.mysteryEncounterData.encounteredEvents.forEach(val => { @@ -2681,10 +2682,23 @@ export default class BattleScene extends SceneBase { // If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available while (availableEncounters.length === 0 && tier >= 0) { availableEncounters = biomeMysteryEncounters - .filter((encounterType) => - allMysteryEncounters[encounterType]?.meetsRequirements(this) && - allMysteryEncounters[encounterType].encounterTier === tier && - (isNullOrUndefined(previousEncounter) || encounterType !== previousEncounter)) + .filter((encounterType) => { + if (allMysteryEncounters[encounterType].encounterTier !== tier) { // Encounter is in tier + return false; + } + if (!allMysteryEncounters[encounterType]?.meetsRequirements(this)) { // Meets encounter requirements + return false; + } + if (!isNullOrUndefined(previousEncounter) && encounterType === previousEncounter) { // Previous encounter was not this one + return false; + } + if (this.mysteryEncounterData.encounteredEvents?.length > 0 && // Encounter has not exceeded max allowed encounters + allMysteryEncounters[encounterType].maxAllowedEncounters > 0 + && this.mysteryEncounterData.encounteredEvents.filter(e => e[0] === encounterType).length >= allMysteryEncounters[encounterType].maxAllowedEncounters) { + return false; + } + return true; + }) .map((m) => (allMysteryEncounters[m])); tier--; } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 1de2522c522..983be1fdb2a 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -50,6 +50,7 @@ export default interface IMysteryEncounter { hideBattleIntroMessage?: boolean; hideIntroVisuals?: boolean; catchAllowed?: boolean; + maxAllowedEncounters?: number; doEncounterExp?: (scene: BattleScene) => boolean; doEncounterRewards?: (scene: BattleScene) => boolean; onInit?: (scene: BattleScene) => boolean; @@ -144,6 +145,8 @@ export default class IMysteryEncounter implements IMysteryEncounter { } this.encounterTier = this.encounterTier ? this.encounterTier : MysteryEncounterTier.COMMON; this.dialogue = this.dialogue ?? {}; + // Default max is 1 for ROGUE encounters, 3 for others + this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? 1 : 3; this.encounterVariant = MysteryEncounterVariant.DEFAULT; this.requirements = this.requirements ? this.requirements : []; this.hideBattleIntroMessage = !isNullOrUndefined(this.hideBattleIntroMessage) ? this.hideBattleIntroMessage : false; @@ -440,9 +443,9 @@ export class MysteryEncounterBuilder implements Partial { * If not specified, defaults to COMMON * Tiers are: * COMMON 32/64 odds - * UNCOMMON 16/64 odds - * RARE 10/64 odds - * SUPER_RARE 6/64 odds + * GREAT 16/64 odds + * ULTRA 10/64 odds + * ROGUE 6/64 odds * ULTRA_RARE Not currently used * @param encounterTier * @returns @@ -451,6 +454,15 @@ export class MysteryEncounterBuilder implements Partial { return Object.assign(this, { encounterTier: encounterTier }); } + /** + * Sets the maximum number of times that an encounter can spawn in a given Classic run + * @param maxAllowedEncounters + * @returns + */ + withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required> { + return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters }); + } + /** * Specifies a requirement for an encounter * For example, passing requirement as "new WaveCountRequirement([2, 180])" would create a requirement that the encounter can only be spawned between waves 2 and 180 diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 6fa15ec373d..88004fae989 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -634,7 +634,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n // Calculate encounter rarity // Common / Uncommon / Rare / Super Rare (base is out of 128) - const tierWeights = [61, 40, 21, 6]; + const tierWeights = [64, 40, 21, 3]; // Adjust tier weights by currently encountered events (pity system that lowers odds of multiple common/uncommons) tierWeights[0] = tierWeights[0] - 6 * numEncounters[0];