mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-30 10:46:05 +00:00
Merge branch 'mystery-battle-events' into event/getting-lost-at-the-sea
This commit is contained in:
commit
f2031c78e9
17
.github/ISSUE_TEMPLATE/art_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/art_request.md
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Art Request
|
||||||
|
about: Create an art request for a specific Encounter
|
||||||
|
title: "[Art]"
|
||||||
|
labels: ["art request"]
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Link to Mystery Encounter Issue/PR**
|
||||||
|
<!-- If applicable, link to the specific encounter this art is for. -->
|
||||||
|
|
||||||
|
**Describe the Art**
|
||||||
|
<!-- A clear and concise description of what the art is. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the request here. -->
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -2,7 +2,7 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: "[BUG]"
|
title: "[BUG]"
|
||||||
labels: Bug
|
labels: ["bug"]
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -2,7 +2,7 @@
|
|||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: "[Feature]"
|
title: "[Feature]"
|
||||||
labels: enhancement
|
labels: ["enhancement/util"]
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
2
.github/ISSUE_TEMPLATE/mystery_event.yml
vendored
2
.github/ISSUE_TEMPLATE/mystery_event.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: Mystery Event
|
name: Mystery Event
|
||||||
description: Propose a new mystery event
|
description: Propose a new mystery event
|
||||||
title: "[Event] "
|
title: "[Event] "
|
||||||
labels: ["event proposal"]
|
labels: ["event proposal", "balance review"]
|
||||||
projects: ["AsdarDevelops/1"]
|
projects: ["AsdarDevelops/1"]
|
||||||
body:
|
body:
|
||||||
- type: input
|
- type: input
|
||||||
|
124
MEs_Proposal_Guide.md
Normal file
124
MEs_Proposal_Guide.md
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# Event / Mystery Encounter Proposal Guidelines + The poing of this document
|
||||||
|
|
||||||
|
If you want to add an Event to our branch, read the guidelines below and then create an "New Issue" here:
|
||||||
|
https://github.com/AsdarDevelops/PokeRogue-Events/issues
|
||||||
|
|
||||||
|
The idea for this document is __not__ to stiffle creativity, but rather to guide a new Event creator, facilitate the reviewer's job, as well as keep Event philosophy consistent.
|
||||||
|
|
||||||
|
# What should I know before proposing an Event
|
||||||
|
|
||||||
|
Events are categorised in tiers/rarity. These are the rarities at the time of writing:
|
||||||
|
|
||||||
|
- ## ⚪ Common Events
|
||||||
|
- ## 🔵 Great Events
|
||||||
|
- ## 🟡 Ultra Events
|
||||||
|
- ## 🔴 Rogue Events
|
||||||
|
|
||||||
|
## Do tiers mean anything?
|
||||||
|
|
||||||
|
Yes, and now. They overall represent the rate/chance an Event has to spawn, but they are not the only thing that is considered. More importantly, they help discern, at first glance, a few things:
|
||||||
|
|
||||||
|
### Meta progression (Most important):
|
||||||
|
How much does the Event help you to __permanently unlock__:
|
||||||
|
- Rare starters (and candies to some extent)
|
||||||
|
- Better IVs
|
||||||
|
- Natures
|
||||||
|
- Hidden Abilities
|
||||||
|
- Mythical/legendary starters
|
||||||
|
- Shinies
|
||||||
|
|
||||||
|
If you want to add an Event that gives you any of this, it's going to automatically go to 🟡 Ultra or 🔴 Rogue. And the further down the list, the more likely it will have to be 🔴 Rogue instead of 🟡 Ultra.
|
||||||
|
|
||||||
|
For example, __Dark Deal__ is a 🔴 Rogue Event, because it gives you a chance to capture a very powerful PKMN and permanently add it to your Box. Whereas __Mysterious Challenger__, even it might have you fight a very, very strong trainer on the "Brutal Fight", will just give you an item that will stay with you within the boundaries of that run, hence why it could be 🔵 Great or ⚪ Common.
|
||||||
|
|
||||||
|
Note: Bear in mind not even a 🔴 Rogue Event guarantees a shiny at the time of writing.
|
||||||
|
|
||||||
|
### Variance and swinginess (Important)
|
||||||
|
Imagine an Event at wave 12, a mysterious man offers to throw a coin in front of you. This is what happens:
|
||||||
|
|
||||||
|
- Heads: you win the run, get all the pokécandies as if you had won, and the final boss bows to your power.
|
||||||
|
- Tails: you die horribly and lose the run.
|
||||||
|
|
||||||
|
How often do you think this Event should happen? "Never" is the right answer, BUT the lesson here is that this Event would lean towards 🔴 Rogue rather than ⚪ Common, because it's repercussions are so, so much more meaningful than simply encountering your usual wild Pokémon fight. If you want to propose a swingy Event, that's fine, but know that the harder the swings, the higher the rarity.
|
||||||
|
|
||||||
|
- Example: ⚪ "Fight or Flight" will have you fight a boss PKMN from the biome, for a somewhat good reward. You will rarely win or lose your run here.
|
||||||
|
- Example: 🔴 "Dark Deal" will remove a Pokémon from your party. This can be your best Pokémon at the time. You **__can catch a legendary__** which is good for the run, and your box, but you also very well **__can lose the run and not catch anything__**
|
||||||
|
|
||||||
|
### Requirements (Somewhat Important)
|
||||||
|
If an Event is Space Biome only and it needs you to have a Steel type with the moves Extreme Speed and Pluck and the ability Levitate because you need your Pokémon to fix a rocket on the fly, it might very well be ⚪ and still almost no one should find it. Because of this, **in some exceptional cases** you might see an Event with a catchable Mythical Pokémon in a 🟡 Ultra Event. This is because the Biome requirement itself and the other factors are making it much harder to appear.
|
||||||
|
|
||||||
|
__Thus, the more requirements an Event has to even proc, the more it can lean towards ⚪__
|
||||||
|
|
||||||
|
### Simplicity (Somewhat Important)
|
||||||
|
|
||||||
|
__The simpler the Event is at first glance **to the player**, the more it can lean towards ⚪ Common__.
|
||||||
|
Pretty straightforward, and useful when you're dealing with doubts between ⚪ Common or 🔵 Great.
|
||||||
|
|
||||||
|
### What the Event rewards you with (**NOT** Important)
|
||||||
|
"Wait, what?"
|
||||||
|
|
||||||
|
__Excluding Meta Progression__ and __Swinginess__, I couldn't care less what the Event is handling you, it can very well be a Master-Ball __as long as the price you pay for it is right on par with it__. So go wild.
|
||||||
|
|
||||||
|
### That's it for rarity/tier
|
||||||
|
If you still can't figure it out, still suggest a range, like "I think either ⚪ Common or 🔵 Great".
|
||||||
|
|
||||||
|
## What is "Waves"
|
||||||
|
This is the floors/waves where you'd like your Event to happen. (11-179)* is the standard, but think about the following:
|
||||||
|
|
||||||
|
- Is your Event too strong early, but balanced towards the later waves of Classic? Then you might want to consider going for something like 50-179, or 110-179.
|
||||||
|
- Is your Event simply useless towards later stages? Then something like 11-89 might be your best call.
|
||||||
|
- Do you love number four? You can set your Events to be only multiples of 4. Hey, if it makes sense... 🤷🏻♂️
|
||||||
|
|
||||||
|
## Biomes
|
||||||
|
|
||||||
|
Currently, you can set the Events to happen in the specific Biomes of your choosing, but there are other options too that you can use:
|
||||||
|
### `ANY` Biome
|
||||||
|
|
||||||
|
Fairly self explanatory, it's any/all biomes.
|
||||||
|
|
||||||
|
### `EXTREME_BIOMES` and `NON-EXTREME_BIOMES`
|
||||||
|
|
||||||
|
These are all the biomes in two mutually exclusive lists. The main point of this split is to avoid "Breeders in Space" type situations, with random trainers saluting you while on the moon.
|
||||||
|
|
||||||
|
#### EXTREME_BIOMES
|
||||||
|
|
||||||
|
- Abyss
|
||||||
|
- Badlands
|
||||||
|
- Desert
|
||||||
|
- Ice Cave
|
||||||
|
- Seabed
|
||||||
|
- Space
|
||||||
|
- Volcano
|
||||||
|
- Wasteland
|
||||||
|
|
||||||
|
#### NON-EXTREME_BIOMES
|
||||||
|
|
||||||
|
Every other biome not on [`EXTREME_BIOMES`](https://github.com/AsdarDevelops/PokeRogue-Events/blob/Asdar/MEs_Proposal_Guide.md#extreme_biomes)
|
||||||
|
|
||||||
|
|
||||||
|
### HUMAN_TRANSITABLE_BIOMES
|
||||||
|
|
||||||
|
For **__people__**, things like trainers, traders, youngster Joey and the people of the world. These are the biomes where you would feaseably find human beings during your journey.
|
||||||
|
|
||||||
|
They are: TOWN, PLAIN, GRASS, TALL_GRASS, METROPOLIS, FOREST, SWAMP, BEACH, LAKE, MOUNTAIN, BADLANDS, CAVE, DESERT, ICE_CAVE, MEADOW, POWER_PLANT, GRAVEYARD, DOJO, FACTORY, RUINS, CONSTRUCTION_SITE, JUNGLE, FAIRY_CAVE, TEMPLE, SLUM, SNOWY_FOREST, ISLAND, LABORATORY
|
||||||
|
|
||||||
|
### CIVILIZATION_BIOMES
|
||||||
|
These are the places where you might find not only people, but also __are or can be near buildings__, a PKMN Center, a shopping center, a school.
|
||||||
|
|
||||||
|
They are: TOWN, PLAINS, GRASS, TALL_GRASS, METROPOLIS, BEACH, LAKE, MEADOW, POWER_PLANT, GRAVEYARD, DOJO, FACTORY, CONSTRUCTION_SITE, SLUM, ISLAND
|
||||||
|
|
||||||
|
You can use these tags, or the specific Biome, or a combination of both, to let us know where you'd like the Event to happen.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
For now, I'll leave this as a WIP as we are still ironing out the details of what __can__ be done with Events, so just let your imagination run wild. __**Maximum of 4 selectable options at a time though, as that's what the UI can handle**__.
|
||||||
|
|
||||||
|
## Other tips for being more likely to get your Event approved
|
||||||
|
|
||||||
|
- Present your idea in an organized matter. The easier it is to read and understand, the more likeky it is to be approved. This means try to follow the template provided, one idea = one paragraph, and good spelling. If your mother tongue isn't English, do not worry too much, but the effort is always appreciated.
|
||||||
|
|
||||||
|
- Be specific, the more clear the whole flow of the Event is, and the less "holes" there are in your design, the more likely it gets approved. It will mean less guesswork and back and forth with the devs.
|
||||||
|
|
||||||
|
- Read what current MEs do. Read what rejected MEs proposed. The more you know about the feature, the better your design will likely be.
|
||||||
|
|
||||||
|
- Go wild.
|
83
public/images/mystery-encounters/bait.json
Normal file
83
public/images/mystery-encounters/bait.json
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"image": "bait.png",
|
||||||
|
"format": "RGBA8888",
|
||||||
|
"size": {
|
||||||
|
"w": 14,
|
||||||
|
"h": 43
|
||||||
|
},
|
||||||
|
"scale": 1,
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"filename": "0001.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 12,
|
||||||
|
"h": 16
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 3,
|
||||||
|
"w": 12,
|
||||||
|
"h": 13
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 1,
|
||||||
|
"w": 12,
|
||||||
|
"h": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "0002.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 12,
|
||||||
|
"h": 16
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 3,
|
||||||
|
"w": 12,
|
||||||
|
"h": 13
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 16,
|
||||||
|
"w": 12,
|
||||||
|
"h": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "0003.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 12,
|
||||||
|
"h": 16
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 5,
|
||||||
|
"w": 11,
|
||||||
|
"h": 11
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 31,
|
||||||
|
"w": 11,
|
||||||
|
"h": 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"app": "https://www.codeandweb.com/texturepacker",
|
||||||
|
"version": "3.0",
|
||||||
|
"smartupdate": "$TexturePacker:SmartUpdate:f0ec04fcd67ac346dce973693711d032:b697e09191c4312b8faaa0a080a309b7:1af241a52e61fa01ca849aa03c112f85$"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/images/mystery-encounters/bait.png
Normal file
BIN
public/images/mystery-encounters/bait.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 277 B |
@ -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 |
104
public/images/mystery-encounters/mud.json
Normal file
104
public/images/mystery-encounters/mud.json
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"image": "mud.png",
|
||||||
|
"format": "RGBA8888",
|
||||||
|
"size": {
|
||||||
|
"w": 18,
|
||||||
|
"h": 55
|
||||||
|
},
|
||||||
|
"scale": 1,
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"filename": "0002.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 16,
|
||||||
|
"h": 16
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 3,
|
||||||
|
"w": 16,
|
||||||
|
"h": 13
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 1,
|
||||||
|
"w": 16,
|
||||||
|
"h": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "0003.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 16,
|
||||||
|
"h": 16
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 4,
|
||||||
|
"w": 16,
|
||||||
|
"h": 12
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 16,
|
||||||
|
"w": 16,
|
||||||
|
"h": 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "0004.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 16,
|
||||||
|
"h": 16
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 7,
|
||||||
|
"w": 16,
|
||||||
|
"h": 9
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 30,
|
||||||
|
"w": 16,
|
||||||
|
"h": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "0001.png",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": true,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 16,
|
||||||
|
"h": 16
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 3,
|
||||||
|
"w": 14,
|
||||||
|
"h": 13
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 41,
|
||||||
|
"w": 14,
|
||||||
|
"h": 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"app": "https://www.codeandweb.com/texturepacker",
|
||||||
|
"version": "3.0",
|
||||||
|
"smartupdate": "$TexturePacker:SmartUpdate:a9f7ae83758a2dffaacdaba2ee9dc2e2:0ebff9db47ce74a0ec049f5d74d589fa:c64f6b8befc3d5e9f836246d2b9536be$"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/images/mystery-encounters/mud.png
Normal file
BIN
public/images/mystery-encounters/mud.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 361 B |
@ -9,7 +9,8 @@ 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";
|
||||||
import { EnemyPartyConfig, EnemyPokemonConfig, getRandomPlayerPokemon, getRandomSpeciesByStarterTier, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../mystery-encounter-utils";
|
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
|
||||||
|
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
|
||||||
/** i18n namespace for encounter */
|
/** i18n namespace for encounter */
|
||||||
const namespace = "mysteryEncounter:dark_deal";
|
const namespace = "mysteryEncounter:dark_deal";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
leaveEncounterWithoutBattle,
|
leaveEncounterWithoutBattle,
|
||||||
setEncounterRewards,
|
setEncounterRewards,
|
||||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
import { randSeedInt } from "#app/utils";
|
import { randSeedInt } from "#app/utils";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
selectPokemonForOption,
|
selectPokemonForOption,
|
||||||
setEncounterExp,
|
setEncounterExp,
|
||||||
setEncounterRewards,
|
setEncounterRewards,
|
||||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { TempBattleStat } from "#app/data/temp-battle-stat";
|
import { TempBattleStat } from "#app/data/temp-battle-stat";
|
||||||
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
|
@ -4,10 +4,8 @@ import {
|
|||||||
EnemyPartyConfig,
|
EnemyPartyConfig,
|
||||||
initBattleWithEnemyConfig,
|
initBattleWithEnemyConfig,
|
||||||
leaveEncounterWithoutBattle,
|
leaveEncounterWithoutBattle,
|
||||||
queueEncounterMessage,
|
setEncounterRewards
|
||||||
setEncounterRewards,
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
showEncounterText,
|
|
||||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
|
||||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||||
import Pokemon from "#app/field/pokemon";
|
import Pokemon from "#app/field/pokemon";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
@ -28,6 +26,7 @@ import IMysteryEncounter, {
|
|||||||
MysteryEncounterTier,
|
MysteryEncounterTier,
|
||||||
} from "../mystery-encounter";
|
} from "../mystery-encounter";
|
||||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||||
|
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:fight_or_flight";
|
const namespace = "mysteryEncounter:fight_or_flight";
|
||||||
|
@ -2,7 +2,7 @@ import {
|
|||||||
EnemyPartyConfig,
|
EnemyPartyConfig,
|
||||||
initBattleWithEnemyConfig,
|
initBattleWithEnemyConfig,
|
||||||
setEncounterRewards,
|
setEncounterRewards,
|
||||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import {
|
import {
|
||||||
trainerConfigs,
|
trainerConfigs,
|
||||||
TrainerPartyCompoundTemplate,
|
TrainerPartyCompoundTemplate,
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
getHighestLevelPlayerPokemon,
|
|
||||||
koPlayerPokemon,
|
|
||||||
leaveEncounterWithoutBattle,
|
leaveEncounterWithoutBattle,
|
||||||
queueEncounterMessage,
|
setEncounterRewards
|
||||||
setEncounterRewards,
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
showEncounterText,
|
|
||||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
import { GameOverPhase } from "#app/phases";
|
import { GameOverPhase } from "#app/phases";
|
||||||
import { randSeedInt } from "#app/utils";
|
import { randSeedInt } from "#app/utils";
|
||||||
@ -16,6 +12,8 @@ import IMysteryEncounter, {
|
|||||||
MysteryEncounterTier,
|
MysteryEncounterTier,
|
||||||
} from "../mystery-encounter";
|
} from "../mystery-encounter";
|
||||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||||
|
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
|
||||||
export const MysteriousChestEncounter: IMysteryEncounter =
|
export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(
|
MysteryEncounterBuilder.withEncounterType(
|
||||||
@ -30,7 +28,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 +74,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 +86,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 +94,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 +102,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,20 +112,12 @@ 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 (scene.getParty().filter((p) => p.isAllowedInBattle()).length === 0) {
|
||||||
).then(() => {
|
|
||||||
if (
|
|
||||||
scene.getParty().filter((p) => p.isAllowedInBattle()).length ===
|
|
||||||
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));
|
||||||
|
477
src/data/mystery-encounters/encounters/safari-zone-encounter.ts
Normal file
477
src/data/mystery-encounters/encounters/safari-zone-encounter.ts
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
import BattleScene from "../../../battle-scene";
|
||||||
|
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, MysteryEncounterVariant } from "../mystery-encounter";
|
||||||
|
import MysteryEncounterOption, { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
|
import { TrainerSlot } from "#app/data/trainer-config";
|
||||||
|
import { ScanIvsPhase, SummonPhase, VictoryPhase } from "#app/phases";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier";
|
||||||
|
import { EnemyPokemon } from "#app/field/pokemon";
|
||||||
|
import { PokeballType } from "#app/data/pokeball";
|
||||||
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
|
import { IntegerHolder, randSeedInt } from "#app/utils";
|
||||||
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
|
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
|
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
|
/** the i18n namespace for the encounter */
|
||||||
|
const namespace = "mysteryEncounter:safari_zone";
|
||||||
|
|
||||||
|
export const SafariZoneEncounter: IMysteryEncounter =
|
||||||
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
|
||||||
|
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||||
|
.withSceneWaveRangeRequirement(10, 180)
|
||||||
|
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||||
|
.withIntroSpriteConfigs([
|
||||||
|
{
|
||||||
|
spriteKey: "chest_blue",
|
||||||
|
fileRoot: "mystery-encounters",
|
||||||
|
hasShadow: true,
|
||||||
|
x: 4,
|
||||||
|
y: 10,
|
||||||
|
yShadowOffset: 3
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withIntroDialogue([
|
||||||
|
{
|
||||||
|
text: `${namespace}_intro_message`,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withTitle(`${namespace}_title`)
|
||||||
|
.withDescription(`${namespace}_description`)
|
||||||
|
.withQuery(`${namespace}_query`)
|
||||||
|
.withOption(new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||||
|
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}_option_1_label`,
|
||||||
|
buttonTooltip: `${namespace}_option_1_tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}_option_1_selected_message`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Start safari encounter
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
encounter.encounterVariant = MysteryEncounterVariant.SAFARI_BATTLE;
|
||||||
|
encounter.misc = {
|
||||||
|
safariPokemonRemaining: 3
|
||||||
|
};
|
||||||
|
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||||
|
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
|
||||||
|
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims");
|
||||||
|
scene.loadSe("PRSFX- Taunt2", "battle_anims");
|
||||||
|
scene.loadAtlas("bait", "mystery-encounters");
|
||||||
|
scene.loadAtlas("mud", "mystery-encounters");
|
||||||
|
await summonSafariPokemon(scene);
|
||||||
|
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, hideDescription: true });
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withSimpleOption(
|
||||||
|
{
|
||||||
|
buttonLabel: `${namespace}_option_2_label`,
|
||||||
|
buttonTooltip: `${namespace}_option_2_tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}_option_2_selected_message`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
async (scene: BattleScene) => {
|
||||||
|
// Leave encounter with no rewards or exp
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SAFARI ZONE MINIGAME OPTIONS
|
||||||
|
*
|
||||||
|
* Catch and flee rate **stages** are calculated in the same way stat changes are (they range from -6/+6)
|
||||||
|
* https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Great_Marsh_and_Johto_Safari_Zone
|
||||||
|
*
|
||||||
|
* Catch Rate calculation:
|
||||||
|
* 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 safariZoneGameOptions: 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);
|
||||||
|
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, 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) {
|
||||||
|
await showEncounterText(scene, i18next.t(`${namespace}_pokemon_busy_eating`, { pokemonName: pokemon.name }), 1500, false );
|
||||||
|
} else {
|
||||||
|
await showEncounterText(scene, i18next.t(`${namespace}_pokemon_eating`, { pokemonName: pokemon.name }), 1500, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
await showEncounterText(scene, i18next.t(`${namespace}_pokemon_beside_itself_angry`, { pokemonName: pokemon.name }), 1500, false );
|
||||||
|
} else {
|
||||||
|
await showEncounterText(scene, i18next.t(`${namespace}_pokemon_angry`, { pokemonName: pokemon.name }), 1500, false );
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
|
||||||
|
} else {
|
||||||
|
// End safari mode
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
];
|
||||||
|
|
||||||
|
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 * 1000 + 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));
|
||||||
|
|
||||||
|
showEncounterText(scene, i18next.t("battle:singleWildAppeared", { pokemonName: pokemon.name }), 1500, false)
|
||||||
|
.then(() => {
|
||||||
|
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
|
||||||
|
if (ivScannerModifier) {
|
||||||
|
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||||
|
const baseCatchRate = pokemon.species.catchRate;
|
||||||
|
// Catch stage ranges from -6 to +6 (like stat boost stages)
|
||||||
|
const safariCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage;
|
||||||
|
// 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)));
|
||||||
|
return trainerThrowPokeball(scene, pokemon, PokeballType.POKEBALL, ballTwitchRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||||
|
const originalY: number = pokemon.y;
|
||||||
|
|
||||||
|
const fpOffset = pokemon.getFieldPositionOffset();
|
||||||
|
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "bait", "0001.png");
|
||||||
|
bait.setOrigin(0.5, 0.625);
|
||||||
|
scene.field.add(bait);
|
||||||
|
|
||||||
|
scene.playSound("pb_throw");
|
||||||
|
|
||||||
|
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: () => {
|
||||||
|
|
||||||
|
let index = 1;
|
||||||
|
scene.time.delayedCall(768, () => {
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokemon,
|
||||||
|
duration: 150,
|
||||||
|
ease: "Cubic.easeOut",
|
||||||
|
yoyo: true,
|
||||||
|
y: originalY - 5,
|
||||||
|
loop: 6,
|
||||||
|
onStart: () => {
|
||||||
|
scene.playSound("PRSFX- Bug Bite");
|
||||||
|
bait.setFrame("0002.png");
|
||||||
|
},
|
||||||
|
onLoop: () => {
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
scene.playSound("PRSFX- Bug Bite");
|
||||||
|
}
|
||||||
|
if (index === 4) {
|
||||||
|
bait.setFrame("0003.png");
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
scene.time.delayedCall(256, () => {
|
||||||
|
bait.destroy();
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||||
|
const originalY: number = pokemon.y;
|
||||||
|
|
||||||
|
const fpOffset = pokemon.getFieldPositionOffset();
|
||||||
|
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "mud", "0001.png");
|
||||||
|
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`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mud throw and splat
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: mud,
|
||||||
|
x: { value: 230 + fpOffset[0], ease: "Linear" },
|
||||||
|
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||||
|
duration: 500,
|
||||||
|
onComplete: () => {
|
||||||
|
// Mud frame 2
|
||||||
|
scene.playSound("PRSFX- Sludge Bomb2");
|
||||||
|
mud.setFrame("0002.png");
|
||||||
|
// Mud splat
|
||||||
|
scene.time.delayedCall(512, () => {
|
||||||
|
mud.setFrame("0003.png");
|
||||||
|
scene.time.delayedCall(512, () => {
|
||||||
|
mud.setFrame("0004.png");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryChangeFleeStage(scene: BattleScene, change: number, chance?: number): boolean {
|
||||||
|
if (chance && randSeedInt(10) >= chance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const currentFleeStage = scene.currentBattle.mysteryEncounter.misc.fleeStage ?? 0;
|
||||||
|
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) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const currentCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage ?? 0;
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||||
|
} else {
|
||||||
|
// End safari mode
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scene.queueMessage(i18next.t(`${namespace}_pokemon_watching`, { pokemonName: pokemon.name }), 0, null, 1000);
|
||||||
|
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { generateModifierTypeOption, leaveEncounterWithoutBattle, queueEncounterMessage, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
@ -10,6 +10,7 @@ import BattleScene from "../../../battle-scene";
|
|||||||
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";
|
||||||
import { MoneyRequirement } from "../mystery-encounter-requirements";
|
import { MoneyRequirement } from "../mystery-encounter-requirements";
|
||||||
|
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounter:shady_vitamin_dealer";
|
const namespace = "mysteryEncounter:shady_vitamin_dealer";
|
||||||
@ -28,15 +29,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,8 @@ import { Status, StatusEffect } from "../../status-effect";
|
|||||||
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";
|
||||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||||
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, queueEncounterMessage, setEncounterExp, setEncounterRewards, } from "../mystery-encounter-utils";
|
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
|
||||||
|
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
/** i18n namespace for the encounter */
|
/** i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:sleeping_snorlax";
|
const namespace = "mysteryEncounter:sleeping_snorlax";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Ability, allAbilities } from "#app/data/ability";
|
import { Ability, allAbilities } from "#app/data/ability";
|
||||||
import { EnemyPartyConfig, getEncounterText, initBattleWithEnemyConfig, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
import { EnemyPartyConfig, initBattleWithEnemyConfig, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { getNatureName, Nature } from "#app/data/nature";
|
import { getNatureName, Nature } from "#app/data/nature";
|
||||||
import { speciesStarters } from "#app/data/pokemon-species";
|
import { speciesStarters } from "#app/data/pokemon-species";
|
||||||
import { Stat } from "#app/data/pokemon-stat";
|
import { Stat } from "#app/data/pokemon-stat";
|
||||||
@ -16,6 +16,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||||||
import BattleScene from "../../../battle-scene";
|
import BattleScene from "../../../battle-scene";
|
||||||
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";
|
||||||
|
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
/** The i18n namespace for the encounter */
|
/** The i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:training_session";
|
const namespace = "mysteryEncounter:training_session";
|
||||||
@ -32,7 +33,9 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||||||
spriteKey: "training_gear",
|
spriteKey: "training_gear",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
y: 3,
|
y: 6,
|
||||||
|
x: 5,
|
||||||
|
yShadowOffset: -2
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
@ -162,7 +165,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||||||
scene.addModifier(mod, true, false, false, true);
|
scene.addModifier(mod, true, false, false, true);
|
||||||
}
|
}
|
||||||
scene.updateModifiers(true);
|
scene.updateModifiers(true);
|
||||||
scene.queueMessage(getEncounterText(scene, `${namespace}_battle_finished_1`), null, true);
|
queueEncounterMessage(scene, `${namespace}_battle_finished_1`);
|
||||||
};
|
};
|
||||||
|
|
||||||
setEncounterRewards(
|
setEncounterRewards(
|
||||||
@ -234,11 +237,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||||||
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||||
|
|
||||||
const onBeforeRewardsPhase = () => {
|
const onBeforeRewardsPhase = () => {
|
||||||
scene.queueMessage(
|
queueEncounterMessage(scene, `${namespace}_battle_finished_2`);
|
||||||
getEncounterText(scene, `${namespace}_battle_finished_2`),
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
// Add the pokemon back to party with Nature change
|
// Add the pokemon back to party with Nature change
|
||||||
playerPokemon.setNature(encounter.misc.chosenNature);
|
playerPokemon.setNature(encounter.misc.chosenNature);
|
||||||
scene.gameData.setPokemonCaught(playerPokemon, false);
|
scene.gameData.setPokemonCaught(playerPokemon, false);
|
||||||
@ -333,7 +332,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||||||
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||||
|
|
||||||
const onBeforeRewardsPhase = () => {
|
const onBeforeRewardsPhase = () => {
|
||||||
scene.queueMessage(getEncounterText(scene, `${namespace}_battle_finished_3`), null, true);
|
queueEncounterMessage(scene, `${namespace}_battle_finished_3`);
|
||||||
// Add the pokemon back to party with ability change
|
// Add the pokemon back to party with ability change
|
||||||
const abilityIndex = encounter.misc.abilityIndex;
|
const abilityIndex = encounter.misc.abilityIndex;
|
||||||
if (!!playerPokemon.getFusionSpeciesForm()) {
|
if (!!playerPokemon.getFusionSpeciesForm()) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { EnemyPartyConfig } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
@ -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 {
|
||||||
@ -57,14 +58,16 @@ export default interface IMysteryEncounter {
|
|||||||
* Requirements
|
* Requirements
|
||||||
*/
|
*/
|
||||||
requirements?: EncounterSceneRequirement[];
|
requirements?: EncounterSceneRequirement[];
|
||||||
|
/** Primary Pokemon is a single pokemon randomly selected from the party that meet ALL primary pokemon requirements */
|
||||||
primaryPokemonRequirements?: EncounterPokemonRequirement[];
|
primaryPokemonRequirements?: EncounterPokemonRequirement[];
|
||||||
secondaryPokemonRequirements?: EncounterPokemonRequirement[]; // A list of requirements that must ALL be met by a subset of pokemon to trigger the event
|
/**
|
||||||
|
* Secondary Pokemon are pokemon that meet ALL secondary pokemon requirements
|
||||||
|
* Note that an individual requirement may require multiple pokemon, but the resulting pokemon after all secondary requirements are met may be lower than expected
|
||||||
|
* If the primary pokemon and secondary pokemon are the same and ExcludePrimaryFromSupportRequirements flag is true, primary pokemon may be promoted from secondary pool
|
||||||
|
*/
|
||||||
|
secondaryPokemonRequirements?: EncounterPokemonRequirement[];
|
||||||
excludePrimaryFromSupportRequirements?: boolean;
|
excludePrimaryFromSupportRequirements?: boolean;
|
||||||
// Primary Pokemon is a single pokemon randomly selected from a set of pokemon that meet ALL primary pokemon requirements
|
|
||||||
primaryPokemon?: PlayerPokemon;
|
primaryPokemon?: PlayerPokemon;
|
||||||
// Support Pokemon are pokemon that meet ALL support pokemon requirements.
|
|
||||||
// Note that an individual requirement may require multiple pokemon, but the resulting pokemon after all secondary requirements are met may be lower than expected
|
|
||||||
// If the primary pokemon and supporting pokemon are the same and ExcludePrimaryFromSupportRequirements flag is true, primary pokemon may be promoted from secondary pool
|
|
||||||
secondaryPokemon?: PlayerPokemon[];
|
secondaryPokemon?: PlayerPokemon[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,7 +120,11 @@ export default interface IMysteryEncounter {
|
|||||||
* Defaults to 1
|
* Defaults to 1
|
||||||
*/
|
*/
|
||||||
expMultiplier?: number;
|
expMultiplier?: number;
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
@ -11,6 +11,7 @@ import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-e
|
|||||||
import { SleepingSnorlaxEncounter } from "./encounters/sleeping-snorlax-encounter";
|
import { SleepingSnorlaxEncounter } from "./encounters/sleeping-snorlax-encounter";
|
||||||
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
|
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
|
||||||
import IMysteryEncounter from "./mystery-encounter";
|
import IMysteryEncounter from "./mystery-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;
|
||||||
@ -163,13 +164,16 @@ export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
|
|||||||
[Biome.TALL_GRASS, []],
|
[Biome.TALL_GRASS, []],
|
||||||
[Biome.METROPOLIS, []],
|
[Biome.METROPOLIS, []],
|
||||||
[Biome.FOREST, [
|
[Biome.FOREST, [
|
||||||
MysteryEncounterType.SLEEPING_SNORLAX
|
MysteryEncounterType.SLEEPING_SNORLAX,
|
||||||
|
MysteryEncounterType.SAFARI_ZONE
|
||||||
]],
|
]],
|
||||||
|
|
||||||
[Biome.SEA, [
|
[Biome.SEA, [
|
||||||
MysteryEncounterType.LOST_AT_SEA
|
MysteryEncounterType.LOST_AT_SEA
|
||||||
]],
|
]],
|
||||||
[Biome.SWAMP, []],
|
[Biome.SWAMP, [
|
||||||
|
MysteryEncounterType.SAFARI_ZONE
|
||||||
|
]],
|
||||||
[Biome.BEACH, []],
|
[Biome.BEACH, []],
|
||||||
[Biome.LAKE, []],
|
[Biome.LAKE, []],
|
||||||
[Biome.SEABED, []],
|
[Biome.SEABED, []],
|
||||||
@ -193,7 +197,9 @@ export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
|
|||||||
[Biome.ABYSS, []],
|
[Biome.ABYSS, []],
|
||||||
[Biome.SPACE, []],
|
[Biome.SPACE, []],
|
||||||
[Biome.CONSTRUCTION_SITE, []],
|
[Biome.CONSTRUCTION_SITE, []],
|
||||||
[Biome.JUNGLE, []],
|
[Biome.JUNGLE, [
|
||||||
|
MysteryEncounterType.SAFARI_ZONE
|
||||||
|
]],
|
||||||
[Biome.FAIRY_CAVE, []],
|
[Biome.FAIRY_CAVE, []],
|
||||||
[Biome.TEMPLE, []],
|
[Biome.TEMPLE, []],
|
||||||
[Biome.SLUM, []],
|
[Biome.SLUM, []],
|
||||||
@ -212,6 +218,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;
|
||||||
allMysteryEncounters[MysteryEncounterType.LOST_AT_SEA] = LostAtSeaEncounter;
|
allMysteryEncounters[MysteryEncounterType.LOST_AT_SEA] = LostAtSeaEncounter;
|
||||||
|
|
||||||
// Add extreme encounters to biome map
|
// Add extreme encounters to biome map
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import { getTextWithColors, TextStyle } from "#app/ui/text";
|
||||||
|
import { UiTheme } from "#enums/ui-theme";
|
||||||
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
export function getEncounterText(scene: BattleScene, keyOrString: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
|
||||||
|
if (isNullOrUndefined(keyOrString)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let textString: string = getTextWithDialogueTokens(scene, keyOrString);
|
||||||
|
|
||||||
|
// Can only color the text if a Primary Style is defined
|
||||||
|
// primaryStyle is applied to all text that does not have its own specified style
|
||||||
|
if (primaryStyle) {
|
||||||
|
textString = getTextWithColors(textString, primaryStyle, uiTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextWithDialogueTokens(scene: BattleScene, keyOrString: string): string {
|
||||||
|
if (isNullOrUndefined(keyOrString)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i18next.exists(keyOrString, scene.currentBattle?.mysteryEncounter?.dialogueTokens)) {
|
||||||
|
const stringArray = [`${keyOrString}`] as any;
|
||||||
|
stringArray.raw = [`${keyOrString}`];
|
||||||
|
return i18next.t(stringArray, scene.currentBattle?.mysteryEncounter?.dialogueTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyOrString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will queue a message in UI with injected encounter data tokens
|
||||||
|
* @param scene
|
||||||
|
* @param contentKey
|
||||||
|
*/
|
||||||
|
export function queueEncounterMessage(scene: BattleScene, contentKey: string): void {
|
||||||
|
const text: string = getEncounterText(scene, contentKey);
|
||||||
|
scene.queueMessage(text, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will display a message in UI with injected encounter data tokens
|
||||||
|
* @param scene
|
||||||
|
* @param contentKey
|
||||||
|
* @param prompt
|
||||||
|
* @param callbackDelay
|
||||||
|
*/
|
||||||
|
export function showEncounterText(scene: BattleScene, contentKey: string, callbackDelay: number = 0, prompt: boolean = true): Promise<void> {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
const text: string = getEncounterText(scene, contentKey);
|
||||||
|
scene.ui.showText(text, null, () => resolve(), callbackDelay, prompt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will display a dialogue (with speaker title) in UI with injected encounter data tokens
|
||||||
|
* @param scene
|
||||||
|
* @param textContentKey
|
||||||
|
* @param speakerContentKey
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, callback?: Function) {
|
||||||
|
const text: string = getEncounterText(scene, textContentKey);
|
||||||
|
const speaker: string = getEncounterText(scene, speakerContentKey);
|
||||||
|
scene.ui.showDialogue(text, speaker, null, callback, 0, 0);
|
||||||
|
}
|
@ -1,223 +1,30 @@
|
|||||||
import { BattleType } from "#app/battle";
|
|
||||||
import { biomeLinks } from "#app/data/biomes";
|
|
||||||
import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
|
||||||
import { Type } from "#app/data/type";
|
|
||||||
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
|
||||||
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
|
|
||||||
import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, PokemonHeldItemModifierType, getModifierPoolForType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
|
||||||
import * as Overrides from "#app/overrides";
|
|
||||||
import { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
|
|
||||||
import { MysteryEncounterBattlePhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
|
|
||||||
import PokemonData from "#app/system/pokemon-data";
|
|
||||||
import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
|
||||||
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
|
||||||
import { TextStyle, getTextWithColors } from "#app/ui/text";
|
|
||||||
import { Mode } from "#app/ui/ui";
|
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
||||||
import { Biome } from "#enums/biome";
|
|
||||||
import { Species } from "#enums/species";
|
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
|
||||||
import { UiTheme } from "#enums/ui-theme";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BattleScene from "../../battle-scene";
|
import { BattleType } from "#app/battle";
|
||||||
import Trainer, { TrainerVariant } from "../../field/trainer";
|
import BattleScene from "../../../battle-scene";
|
||||||
import * as Utils from "../../utils";
|
import PokemonSpecies from "../../pokemon-species";
|
||||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "../pokemon-species";
|
import { MysteryEncounterVariant } from "../mystery-encounter";
|
||||||
import { Status, StatusEffect } from "../status-effect";
|
import { Status, StatusEffect } from "../../status-effect";
|
||||||
import { TrainerConfig, TrainerSlot, trainerConfigs } from "../trainer-config";
|
import { TrainerConfig, trainerConfigs, TrainerSlot } from "../../trainer-config";
|
||||||
import { MysteryEncounterVariant } from "./mystery-encounter";
|
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import Trainer, { TrainerVariant } from "../../../field/trainer";
|
||||||
/**
|
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
|
||||||
*
|
import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||||
* Will never remove the player's last non-fainted Pokemon (if they only have 1)
|
import { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
|
||||||
* Otherwise, picks a Pokemon completely at random and removes from the party
|
import { MysteryEncounterBattlePhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
|
||||||
* @param scene
|
import * as Utils from "../../../utils";
|
||||||
* @param isAllowedInBattle - default false. If true, only picks from unfainted mons. If there is only 1 unfainted mon left and doNotReturnLastAbleMon is also true, will return fainted mon
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
* @param doNotReturnLastAbleMon - If true, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
* @returns
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
*/
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: boolean = false, doNotReturnLastAbleMon: boolean = false): PlayerPokemon {
|
import { Biome } from "#enums/biome";
|
||||||
const party = scene.getParty();
|
import { biomeLinks } from "#app/data/biomes";
|
||||||
let chosenIndex: number;
|
import { Mode } from "#app/ui/ui";
|
||||||
let chosenPokemon: PlayerPokemon;
|
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||||
const unfaintedMons = party.filter(p => p.isAllowedInBattle());
|
import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
const faintedMons = party.filter(p => !p.isAllowedInBattle());
|
import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
import * as Overrides from "#app/overrides";
|
||||||
if (doNotReturnLastAbleMon && unfaintedMons.length === 1) {
|
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
chosenIndex = Utils.randSeedInt(faintedMons.length);
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
chosenPokemon = faintedMons.at(chosenIndex);
|
|
||||||
} else if (isAllowedInBattle) {
|
|
||||||
chosenIndex = Utils.randSeedInt(unfaintedMons.length);
|
|
||||||
chosenPokemon = unfaintedMons.at(chosenIndex);
|
|
||||||
} else {
|
|
||||||
chosenIndex = Utils.randSeedInt(party.length);
|
|
||||||
chosenPokemon = party.at(chosenIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return chosenPokemon;
|
|
||||||
}
|
|
||||||
|
|
||||||
// export function getTokensFromScene(scene: BattleScene, reqs: EncounterSceneRequirement[]): Array<[RegExp, String]> {
|
|
||||||
// const arr = [];
|
|
||||||
// if (scene) {
|
|
||||||
// for (const req of reqs) {
|
|
||||||
// req.getDialogueToken(scene);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return arr;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ties are broken by whatever mon is closer to the front of the party
|
|
||||||
* @param scene
|
|
||||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
|
||||||
const party = scene.getParty();
|
|
||||||
let pokemon: PlayerPokemon;
|
|
||||||
party.every(p => {
|
|
||||||
if (unfainted && p.isFainted()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pokemon = pokemon ? pokemon?.level < p?.level ? p : pokemon : p;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return pokemon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ties are broken by whatever mon is closer to the front of the party
|
|
||||||
* @param scene
|
|
||||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
|
||||||
const party = scene.getParty();
|
|
||||||
let pokemon: PlayerPokemon;
|
|
||||||
party.every(p => {
|
|
||||||
if (unfainted && p.isFainted()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pokemon = pokemon ? pokemon?.level > p?.level ? p : pokemon : p;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return pokemon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* NOTE: This returns ANY random species, including those locked behind eggs, etc.
|
|
||||||
* @param starterTiers
|
|
||||||
* @param excludedSpecies
|
|
||||||
* @param types
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
|
|
||||||
let min = starterTiers instanceof Array ? starterTiers[0] : starterTiers;
|
|
||||||
let max = starterTiers instanceof Array ? starterTiers[1] : starterTiers;
|
|
||||||
|
|
||||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
|
||||||
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
|
||||||
.filter(s => getPokemonSpecies(s[0]) && (!excludedSpecies || !excludedSpecies.includes(s[0])))
|
|
||||||
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
|
||||||
|
|
||||||
if (!isNullOrUndefined(types) && types.length > 0) {
|
|
||||||
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || types.includes(s[0].type2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
|
||||||
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
|
|
||||||
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
|
|
||||||
while (tryFilterStarterTiers.length === 0 && (min !== 0 && max !== 10)) {
|
|
||||||
if (min > 0) {
|
|
||||||
min--;
|
|
||||||
} else {
|
|
||||||
max++;
|
|
||||||
}
|
|
||||||
|
|
||||||
tryFilterStarterTiers = filteredSpecies.filter(s => s[1] >= min && s[1] <= max);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tryFilterStarterTiers.length > 0) {
|
|
||||||
const index = Utils.randSeedInt(tryFilterStarterTiers.length);
|
|
||||||
return Phaser.Math.RND.shuffle(tryFilterStarterTiers)[index][0].speciesId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Species.BULBASAUR;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function koPlayerPokemon(pokemon: PlayerPokemon) {
|
|
||||||
pokemon.hp = 0;
|
|
||||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
|
||||||
pokemon.updateInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEncounterText(scene: BattleScene, textKey: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
|
|
||||||
if (isNullOrUndefined(textKey)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stringArray = [`${textKey}`] as any;
|
|
||||||
stringArray.raw = [`${textKey}`];
|
|
||||||
let textString: string = getTextWithDialogueTokens(scene, stringArray);
|
|
||||||
|
|
||||||
// Can only color the text if a Primary Style is defined
|
|
||||||
// primaryStyle is applied to all text that does not have its own specified style
|
|
||||||
if (primaryStyle) {
|
|
||||||
textString = getTextWithColors(textString, primaryStyle, uiTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
return textString;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTextWithDialogueTokens(scene: BattleScene, textKey: TemplateStringsArray): string {
|
|
||||||
if (isNullOrUndefined(textKey)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return i18next.t(textKey, scene.currentBattle?.mysteryEncounter?.dialogueTokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will queue a message in UI with injected encounter data tokens
|
|
||||||
* @param scene
|
|
||||||
* @param contentKey
|
|
||||||
*/
|
|
||||||
export function queueEncounterMessage(scene: BattleScene, contentKey: string): void {
|
|
||||||
const text: string = getEncounterText(scene, contentKey);
|
|
||||||
scene.queueMessage(text, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will display a message in UI with injected encounter data tokens
|
|
||||||
* @param scene
|
|
||||||
* @param contentKey
|
|
||||||
*/
|
|
||||||
export function showEncounterText(scene: BattleScene, contentKey: string): Promise<void> {
|
|
||||||
return new Promise<void>(resolve => {
|
|
||||||
const text: string = getEncounterText(scene, contentKey);
|
|
||||||
scene.ui.showText(text, null, () => resolve(), 0, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will display a dialogue (with speaker title) in UI with injected encounter data tokens
|
|
||||||
* @param scene
|
|
||||||
* @param textContentKey
|
|
||||||
* @param speakerContentKey
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, callback?: Function) {
|
|
||||||
const text: string = getEncounterText(scene, textContentKey);
|
|
||||||
const speaker: string = getEncounterText(scene, speakerContentKey);
|
|
||||||
scene.ui.showDialogue(text, speaker, null, callback, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EnemyPokemonConfig {
|
export class EnemyPokemonConfig {
|
||||||
species: PokemonSpecies;
|
species: PokemonSpecies;
|
||||||
@ -427,6 +234,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 +271,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
|
||||||
@ -519,8 +331,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
|||||||
if (!textPromptKey) {
|
if (!textPromptKey) {
|
||||||
displayOptions();
|
displayOptions();
|
||||||
} else {
|
} else {
|
||||||
const secondOptionSelectPrompt = getEncounterText(scene, textPromptKey, TextStyle.MESSAGE);
|
showEncounterText(scene, textPromptKey).then(() => displayOptions());
|
||||||
scene.ui.showText(secondOptionSelectPrompt, null, displayOptions, null, true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -574,7 +385,7 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust
|
|||||||
* Will initialize exp phases into the phase queue (these are in addition to any combat or other exp earned)
|
* Will initialize exp phases into the phase queue (these are in addition to any combat or other exp earned)
|
||||||
* Exp Share and Exp Balance will still function as normal
|
* Exp Share and Exp Balance will still function as normal
|
||||||
* @param scene - Battle Scene
|
* @param scene - Battle Scene
|
||||||
* @param participantId - ids of party pokemon that get full exp value. Other party members will receive Exp Share amounts
|
* @param participantIds - ids of party pokemon that get full exp value. Other party members will receive Exp Share amounts
|
||||||
* @param baseExpValue - gives exp equivalent to a pokemon of the wave index's level.
|
* @param baseExpValue - gives exp equivalent to a pokemon of the wave index's level.
|
||||||
* Guidelines:
|
* Guidelines:
|
||||||
* 36 - Sunkern (lowest in game)
|
* 36 - Sunkern (lowest in game)
|
||||||
@ -588,8 +399,7 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust
|
|||||||
* https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_effort_value_yield_(Generation_IX)
|
* https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_effort_value_yield_(Generation_IX)
|
||||||
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
|
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
|
||||||
*/
|
*/
|
||||||
export function setEncounterExp(scene: BattleScene, participantId: integer | integer[], baseExpValue: number, useWaveIndex: boolean = true) {
|
export function setEncounterExp(scene: BattleScene, participantIds: integer[], baseExpValue: number, useWaveIndex: boolean = true) {
|
||||||
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
|
|
||||||
scene.currentBattle.mysteryEncounter.doEncounterExp = (scene: BattleScene) => {
|
scene.currentBattle.mysteryEncounter.doEncounterExp = (scene: BattleScene) => {
|
||||||
const party = scene.getParty();
|
const party = scene.getParty();
|
||||||
const expShareModifier = scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier;
|
const expShareModifier = scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier;
|
||||||
@ -675,6 +485,26 @@ export function setEncounterExp(scene: BattleScene, participantId: integer | int
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class OptionSelectSettings {
|
||||||
|
hideDescription?: boolean;
|
||||||
|
slideInDescription?: boolean;
|
||||||
|
overrideTitle?: string;
|
||||||
|
overrideDescription?: string;
|
||||||
|
overrideQuery?: string;
|
||||||
|
overrideOptions?: MysteryEncounterOption[];
|
||||||
|
startingCursorIndex?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to queue a new series of Options to select for an Encounter
|
||||||
|
* MUST be used only in onOptionPhase, will not work in onPreOptionPhase or onPostOptionPhase
|
||||||
|
* @param scene
|
||||||
|
* @param optionSelectSettings
|
||||||
|
*/
|
||||||
|
export function initSubsequentOptionSelect(scene: BattleScene, optionSelectSettings: OptionSelectSettings) {
|
||||||
|
scene.pushPhase(new MysteryEncounterPhase(scene, optionSelectSettings));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@ -689,7 +519,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))) {
|
||||||
@ -704,6 +536,32 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hideMysteryEncounterIntroVisuals(scene: BattleScene): Promise<boolean> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const introVisuals = scene.currentBattle.mysteryEncounter.introVisuals;
|
||||||
|
if (introVisuals) {
|
||||||
|
// Hide
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: introVisuals,
|
||||||
|
x: "+=16",
|
||||||
|
y: "-=16",
|
||||||
|
alpha: 0,
|
||||||
|
ease: "Sine.easeInOut",
|
||||||
|
duration: 750,
|
||||||
|
onComplete: () => {
|
||||||
|
scene.field.remove(introVisuals);
|
||||||
|
introVisuals.setVisible(false);
|
||||||
|
introVisuals.destroy();
|
||||||
|
scene.currentBattle.mysteryEncounter.introVisuals = null;
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: remove once encounter spawn rate is finalized
|
* TODO: remove once encounter spawn rate is finalized
|
||||||
* Just a helper function to calculate aggregate stats for MEs in a Classic run
|
* Just a helper function to calculate aggregate stats for MEs in a Classic run
|
||||||
@ -815,52 +673,3 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
|||||||
|
|
||||||
console.log(`Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Uncommons: ${uncommonMean}\nAvg Rares: ${rareMean}\nAvg Super Rares: ${superRareMean}`);
|
console.log(`Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Uncommons: ${uncommonMean}\nAvg Rares: ${rareMean}\nAvg Super Rares: ${superRareMean}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles applying hp changes to a player pokemon.
|
|
||||||
* Takes care of not going below `0`, above max-hp, adding `FNT` status correctly and updating the pokemon info.
|
|
||||||
* TODO: handle special cases like wonder-guard/ninjask
|
|
||||||
*
|
|
||||||
* @param pokemon the player pokemon to apply the hp change to
|
|
||||||
* @param damage the hp change amount. Positive for heal. Negative for damage
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function applyHpChangeToPokemon(pokemon: PlayerPokemon, value: number) {
|
|
||||||
const hpChange = Math.round(pokemon.hp + value);
|
|
||||||
const nextHp = Math.max(Math.min(hpChange, pokemon.getMaxHp()), 0);
|
|
||||||
if (nextHp === 0) {
|
|
||||||
koPlayerPokemon(pokemon);
|
|
||||||
} else {
|
|
||||||
pokemon.hp = nextHp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles applying damage to a player pokemon
|
|
||||||
*
|
|
||||||
* @param pokemon the player pokemon to apply damage to
|
|
||||||
* @param damage the amount of damage to apply
|
|
||||||
* @see {@linkcode applyHpChangeToPokemon}
|
|
||||||
*/
|
|
||||||
export function applyDamageToPokemon(pokemon: PlayerPokemon, damage: number) {
|
|
||||||
if (damage <= 0) {
|
|
||||||
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
|
|
||||||
}
|
|
||||||
|
|
||||||
applyHpChangeToPokemon(pokemon, -damage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles applying heal to a player pokemon
|
|
||||||
*
|
|
||||||
* @param pokemon the player pokemon to apply heal to
|
|
||||||
* @param heal the amount of heal to apply
|
|
||||||
* @see {@linkcode applyHpChangeToPokemon}
|
|
||||||
*/
|
|
||||||
export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
|
|
||||||
if (heal <= 0) {
|
|
||||||
console.warn("Damaging pokemong with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.");
|
|
||||||
}
|
|
||||||
|
|
||||||
applyHpChangeToPokemon(pokemon, heal);
|
|
||||||
}
|
|
451
src/data/mystery-encounters/utils/encounter-pokemon-utils.ts
Normal file
451
src/data/mystery-encounters/utils/encounter-pokemon-utils.ts
Normal file
@ -0,0 +1,451 @@
|
|||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||||
|
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
|
import { VictoryPhase } from "#app/phases";
|
||||||
|
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "#app/data/pokeball";
|
||||||
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
|
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
|
||||||
|
import { getStatusEffectCatchRateMultiplier, StatusEffect } from "#app/data/status-effect";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { achvs } from "#app/system/achv";
|
||||||
|
import { Mode } from "#app/ui/ui";
|
||||||
|
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||||
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Will never remove the player's last non-fainted Pokemon (if they only have 1)
|
||||||
|
* Otherwise, picks a Pokemon completely at random and removes from the party
|
||||||
|
* @param scene
|
||||||
|
* @param isAllowedInBattle - default false. If true, only picks from unfainted mons. If there is only 1 unfainted mon left and doNotReturnLastAbleMon is also true, will return fainted mon
|
||||||
|
* @param doNotReturnLastAbleMon - If true, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: boolean = false, doNotReturnLastAbleMon: boolean = false): PlayerPokemon {
|
||||||
|
const party = scene.getParty();
|
||||||
|
let chosenIndex: number;
|
||||||
|
let chosenPokemon: PlayerPokemon;
|
||||||
|
const unfaintedMons = party.filter(p => p.isAllowedInBattle());
|
||||||
|
const faintedMons = party.filter(p => !p.isAllowedInBattle());
|
||||||
|
|
||||||
|
if (doNotReturnLastAbleMon && unfaintedMons.length === 1) {
|
||||||
|
chosenIndex = randSeedInt(faintedMons.length);
|
||||||
|
chosenPokemon = faintedMons.at(chosenIndex);
|
||||||
|
} else if (isAllowedInBattle) {
|
||||||
|
chosenIndex = randSeedInt(unfaintedMons.length);
|
||||||
|
chosenPokemon = unfaintedMons.at(chosenIndex);
|
||||||
|
} else {
|
||||||
|
chosenIndex = randSeedInt(party.length);
|
||||||
|
chosenPokemon = party.at(chosenIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chosenPokemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ties are broken by whatever mon is closer to the front of the party
|
||||||
|
* @param scene
|
||||||
|
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||||
|
const party = scene.getParty();
|
||||||
|
let pokemon: PlayerPokemon;
|
||||||
|
party.every(p => {
|
||||||
|
if (unfainted && p.isFainted()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pokemon = pokemon ? pokemon?.level < p?.level ? p : pokemon : p;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return pokemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ties are broken by whatever mon is closer to the front of the party
|
||||||
|
* @param scene
|
||||||
|
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||||
|
const party = scene.getParty();
|
||||||
|
let pokemon: PlayerPokemon;
|
||||||
|
party.every(p => {
|
||||||
|
if (unfainted && p.isFainted()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pokemon = pokemon ? pokemon?.level > p?.level ? p : pokemon : p;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return pokemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* NOTE: This returns ANY random species, including those locked behind eggs, etc.
|
||||||
|
* @param starterTiers
|
||||||
|
* @param excludedSpecies
|
||||||
|
* @param types
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
|
||||||
|
let min = starterTiers instanceof Array ? starterTiers[0] : starterTiers;
|
||||||
|
let max = starterTiers instanceof Array ? starterTiers[1] : starterTiers;
|
||||||
|
|
||||||
|
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
||||||
|
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
||||||
|
.filter(s => getPokemonSpecies(s[0]) && (!excludedSpecies || !excludedSpecies.includes(s[0])))
|
||||||
|
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||||
|
|
||||||
|
if (!isNullOrUndefined(types) && types.length > 0) {
|
||||||
|
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || types.includes(s[0].type2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
||||||
|
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
|
||||||
|
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
|
||||||
|
while (tryFilterStarterTiers.length === 0 && (min !== 0 && max !== 10)) {
|
||||||
|
if (min > 0) {
|
||||||
|
min--;
|
||||||
|
} else {
|
||||||
|
max++;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryFilterStarterTiers = filteredSpecies.filter(s => s[1] >= min && s[1] <= max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tryFilterStarterTiers.length > 0) {
|
||||||
|
const index = randSeedInt(tryFilterStarterTiers.length);
|
||||||
|
return Phaser.Math.RND.shuffle(tryFilterStarterTiers)[index][0].speciesId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Species.BULBASAUR;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function koPlayerPokemon(pokemon: PlayerPokemon) {
|
||||||
|
pokemon.hp = 0;
|
||||||
|
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||||
|
pokemon.updateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative to using AttemptCapturePhase
|
||||||
|
* Assumes player sprite is visible on the screen (this is intended for non-combat uses)
|
||||||
|
*
|
||||||
|
* Can await returned promise to wait for throw animation completion before continuing
|
||||||
|
*
|
||||||
|
* @param scene
|
||||||
|
* @param pokemon
|
||||||
|
* @param pokeballType
|
||||||
|
* @param ballTwitchRate - can pass custom ball catch rates (for special events, like safari)
|
||||||
|
*/
|
||||||
|
export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, pokeballType: PokeballType, ballTwitchRate?: number): Promise<boolean> {
|
||||||
|
const originalY: number = pokemon.y;
|
||||||
|
|
||||||
|
if (!ballTwitchRate) {
|
||||||
|
const _3m = 3 * pokemon.getMaxHp();
|
||||||
|
const _2h = 2 * pokemon.hp;
|
||||||
|
const catchRate = pokemon.species.catchRate;
|
||||||
|
const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType);
|
||||||
|
const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1;
|
||||||
|
const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier);
|
||||||
|
ballTwitchRate = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const fpOffset = pokemon.getFieldPositionOffset();
|
||||||
|
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
|
||||||
|
const pokeball: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey);
|
||||||
|
pokeball.setOrigin(0.5, 0.625);
|
||||||
|
scene.field.add(pokeball);
|
||||||
|
|
||||||
|
scene.playSound("pb_throw");
|
||||||
|
scene.time.delayedCall(300, () => {
|
||||||
|
scene.field.moveBelow(pokeball as Phaser.GameObjects.GameObject, pokemon);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||||
|
scene.time.delayedCall(512, () => {
|
||||||
|
// Trainer throw frames
|
||||||
|
scene.trainer.setFrame("2");
|
||||||
|
scene.time.delayedCall(256, () => {
|
||||||
|
scene.trainer.setFrame("3");
|
||||||
|
scene.time.delayedCall(768, () => {
|
||||||
|
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pokeball move and catch logic
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokeball,
|
||||||
|
x: { value: 236 + fpOffset[0], ease: "Linear" },
|
||||||
|
y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||||
|
duration: 500,
|
||||||
|
onComplete: () => {
|
||||||
|
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||||
|
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`));
|
||||||
|
scene.playSound("pb_rel");
|
||||||
|
pokemon.tint(getPokeballTintColor(pokeballType));
|
||||||
|
|
||||||
|
addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType);
|
||||||
|
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokemon,
|
||||||
|
duration: 500,
|
||||||
|
ease: "Sine.easeIn",
|
||||||
|
scale: 0.25,
|
||||||
|
y: 20,
|
||||||
|
onComplete: () => {
|
||||||
|
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||||
|
pokemon.setVisible(false);
|
||||||
|
scene.playSound("pb_catch");
|
||||||
|
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}`));
|
||||||
|
|
||||||
|
const doShake = () => {
|
||||||
|
let shakeCount = 0;
|
||||||
|
const pbX = pokeball.x;
|
||||||
|
const shakeCounter = scene.tweens.addCounter({
|
||||||
|
from: 0,
|
||||||
|
to: 1,
|
||||||
|
repeat: 4,
|
||||||
|
yoyo: true,
|
||||||
|
ease: "Cubic.easeOut",
|
||||||
|
duration: 250,
|
||||||
|
repeatDelay: 500,
|
||||||
|
onUpdate: t => {
|
||||||
|
if (shakeCount && shakeCount < 4) {
|
||||||
|
const value = t.getValue();
|
||||||
|
const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1;
|
||||||
|
pokeball.setX(pbX + value * 4 * directionMultiplier);
|
||||||
|
pokeball.setAngle(value * 27.5 * directionMultiplier);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRepeat: () => {
|
||||||
|
if (!pokemon.species.isObtainable()) {
|
||||||
|
shakeCounter.stop();
|
||||||
|
failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false));
|
||||||
|
} else if (shakeCount++ < 3) {
|
||||||
|
if (randSeedInt(65536) < ballTwitchRate) {
|
||||||
|
scene.playSound("pb_move");
|
||||||
|
} else {
|
||||||
|
shakeCounter.stop();
|
||||||
|
failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scene.playSound("pb_lock");
|
||||||
|
addPokeballCaptureStars(scene, pokeball);
|
||||||
|
|
||||||
|
const pbTint = scene.add.sprite(pokeball.x, pokeball.y, "pb", "pb");
|
||||||
|
pbTint.setOrigin(pokeball.originX, pokeball.originY);
|
||||||
|
pbTint.setTintFill(0);
|
||||||
|
pbTint.setAlpha(0);
|
||||||
|
scene.field.add(pbTint);
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pbTint,
|
||||||
|
alpha: 0.375,
|
||||||
|
duration: 200,
|
||||||
|
easing: "Sine.easeOut",
|
||||||
|
onComplete: () => {
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pbTint,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 200,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
onComplete: () => pbTint.destroy()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
catchPokemon(scene, pokemon, pokeball, pokeballType).then(() => resolve(true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
scene.time.delayedCall(250, () => doPokeballBounceAnim(scene, pokeball, 16, 72, 350, doShake));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
scene.playSound("pb_rel");
|
||||||
|
pokemon.setY(originalY);
|
||||||
|
if (pokemon.status?.effect !== StatusEffect.SLEEP) {
|
||||||
|
pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 });
|
||||||
|
}
|
||||||
|
pokemon.tint(getPokeballTintColor(pokeballType));
|
||||||
|
pokemon.setVisible(true);
|
||||||
|
pokemon.untint(250, "Sine.easeOut");
|
||||||
|
|
||||||
|
const pokeballAtlasKey = getPokeballAtlasKey(pokeballType);
|
||||||
|
pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`);
|
||||||
|
scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`));
|
||||||
|
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokemon,
|
||||||
|
duration: 250,
|
||||||
|
ease: "Sine.easeOut",
|
||||||
|
scale: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.currentBattle.lastUsedPokeball = pokeballType;
|
||||||
|
removePb(scene, pokeball);
|
||||||
|
|
||||||
|
scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.name }), null, () => resolve(), null, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType): Promise<void> {
|
||||||
|
scene.unshiftPhase(new VictoryPhase(scene, BattlerIndex.ENEMY));
|
||||||
|
|
||||||
|
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
|
||||||
|
|
||||||
|
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
|
||||||
|
scene.validateAchv(achvs.HIDDEN_ABILITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pokemon.species.subLegendary) {
|
||||||
|
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pokemon.species.legendary) {
|
||||||
|
scene.validateAchv(achvs.CATCH_LEGENDARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pokemon.species.mythical) {
|
||||||
|
scene.validateAchv(achvs.CATCH_MYTHICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.pokemonInfoContainer.show(pokemon, true);
|
||||||
|
|
||||||
|
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => {
|
||||||
|
const end = () => {
|
||||||
|
scene.pokemonInfoContainer.hide();
|
||||||
|
removePb(scene, pokeball);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
const removePokemon = () => {
|
||||||
|
scene.field.remove(pokemon, true);
|
||||||
|
};
|
||||||
|
const addToParty = () => {
|
||||||
|
const newPokemon = pokemon.addToParty(pokeballType);
|
||||||
|
const modifiers = scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false);
|
||||||
|
if (scene.getParty().filter(p => p.isShiny()).length === 6) {
|
||||||
|
scene.validateAchv(achvs.SHINY_PARTY);
|
||||||
|
}
|
||||||
|
Promise.all(modifiers.map(m => scene.addModifier(m, true))).then(() => {
|
||||||
|
scene.updateModifiers(true);
|
||||||
|
removePokemon();
|
||||||
|
if (newPokemon) {
|
||||||
|
newPokemon.loadAssets().then(end);
|
||||||
|
} else {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||||
|
if (scene.getParty().length === 6) {
|
||||||
|
const promptRelease = () => {
|
||||||
|
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => {
|
||||||
|
scene.pokemonInfoContainer.makeRoomForConfirmUi();
|
||||||
|
scene.ui.setMode(Mode.CONFIRM, () => {
|
||||||
|
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
|
||||||
|
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
|
if (slotIndex < 6) {
|
||||||
|
addToParty();
|
||||||
|
} else {
|
||||||
|
promptRelease();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, () => {
|
||||||
|
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
|
removePokemon();
|
||||||
|
end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
promptRelease();
|
||||||
|
} else {
|
||||||
|
addToParty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 0, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) {
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokeball,
|
||||||
|
duration: 250,
|
||||||
|
delay: 250,
|
||||||
|
ease: "Sine.easeIn",
|
||||||
|
alpha: 0,
|
||||||
|
onComplete: () => pokeball.destroy()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
// Ease pokemon out
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokemon,
|
||||||
|
x: "+=16",
|
||||||
|
y: "-=16",
|
||||||
|
alpha: 0,
|
||||||
|
duration: 1000,
|
||||||
|
ease: "Sine.easeIn",
|
||||||
|
scale: pokemon.getSpriteScale(),
|
||||||
|
onComplete: () => {
|
||||||
|
pokemon.setVisible(false);
|
||||||
|
scene.field.remove(pokemon, true);
|
||||||
|
showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.name }), 600, false)
|
||||||
|
.then(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
// Ease pokemon out
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: pokemon,
|
||||||
|
x: "+=16",
|
||||||
|
y: "-=16",
|
||||||
|
alpha: 0,
|
||||||
|
duration: 1000,
|
||||||
|
ease: "Sine.easeIn",
|
||||||
|
scale: pokemon.getSpriteScale(),
|
||||||
|
onComplete: () => {
|
||||||
|
pokemon.setVisible(false);
|
||||||
|
scene.field.remove(pokemon, true);
|
||||||
|
showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.name }), 600, false)
|
||||||
|
.then(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -8,5 +8,6 @@ export enum MysteryEncounterType {
|
|||||||
DEPARTMENT_STORE_SALE,
|
DEPARTMENT_STORE_SALE,
|
||||||
SHADY_VITAMIN_DEALER,
|
SHADY_VITAMIN_DEALER,
|
||||||
FIELD_TRIP,
|
FIELD_TRIP,
|
||||||
|
SAFARI_ZONE,
|
||||||
LOST_AT_SEA //might be generalized later on
|
LOST_AT_SEA //might be generalized later on
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,8 @@ export class MysteryEncounterSpriteConfig {
|
|||||||
x?: number;
|
x?: number;
|
||||||
/** Y offset */
|
/** Y offset */
|
||||||
y?: number;
|
y?: number;
|
||||||
|
/** Y shadow offset */
|
||||||
|
yShadowOffset?: number;
|
||||||
/** Sprite scale. `0` - `n` */
|
/** Sprite scale. `0` - `n` */
|
||||||
scale?: number;
|
scale?: number;
|
||||||
/** If you are using an item sprite, set to `true` */
|
/** If you are using an item sprite, set to `true` */
|
||||||
@ -68,10 +70,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;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -90,12 +92,13 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
||||||
|
|
||||||
this.spriteConfigs?.forEach((config) => {
|
this.spriteConfigs?.forEach((config) => {
|
||||||
const { spriteKey, isItem, hasShadow, scale, x, y, alpha } = config;
|
const { spriteKey, isItem, hasShadow, scale, x, y, yShadowOffset, alpha } = config;
|
||||||
|
|
||||||
let sprite: GameObjects.Sprite;
|
let sprite: GameObjects.Sprite;
|
||||||
let tintSprite: GameObjects.Sprite;
|
let tintSprite: GameObjects.Sprite;
|
||||||
|
|
||||||
if (!isItem) {
|
if (!isItem) {
|
||||||
sprite = getSprite(spriteKey, hasShadow);
|
sprite = getSprite(spriteKey, hasShadow, yShadowOffset);
|
||||||
tintSprite = getSprite(spriteKey);
|
tintSprite = getSprite(spriteKey);
|
||||||
} else {
|
} else {
|
||||||
sprite = getItemSprite(spriteKey);
|
sprite = getItemSprite(spriteKey);
|
||||||
@ -116,8 +119,8 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
tintSprite.setPosition(origin + x, tintSprite.y);
|
tintSprite.setPosition(origin + x, tintSprite.y);
|
||||||
}
|
}
|
||||||
if (y) {
|
if (y) {
|
||||||
sprite.setPosition(sprite.x, y);
|
sprite.setPosition(sprite.x, sprite.y + y);
|
||||||
tintSprite.setPosition(tintSprite.x, y);
|
tintSprite.setPosition(tintSprite.x, tintSprite.y + 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 = {
|
|||||||
// 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 = {
|
|||||||
"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 = {
|
|||||||
"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.\n\nBeware, though. These Pokémon may flee before you're able to catch them!",
|
||||||
|
"safari_zone_query": "Would you like to enter?",
|
||||||
|
"safari_zone_option_1_label": "Enter",
|
||||||
|
"safari_zone_option_1_tooltip": "(-) Pay {{option1Money, money}}\n@[SUMMARY_GREEN]{(?) Safari Zone}",
|
||||||
|
"safari_zone_option_2_label": "Leave",
|
||||||
|
"safari_zone_option_2_tooltip": "(-) No Rewards",
|
||||||
|
"safari_zone_option_1_selected_message": "Time to test your luck!",
|
||||||
|
"safari_zone_option_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 = {
|
|||||||
$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",
|
||||||
|
@ -66,9 +66,10 @@ import { Species } from "#enums/species";
|
|||||||
import { TrainerType } from "#enums/trainer-type";
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
import { MysteryEncounterVariant } from "#app/data/mystery-encounters/mystery-encounter";
|
import { MysteryEncounterVariant } from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phase";
|
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phase";
|
||||||
import { getEncounterText, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/mystery-encounter-utils";
|
import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
|
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
|
||||||
import { isNullOrUndefined } from "./utils";
|
import { isNullOrUndefined } from "./utils";
|
||||||
|
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
const { t } = i18next;
|
const { t } = i18next;
|
||||||
|
|
||||||
@ -1650,7 +1651,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 +1681,7 @@ 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) {
|
if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || (this.scene.currentBattle.waveIndex % 10) === 1 || this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||||
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
||||||
this.queuePostSummon();
|
this.queuePostSummon();
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@ import i18next from "i18next";
|
|||||||
import BattleScene from "../battle-scene";
|
import BattleScene from "../battle-scene";
|
||||||
import { Phase } from "../phase";
|
import { Phase } from "../phase";
|
||||||
import { Mode } from "../ui/ui";
|
import { Mode } from "../ui/ui";
|
||||||
import {
|
import { hideMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
getEncounterText
|
|
||||||
} from "../data/mystery-encounters/mystery-encounter-utils";
|
|
||||||
import { CheckSwitchPhase, NewBattlePhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases";
|
import { CheckSwitchPhase, NewBattlePhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases";
|
||||||
import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option";
|
import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option";
|
||||||
import { MysteryEncounterVariant } from "../data/mystery-encounters/mystery-encounter";
|
import { MysteryEncounterVariant } from "../data/mystery-encounters/mystery-encounter";
|
||||||
@ -15,6 +13,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 { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will handle (in order):
|
* Will handle (in order):
|
||||||
@ -26,8 +25,17 @@ 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) {
|
optionSelectSettings: OptionSelectSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param scene
|
||||||
|
* @param optionSelectSettings - allows overriding the typical options of an encounter with new ones
|
||||||
|
* Mostly useful for having repeated queries during a single encounter, where the queries and options may differ each time
|
||||||
|
*/
|
||||||
|
constructor(scene: BattleScene, optionSelectSettings?: OptionSelectSettings) {
|
||||||
super(scene);
|
super(scene);
|
||||||
|
this.optionSelectSettings = optionSelectSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@ -37,12 +45,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
|
||||||
|
const offset = this.scene.currentBattle.mysteryEncounter.seedOffset ?? this.scene.currentBattle.waveIndex * 1000;
|
||||||
|
this.scene.currentBattle.mysteryEncounter.seedOffset = offset + 512;
|
||||||
|
|
||||||
|
if (!this.optionSelectSettings) {
|
||||||
|
// Sets flag that ME was encountered, only if this is not a followup option select phase
|
||||||
// Can be used in later MEs to check for requirements to spawn, etc.
|
// 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]);
|
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.optionSelectSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOptionSelect(option: MysteryEncounterOption, index: number): boolean {
|
handleOptionSelect(option: MysteryEncounterOption, index: number): boolean {
|
||||||
@ -61,24 +75,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);
|
||||||
@ -134,47 +148,21 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
|
|||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
if (this.scene.currentBattle.mysteryEncounter.hideIntroVisuals) {
|
if (this.scene.currentBattle.mysteryEncounter.hideIntroVisuals) {
|
||||||
this.hideMysteryEncounterIntroVisuals().then(() => {
|
hideMysteryEncounterIntroVisuals(this.scene).then(() => {
|
||||||
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);
|
||||||
});
|
});
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hideMysteryEncounterIntroVisuals(): Promise<boolean> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const introVisuals = this.scene.currentBattle.mysteryEncounter.introVisuals;
|
|
||||||
if (introVisuals) {
|
|
||||||
// Hide
|
|
||||||
this.scene.tweens.add({
|
|
||||||
targets: introVisuals,
|
|
||||||
x: "+=16",
|
|
||||||
y: "-=16",
|
|
||||||
alpha: 0,
|
|
||||||
ease: "Sine.easeInOut",
|
|
||||||
duration: 750,
|
|
||||||
onComplete: () => {
|
|
||||||
this.scene.field.remove(introVisuals);
|
|
||||||
introVisuals.setVisible(false);
|
|
||||||
introVisuals.destroy();
|
|
||||||
this.scene.currentBattle.mysteryEncounter.introVisuals = null;
|
|
||||||
resolve(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
resolve(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -265,7 +253,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 +378,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 +412,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 ?? 0;
|
||||||
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));
|
||||||
|
@ -1,208 +0,0 @@
|
|||||||
import {beforeAll, describe, expect, it} from "vitest";
|
|
||||||
import { getBattleStatName, getBattleStatLevelChangeDescription } from "#app/data/battle-stat.js";
|
|
||||||
import { BattleStat } from "#app/data/battle-stat.js";
|
|
||||||
import { pokemonInfo as enPokemonInfo } from "#app/locales/en/pokemon-info.js";
|
|
||||||
import { battle as enBattleStat } from "#app/locales/en/battle.js";
|
|
||||||
import { pokemonInfo as dePokemonInfo } from "#app/locales/de/pokemon-info.js";
|
|
||||||
import { battle as deBattleStat } from "#app/locales/de/battle.js";
|
|
||||||
import { pokemonInfo as esPokemonInfo } from "#app/locales/es/pokemon-info.js";
|
|
||||||
import { battle as esBattleStat } from "#app/locales/es/battle.js";
|
|
||||||
import { pokemonInfo as frPokemonInfo } from "#app/locales/fr/pokemon-info.js";
|
|
||||||
import { battle as frBattleStat } from "#app/locales/fr/battle.js";
|
|
||||||
import { pokemonInfo as itPokemonInfo } from "#app/locales/it/pokemon-info.js";
|
|
||||||
import { battle as itBattleStat } from "#app/locales/it/battle.js";
|
|
||||||
import { pokemonInfo as koPokemonInfo } from "#app/locales/ko/pokemon-info.js";
|
|
||||||
import { battle as koBattleStat } from "#app/locales/ko/battle.js";
|
|
||||||
import { pokemonInfo as ptBrPokemonInfo } from "#app/locales/pt_BR/pokemon-info.js";
|
|
||||||
import { battle as ptBrBattleStat } from "#app/locales/pt_BR/battle.js";
|
|
||||||
import { pokemonInfo as zhCnPokemonInfo } from "#app/locales/zh_CN/pokemon-info.js";
|
|
||||||
import { battle as zhCnBattleStat } from "#app/locales/zh_CN/battle.js";
|
|
||||||
import { pokemonInfo as zhTwPokemonInfo } from "#app/locales/zh_TW/pokemon-info.js";
|
|
||||||
import { battle as zhTwBattleStat } from "#app/locales/zh_TW/battle.js";
|
|
||||||
|
|
||||||
import i18next, {initI18n} from "#app/plugins/i18n";
|
|
||||||
import { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor";
|
|
||||||
|
|
||||||
interface BattleStatTestUnit {
|
|
||||||
stat: BattleStat,
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BattleStatLevelTestUnit {
|
|
||||||
levels: integer,
|
|
||||||
up: boolean,
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function testBattleStatName(stat: BattleStat, expectMessage: string) {
|
|
||||||
const message = getBattleStatName(stat);
|
|
||||||
console.log(`message ${message}, expected ${expectMessage}`);
|
|
||||||
expect(message).toBe(expectMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testBattleStatLevelChangeDescription(levels: integer, up: boolean, expectMessage: string) {
|
|
||||||
const message = getBattleStatLevelChangeDescription("{{pokemonNameWithAffix}}", "{{stats}}", levels, up);
|
|
||||||
console.log(`message ${message}, expected ${expectMessage}`);
|
|
||||||
expect(message).toBe(expectMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Test for BattleStat Localization", () => {
|
|
||||||
const battleStatUnits: BattleStatTestUnit[] = [];
|
|
||||||
const battleStatLevelUnits: BattleStatLevelTestUnit[] = [];
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
initI18n();
|
|
||||||
|
|
||||||
battleStatUnits.push({ stat: BattleStat.ATK, key: "Stat.ATK" });
|
|
||||||
battleStatUnits.push({ stat: BattleStat.DEF, key: "Stat.DEF" });
|
|
||||||
battleStatUnits.push({ stat: BattleStat.SPATK, key: "Stat.SPATK" });
|
|
||||||
battleStatUnits.push({ stat: BattleStat.SPDEF, key: "Stat.SPDEF" });
|
|
||||||
battleStatUnits.push({ stat: BattleStat.SPD, key: "Stat.SPD" });
|
|
||||||
battleStatUnits.push({ stat: BattleStat.ACC, key: "Stat.ACC" });
|
|
||||||
battleStatUnits.push({ stat: BattleStat.EVA, key: "Stat.EVA" });
|
|
||||||
|
|
||||||
battleStatLevelUnits.push({ levels: 1, up: true, key: "statRose" });
|
|
||||||
battleStatLevelUnits.push({ levels: 2, up: true, key: "statSharplyRose" });
|
|
||||||
battleStatLevelUnits.push({ levels: 3, up: true, key: "statRoseDrastically" });
|
|
||||||
battleStatLevelUnits.push({ levels: 4, up: true, key: "statRoseDrastically" });
|
|
||||||
battleStatLevelUnits.push({ levels: 5, up: true, key: "statRoseDrastically" });
|
|
||||||
battleStatLevelUnits.push({ levels: 6, up: true, key: "statRoseDrastically" });
|
|
||||||
battleStatLevelUnits.push({ levels: 7, up: true, key: "statWontGoAnyHigher" });
|
|
||||||
battleStatLevelUnits.push({ levels: 1, up: false, key: "statFell" });
|
|
||||||
battleStatLevelUnits.push({ levels: 2, up: false, key: "statHarshlyFell" });
|
|
||||||
battleStatLevelUnits.push({ levels: 3, up: false, key: "statSeverelyFell" });
|
|
||||||
battleStatLevelUnits.push({ levels: 4, up: false, key: "statSeverelyFell" });
|
|
||||||
battleStatLevelUnits.push({ levels: 5, up: false, key: "statSeverelyFell" });
|
|
||||||
battleStatLevelUnits.push({ levels: 6, up: false, key: "statSeverelyFell" });
|
|
||||||
battleStatLevelUnits.push({ levels: 7, up: false, key: "statWontGoAnyLower" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in English", async () => {
|
|
||||||
i18next.changeLanguage("en");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, enPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in English", async () => {
|
|
||||||
i18next.changeLanguage("en");
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, enBattleStat[unit.key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in Español", async () => {
|
|
||||||
i18next.changeLanguage("es");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, esPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in Español", async () => {
|
|
||||||
i18next.changeLanguage("es");
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, esBattleStat[unit.key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in Italiano", async () => {
|
|
||||||
i18next.changeLanguage("it");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, itPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in Italiano", async () => {
|
|
||||||
i18next.changeLanguage("it");
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, itBattleStat[unit.key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in Français", async () => {
|
|
||||||
i18next.changeLanguage("fr");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, frPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in Français", async () => {
|
|
||||||
i18next.changeLanguage("fr");
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, frBattleStat[unit.key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in Deutsch", async () => {
|
|
||||||
i18next.changeLanguage("de");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, dePokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in Deutsch", async () => {
|
|
||||||
i18next.changeLanguage("de");
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, deBattleStat[unit.key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in Português (BR)", async () => {
|
|
||||||
i18next.changeLanguage("pt-BR");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, ptBrPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in Português (BR)", async () => {
|
|
||||||
i18next.changeLanguage("pt-BR");
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, ptBrBattleStat[unit.key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in 简体中文", async () => {
|
|
||||||
i18next.changeLanguage("zh-CN");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, zhCnPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in 简体中文", async () => {
|
|
||||||
i18next.changeLanguage("zh-CN");
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, zhCnBattleStat[unit.key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in 繁體中文", async () => {
|
|
||||||
i18next.changeLanguage("zh-TW");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, zhTwPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in 繁體中文", async () => {
|
|
||||||
i18next.changeLanguage("zh-TW");
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, zhTwBattleStat[unit.key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatName() in 한국어", async () => {
|
|
||||||
await i18next.changeLanguage("ko");
|
|
||||||
battleStatUnits.forEach(unit => {
|
|
||||||
testBattleStatName(unit.stat, koPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Test getBattleStatLevelChangeDescription() in 한국어", async () => {
|
|
||||||
i18next.changeLanguage("ko", () => {
|
|
||||||
battleStatLevelUnits.forEach(unit => {
|
|
||||||
const processor = new KoreanPostpositionProcessor();
|
|
||||||
const message = processor.process(koBattleStat[unit.key]);
|
|
||||||
testBattleStatLevelChangeDescription(unit.levels, unit.up, message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,42 +0,0 @@
|
|||||||
import {afterEach, beforeAll, describe, expect, it} from "vitest";
|
|
||||||
import Phaser from "phaser";
|
|
||||||
import GameManager from "#app/test/utils/gameManager";
|
|
||||||
import {Species} from "#enums/species";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import {initI18n} from "#app/plugins/i18n";
|
|
||||||
|
|
||||||
describe("Lokalization - french", () => {
|
|
||||||
let phaserGame: Phaser.Game;
|
|
||||||
let game: GameManager;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
initI18n();
|
|
||||||
phaserGame = new Phaser.Game({
|
|
||||||
type: Phaser.HEADLESS,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
game.phaseInterceptor.restoreOg();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("test bulbasaur name english", async () => {
|
|
||||||
game = new GameManager(phaserGame);
|
|
||||||
await game.startBattle([
|
|
||||||
Species.BULBASAUR,
|
|
||||||
]);
|
|
||||||
expect(game.scene.getParty()[0].name).toBe("Bulbasaur");
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("test bulbasaure name french", async () => {
|
|
||||||
const locale = "fr";
|
|
||||||
i18next.changeLanguage(locale);
|
|
||||||
localStorage.setItem("prLang", locale);
|
|
||||||
game = new GameManager(phaserGame);
|
|
||||||
|
|
||||||
await game.startBattle([
|
|
||||||
Species.BULBASAUR,
|
|
||||||
]);
|
|
||||||
expect(game.scene.getParty()[0].name).toBe("Bulbizarre");
|
|
||||||
}, 20000);
|
|
||||||
});
|
|
@ -1,300 +0,0 @@
|
|||||||
import { beforeAll, describe, afterEach, expect, it, vi } from "vitest";
|
|
||||||
import {
|
|
||||||
StatusEffect,
|
|
||||||
getStatusEffectActivationText,
|
|
||||||
getStatusEffectDescriptor,
|
|
||||||
getStatusEffectHealText,
|
|
||||||
getStatusEffectObtainText,
|
|
||||||
getStatusEffectOverlapText,
|
|
||||||
} from "#app/data/status-effect";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import { mockI18next } from "../utils/testUtils";
|
|
||||||
|
|
||||||
const pokemonName = "PKM";
|
|
||||||
const sourceText = "SOURCE";
|
|
||||||
|
|
||||||
describe("status-effect", () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
i18next.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("NONE", () => {
|
|
||||||
const statusEffect = StatusEffect.NONE;
|
|
||||||
|
|
||||||
it("should return the obtain text", () => {
|
|
||||||
mockI18next();
|
|
||||||
|
|
||||||
const text = getStatusEffectObtainText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:none.obtain");
|
|
||||||
|
|
||||||
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
|
|
||||||
expect(emptySourceText).toBe("statusEffect:none.obtain");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the source-obtain text", () => {
|
|
||||||
mockI18next();
|
|
||||||
|
|
||||||
const text = getStatusEffectObtainText(statusEffect, pokemonName, sourceText);
|
|
||||||
expect(text).toBe("statusEffect:none.obtainSource");
|
|
||||||
|
|
||||||
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
|
|
||||||
expect(emptySourceText).not.toBe("statusEffect:none.obtainSource");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the activation text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectActivationText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:none.activation");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the overlap text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectOverlapText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:none.overlap");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the heal text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectHealText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:none.heal");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the descriptor", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectDescriptor(statusEffect);
|
|
||||||
expect(text).toBe("statusEffect:none.description");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("POISON", () => {
|
|
||||||
const statusEffect = StatusEffect.POISON;
|
|
||||||
|
|
||||||
it("should return the obtain text", () => {
|
|
||||||
mockI18next();
|
|
||||||
|
|
||||||
const text = getStatusEffectObtainText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:poison.obtain");
|
|
||||||
|
|
||||||
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
|
|
||||||
expect(emptySourceText).toBe("statusEffect:poison.obtain");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the activation text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectActivationText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:poison.activation");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the descriptor", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectDescriptor(statusEffect);
|
|
||||||
expect(text).toBe("statusEffect:poison.description");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the heal text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectHealText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:poison.heal");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the overlap text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectOverlapText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:poison.overlap");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("TOXIC", () => {
|
|
||||||
const statusEffect = StatusEffect.TOXIC;
|
|
||||||
|
|
||||||
it("should return the obtain text", () => {
|
|
||||||
mockI18next();
|
|
||||||
|
|
||||||
const text = getStatusEffectObtainText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:toxic.obtain");
|
|
||||||
|
|
||||||
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
|
|
||||||
expect(emptySourceText).toBe("statusEffect:toxic.obtain");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the activation text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectActivationText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:toxic.activation");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the descriptor", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectDescriptor(statusEffect);
|
|
||||||
expect(text).toBe("statusEffect:toxic.description");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the heal text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectHealText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:toxic.heal");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the overlap text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectOverlapText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:toxic.overlap");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("PARALYSIS", () => {
|
|
||||||
const statusEffect = StatusEffect.PARALYSIS;
|
|
||||||
|
|
||||||
it("should return the obtain text", () => {
|
|
||||||
mockI18next();
|
|
||||||
|
|
||||||
const text = getStatusEffectObtainText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:paralysis.obtain");
|
|
||||||
|
|
||||||
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
|
|
||||||
expect(emptySourceText).toBe("statusEffect:paralysis.obtain");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the activation text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectActivationText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:paralysis.activation");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the descriptor", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectDescriptor(statusEffect);
|
|
||||||
expect(text).toBe("statusEffect:paralysis.description");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the heal text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectHealText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:paralysis.heal");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the overlap text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectOverlapText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:paralysis.overlap");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("SLEEP", () => {
|
|
||||||
const statusEffect = StatusEffect.SLEEP;
|
|
||||||
|
|
||||||
it("should return the obtain text", () => {
|
|
||||||
mockI18next();
|
|
||||||
|
|
||||||
const text = getStatusEffectObtainText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:sleep.obtain");
|
|
||||||
|
|
||||||
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
|
|
||||||
expect(emptySourceText).toBe("statusEffect:sleep.obtain");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the activation text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectActivationText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:sleep.activation");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the descriptor", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectDescriptor(statusEffect);
|
|
||||||
expect(text).toBe("statusEffect:sleep.description");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the heal text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectHealText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:sleep.heal");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the overlap text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectOverlapText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:sleep.overlap");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("FREEZE", () => {
|
|
||||||
const statusEffect = StatusEffect.FREEZE;
|
|
||||||
|
|
||||||
it("should return the obtain text", () => {
|
|
||||||
mockI18next();
|
|
||||||
|
|
||||||
const text = getStatusEffectObtainText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:freeze.obtain");
|
|
||||||
|
|
||||||
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
|
|
||||||
expect(emptySourceText).toBe("statusEffect:freeze.obtain");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the activation text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectActivationText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:freeze.activation");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the descriptor", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectDescriptor(statusEffect);
|
|
||||||
expect(text).toBe("statusEffect:freeze.description");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the heal text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectHealText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:freeze.heal");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the overlap text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectOverlapText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:freeze.overlap");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("BURN", () => {
|
|
||||||
const statusEffect = StatusEffect.BURN;
|
|
||||||
|
|
||||||
it("should return the obtain text", () => {
|
|
||||||
mockI18next();
|
|
||||||
|
|
||||||
const text = getStatusEffectObtainText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:burn.obtain");
|
|
||||||
|
|
||||||
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
|
|
||||||
expect(emptySourceText).toBe("statusEffect:burn.obtain");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the activation text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectActivationText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:burn.activation");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the descriptor", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectDescriptor(statusEffect);
|
|
||||||
expect(text).toBe("statusEffect:burn.description");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the heal text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectHealText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:burn.heal");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the overlap text", () => {
|
|
||||||
mockI18next();
|
|
||||||
const text = getStatusEffectOverlapText(statusEffect, pokemonName);
|
|
||||||
expect(text).toBe("statusEffect:burn.overlap");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.resetAllMocks();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,11 +1,6 @@
|
|||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import GameManager from "#app/test/utils/gameManager";
|
import GameManager from "#app/test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import {
|
|
||||||
getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon,
|
|
||||||
getRandomPlayerPokemon, getRandomSpeciesByStarterTier, getEncounterText,
|
|
||||||
koPlayerPokemon, queueEncounterMessage, showEncounterDialogue, showEncounterText,
|
|
||||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
|
||||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
@ -14,6 +9,8 @@ import IMysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
|||||||
import { MessagePhase } from "#app/phases";
|
import { MessagePhase } from "#app/phases";
|
||||||
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||||
import { Type } from "#app/data/type";
|
import { Type } from "#app/data/type";
|
||||||
|
import { getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon, getRandomPlayerPokemon, getRandomSpeciesByStarterTier, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
import { getEncounterText, queueEncounterMessage, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
describe("Mystery Encounter Utils", () => {
|
describe("Mystery Encounter Utils", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
|
@ -10,7 +10,8 @@ import MysteryEncounterOption, { EncounterOptionMode } from "../data/mystery-enc
|
|||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { isNullOrUndefined } from "../utils";
|
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 { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
|
||||||
export default class MysteryEncounterUiHandler extends UiHandler {
|
export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
private cursorContainer: Phaser.GameObjects.Container;
|
private cursorContainer: Phaser.GameObjects.Container;
|
||||||
@ -27,7 +28,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: OptionSelectSettings;
|
||||||
|
private encounterOptions: MysteryEncounterOption[] = [];
|
||||||
private optionsMeetsReqs: boolean[];
|
private optionsMeetsReqs: boolean[];
|
||||||
|
|
||||||
protected viewPartyIndex: integer = 0;
|
protected viewPartyIndex: integer = 0;
|
||||||
@ -70,21 +72,26 @@ 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 OptionSelectSettings ?? {};
|
||||||
|
const showDescriptionContainer = isNullOrUndefined(this.overrideSettings?.hideDescription) ? true : !this.overrideSettings?.hideDescription;
|
||||||
|
const slideInDescription = isNullOrUndefined(this.overrideSettings?.slideInDescription) ? true : this.overrideSettings?.slideInDescription;
|
||||||
|
const startingCursorIndex = this.overrideSettings?.startingCursorIndex ?? 0;
|
||||||
|
|
||||||
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(() => {
|
||||||
this.unblockInput();
|
this.unblockInput();
|
||||||
}, 1500);
|
}, 1000);
|
||||||
}
|
}
|
||||||
this.displayOptionTooltip();
|
this.displayOptionTooltip();
|
||||||
|
|
||||||
@ -100,12 +107,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: OptionSelectSettings = {
|
||||||
|
...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 +264,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 +307,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 +318,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;
|
||||||
@ -367,7 +378,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||||||
descriptionTextMaskRect.setScale(6);
|
descriptionTextMaskRect.setScale(6);
|
||||||
descriptionTextMaskRect.fillStyle(0xFFFFFF);
|
descriptionTextMaskRect.fillStyle(0xFFFFFF);
|
||||||
descriptionTextMaskRect.beginPath();
|
descriptionTextMaskRect.beginPath();
|
||||||
descriptionTextMaskRect.fillRect(6, 54, 206, 60);
|
descriptionTextMaskRect.fillRect(6, 53, 206, 57);
|
||||||
|
|
||||||
const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask();
|
const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask();
|
||||||
|
|
||||||
@ -424,7 +435,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 +485,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
Block a user