Merge beta into sparklyswirlbug2

This commit is contained in:
Christopher Schmidt 2024-08-12 12:43:14 -04:00
commit 9edbc064a6
567 changed files with 37387 additions and 11333 deletions

View File

@ -21,7 +21,7 @@ body:
- type: textarea
id: session-file
attributes:
label: User data export file
label: Session export file
description: Open Menu → ManageData → Export Session → Select slot. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
validations:
@ -29,7 +29,7 @@ body:
- type: textarea
id: data-file
attributes:
label: Session export file
label: User data export file
description: Open Menu → ManageData → Export Data. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
validations:

View File

@ -2,10 +2,10 @@
<!-- Make sure that this PR is not overlapping with someone else's work -->
<!-- Please try to keep the PR self-contained (and small) -->
## What are the changes?
## What are the changes the user will see?
<!-- Summarize what are the changes from a user perspective on the application -->
## Why am I doing these changes the user will see?
## Why am I making these changes?
<!-- Explain why you decided to introduce these changes -->
<!-- Does it come from an issue or another PR? Please link it -->
<!-- Explain why you believe this can enhance user experience -->

View File

@ -81,6 +81,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
- kyledove
- Brumirage
- pkmn_realidea (Paid Commissions)
- IceJkai
### 🎨 Trainer Portraits
- pkmn_realidea (Paid Commissions)

310
index.css
View File

@ -1,16 +1,8 @@
/* Global */
:root {
--color-base: hsl(0, 0%, 55%);
--color-light: hsl(0, 0%, 90%);
--color-dark: hsl(0, 0%, 10%);
--controls-size: 10vh;
--text-shadow-size: 0.65vh;
}
@media (orientation: landscape) {
:root {
--controls-size: 20vh;
--text-shadow-size: 1.3vh;
}
}
html {
@ -43,33 +35,181 @@ body {
transform-origin: top !important;
}
#layout:fullscreen #dpad, #layout:fullscreen {
bottom: 6rem;
}
input:-internal-autofill-selected {
-webkit-background-clip: text;
background-clip: text;
}
/* Need adjust input font-size */
input {
font-size: 3.2rem;
}
.hidden {
display: none !important;
}
input:-internal-autofill-selected {
-webkit-background-clip: text;
background-clip: text;
}
/* Touch Controls: */
#touchControls {
--text-shadow-size: 0.65vh;
--controls-size: 10vh;
--touch-control-opacity: 0.6;
--controls-padding: 1rem;
--controls-size-with-padding: calc(var(--controls-size) + var(--controls-padding));
--controls-size-with-wide-padding: calc(var(--controls-size) *1.2 + var(--controls-padding));
--control-group-extra-size: calc(var(--controls-size) * 0.8);
--control-group-extra-wide-size: calc(var(--controls-size) * 1.2);
--control-group-extra-2-offset: calc(var(--controls-size-with-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
--control-group-extra-1-offset: calc(var(--controls-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
--small-control-size: calc(var(--controls-size) / 3);
--rect-control-size: calc(var(--controls-size) * 0.74);
font-family: 'emerald';
font-size: var(--controls-size);
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
color: var(--color-light);
}
@media (orientation: landscape) {
#touchControls {
--controls-size: 20vh;
--text-shadow-size: 1.3vh;
--small-button-offset: 4vh;
}
}
#touchControls:not(.visible) {
display: none;
}
#dpad, #apad {
#touchControls .active {
opacity: var(--touch-control-opacity);
}
.control-group {
position: fixed;
bottom: 1rem;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
width: var(--controls-size);
}
.control-group-dpad {
width: calc(2 * var(--controls-size));
height: calc(2 * var(--controls-size));
}
.control-group-extra {
width: var(--control-group-extra-size);
height: var(--control-group-extra-size);
}
/* Hide buttons on specific UIs */
/* Show #apadPreviousTab and #apadNextTab only in settings, except in touch configuration panel */
#touchControls:not([data-ui-mode^='SETTINGS']) #apadPreviousTab,
#touchControls:not([data-ui-mode^='SETTINGS']) #apadNextTab,
#touchControls:is(.config-mode) #apadPreviousTab,
#touchControls:is(.config-mode) #apadNextTab {
display: none;
}
/* Show #apadInfo only in battle */
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']) #apadInfo {
display: none;
}
/* Show #apadStats only in battle and shop */
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apadStats {
display: none;
}
/* Show cycle buttons only on STARTER_SELECT and on touch configuration panel */
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadOpenFilters,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleForm,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleShiny,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleAbility,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleGender,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleVariant {
display: none;
}
/* Configuration toolbar */
#configToolbar {
width: 100%;
position: fixed;
top: 1rem;
left: 0;
z-index: 9;
user-select: none;
}
#configToolbar .column {
display: flex;
flex-direction: column;
align-items: center;
gap: 10%;
padding: 0 var(--controls-padding);
}
#configToolbar .button-row {
display: flex;
justify-content: space-evenly;
width: 100%;
}
#configToolbar .info-row {
display: flex;
justify-content: flex-start;
width: 100%;
}
#configToolbar .button {
z-index: 3;
background-color: var(--color-base);
display: flex;
justify-content: center;
align-items: center;
text-align: center;
border-radius: 10%;
height: var(--small-control-size);
font-size: var(--small-control-size);
border-radius: 8px;
padding: 2px 8px;
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
}
@media (orientation: portrait) {
#dpad, #apad {
bottom: calc(1rem + env(safe-area-inset-bottom));
}
#configToolbar .button:active {
opacity: var(--touch-control-opacity)
}
#configToolbar .orientation-label {
font-size: var(--small-control-size);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
}
/* dpad */
#dpad {
left: 1rem;
}
#apad {
right: 1rem;
z-index: 3;
opacity: 0.8;
}
#dpad svg {
@ -78,114 +218,84 @@ input {
fill: var(--color-base);
}
#dpad svg rect {
opacity: 0.6;
}
/* apad buttons */
#apad > * {
width: var(--controls-size);
height: var(--controls-size);
}
#apad .apadBtn {
width: var(--controls-size);
height: var(--controls-size);
.apad-button {
background-color: var(--color-base);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
right: 0;
bottom: 0;
width: var(--controls-size);
height: var(--controls-size);
opacity: 0.8;
border-radius: 8px;
}
#apad .apadLabel {
font-family: 'emerald';
font-size: var(--controls-size);
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
color: var(--color-light);
.apad-small {
width: var(--small-control-size);
height: var(--small-control-size);
}
.apad-label {
user-select: none;
height: 100%;
margin-right: -2px;
}
#apad .apadLabelSmall {
font-size: calc(var(--controls-size) / 3);
.apad-small > .apad-label {
font-size: var(--small-control-size);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
}
#apad #apadLabelAction, #apad #apadLabelCancel {
margin-left: calc(var(--controls-size) / 3);
line-height: 0.9;
}
#apad > :nth-child(2) {
position: relative;
right: var(--controls-size);
}
#apad .apadRectBtn {
position: relative;
.apad-rectangle {
text-align: center;
padding-right: 10%;
border-radius: 10%;
bottom: calc(var(--controls-size) * 0.05);
width: calc(var(--controls-size) * 0.6);
height: calc(var(--controls-size) * 0.3);
width: var(--rect-control-size);
height: var(--small-control-size);
}
#apad .apadSqBtn {
border-radius: 10%;
width: calc(var(--controls-size) * 0.3);
height: calc(var(--controls-size) * 0.3);
.apad-square {
width: var(--small-control-size);
height: var(--small-control-size);
}
#apad .apadBtnContainer {
position: relative;
display: flex;
.apad-circle {
width: var(--controls-size);
height: var(--controls-size);
border-radius: 50%;
}
#apad .apadRectBtnContainer {
flex-wrap: wrap;
margin-top: calc(var(--controls-size) * -0.8);
left: calc(var(--controls-size) * 0.175);
height: calc(var(--controls-size) * 0.8);
/* Defaults:*/
#control-group-dpad {
left: var(--controls-padding);
bottom: var(--controls-padding);
}
#apad .apadSqBtnContainer {
flex-wrap: wrap;
justify-content: space-evenly;
align-items: center;
margin-bottom: calc(var(--controls-size) * -0.8);
top: calc(var(--controls-size) * -0.9);
width: calc(var(--controls-size) * 0.8);
height: calc(var(--controls-size) * 0.8);
#control-group-action {
right: var(--controls-padding);
bottom: var(--controls-size-with-padding);
}
#apad .apadRectBtnContainer > #apadMenu {
align-self: flex-end;
#control-group-cancel {
right: var(--controls-size-with-wide-padding);
bottom: var(--controls-padding);
}
#apad .apadRectBtnContainer > .apadSqBtn:not(:first-child) {
margin-left: 10%;
#control-group-extra-1 {
right: var(--control-group-extra-1-offset);
bottom: var(--control-group-extra-1-offset);
}
#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn:not(.apadBattle),
#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer > .apadSqBtn:not(.apadBattle)
{
display: none;
#control-group-extra-2 {
width: var(--control-group-extra-wide-size);
right: var(--control-group-extra-2-offset);
bottom: var(--control-group-extra-2-offset);
}
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apad .apadBattle {
display: none;
}
#apad .apadRectBtnContainer + .apadSqBtnContainer {
top: calc(var(--controls-size) * -1.9);
left: calc(var(--controls-size) * -0.9);
}
#apad .apadBtnContainer .apadLabel {
margin-left: calc(var(--controls-size) / 12);
line-height: 0.8;
}
#dpad path:not(.active), #apad .apadBtn:not(.active) {
opacity: 0.6;
}
/* Layout */
#layout:fullscreen #dpad, #layout:fullscreen #apad {
bottom: 6rem;

View File

@ -64,54 +64,84 @@
<body>
<div id="app"></div>
<div id="touchControls">
<div id="dpad" class="unselectable">
<div class="left">
<div id="control-group-dpad" class="control-group control-group-dpad">
<div id="dpad" data-control-key="DPAD">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
<path id="dpadUp" data-key="UP" d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
<path id="dpadRight" data-key="RIGHT" d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
<path id="dpadDown" data-key="DOWN" d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
<path id="dpadLeft" data-key="LEFT" d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
<path id="dpadUp" data-key="UP"
d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
<path id="dpadRight" data-key="RIGHT"
d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
<path id="dpadDown" data-key="DOWN"
d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
<path id="dpadLeft" data-key="LEFT"
d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
<rect id="dpadCenter" x="24" y="24" width="24" height="24" />
</svg>
</div>
</div>
</div>
<div id="apad" class="unselectable">
<div id="apadAction" class="apadCircBtn apadBtn" data-key="ACTION">
<text id="apadLabelAction" class="apadLabel">A</text>
</div>
<div id="apadCancel" class="apadCircBtn apadBtn" data-key="CANCEL">
<text id="apadLabelCancel" class="apadLabel">B</text>
</div>
<div class="apadBtnContainer apadRectBtnContainer">
<div id="apadCycleShiny" class="apadSqBtn apadBtn" data-key="CYCLE_SHINY">
<text class="apadLabel apadLabelSmall">R</text>
</div>
<div id="apadCycleVariant" class="apadSqBtn apadBtn" data-key="V">
<text class="apadLabel apadLabelSmall">V</text>
</div>
<div id="apadStats" class="apadRectBtn apadBtn apadBattle" data-key="STATS">
<text class="apadLabel apadLabelSmall">C</text>
</div>
<div id="apadMenu" class="apadRectBtn apadBtn" data-key="MENU">
<text class="apadLabel apadLabelSmall">Menu</text>
<div class="right">
<div id="control-group-action" class="control-group">
<div id="apadAction" class="apad-button apad-circle" data-key="ACTION">
<span class="apad-label">A</span>
</div>
</div>
<div class="apadBtnContainer apadSqBtnContainer">
<div id="apadCycleForm" class="apadSqBtn apadBtn" data-key="CYCLE_FORM">
<text class="apadLabel apadLabelSmall">F</text>
</div>
<div id="apadCycleGender" class="apadSqBtn apadBtn" data-key="CYCLE_GENDER">
<text class="apadLabel apadLabelSmall">G</text>
</div>
<div id="apadCycleAbility" class="apadSqBtn apadBtn" data-key="CYCLE_ABILITY">
<text class="apadLabel apadLabelSmall">E</text>
</div>
<div id="apadCycleNature" class="apadSqBtn apadBtn" data-key="CYCLE_NATURE">
<text class="apadLabel apadLabelSmall">N</text>
</div>
<div id="apadInfo" class="apadRectBtn apadBtn apadBattle" data-key="V">
<text class="apadLabel apadLabelSmall">V</text>
<div id="control-group-cancel" class="control-group">
<div id="apadCancel" class="apad-button apad-circle" data-key="CANCEL">
<span class="apad-label">B</span>
</div>
</div>
<div id="control-group-extra-1" class="control-group control-group-extra">
<!-- buttons to navigate settings tabs -->
<div id="apadPreviousTab" class="apad-button apad-square apad-small" data-key="CYCLE_FORM">
<span class="apad-label">F</span>
</div>
<div id="apadNextTab" class="apad-button apad-square apad-small" data-key="CYCLE_SHINY">
<span class="apad-label">R</span>
</div>
<!-- buttons to open filter menu in starter select -->
<div id="apadOpenFilters" class="apad-button apad-rectangle apad-small" data-key="STATS">
<span class="apad-label">C</span>
</div>
<!-- main menu button -->
<div id="apadMenu" class="apad-button apad-rectangle apad-small" data-key="MENU">
<span class="apad-label">Menu</span>
</div>
</div>
<div id="control-group-extra-2" class="control-group control-group-extra">
<!-- buttons to cycle through pokemon characteristics in starter select -->
<div id="apadCycleForm" class="apad-button apad-square apad-small" data-key="CYCLE_FORM">
<span class="apad-label">F</span>
</div>
<div id="apadCycleGender" class="apad-button apad-square apad-small" data-key="CYCLE_GENDER">
<span class="apad-label">G</span>
</div>
<div id="apadCycleShiny" class="apad-button apad-square apad-small" data-key="CYCLE_SHINY">
<span class="apad-label">R</span>
</div>
<div id="apadCycleAbility" class="apad-button apad-square apad-small" data-key="CYCLE_ABILITY">
<span class="apad-label">E</span>
</div>
<div id="apadCycleNature" class="apad-button apad-square apad-small" data-key="CYCLE_NATURE">
<span class="apad-label">N</span>
</div>
<div id="apadCycleVariant" class="apad-button apad-square apad-small" data-key="V">
<span class="apad-label">V</span>
</div>
<!-- buttons to display battle-specific information -->
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="V">
<span class="apad-label">V</span>
</div>
<div id="apadStats" class="apad-button apad-rectangle apad-small" data-key="STATS">
<span class="apad-label">C</span>
</div>
</div>
</div>
</div>
<div id="tnc-links">

View File

@ -13,6 +13,7 @@
"test:cov": "vitest run --project pre && vitest run --project main --coverage",
"test:watch": "vitest run --project pre && vitest watch --project main --coverage",
"test:silent": "vitest run --project pre && vitest run --project main --silent",
"typecheck": "tsc --noEmit",
"eslint": "eslint --fix .",
"eslint-ci": "eslint .",
"docs": "typedoc",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "aqua_admin_f.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:efd07ff3ed1e610150a4b8ca18974343:d9b85b9eb11182e9e4669e2bd8b08694:72b7b50231708a9486d5f315824e4df1$"
}
}

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "aqua_admin_m.png",
"image": "archer.png",
"format": "RGBA8888",
"size": {
"w": 80,

View File

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 645 B

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "rocket_admin_m.png",
"image": "ariana.png",
"format": "RGBA8888",
"size": {
"w": 80,

View File

Before

Width:  |  Height:  |  Size: 736 B

After

Width:  |  Height:  |  Size: 736 B

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "magma_admin_f.png",
"image": "bryony.png",
"format": "RGBA8888",
"size": {
"w": 80,

View File

Before

Width:  |  Height:  |  Size: 671 B

After

Width:  |  Height:  |  Size: 671 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "courtney.png",
"format": "RGBA8888",
"size": {
"w": 52,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 52,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 52,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 52,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "flare_admin_m.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:c30bf82452209a923f4becf13d275a9a:a6355b09f92c9c0388d0b919010f587f:0638dbf213f8a974eb5af76eb1e5ddeb$"
}
}

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "galactic_admin_f.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "galactic_admin_m.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:3012867f03f02c4ee67a8ab3ad5a000e:77a5f60f1adc158664b3b2ee17bf30fe:7e8259b5177c0a76e5d02d6bdc66affe$"
}
}

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "flare_admin_f.png",
"image": "jupiter.png",
"format": "RGBA8888",
"size": {
"w": 80,

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "magma_admin_m.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:f63ad48affc076f60fae78992c96a2bf:80928b32710abcb28c07c6fc5a425d99:3b961d8852b62aaf24ceb2030c036515$"
}
}

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "mars.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

View File

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "matt.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "petrel.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "proton.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "rocket_admin_f.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "saturn.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

View File

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 689 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "shelly.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

View File

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 865 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "tabitha.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

View File

Before

Width:  |  Height:  |  Size: 845 B

After

Width:  |  Height:  |  Size: 845 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "xerosic.png",
"format": "RGBA8888",
"size": {
"w": 80,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
},
"frame": {
"x": 0,
"y": 0,
"w": 80,
"h": 80
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}

View File

Before

Width:  |  Height:  |  Size: 789 B

After

Width:  |  Height:  |  Size: 789 B

440
public/images/types_ja.json Normal file
View File

@ -0,0 +1,440 @@
{
"textures": [
{
"image": "types_ja.png",
"format": "RGBA8888",
"size": {
"w": 32,
"h": 280
},
"scale": 1,
"frames": [
{
"filename": "unknown",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
}
},
{
"filename": "bug",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 14,
"w": 32,
"h": 14
}
},
{
"filename": "dark",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 28,
"w": 32,
"h": 14
}
},
{
"filename": "dragon",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 42,
"w": 32,
"h": 14
}
},
{
"filename": "electric",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 56,
"w": 32,
"h": 14
}
},
{
"filename": "fairy",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 70,
"w": 32,
"h": 14
}
},
{
"filename": "fighting",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 84,
"w": 32,
"h": 14
}
},
{
"filename": "fire",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 98,
"w": 32,
"h": 14
}
},
{
"filename": "flying",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 112,
"w": 32,
"h": 14
}
},
{
"filename": "ghost",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 126,
"w": 32,
"h": 14
}
},
{
"filename": "grass",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 140,
"w": 32,
"h": 14
}
},
{
"filename": "ground",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 154,
"w": 32,
"h": 14
}
},
{
"filename": "ice",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 168,
"w": 32,
"h": 14
}
},
{
"filename": "normal",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 182,
"w": 32,
"h": 14
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 196,
"w": 32,
"h": 14
}
},
{
"filename": "psychic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 210,
"w": 32,
"h": 14
}
},
{
"filename": "rock",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 224,
"w": 32,
"h": 14
}
},
{
"filename": "steel",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 238,
"w": 32,
"h": 14
}
},
{
"filename": "water",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 252,
"w": 32,
"h": 14
}
},
{
"filename": "stellar",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 266,
"w": 32,
"h": 14
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:f14cf47d9a8f1d40c8e03aa6ba00fff3:6fc4227b57a95d429a1faad4280f7ec8:5961efbfbf4c56b8745347e7a663a32f$"
}
}

BIN
public/images/types_ja.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

View File

@ -176,6 +176,27 @@
"w": 12,
"h": 6
}
},
{
"filename": "HP",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 9,
"h": 8
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 8,
"h": 6
},
"frame": {
"x": 112,
"y": 0,
"w": 8,
"h": 6
}
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 451 B

View File

@ -281,6 +281,27 @@
"w": 9,
"h": 8
}
},
{
"filename": "empty",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 1,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 1,
"h": 8
},
"frame": {
"x": 117,
"y": 0,
"w": 1,
"h": 8
}
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 435 B

View File

@ -176,6 +176,27 @@
"w": 13,
"h": 7
}
},
{
"filename": "HP",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 9,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 1,
"w": 9,
"h": 7
},
"frame": {
"x": 120,
"y": 0,
"w": 9,
"h": 7
}
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 496 B

View File

@ -281,6 +281,27 @@
"w": 9,
"h": 8
}
},
{
"filename": "empty",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 1,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 1,
"h": 8
},
"frame": {
"x": 117,
"y": 0,
"w": 1,
"h": 8
}
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 B

After

Width:  |  Height:  |  Size: 499 B

3
src/@types/common.ts Normal file
View File

@ -0,0 +1,3 @@
import BattleScene from "#app/battle-scene.js";
export type ConditionFn = (scene: BattleScene, args?: any[]) => boolean;

View File

@ -8,7 +8,7 @@ export interface UserInfo {
googleId: string;
}
export let loggedInUser: UserInfo = null;
export let loggedInUser: UserInfo | null = null;
// This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting
export const clientSessionId = Utils.randomString(32);
@ -30,11 +30,13 @@ export function updateUserInfo(): Promise<[boolean, integer]> {
loggedInUser.lastSessionSlot = lastSessionSlot;
// Migrate old data from before the username was appended
[ "data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4" ].map(d => {
if (localStorage.hasOwnProperty(d)) {
if (localStorage.hasOwnProperty(`${d}_${loggedInUser.username}`)) {
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, localStorage.getItem(`${d}_${loggedInUser.username}`));
const lsItem = localStorage.getItem(d);
if (lsItem && !!loggedInUser?.username) {
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
if (lsUserItem) {
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, lsUserItem);
}
localStorage.setItem(`${d}_${loggedInUser.username}`, localStorage.getItem(d));
localStorage.setItem(`${d}_${loggedInUser.username}`, lsItem);
localStorage.removeItem(d);
}
});

View File

@ -55,7 +55,6 @@ import {UiInputs} from "./ui-inputs";
import { NewArenaEvent } from "./events/battle-scene";
import { ArenaFlyout } from "./ui/arena-flyout";
import { EaseType } from "#enums/ease-type";
import { Abilities } from "#enums/abilities";
import { BattleSpec } from "#enums/battle-spec";
import { BattleStyle } from "#enums/battle-style";
import { Biome } from "#enums/biome";
@ -106,8 +105,8 @@ export default class BattleScene extends SceneBase {
public inputController: InputsController;
public uiInputs: UiInputs;
public sessionPlayTime: integer = null;
public lastSavePlayTime: integer = null;
public sessionPlayTime: integer | null = null;
public lastSavePlayTime: integer | null = null;
public masterVolume: number = 0.5;
public bgmVolume: number = 1;
public seVolume: number = 1;
@ -122,6 +121,7 @@ export default class BattleScene extends SceneBase {
public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1";
public enableMoveInfo: boolean = true;
public enableRetries: boolean = false;
public hideIvs: boolean = false;
/**
* Determines the condition for a notification should be shown for Candy Upgrades
* - 0 = 'Off'
@ -192,8 +192,8 @@ export default class BattleScene extends SceneBase {
private phaseQueuePrependSpliceIndex: integer;
private nextCommandPhaseQueue: Phase[];
private currentPhase: Phase;
private standbyPhase: Phase;
private currentPhase: Phase | null;
private standbyPhase: Phase | null;
public field: Phaser.GameObjects.Container;
public fieldUI: Phaser.GameObjects.Container;
public charSprite: CharSprite;
@ -213,7 +213,7 @@ export default class BattleScene extends SceneBase {
public score: integer;
public lockModifierTiers: boolean;
public trainer: Phaser.GameObjects.Sprite;
public lastEnemyTrainer: Trainer;
public lastEnemyTrainer: Trainer | null;
public currentBattle: Battle;
public pokeballCounts: PokeballCounts;
public money: integer;
@ -251,7 +251,7 @@ export default class BattleScene extends SceneBase {
public spritePipeline: SpritePipeline;
private bgm: AnySound;
private bgmResumeTimer: Phaser.Time.TimerEvent;
private bgmResumeTimer: Phaser.Time.TimerEvent | null;
private bgmCache: Set<string> = new Set();
private playTimeTimer: Phaser.Time.TimerEvent;
@ -700,7 +700,11 @@ export default class BattleScene extends SceneBase {
hasExpSprite(key: string): boolean {
const keyMatch = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/g.exec(key);
let k = keyMatch[4];
if (!keyMatch) {
return false;
}
let k = keyMatch[4]!;
if (keyMatch[2]) {
k += "s";
}
@ -723,7 +727,7 @@ export default class BattleScene extends SceneBase {
return this.party;
}
getPlayerPokemon(): PlayerPokemon {
getPlayerPokemon(): PlayerPokemon | undefined {
return this.getPlayerField().find(p => p.isActive());
}
@ -740,7 +744,7 @@ export default class BattleScene extends SceneBase {
return this.currentBattle?.enemyParty || [];
}
getEnemyPokemon(): EnemyPokemon {
getEnemyPokemon(): EnemyPokemon | undefined {
return this.getEnemyField().find(p => p.isActive());
}
@ -764,6 +768,27 @@ export default class BattleScene extends SceneBase {
: ret;
}
/**
* Used in doubles battles to redirect moves from one pokemon to another when one faints or is removed from the field
* @param removedPokemon {@linkcode Pokemon} the pokemon that is being removed from the field (flee, faint), moves to be redirected FROM
* @param allyPokemon {@linkcode Pokemon} the pokemon that will have the moves be redirected TO
*/
redirectPokemonMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void {
// failsafe: if not a double battle just return
if (this.currentBattle.double === false) {
return;
}
if (allyPokemon?.isActive(true)) {
let targetingMovePhase: MovePhase;
do {
targetingMovePhase = this.findPhase(mp => mp instanceof MovePhase && mp.targets.length === 1 && mp.targets[0] === removedPokemon.getBattlerIndex() && mp.pokemon.isPlayer() !== allyPokemon.isPlayer()) as MovePhase;
if (targetingMovePhase && targetingMovePhase.targets[0] !== allyPokemon.getBattlerIndex()) {
targetingMovePhase.targets[0] = allyPokemon.getBattlerIndex();
}
} while (targetingMovePhase);
}
}
/**
* Returns the ModifierBar of this scene, which is declared private and therefore not accessible elsewhere
* @returns {ModifierBar}
@ -782,12 +807,12 @@ export default class BattleScene extends SceneBase {
return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles;
}
getPokemonById(pokemonId: integer): Pokemon {
getPokemonById(pokemonId: integer): Pokemon | null {
const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId);
return findInParty(this.getParty()) || findInParty(this.getEnemyParty());
return (findInParty(this.getParty()) || findInParty(this.getEnemyParty())) ?? null;
}
addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon {
addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon {
const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (postProcess) {
postProcess(pokemon);
@ -957,7 +982,8 @@ export default class BattleScene extends SceneBase {
p.destroy();
}
this.currentBattle = null;
//@ts-ignore - allowing `null` for currentBattle causes a lot of trouble
this.currentBattle = null; // TODO: resolve ts-ignore
this.biomeWaveText.setText(startingWave.toString());
this.biomeWaveText.setVisible(false);
@ -1021,14 +1047,14 @@ export default class BattleScene extends SceneBase {
}
}
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle {
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle | null {
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
let newDouble: boolean;
let newDouble: boolean | undefined;
let newBattleType: BattleType;
let newTrainer: Trainer;
let newTrainer: Trainer | undefined;
let battleConfig: FixedBattleConfig = null;
let battleConfig: FixedBattleConfig | null = null;
this.resetSeed(newWaveIndex);
@ -1038,7 +1064,7 @@ export default class BattleScene extends SceneBase {
battleConfig = this.gameMode.getFixedBattle(newWaveIndex);
newDouble = battleConfig.double;
newBattleType = battleConfig.battleType;
this.executeWithSeedOffset(() => newTrainer = battleConfig.getTrainer(this), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8);
this.executeWithSeedOffset(() => newTrainer = battleConfig?.getTrainer(this), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8);
if (newTrainer) {
this.field.add(newTrainer);
}
@ -1078,7 +1104,7 @@ export default class BattleScene extends SceneBase {
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance));
newDouble = !Utils.randSeedInt(doubleChance.value);
} else if (newBattleType === BattleType.TRAINER) {
newDouble = newTrainer.variant === TrainerVariant.DOUBLE;
newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
}
} else if (!battleConfig) {
newDouble = !!double;
@ -1110,27 +1136,11 @@ export default class BattleScene extends SceneBase {
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
if (!waveIndex && lastBattle) {
let isNewBiome = !(lastBattle.waveIndex % 10) || ((this.gameMode.hasShortBiomes || this.gameMode.isDaily) && (lastBattle.waveIndex % 50) === 49);
if (!isNewBiome && this.gameMode.hasShortBiomes && (lastBattle.waveIndex % 10) < 9) {
let w = lastBattle.waveIndex - ((lastBattle.waveIndex % 10) - 1);
let biomeWaves = 1;
while (w < lastBattle.waveIndex) {
let wasNewBiome = false;
this.executeWithSeedOffset(() => {
wasNewBiome = !Utils.randSeedInt(6 - biomeWaves);
}, w << 4);
if (wasNewBiome) {
biomeWaves = 1;
} else {
biomeWaves++;
}
w++;
}
this.executeWithSeedOffset(() => {
isNewBiome = !Utils.randSeedInt(6 - biomeWaves);
}, lastBattle.waveIndex << 4);
}
const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10);
const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily;
const isEndlessFifthWave = this.gameMode.hasShortBiomes && (lastBattle.waveIndex % 5) === 0;
const isWaveIndexMultipleOfFiftyMinusOne = (lastBattle.waveIndex % 50) === 49;
const isNewBiome = isWaveIndexMultipleOfTen || isEndlessFifthWave || (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne);
const resetArenaState = isNewBiome || this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS;
this.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy());
this.trySpreadPokerus();
@ -1142,12 +1152,6 @@ export default class BattleScene extends SceneBase {
playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p)));
for (const pokemon of this.getParty()) {
// Only trigger form change when Eiscue is in Noice form
// Hardcoded Eiscue for now in case it is fused with another pokemon
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
this.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}
@ -1309,7 +1313,7 @@ export default class BattleScene extends SceneBase {
return 5;
}
let isBoss: boolean;
let isBoss: boolean | undefined;
if (forceBoss || (species && (species.subLegendary || species.legendary || species.mythical))) {
isBoss = true;
} else {
@ -1608,7 +1612,7 @@ export default class BattleScene extends SceneBase {
randomSpecies(waveIndex: integer, level: integer, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies {
if (fromArenaPool) {
return this.arena.randomSpecies(waveIndex, level,null , getPartyLuckValue(this.party));
return this.arena.randomSpecies(waveIndex, level, undefined , getPartyLuckValue(this.party));
}
const filteredSpecies = speciesFilter ? [...new Set(allSpecies.filter(s => s.isCatchable()).filter(speciesFilter).map(s => {
if (!filterAllEvolutions) {
@ -1966,11 +1970,11 @@ export default class BattleScene extends SceneBase {
}
/* Phase Functions */
getCurrentPhase(): Phase {
getCurrentPhase(): Phase | null {
return this.currentPhase;
}
getStandbyPhase(): Phase {
getStandbyPhase(): Phase | null {
return this.standbyPhase;
}
@ -2048,7 +2052,10 @@ export default class BattleScene extends SceneBase {
}
if (this.phaseQueuePrepend.length) {
while (this.phaseQueuePrepend.length) {
this.phaseQueue.unshift(this.phaseQueuePrepend.pop());
const poppedPhase = this.phaseQueuePrepend.pop();
if (poppedPhase) {
this.phaseQueue.unshift(poppedPhase);
}
}
}
if (!this.phaseQueue.length) {
@ -2056,23 +2063,26 @@ export default class BattleScene extends SceneBase {
// Clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = [];
}
this.currentPhase = this.phaseQueue.shift();
this.currentPhase = this.phaseQueue.shift() ?? null;
// Check if there are any conditional phases queued
if (this.conditionalQueue?.length) {
// Retrieve the first conditional phase from the queue
const conditionalPhase = this.conditionalQueue.shift();
// Evaluate the condition associated with the phase
if (conditionalPhase[0]()) {
if (conditionalPhase?.[0]()) {
// If the condition is met, add the phase to the phase queue
this.pushPhase(conditionalPhase[1]);
} else {
} else if (conditionalPhase) {
// If the condition is not met, re-add the phase back to the front of the conditional queue
this.conditionalQueue.unshift(conditionalPhase);
} else {
console.warn("condition phase is undefined/null!", conditionalPhase);
}
}
this.currentPhase.start();
this.currentPhase?.start();
}
overridePhase(phase: Phase): boolean {
@ -2087,7 +2097,7 @@ export default class BattleScene extends SceneBase {
return true;
}
findPhase(phaseFilter: (phase: Phase) => boolean): Phase {
findPhase(phaseFilter: (phase: Phase) => boolean): Phase | undefined {
return this.phaseQueue.find(phaseFilter);
}
@ -2146,7 +2156,7 @@ export default class BattleScene extends SceneBase {
* @param promptDelay optional param for MessagePhase constructor
* @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue
*/
queueMessage(message: string, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer, defer?: boolean) {
queueMessage(message: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, defer?: boolean | null) {
const phase = new MessagePhase(this, message, callbackDelay, prompt, promptDelay);
if (!defer) {
// adds to the end of PhaseQueuePrepend
@ -2182,7 +2192,10 @@ export default class BattleScene extends SceneBase {
return Math.floor(moneyValue / 10) * 10;
}
addModifier(modifier: Modifier, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise<boolean> {
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise<boolean> {
if (!modifier) {
return Promise.resolve(false);
}
return new Promise(resolve => {
let success = false;
const soundName = modifier.type.soundName;
@ -2202,7 +2215,7 @@ export default class BattleScene extends SceneBase {
}
} else if (!virtual) {
const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier);
this.queueMessage(`The stack for this item is full.\n You will receive ${defaultModifierType.name} instead.`, null, true);
this.queueMessage(i18next.t("battle:itemStackFull", { fullItemName: modifier.type.name, itemName: defaultModifierType.name }), undefined, true);
return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant).then(success => resolve(success));
}
@ -2302,7 +2315,7 @@ export default class BattleScene extends SceneBase {
return new Promise(resolve => {
const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null;
const cancelled = new Utils.BooleanHolder(false);
Utils.executeIf(source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source, cancelled)).then(() => {
Utils.executeIf(!!source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source! /* checked in condition*/, cancelled)).then(() => {
if (cancelled.value) {
return resolve(false);
}
@ -2383,7 +2396,7 @@ export default class BattleScene extends SceneBase {
}
party.forEach((enemyPokemon: EnemyPokemon, i: integer) => {
const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer.config.isBoss);
const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer?.config.isBoss);
let upgradeChance = 32;
if (isBoss) {
upgradeChance /= 2;
@ -2391,9 +2404,9 @@ export default class BattleScene extends SceneBase {
if (isFinalBoss) {
upgradeChance /= 8;
}
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss);
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss!); // TODO: is this bang correct?
let pokemonModifierChance = modifierChance;
if (this.currentBattle.battleType === BattleType.TRAINER)
if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer)
pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line
let count = 0;
for (let c = 0; c < chances; c++) {
@ -2512,7 +2525,7 @@ export default class BattleScene extends SceneBase {
return (player ? this.modifiers : this.enemyModifiers).filter(m => (modifierFilter as ModifierPredicate)(m));
}
findModifier(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier {
findModifier(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier | undefined {
return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m));
}
@ -2548,7 +2561,7 @@ export default class BattleScene extends SceneBase {
return appliedModifiers;
}
applyModifier(modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier {
applyModifier(modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier | null {
const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args));
for (const modifier of modifiers) {
if (modifier.apply(args)) {
@ -2635,7 +2648,7 @@ export default class BattleScene extends SceneBase {
initFinalBossPhaseTwo(pokemon: Pokemon): void {
if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
this.fadeOutBgm(Utils.fixedInt(2000), false);
this.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, null, () => {
this.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, undefined, () => {
this.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true);
pokemon.generateAndPopulateMoveset(1);
this.setFieldScale(0.75);

View File

@ -39,7 +39,7 @@ export interface TurnCommand {
}
interface TurnCommands {
[key: integer]: TurnCommand
[key: integer]: TurnCommand | null
}
export default class Battle {
@ -47,8 +47,8 @@ export default class Battle {
public waveIndex: integer;
public battleType: BattleType;
public battleSpec: BattleSpec;
public trainer: Trainer;
public enemyLevels: integer[];
public trainer: Trainer | null;
public enemyLevels: integer[] | undefined;
public enemyParty: EnemyPokemon[];
public seenEnemyPartyMemberIds: Set<integer>;
public double: boolean;
@ -62,26 +62,26 @@ export default class Battle {
public escapeAttempts: integer;
public lastMove: Moves;
public battleSeed: string;
private battleSeedState: string;
private battleSeedState: string | null;
public moneyScattered: number;
public lastUsedPokeball: PokeballType;
public lastUsedPokeball: PokeballType | null;
public playerFaints: number; // The amount of times pokemon on the players side have fainted
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
private rngCounter: integer = 0;
constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer: Trainer, double: boolean) {
constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer?: Trainer, double?: boolean) {
this.gameMode = gameMode;
this.waveIndex = waveIndex;
this.battleType = battleType;
this.trainer = trainer;
this.trainer = trainer!; //TODO: is this bang correct?
this.initBattleSpec();
this.enemyLevels = battleType !== BattleType.TRAINER
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
: trainer.getPartyLevels(this.waveIndex);
: trainer?.getPartyLevels(this.waveIndex);
this.enemyParty = [];
this.seenEnemyPartyMemberIds = new Set<integer>();
this.double = double;
this.double = double!; //TODO: is this bang correct?
this.enemySwitchCounter = 0;
this.turn = 0;
this.playerParticipantIds = new Set<integer>();
@ -159,6 +159,7 @@ export default class Battle {
addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferrable, false).map(i => {
const ret = i as PokemonHeldItemModifier;
//@ts-ignore - this is awful to fix/change
ret.pokemonId = null;
return ret;
}));
@ -177,7 +178,7 @@ export default class Battle {
const userLocale = navigator.language || "en-US";
const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale);
const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount });
scene.queueMessage(message, null, true);
scene.queueMessage(message, undefined, true);
scene.currentBattle.moneyScattered = 0;
}
@ -200,16 +201,16 @@ export default class Battle {
scene.updateScoreText();
}
getBgmOverride(scene: BattleScene): string {
getBgmOverride(scene: BattleScene): string | null {
const battlers = this.enemyParty.slice(0, this.getBattlerCount());
if (this.battleType === BattleType.TRAINER) {
if (!this.started && this.trainer.config.encounterBgm && this.trainer.getEncounterMessages()?.length) {
return `encounter_${this.trainer.getEncounterBgm()}`;
if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer?.getEncounterBgm()}`;
}
if (scene.musicPreference === 0) {
return this.trainer.getBattleBgm();
return this.trainer?.getBattleBgm()!; // TODO: is this bang correct?
} else {
return this.trainer.getMixedBattleBgm();
return this.trainer?.getMixedBattleBgm()!; // TODO: is this bang correct?
}
} else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
return "end_summit";
@ -382,7 +383,7 @@ export default class Battle {
export class FixedBattle extends Battle {
constructor(scene: BattleScene, waveIndex: integer, config: FixedBattleConfig) {
super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : null, config.double);
super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : undefined, config.double);
if (config.getEnemyParty) {
this.enemyParty = config.getEnemyParty(scene);
}
@ -425,22 +426,28 @@ export class FixedBattleConfig {
}
}
/**
* Helper function to generate a random trainer for evil team trainers and the elite 4/champion
* @param trainerPool The TrainerType or list of TrainerTypes that can possibly be generated
* @param randomGender whether or not to randomly (50%) generate a female trainer (for use with evil team grunts)
* @param seedOffset the seed offset to use for the random generation of the trainer
* @returns the generated trainer
*/
function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], randomGender: boolean = false): GetTrainerFunc {
function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], randomGender: boolean = false, seedOffset: number = 0): GetTrainerFunc {
return (scene: BattleScene) => {
const rand = Utils.randSeedInt(trainerPool.length);
const trainerTypes: TrainerType[] = [];
scene.executeWithSeedOffset(() => {
for (const trainerPoolEntry of trainerPool) {
const trainerType = Array.isArray(trainerPoolEntry)
? Utils.randSeedItem(trainerPoolEntry)
: trainerPoolEntry;
trainerTypes.push(trainerType);
}
}, seedOffset);
let trainerGender = TrainerVariant.DEFAULT;
if (randomGender) {
trainerGender = (Utils.randInt(2) === 0) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT;
@ -486,13 +493,13 @@ export const classicFixedBattles: FixedBattleConfigs = {
[64]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ], true)),
[66]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_ADMIN, TrainerType.MAGMA_ADMIN, TrainerType.AQUA_ADMIN, TrainerType.GALACTIC_ADMIN, TrainerType.PLASMA_SAGE, TrainerType.FLARE_ADMIN ], true)),
.setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ] ], true)),
[95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
[112]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT ], true)),
[114]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_ADMIN, TrainerType.MAGMA_ADMIN, TrainerType.AQUA_ADMIN, TrainerType.GALACTIC_ADMIN, TrainerType.PLASMA_SAGE, TrainerType.FLARE_ADMIN ], true)),
.setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ] ], true,1)),
[115]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE ])),
[145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)

View File

@ -20,7 +20,7 @@ export function getKeyWithKeycode(config, keycode) {
*/
export function getSettingNameWithKeycode(config, keycode) {
const key = getKeyWithKeycode(config, keycode);
return config.custom[key];
return key ? config.custom[key] : null;
}
/**
@ -32,7 +32,7 @@ export function getSettingNameWithKeycode(config, keycode) {
*/
export function getIconWithKeycode(config, keycode) {
const key = getKeyWithKeycode(config, keycode);
return config.icons[key];
return key ? config.icons[key] : null;
}
/**
@ -122,15 +122,21 @@ export function assign(config, settingNameTarget, keycode): boolean {
// if it was already bound, we delete the bind
if (previousSettingName) {
const previousKey = getKeyWithSettingName(config, previousSettingName);
if (previousKey) {
config.custom[previousKey] = -1;
}
}
// then, we need to delete the current key for this settingName
const currentKey = getKeyWithSettingName(config, settingNameTarget);
if (currentKey) {
config.custom[currentKey] = -1;
}
// then, the new key is assigned to the new settingName
const newKey = getKeyWithKeycode(config, keycode);
if (newKey) {
config.custom[newKey] = settingNameTarget;
}
return true;
}
@ -145,8 +151,12 @@ export function swap(config, settingNameTarget, keycode) {
const new_key = getKeyWithKeycode(config, keycode);
const new_settingName = getSettingNameWithKey(config, new_key);
if (prev_key) {
config.custom[prev_key] = new_settingName;
}
if (new_key) {
config.custom[new_key] = prev_settingName;
}
return true;
}
@ -161,7 +171,9 @@ export function deleteBind(config, settingName) {
if (config.blacklist.includes(key)) {
return false;
}
if (key) {
config.custom[key] = -1;
}
return true;
}

View File

@ -6,7 +6,7 @@ import { BattleStat, getBattleStatName } from "./battle-stat";
import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages";
import { Weather, WeatherType } from "./weather";
import { BattlerTag, GroundedTag } from "./battler-tags";
import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit } from "./move";
@ -120,7 +120,7 @@ export class Ability implements Localizable {
type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>;
type AbAttrCondition = (pokemon: Pokemon) => boolean;
type PokemonAttackCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
type PokemonStatChangeCondition = (target: Pokemon, statsChanged: BattleStat[], levels: integer) => boolean;
@ -132,11 +132,11 @@ export abstract class AbAttr {
this.showAbility = showAbility;
}
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
return null;
}
@ -226,7 +226,7 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
}
for (const statChangePhase of statChangePhases) {
if (!this.selfTarget && !statChangePhase.getPokemon().summonData) {
if (!this.selfTarget && !statChangePhase.getPokemon()?.summonData) {
pokemon.scene.pushPhase(statChangePhase);
} else { // TODO: This causes the ability bar to be shown at the wrong time
pokemon.scene.unshiftPhase(statChangePhase);
@ -240,30 +240,11 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean;
export class PreDefendAbAttr extends AbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
return false;
}
}
export class PreDefendFormChangeAbAttr extends PreDefendAbAttr {
private formFunc: (p: Pokemon) => integer;
constructor(formFunc: ((p: Pokemon) => integer)) {
super(true);
this.formFunc = formFunc;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
return true;
}
return false;
}
}
export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (pokemon.isFullHp() &&
@ -330,21 +311,6 @@ export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultip
}
}
export class PreDefendMoveDamageToOneAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(condition: PokemonDefendCondition) {
super(condition, 1);
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = Math.floor(pokemon.getMaxHp() / 8);
return true;
}
return false;
}
}
/**
* Determines whether a Pokemon is immune to a move because of an ability.
* @extends PreDefendAbAttr
@ -352,14 +318,14 @@ export class PreDefendMoveDamageToOneAbAttr extends ReceivedMoveDamageMultiplier
* @see {@linkcode getCondition}
*/
export class TypeImmunityAbAttr extends PreDefendAbAttr {
private immuneType: Type;
private condition: AbAttrCondition;
private immuneType: Type | null;
private condition: AbAttrCondition | null;
constructor(immuneType: Type, condition?: AbAttrCondition) {
constructor(immuneType: Type | null, condition?: AbAttrCondition) {
super();
this.immuneType = immuneType;
this.condition = condition;
this.condition = condition!; // TODO: is this bang correct?
}
/**
@ -377,20 +343,36 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
if ([ MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE ].includes(move.moveTarget)) {
return false;
}
if (attacker !== pokemon && move.type === this.immuneType) {
(args[0] as Utils.NumberHolder).value = 0;
return true;
}
return false;
}
getCondition(): AbAttrCondition {
override getCondition(): AbAttrCondition | null {
return this.condition;
}
}
export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr {
constructor(immuneType: Type, condition?: AbAttrCondition) {
super(immuneType, condition);
}
/**
* Applies immunity if the move used is not a status move.
* Type immunity abilities that do not give additional benefits (HP recovery, stat boosts, etc) are not immune to status moves of the type
* Example: Levitate
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (move.category !== MoveCategory.STATUS) {
return super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
}
return false;
}
}
export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
constructor(immuneType: Type) {
super(immuneType);
@ -491,48 +473,52 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
}
export class PostDefendAbAttr extends AbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
return false;
}
}
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.category === MoveCategory.SPECIAL || move.category === MoveCategory.PHYSICAL)) {
const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt);
if (!recoilDamage) {
return false;
}
pokemon.damageAndUpdate(recoilDamage, HitResult.OTHER);
pokemon.turnData.damageTaken += recoilDamage;
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postDefendDisguise", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return true;
}
return false;
}
}
export class PostDefendFormChangeAbAttr extends PostDefendAbAttr {
private formFunc: (p: Pokemon) => integer;
constructor(formFunc: ((p: Pokemon) => integer)) {
/**
* Applies the effects of Gulp Missile when the user is hit by an attack.
* @extends PostDefendAbAttr
*/
export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr {
constructor() {
super(true);
this.formFunc = formFunc;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
return true;
}
/**
* Damages the attacker and triggers the secondary effect based on the form or the BattlerTagType.
* @param {Pokemon} pokemon - The defending Pokemon.
* @param passive - n/a
* @param {Pokemon} attacker - The attacking Pokemon.
* @param {Move} move - The move being used.
* @param {HitResult} hitResult - n/a
* @param {any[]} args - n/a
* @returns Whether the effects of the ability are applied.
*/
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
const battlerTag = pokemon.getTag(GulpMissileTag);
if (!battlerTag || move.category === MoveCategory.STATUS || pokemon.getTag(SemiInvulnerableTag)) {
return false;
}
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
if (!cancelled.value) {
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), HitResult.OTHER);
}
if (battlerTag.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ BattleStat.DEF ], -1));
} else {
attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon);
}
pokemon.removeTag(battlerTag.tagType);
return true;
}
}
export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
@ -838,7 +824,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr
export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
private chance: integer;
private tagType: BattlerTagType;
private turnCount: integer;
private turnCount: integer | undefined;
constructor(chance: integer, tagType: BattlerTagType, turnCount?: integer) {
super();
@ -875,7 +861,7 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr {
}
getCondition(): AbAttrCondition {
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
}
}
@ -1066,7 +1052,7 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr {
}
export class PreAttackAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -1079,7 +1065,7 @@ export class PreAttackAbAttr extends AbAttr {
export class MoveEffectChanceMultiplierAbAttr extends AbAttr {
private chanceMultiplier: number;
constructor(chanceMultiplier?: number) {
constructor(chanceMultiplier: number) {
super(true);
this.chanceMultiplier = chanceMultiplier;
}
@ -1460,7 +1446,7 @@ export class FieldMovePowerBoostAbAttr extends AbAttr {
this.powerMultiplier = powerMultiplier;
}
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
applyPreAttack(pokemon: Pokemon | null, passive: boolean | null, defender: Pokemon | null, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
@ -1514,14 +1500,14 @@ export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr
export class BattleStatMultiplierAbAttr extends AbAttr {
private battleStat: BattleStat;
private multiplier: number;
private condition: PokemonAttackCondition;
private condition: PokemonAttackCondition | null;
constructor(battleStat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) {
super(false);
this.battleStat = battleStat;
this.multiplier = multiplier;
this.condition = condition;
this.condition = condition!; // TODO: is this bang correct?
}
applyBattleStat(pokemon: Pokemon, passive: boolean, battleStat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise<boolean> {
@ -1550,7 +1536,7 @@ export class PostAttackAbAttr extends AbAttr {
* applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr}
* for an example of an effect that does not require a damaging move.
*/
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
// When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements.
// If attackRequired is false, we always defer to the secondary requirements.
if (this.attackCondition(pokemon, defender, move)) {
@ -1563,18 +1549,18 @@ export class PostAttackAbAttr extends AbAttr {
/**
* This method is only called after {@link applyPostAttack} has already been applied. Use this for handling checks specific to the ability in question.
*/
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
return false;
}
}
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
private stealCondition: PokemonAttackCondition;
private stealCondition: PokemonAttackCondition | null;
constructor(stealCondition?: PokemonAttackCondition) {
super();
this.stealCondition = stealCondition;
this.stealCondition = stealCondition!; // TODO: is this bang correct?
}
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
@ -1658,12 +1644,12 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
}
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
private condition: PokemonDefendCondition;
private condition: PokemonDefendCondition | null;
constructor(condition?: PokemonDefendCondition) {
super();
this.condition = condition;
this.condition = condition!; // TODO: is this bang correct?
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
@ -2154,17 +2140,17 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
}
if (
target.getAbility().hasAttr(UncopiableAbilityAbAttr) &&
target!.getAbility().hasAttr(UncopiableAbilityAbAttr) &&
// Wonder Guard is normally uncopiable so has the attribute, but Trace specifically can copy it
!(pokemon.hasAbility(Abilities.TRACE) && target.getAbility().id === Abilities.WONDER_GUARD)
!(pokemon.hasAbility(Abilities.TRACE) && target!.getAbility().id === Abilities.WONDER_GUARD)
) {
return false;
}
this.target = target;
this.targetAbilityName = allAbilities[target.getAbility().id].name;
pokemon.summonData.ability = target.getAbility().id;
setAbilityRevealed(target);
this.target = target!;
this.targetAbilityName = allAbilities[target!.getAbility().id].name;
pokemon.summonData.ability = target!.getAbility().id;
setAbilityRevealed(target!);
pokemon.updateInfo();
return true;
@ -2211,7 +2197,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt
}
for (const pokemon of allowedParty) {
if (this.statusEffect.includes(pokemon.status?.effect)) {
if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) {
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
pokemon.resetStatus(false);
pokemon.updateInfo();
@ -2267,6 +2253,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
target = targets[0];
}
target = target!; // compiler doesn't know its guranteed to be defined
pokemon.summonData.speciesForm = target.getSpeciesForm();
pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm();
pokemon.summonData.ability = target.getAbility().id;
@ -2274,7 +2261,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
pokemon.summonData.fusionGender = target.getFusionGender();
pokemon.summonData.stats = [ pokemon.stats[Stat.HP] ].concat(target.stats.slice(1));
pokemon.summonData.battleStats = target.summonData.battleStats.slice(0);
pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m.moveId, m.ppUsed, m.ppUp));
pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m!.moveId, m!.ppUsed, m!.ppUp)); // TODO: are those bangs correct?
pokemon.summonData.types = target.getTypes();
pokemon.scene.playSound("PRSFX- Transform");
@ -2321,7 +2308,7 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
* @returns {boolean} Returns true if the weather clears, otherwise false.
*/
applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
const weatherType = pokemon.scene.arena.weather.weatherType;
const weatherType = pokemon.scene.arena.weather?.weatherType;
let turnOffWeather = false;
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
@ -2402,18 +2389,18 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr {
}
export class PreStatChangeAbAttr extends AbAttr {
applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreStatChange(pokemon: Pokemon | null, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false;
}
}
export class ProtectStatAbAttr extends PreStatChangeAbAttr {
private protectedStat: BattleStat;
private protectedStat: BattleStat | null;
constructor(protectedStat?: BattleStat) {
super();
this.protectedStat = protectedStat;
this.protectedStat = protectedStat!; // TODO: is this bang correct?
}
applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -2429,7 +2416,7 @@ export class ProtectStatAbAttr extends PreStatChangeAbAttr {
return i18next.t("abilityTriggers:protectStat", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName,
statName: this.protectedStat !== undefined ? getBattleStatName(this.protectedStat) : i18next.t("battle:stats")
statName: this.protectedStat ? getBattleStatName(this.protectedStat) : i18next.t("battle:stats")
});
}
}
@ -2469,7 +2456,7 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
}
export class PreSetStatusAbAttr extends AbAttr {
applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -2680,7 +2667,7 @@ export class BlockStatusDamageAbAttr extends AbAttr {
* @returns Returns true if status damage is blocked
*/
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.effects.includes(pokemon.status?.effect)) {
if (pokemon.status && this.effects.includes(pokemon.status.effect)) {
cancelled.value = true;
return true;
}
@ -2719,7 +2706,7 @@ export class IncrementMovePriorityAbAttr extends AbAttr {
export class IgnoreContactAbAttr extends AbAttr { }
export class PreWeatherEffectAbAttr extends AbAttr {
applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather | null, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -2750,7 +2737,7 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr {
constructor(affectsImmutable?: boolean) {
super();
this.affectsImmutable = affectsImmutable;
this.affectsImmutable = affectsImmutable!; // TODO: is this bang correct?
}
applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -2801,7 +2788,7 @@ function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
return false;
}
const weatherType = pokemon.scene.arena.weather?.weatherType;
return weatherType && weatherTypes.indexOf(weatherType) > -1;
return !!weatherType && weatherTypes.indexOf(weatherType) > -1;
};
}
@ -2810,15 +2797,15 @@ function getAnticipationCondition(): AbAttrCondition {
for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) {
// move is super effective
if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true) >= 2) {
if (move!.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move!.getMove().type, opponent, true) >= 2) { // TODO: is this bang correct?
return true;
}
// move is a OHKO
if (move.getMove().hasAttr(OneHitKOAttr)) {
if (move!.getMove().hasAttr(OneHitKOAttr)) { // TODO: is this bang correct?
return true;
}
// edge case for hidden power, type is computed
if (move.getMove().id === Moves.HIDDEN_POWER) {
if (move!.getMove().id === Moves.HIDDEN_POWER) { // TODO: is this bang correct?
const iv_val = Math.floor(((opponent.ivs[Stat.HP] & 1)
+(opponent.ivs[Stat.ATK] & 1) * 2
+(opponent.ivs[Stat.DEF] & 1) * 4
@ -2866,21 +2853,21 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
let movePower = 0;
for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) {
if (move.getMove() instanceof StatusMove) {
if (move!.getMove() instanceof StatusMove) { // TODO: is this bang correct?
movePower = 1;
} else if (move.getMove().hasAttr(OneHitKOAttr)) {
} else if (move!.getMove().hasAttr(OneHitKOAttr)) { // TODO: is this bang correct?
movePower = 150;
} else if (move.getMove().id === Moves.COUNTER || move.getMove().id === Moves.MIRROR_COAT || move.getMove().id === Moves.METAL_BURST) {
} else if (move!.getMove().id === Moves.COUNTER || move!.getMove().id === Moves.MIRROR_COAT || move!.getMove().id === Moves.METAL_BURST) { // TODO: are those bangs correct?
movePower = 120;
} else if (move.getMove().power === -1) {
} else if (move!.getMove().power === -1) { // TODO: is this bang correct?
movePower = 80;
} else {
movePower = move.getMove().power;
movePower = move!.getMove().power; // TODO: is this bang correct?
}
if (movePower > maxPowerSeen) {
maxPowerSeen = movePower;
maxMove = move.getName();
maxMove = move!.getName(); // TODO: is this bang correct?
}
}
}
@ -2941,7 +2928,7 @@ export class PostWeatherLapseAbAttr extends AbAttr {
this.weatherTypes = weatherTypes;
}
applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather, args: any[]): boolean | Promise<boolean> {
applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather | null, args: any[]): boolean | Promise<boolean> {
return false;
}
@ -3024,7 +3011,7 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr
function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition {
return (pokemon: Pokemon) => {
const terrainType = pokemon.scene.arena.terrain?.terrainType;
return terrainType && terrainTypes.indexOf(terrainType) > -1;
return !!terrainType && terrainTypes.indexOf(terrainType) > -1;
};
}
@ -3056,7 +3043,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
* @returns Returns true if healed from status, false if not
*/
applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
if (this.effects.includes(pokemon.status?.effect)) {
if (pokemon.status && this.effects.includes(pokemon.status.effect)) {
if (!pokemon.isFullHp()) {
const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
@ -3292,7 +3279,7 @@ export class FetchBallAbAttr extends PostTurnAbAttr {
*/
applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
const lastUsed = pokemon.scene.currentBattle.lastUsedPokeball;
if (lastUsed !== null && pokemon.isPlayer) {
if (lastUsed !== null && !!pokemon.isPlayer) {
pokemon.scene.pokeballCounts[lastUsed]++;
pokemon.scene.currentBattle.lastUsedPokeball = null;
pokemon.scene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed) }));
@ -3573,7 +3560,8 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr {
const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot;
if (postBattleLoot.length) {
const randItem = Utils.randSeedItem(postBattleLoot);
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) {
//@ts-ignore - TODO see below
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) { // TODO: fix. This is a promise!?
postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name }));
return true;
@ -3605,7 +3593,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
* @returns {boolean} Returns true if the weather clears, otherwise false.
*/
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const weatherType = pokemon.scene.arena.weather.weatherType;
const weatherType = pokemon.scene.arena.weather?.weatherType;
let turnOffWeather = false;
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
@ -4000,34 +3988,44 @@ export class PostSummonStatChangeOnArenaAbAttr extends PostSummonStatChangeAbAtt
}
/**
* Takes no damage from the first hit of a physical move.
* This is used in Ice Face ability.
* Takes no damage from the first hit of a damaging move.
* This is used in the Disguise and Ice Face abilities.
* @extends ReceivedMoveDamageMultiplierAbAttr
*/
export class IceFaceBlockPhysicalAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
private multiplier: number;
private tagType: BattlerTagType;
private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined;
private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string;
constructor(condition: PokemonDefendCondition, multiplier: number) {
constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) {
super(condition, multiplier);
this.multiplier = multiplier;
this.tagType = tagType;
this.recoilDamageFunc = recoilDamageFunc;
this.triggerMessageFunc = triggerMessageFunc;
}
/**
* Applies the Ice Face pre-defense ability to the Pokémon.
* Removes BattlerTagType.ICE_FACE when hit by physical attack and is in Ice Face form.
* Applies the pre-defense ability to the Pokémon.
* Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form.
*
* @param {Pokemon} pokemon - The Pokémon with the Ice Face ability.
* @param {boolean} passive - Whether the ability is passive.
* @param {Pokemon} attacker - The attacking Pokémon.
* @param {PokemonMove} move - The move being used.
* @param {Utils.BooleanHolder} cancelled - A holder for whether the move was cancelled.
* @param {any[]} args - Additional arguments.
* @returns {boolean} - Whether the immunity was applied.
* @param {Pokemon} pokemon The Pokémon with the ability.
* @param {boolean} passive n/a
* @param {Pokemon} attacker The attacking Pokémon.
* @param {PokemonMove} move The move being used.
* @param {Utils.BooleanHolder} cancelled n/a
* @param {any[]} args Additional arguments.
* @returns {boolean} Whether the immunity was applied.
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = this.multiplier;
pokemon.removeTag(BattlerTagType.ICE_FACE);
pokemon.removeTag(this.tagType);
if (this.recoilDamageFunc) {
pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), HitResult.OTHER);
}
return true;
}
@ -4035,14 +4033,14 @@ export class IceFaceBlockPhysicalAbAttr extends ReceivedMoveDamageMultiplierAbAt
}
/**
* Gets the message triggered when the Pokémon avoids damage using the Ice Face ability.
* @param {Pokemon} pokemon - The Pokémon with the Ice Face ability.
* @param {string} abilityName - The name of the ability.
* @param {...any} args - Additional arguments.
* @returns {string} - The trigger message.
* Gets the message triggered when the Pokémon avoids damage using the form-changing ability.
* @param {Pokemon} pokemon The Pokémon with the ability.
* @param {string} abilityName The name of the ability.
* @param {...any} args n/a
* @returns {string} The trigger message.
*/
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName });
return this.triggerMessageFunc(pokemon, abilityName);
}
}
@ -4077,7 +4075,7 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
const turnCommand =
pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
const isCommandFight = turnCommand?.command === Command.FIGHT;
const move = allMoves[turnCommand.move?.move];
const move = turnCommand?.move?.move ?allMoves[turnCommand.move.move] : null;
const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
if (isCommandFight && isDamageMove) {
@ -4096,14 +4094,14 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
async function applyAbAttrsInternal<TAttr extends AbAttr>(
attrType: Constructor<TAttr>,
pokemon: Pokemon,
pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<TAttr>,
args: any[],
showAbilityInstant: boolean = false,
quiet: boolean = false,
) {
for (const passive of [false, true]) {
if (!pokemon.canApplyAbility(passive)) {
if (!pokemon?.canApplyAbility(passive)) {
continue;
}
@ -4151,7 +4149,7 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
}
}
export function applyAbAttrs(attrType: Constructor<AbAttr>, pokemon: Pokemon, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
export function applyAbAttrs(attrType: Constructor<AbAttr>, pokemon: Pokemon, cancelled: Utils.BooleanHolder | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<AbAttr>(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, cancelled, args), args);
}
@ -4161,13 +4159,13 @@ export function applyPostBattleInitAbAttrs(attrType: Constructor<PostBattleInitA
}
export function applyPreDefendAbAttrs(attrType: Constructor<PreDefendAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, simulated);
}
export function applyPostDefendAbAttrs(attrType: Constructor<PostDefendAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args);
}
@ -4197,12 +4195,12 @@ export function applyFieldBattleStatMultiplierAbAttrs(attrType: Constructor<Fiel
}
export function applyPreAttackAbAttrs(attrType: Constructor<PreAttackAbAttr>,
pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise<void> {
pokemon: Pokemon, defender: Pokemon | null, move: Move, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args);
}
export function applyPostAttackAbAttrs(attrType: Constructor<PostAttackAbAttr>,
pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args);
}
@ -4227,7 +4225,7 @@ export function applyPreSwitchOutAbAttrs(attrType: Constructor<PreSwitchOutAbAtt
}
export function applyPreStatChangeAbAttrs(attrType: Constructor<PreStatChangeAbAttr>,
pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreStatChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, stat, cancelled, args), args);
}
@ -4237,7 +4235,7 @@ export function applyPostStatChangeAbAttrs(attrType: Constructor<PostStatChangeA
}
export function applyPreSetStatusAbAttrs(attrType: Constructor<PreSetStatusAbAttr>,
pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, effect, cancelled, args), args, false, !simulated);
}
@ -4248,7 +4246,7 @@ export function applyPreApplyBattlerTagAbAttrs(attrType: Constructor<PreApplyBat
}
export function applyPreWeatherEffectAbAttrs(attrType: Constructor<PreWeatherEffectAbAttr>,
pokemon: Pokemon, weather: Weather, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon, weather: Weather | null, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreWeatherDamageAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, weather, cancelled, args), args, true);
}
@ -4263,7 +4261,7 @@ export function applyPostWeatherChangeAbAttrs(attrType: Constructor<PostWeatherC
}
export function applyPostWeatherLapseAbAttrs(attrType: Constructor<PostWeatherLapseAbAttr>,
pokemon: Pokemon, weather: Weather, ...args: any[]): Promise<void> {
pokemon: Pokemon, weather: Weather | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostWeatherLapseAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, weather, args), args);
}
@ -4363,7 +4361,7 @@ export function initAbilities() {
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(),
new Ability(Abilities.FLASH_FIRE, 3)
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1, (pokemon: Pokemon) => !pokemon.status || pokemon.status.effect !== StatusEffect.FREEZE)
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1)
.ignorable(),
new Ability(Abilities.SHIELD_DUST, 3)
.attr(IgnoreMoveEffectsAbAttr)
@ -4393,7 +4391,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr)
.ignorable(),
new Ability(Abilities.LEVITATE, 3)
.attr(TypeImmunityAbAttr, Type.GROUND, (pokemon: Pokemon) => !pokemon.getTag(GroundedTag) && !pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))
.attr(AttackTypeImmunityAbAttr, Type.GROUND, (pokemon: Pokemon) => !pokemon.getTag(GroundedTag) && !pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))
.ignorable(),
new Ability(Abilities.EFFECT_SPORE, 3)
.attr(EffectSporeAbAttr),
@ -4552,8 +4550,8 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1)
.ignorable(),
new Ability(Abilities.RIVALRY, 4)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.gender !== Gender.GENDERLESS && target.gender !== Gender.GENDERLESS && user.gender === target.gender, 1.25, true)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.gender !== Gender.GENDERLESS && target.gender !== Gender.GENDERLESS && user.gender !== target.gender, 0.75),
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true)
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75),
new Ability(Abilities.STEADFAST, 4)
.attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1),
new Ability(Abilities.SNOW_CLOAK, 4)
@ -4643,7 +4641,8 @@ export function initAbilities() {
.attr(IgnoreOpponentStatChangesAbAttr)
.ignorable(),
new Ability(Abilities.TINTED_LENS, 4)
.attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5),
//@ts-ignore
.attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5), // TODO: fix TS issues
new Ability(Abilities.FILTER, 4)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
.ignorable(),
@ -4725,9 +4724,9 @@ export function initAbilities() {
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.isFullHp(), 0.5)
.ignorable(),
new Ability(Abilities.TOXIC_BOOST, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.PHYSICAL && (user.status?.effect === StatusEffect.POISON || user.status?.effect === StatusEffect.TOXIC), 1.5),
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC), 1.5),
new Ability(Abilities.FLARE_BOOST, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user.status?.effect === StatusEffect.BURN, 1.5),
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN, 1.5),
new Ability(Abilities.HARVEST, 5)
.attr(
PostTurnLootAbAttr,
@ -4760,7 +4759,8 @@ export function initAbilities() {
.attr(WonderSkinAbAttr)
.ignorable(),
new Ability(Abilities.ANALYTIC, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command !== Command.FIGHT, 1.3),
//@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command !== Command.FIGHT, 1.3), // TODO fix TS issues
new Ability(Abilities.ILLUSION, 5)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
@ -4910,7 +4910,7 @@ export function initAbilities() {
new Ability(Abilities.WATER_COMPACTION, 7)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2),
new Ability(Abilities.MERCILESS, 7)
.attr(ConditionalCritAbAttr, (user, target, move) => target.status?.effect === StatusEffect.TOXIC || target.status?.effect === StatusEffect.POISON),
.attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
new Ability(Abilities.SHIELDS_DOWN, 7)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
@ -4922,7 +4922,8 @@ export function initAbilities() {
.bypassFaint()
.partial(),
new Ability(Abilities.STAKEOUT, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2),
//@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2), // TODO: fix TS issues
new Ability(Abilities.WATER_BUBBLE, 7)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2)
@ -4956,20 +4957,18 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.DISGUISE, 7)
.attr(PreDefendMoveDamageToOneAbAttr, (target, user, move) => target.formIndex === 0 && target.getAttackTypeEffectiveness(move.type, user) > 0)
.attr(PostSummonFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostDefendFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
.attr(PreDefendFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
.attr(PostDefendDisguiseAbAttr)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.ignorable()
.partial(),
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
.attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getAttackTypeEffectiveness(move.type, user) > 0, 0, BattlerTagType.DISGUISE,
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
(pokemon) => Math.floor(pokemon.getMaxHp() / 8))
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.ignorable(),
new Ability(Abilities.BATTLE_BOND, 7)
.attr(PostVictoryFormChangeAbAttr, () => 2)
.attr(PostBattleInitFormChangeAbAttr, () => 1)
@ -5062,7 +5061,8 @@ export function initAbilities() {
new Ability(Abilities.PRISM_ARMOR, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75),
new Ability(Abilities.NEUROFORCE, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25),
//@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25), // TODO: fix TS issues
new Ability(Abilities.INTREPID_SWORD, 8)
.attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true)
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
@ -5087,7 +5087,9 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.unimplemented(),
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(PostDefendGulpMissileAbAttr),
new Ability(Abilities.STALWART, 8)
.attr(BlockRedirectAbAttr),
new Ability(Abilities.STEAM_ENGINE, 8)
@ -5115,7 +5117,9 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0)
// When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
.attr(IceFaceBlockPhysicalAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0)
.attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
(pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }))
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.ignorable(),
new Ability(Abilities.POWER_SPOT, 8)
.attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL], 1.3),
@ -5145,8 +5149,10 @@ export function initAbilities() {
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(),
new Ability(Abilities.HUNGER_SWITCH, 8)
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1)
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0)
//@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) // TODO: fix ts-ignore
//@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0) // TODO: fix ts-ignore
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)

View File

@ -25,12 +25,12 @@ export enum ArenaTagSide {
export abstract class ArenaTag {
public tagType: ArenaTagType;
public turnCount: integer;
public sourceMove: Moves;
public sourceId: integer;
public sourceMove?: Moves;
public sourceId?: integer;
public side: ArenaTagSide;
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
this.tagType = tagType;
this.turnCount = turnCount;
this.sourceMove = sourceMove;
@ -56,7 +56,7 @@ export abstract class ArenaTag {
return this.turnCount < 1 || !!(--this.turnCount);
}
getMoveName(): string {
getMoveName(): string | null {
return this.sourceMove
? allMoves[this.sourceMove].name
: null;
@ -75,9 +75,14 @@ export class MistTag extends ArenaTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
if (this.sourceId) {
const source = arena.scene.getPokemonById(this.sourceId);
if (!quiet) {
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
} else if (!quiet) {
console.warn("Failed to get source for MistTag onAdd");
}
}
}
@ -280,8 +285,14 @@ class MatBlockTag extends ConditionalProtectTag {
}
onAdd(arena: Arena) {
if (this.sourceId) {
const source = arena.scene.getPokemonById(this.sourceId);
if (source) {
arena.scene.queueMessage(i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
} else {
console.warn("Failed to get source for MatBlockTag onAdd");
}
}
}
}
@ -303,6 +314,39 @@ class CraftyShieldTag extends ConditionalProtectTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Lucky_Chant_(move) Lucky Chant}.
* Prevents critical hits against the tag's side.
*/
export class NoCritTag extends ArenaTag {
/**
* Constructor method for the NoCritTag class
* @param turnCount `integer` the number of turns this effect lasts
* @param sourceMove {@linkcode Moves} the move that created this effect
* @param sourceId `integer` the ID of the {@linkcode Pokemon} that created this effect
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
*/
constructor(turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
}
/** Queues a message upon adding this effect to the field */
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, {
moveName: this.getMoveName()
}));
}
/** Queues a message upon removing this effect from the field */
onRemove(arena: Arena): void {
const source = arena.scene.getPokemonById(this.sourceId!); // TODO: is this bang correct?
arena.scene.queueMessage(i18next.t("arenaTag:noCritOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
moveName: this.getMoveName()
}));
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) Wish}.
* Heals the Pokémon in the user's position the turn after Wish is used.
@ -317,10 +361,16 @@ class WishTag extends ArenaTag {
}
onAdd(arena: Arena): void {
if (this.sourceId) {
const user = arena.scene.getPokemonById(this.sourceId);
if (user) {
this.battlerIndex = user.getBattlerIndex();
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) });
this.healHp = Math.max(Math.floor(user.getMaxHp() / 2), 1);
} else {
console.warn("Failed to get source for WishTag onAdd");
}
}
}
onRemove(arena: Arena): void {
@ -461,8 +511,8 @@ class SpikesTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
if (!quiet) {
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
}
}
@ -506,8 +556,8 @@ class ToxicSpikesTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
if (!quiet) {
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
}
}
@ -556,7 +606,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
class DelayedAttackTag extends ArenaTag {
public targetIndex: BattlerIndex;
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, targetIndex: BattlerIndex) {
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: integer, targetIndex: BattlerIndex) {
super(tagType, 3, sourceMove, sourceId);
this.targetIndex = targetIndex;
@ -566,7 +616,7 @@ class DelayedAttackTag extends ArenaTag {
const ret = super.lapse(arena);
if (!ret) {
arena.scene.unshiftPhase(new MoveEffectPhase(arena.scene, this.sourceId, [ this.targetIndex ], new PokemonMove(this.sourceMove, 0, 0, true)));
arena.scene.unshiftPhase(new MoveEffectPhase(arena.scene, this.sourceId!, [ this.targetIndex ], new PokemonMove(this.sourceMove!, 0, 0, true))); // TODO: are those bangs correct?
}
return ret;
@ -588,8 +638,8 @@ class StealthRockTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
if (!quiet) {
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() }));
}
}
@ -597,7 +647,7 @@ class StealthRockTag extends ArenaTrapTag {
getDamageHpRatio(pokemon: Pokemon): number {
const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK, undefined, true);
let damageHpRatio: number;
let damageHpRatio: number = 0;
switch (effectiveness) {
case 0:
@ -663,8 +713,8 @@ class StickyWebTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
if (!quiet) {
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
}
}
@ -702,7 +752,10 @@ export class TrickRoomTag extends ArenaTag {
}
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(arena.scene.getPokemonById(this.sourceId)) }));
const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (source) {
arena.scene.queueMessage(i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
}
}
onRemove(arena: Arena): void {
@ -749,8 +802,8 @@ class TailwindTag extends ArenaTag {
arena.scene.queueMessage(i18next.t(`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
const source = arena.scene.getPokemonById(this.sourceId);
const party = source.isPlayer() ? source.scene.getPlayerField() : source.scene.getEnemyField();
const source = arena.scene.getPokemonById(this.sourceId!); //TODO: this bang is questionable!
const party = (source?.isPlayer() ? source.scene.getPlayerField() : source?.scene.getEnemyField()) ?? [];
for (const pokemon of party) {
// Apply the CHARGED tag to party members with the WIND_POWER ability
@ -791,7 +844,7 @@ class HappyHourTag extends ArenaTag {
}
}
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag {
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
switch (tagType) {
case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side);
@ -803,6 +856,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new MatBlockTag(sourceId, side);
case ArenaTagType.CRAFTY_SHIELD:
return new CraftyShieldTag(sourceId, side);
case ArenaTagType.NO_CRIT:
return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct?
case ArenaTagType.MUD_SPORT:
return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT:
@ -813,7 +868,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new ToxicSpikesTag(sourceId, side);
case ArenaTagType.FUTURE_SIGHT:
case ArenaTagType.DOOM_DESIRE:
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex);
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang
case ArenaTagType.WISH:
return new WishTag(turnCount, sourceId, side);
case ArenaTagType.STEALTH_ROCK:
@ -834,5 +889,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side);
default:
return null;
}
}

View File

@ -1,6 +1,6 @@
//import { battleAnimRawData } from "./battle-anim-raw-data";
import BattleScene from "../battle-scene";
import { AttackMove, ChargeAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move";
import { AttackMove, BeakBlastHeaderAttr, ChargeAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move";
import Pokemon from "../field/pokemon";
import * as Utils from "../utils";
import { BattlerIndex } from "../battle";
@ -128,7 +128,7 @@ export class AnimConfig {
for (const fte of Object.keys(frameTimedEvents)) {
const timedEvents: AnimTimedEvent[] = [];
for (const te of frameTimedEvents[fte]) {
let timedEvent: AnimTimedEvent;
let timedEvent: AnimTimedEvent | undefined;
switch (te.eventType) {
case "AnimTimedSoundEvent":
timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te);
@ -140,7 +140,8 @@ export class AnimConfig {
timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te);
break;
}
timedEvents.push(timedEvent);
timedEvent && timedEvents.push(timedEvent);
}
this.frameTimedEvents.set(parseInt(fte), timedEvents);
}
@ -330,7 +331,7 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
}
return Math.ceil((scene.sound.get(this.resourceName).totalDuration * 1000) / 33.33);
} else {
return Math.ceil((battleAnim.user.cry(soundConfig).totalDuration * 1000) / 33.33);
return Math.ceil((battleAnim.user!.cry(soundConfig).totalDuration * 1000) / 33.33); // TODO: is the bang behind user correct?
}
}
@ -441,15 +442,15 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
}
}
export const moveAnims = new Map<Moves, AnimConfig | [AnimConfig, AnimConfig]>();
export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimConfig]>();
export const moveAnims = new Map<Moves, AnimConfig | [AnimConfig, AnimConfig] | null>();
export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimConfig] | null>();
export const commonAnims = new Map<CommonAnim, AnimConfig>();
export function initCommonAnims(scene: BattleScene): Promise<void> {
return new Promise(resolve => {
const commonAnimNames = Utils.getEnumKeys(CommonAnim);
const commonAnimIds = Utils.getEnumValues(CommonAnim);
const commonAnimFetches = [];
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
for (let ca = 0; ca < commonAnimIds.length; ca++) {
const commonAnimId = commonAnimIds[ca];
commonAnimFetches.push(scene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`)
@ -484,7 +485,8 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
const fetchAnimAndResolve = (move: Moves) => {
scene.cachedFetch(`./battle-anims/${moveName}.json`)
.then(response => {
if (!response.ok) {
const contentType = response.headers.get("content-type");
if (!response.ok || contentType?.indexOf("application/json") === -1) {
console.error(`Could not load animation file for move '${moveName}'`, response.status, response.statusText);
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
return resolve();
@ -498,7 +500,9 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
populateMoveAnim(move, ba);
}
const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0];
const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0]
|| allMoves[move].getAttrs(DelayedAttackAttr)[0]
|| allMoves[move].getAttrs(BeakBlastHeaderAttr)[0];
if (chargeAttr) {
initMoveChargeAnim(scene, chargeAttr.chargeAnim).then(() => resolve());
} else {
@ -569,10 +573,12 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
return new Promise(resolve => {
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
for (const moveId of moveIds) {
const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] || allMoves[moveId].getAttrs(DelayedAttackAttr)[0];
const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0]
|| allMoves[moveId].getAttrs(DelayedAttackAttr)[0]
|| allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0];
if (chargeAttr) {
const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims[0]);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct?
if (Array.isArray(moveChargeAnims)) {
moveAnimations.push(moveChargeAnims[1]);
}
@ -668,21 +674,21 @@ interface SpriteCache {
}
export abstract class BattleAnim {
public user: Pokemon;
public target: Pokemon;
public user: Pokemon | null;
public target: Pokemon | null;
public sprites: Phaser.GameObjects.Sprite[];
public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle;
private srcLine: number[];
private dstLine: number[];
constructor(user: Pokemon, target: Pokemon) {
this.user = user;
this.target = target;
constructor(user?: Pokemon, target?: Pokemon) {
this.user = user!; // TODO: is this bang correct?
this.target = target!; // TODO: is this bang correct?
this.sprites = [];
}
abstract getAnim(): AnimConfig;
abstract getAnim(): AnimConfig | null;
abstract isOppAnim(): boolean;
@ -705,12 +711,12 @@ export abstract class BattleAnim {
const user = !isOppAnim ? this.user : this.target;
const target = !isOppAnim ? this.target : this.user;
const userInitialX = user.x;
const userInitialY = user.y;
const userHalfHeight = user.getSprite().displayHeight / 2;
const targetInitialX = target.x;
const targetInitialY = target.y;
const targetHalfHeight = target.getSprite().displayHeight / 2;
const userInitialX = user!.x; // TODO: is this bang correct?
const userInitialY = user!.y; // TODO: is this bang correct?
const userHalfHeight = user!.getSprite().displayHeight! / 2; // TODO: is this bang correct?
const targetInitialX = target!.x; // TODO: is this bang correct?
const targetInitialY = target!.y; // TODO: is this bang correct?
const targetHalfHeight = target!.getSprite().displayHeight! / 2; // TODO: is this bang correct?
let g = 0;
let u = 0;
@ -742,7 +748,7 @@ export abstract class BattleAnim {
}
const angle = -frame.angle;
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
ret.get(frame.target).set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle });
ret.get(frame.target)!.set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle }); // TODO: is the bang correct?
}
return ret;
@ -750,10 +756,10 @@ export abstract class BattleAnim {
play(scene: BattleScene, callback?: Function) {
const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user : this.target;
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
const target = !isOppAnim ? this.target : this.user;
if (!target.isOnField()) {
if (!target?.isOnField()) {
if (callback) {
callback();
}
@ -781,7 +787,7 @@ export abstract class BattleAnim {
targetSprite.setAlpha(1);
targetSprite.pipelineData["tone"] = [ 0.0, 0.0, 0.0, 0.0 ];
targetSprite.setAngle(0);
if (!this.isHideUser()) {
if (!this.isHideUser() && userSprite) {
userSprite.setVisible(true);
}
if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) {
@ -814,20 +820,20 @@ export abstract class BattleAnim {
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
this.dstLine = [ userInitialX, userInitialY, targetInitialX, targetInitialY ];
let r = anim.frames.length;
let r = anim!.frames.length; // TODO: is this bang correct?
let f = 0;
scene.tweens.addCounter({
duration: Utils.getFrameMs(3),
repeat: anim.frames.length,
repeat: anim!.frames.length, // TODO: is this bang correct?
onRepeat: () => {
if (!f) {
userSprite.setVisible(false);
targetSprite.setVisible(false);
}
const spriteFrames = anim.frames[f];
const frameData = this.getGraphicFrameData(scene, anim.frames[f]);
const spriteFrames = anim!.frames[f]; // TODO: is the bang correcT?
const frameData = this.getGraphicFrameData(scene, anim!.frames[f]); // TODO: is the bang correct?
let u = 0;
let t = 0;
let g = 0;
@ -840,9 +846,9 @@ export abstract class BattleAnim {
const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET];
const spriteSource = isUser ? userSprite : targetSprite;
if ((isUser ? u : t) === sprites.length) {
const sprite = scene.addPokemonSprite(isUser ? user : target, 0, 0, spriteSource.texture, spriteSource.frame.name, true);
[ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = (isUser ? user : target).getSprite().pipelineData[k]);
sprite.setPipelineData("spriteKey", (isUser ? user : target).getBattleSpriteKey());
const sprite = scene.addPokemonSprite(isUser ? user! : target, 0, 0, spriteSource!.texture, spriteSource!.frame.name, true); // TODO: are those bangs correct?
[ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = (isUser ? user! : target).getSprite().pipelineData[k]); // TODO: are those bangs correct?
sprite.setPipelineData("spriteKey", (isUser ? user! : target).getBattleSpriteKey());
sprite.setPipelineData("shiny", (isUser ? user : target).shiny);
sprite.setPipelineData("variant", (isUser ? user : target).variant);
sprite.setPipelineData("ignoreFieldPos", true);
@ -853,7 +859,7 @@ export abstract class BattleAnim {
const spriteIndex = isUser ? u++ : t++;
const pokemonSprite = sprites[spriteIndex];
const graphicFrameData = frameData.get(frame.target).get(spriteIndex);
const graphicFrameData = frameData.get(frame.target)!.get(spriteIndex)!; // TODO: are the bangs correct?
pokemonSprite.setPosition(graphicFrameData.x, graphicFrameData.y - ((spriteSource.height / 2) * (spriteSource.parentContainer.scale - 1)));
pokemonSprite.setAngle(graphicFrameData.angle);
@ -868,7 +874,7 @@ export abstract class BattleAnim {
} else {
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
if (g === sprites.length) {
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim.graphic, 1);
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1); // TODO: is the bang correct?
sprites.push(newSprite);
scene.field.add(newSprite);
spritePriorities.push(1);
@ -881,7 +887,7 @@ export abstract class BattleAnim {
const setSpritePriority = (priority: integer) => {
switch (priority) {
case 0:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon() || scene.getPlayerPokemon());
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon() || scene.getPlayerPokemon()!); // TODO: is this bang correct?
break;
case 1:
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
@ -892,11 +898,11 @@ export abstract class BattleAnim {
if (this.bgSprite) {
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
} else {
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user);
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
}
break;
case AnimFocus.TARGET:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target);
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
@ -906,10 +912,10 @@ export abstract class BattleAnim {
case 3:
switch (frame.focus) {
case AnimFocus.USER:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user);
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
break;
case AnimFocus.TARGET:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target);
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
@ -925,7 +931,7 @@ export abstract class BattleAnim {
moveSprite.setFrame(frame.graphicFrame);
//console.log(AnimFocus[frame.focus]);
const graphicFrameData = frameData.get(frame.target).get(graphicIndex);
const graphicFrameData = frameData.get(frame.target)!.get(graphicIndex)!; // TODO: are those bangs correct?
moveSprite.setPosition(graphicFrameData.x, graphicFrameData.y);
moveSprite.setAngle(graphicFrameData.angle);
moveSprite.setScale(graphicFrameData.scaleX, graphicFrameData.scaleY);
@ -935,8 +941,8 @@ export abstract class BattleAnim {
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
}
}
if (anim.frameTimedEvents.has(f)) {
for (const event of anim.frameTimedEvents.get(f)) {
if (anim?.frameTimedEvents.has(f)) {
for (const event of anim.frameTimedEvents.get(f)!) { // TODO: is this bang correct?
r = Math.max((anim.frames.length - f) + event.execute(scene, this), r);
}
}
@ -980,16 +986,16 @@ export abstract class BattleAnim {
}
export class CommonBattleAnim extends BattleAnim {
public commonAnim: CommonAnim;
public commonAnim: CommonAnim | null;
constructor(commonAnim: CommonAnim, user: Pokemon, target?: Pokemon) {
constructor(commonAnim: CommonAnim | null, user: Pokemon, target?: Pokemon) {
super(user, target || user);
this.commonAnim = commonAnim;
}
getAnim(): AnimConfig {
return commonAnims.get(this.commonAnim);
getAnim(): AnimConfig | null {
return this.commonAnim ? commonAnims.get(this.commonAnim)! : null; // TODO: is this bang correct?
}
isOppAnim(): boolean {
@ -1009,11 +1015,11 @@ export class MoveAnim extends BattleAnim {
getAnim(): AnimConfig {
return moveAnims.get(this.move) instanceof AnimConfig
? moveAnims.get(this.move) as AnimConfig
: moveAnims.get(this.move)[this.user.isPlayer() ? 0 : 1] as AnimConfig;
: moveAnims.get(this.move)![this.user?.isPlayer() ? 0 : 1] as AnimConfig; // TODO: is this bang correct?
}
isOppAnim(): boolean {
return !this.user.isPlayer() && Array.isArray(moveAnims.get(this.move));
return !this.user?.isPlayer() && Array.isArray(moveAnims.get(this.move));
}
protected isHideUser(): boolean {
@ -1035,13 +1041,13 @@ export class MoveChargeAnim extends MoveAnim {
}
isOppAnim(): boolean {
return !this.user.isPlayer() && Array.isArray(chargeAnims.get(this.chargeAnim));
return !this.user?.isPlayer() && Array.isArray(chargeAnims.get(this.chargeAnim));
}
getAnim(): AnimConfig {
return chargeAnims.get(this.chargeAnim) instanceof AnimConfig
? chargeAnims.get(this.chargeAnim) as AnimConfig
: chargeAnims.get(this.chargeAnim)[this.user.isPlayer() ? 0 : 1] as AnimConfig;
: chargeAnims.get(this.chargeAnim)![this.user?.isPlayer() ? 0 : 1] as AnimConfig; // TODO: is this bang correct?
}
}
@ -1059,19 +1065,19 @@ export async function populateAnims() {
moveNameToId[moveName] = move;
}
const seNames = [];//(await fs.readdir('./public/audio/se/battle_anims/')).map(se => se.toString());
const seNames: string[] = [];//(await fs.readdir('./public/audio/se/battle_anims/')).map(se => se.toString());
const animsData = [];//battleAnimRawData.split('!ruby/array:PBAnimation').slice(1);
const animsData : any[] = [];//battleAnimRawData.split('!ruby/array:PBAnimation').slice(1); // TODO: add a proper type
for (let a = 0; a < animsData.length; a++) {
const fields = animsData[a].split("@").slice(1);
const nameField = fields.find(f => f.startsWith("name: "));
let isOppMove: boolean;
let commonAnimId: CommonAnim;
let chargeAnimId: ChargeAnim;
let isOppMove: boolean | undefined;
let commonAnimId: CommonAnim | undefined;
let chargeAnimId: ChargeAnim | undefined;
if (!nameField.startsWith("name: Move:") && !(isOppMove = nameField.startsWith("name: OppMove:"))) {
const nameMatch = commonNamePattern.exec(nameField);
const nameMatch = commonNamePattern.exec(nameField)!; // TODO: is this bang correct?
const name = nameMatch[2].toLowerCase();
if (commonAnimMatchNames.indexOf(name) > -1) {
commonAnimId = commonAnimIds[commonAnimMatchNames.indexOf(name)];
@ -1128,14 +1134,14 @@ export async function populateAnims() {
for (let t = 0; t < timingEntries.length; t++) {
const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",")
.replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1");
const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)[1]);
let resourceName = /name: "(.*?)"/.exec(timingData)[1].replace("''", "");
const timingType = parseInt(/timingType: (\d)/.exec(timingData)[1]);
let timedEvent: AnimTimedEvent;
const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct?
const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct?
let timedEvent: AnimTimedEvent | undefined;
switch (timingType) {
case 0:
if (resourceName && resourceName.indexOf(".") === -1) {
let ext: string;
let ext: string | undefined;
[ "wav", "mp3", "m4a" ].every(e => {
if (seNames.indexOf(`${resourceName}.${e}`) > -1) {
ext = e;
@ -1162,7 +1168,7 @@ export async function populateAnims() {
}
const propPattern = /([a-z]+): (.*?)(?:,|\})/ig;
let propMatch: RegExpExecArray;
while ((propMatch = propPattern.exec(timingData))) {
while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct?
const prop = propMatch[1];
let value: any = propMatch[2];
switch (prop) {
@ -1194,7 +1200,7 @@ export async function populateAnims() {
if (!anim.frameTimedEvents.has(frameIndex)) {
anim.frameTimedEvents.set(frameIndex, []);
}
anim.frameTimedEvents.get(frameIndex).push(timedEvent);
anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct?
}
break;
case "position":

View File

@ -8,7 +8,8 @@ export enum BattleStat {
SPD,
ACC,
EVA,
RAND
RAND,
HP
}
export function getBattleStatName(stat: BattleStat) {
@ -27,6 +28,8 @@ export function getBattleStatName(stat: BattleStat) {
return i18next.t("pokemonInfo:Stat.ACC");
case BattleStat.EVA:
return i18next.t("pokemonInfo:Stat.EVA");
case BattleStat.HP:
return i18next.t("pokemonInfo:Stat.HPStat");
default:
return "???";
}

View File

@ -1,4 +1,4 @@
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims";
import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangeCallback, StatChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
@ -36,11 +36,11 @@ export class BattlerTag {
public sourceMove: Moves;
public sourceId?: number;
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove: Moves, sourceId?: number) {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number) {
this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
this.turnCount = turnCount;
this.sourceMove = sourceMove;
this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId;
}
@ -66,7 +66,7 @@ export class BattlerTag {
return false;
}
getMoveName(): string {
getMoveName(): string | null {
return this.sourceMove
? allMoves[this.sourceMove].name
: null;
@ -110,7 +110,7 @@ export class RechargingTag extends BattlerTag {
/** Cancels the source's move this turn and queues a "__ must recharge!" message */
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsRechargingLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:rechargingLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.getMoveQueue().shift();
}
@ -118,6 +118,44 @@ export class RechargingTag extends BattlerTag {
}
}
/**
* BattlerTag representing the "charge phase" of Beak Blast
* Pokemon with this tag will inflict BURN status on any attacker that makes contact.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Beak_Blast_(move) | Beak Blast}
*/
export class BeakBlastChargingTag extends BattlerTag {
constructor() {
super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1, Moves.BEAK_BLAST);
}
onAdd(pokemon: Pokemon): void {
// Play Beak Blast's charging animation
new MoveChargeAnim(ChargeAnim.BEAK_BLAST_CHARGING, this.sourceMove, pokemon).play(pokemon.scene);
// Queue Beak Blast's header message
pokemon.scene.queueMessage(i18next.t("moveTriggers:startedHeatingUpBeak", { pokemonName: getPokemonNameWithAffix(pokemon) }));
}
/**
* Inflicts `BURN` status on attackers that make contact, and causes this tag
* to be removed after the source makes a move (or the turn ends, whichever comes first)
* @param pokemon {@linkcode Pokemon} the owner of this tag
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
* @returns `true` if invoked with the CUSTOM lapse type; `false` otherwise
*/
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
}
return true;
}
return super.lapse(pokemon, lapseType);
}
}
export class TrappedTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
super(tagType, lapseType, turnCount, sourceMove, sourceId);
@ -139,7 +177,7 @@ export class TrappedTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsTrappedOnRemove", {
pokemon.scene.queueMessage(i18next.t("battlerTags:trappedOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName()
}));
@ -154,7 +192,7 @@ export class TrappedTag extends BattlerTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsTrappedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
return i18next.t("battlerTags:trappedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -185,7 +223,7 @@ export class FlinchedTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsFlinchedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:flinchedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
return super.lapse(pokemon, lapseType);
@ -234,26 +272,26 @@ export class ConfusedTag extends BattlerTag {
super.onAdd(pokemon);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM && super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
// 1/3 chance of hitting self with a 40 base power move
@ -261,7 +299,7 @@ export class ConfusedTag extends BattlerTag {
const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF);
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedLapseHurtItself"));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
pokemon.damageAndUpdate(damage);
pokemon.battleData.hitCount++;
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
@ -299,22 +337,22 @@ export class DestinyBondTag extends BattlerTag {
if (lapseType !== BattlerTagLapseType.CUSTOM) {
return super.lapse(pokemon, lapseType);
}
const source = pokemon.scene.getPokemonById(this.sourceId);
if (!source.isFainted()) {
const source = this.sourceId ? pokemon.scene.getPokemonById(this.sourceId) : null;
if (!source?.isFainted()) {
return true;
}
if (source.getAlly() === pokemon) {
if (source?.getAlly() === pokemon) {
return false;
}
if (pokemon.isBossImmune()) {
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsDestinyBondLapseIsBoss", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:destinyBondLapseIsBoss", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return false;
}
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsDestinyBondLapse", {
i18next.t("battlerTags:destinyBondLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(source),
pokemonNameWithAffix2: getPokemonNameWithAffix(pokemon)
})
@ -330,16 +368,28 @@ export class InfatuatedTag extends BattlerTag {
}
canAdd(pokemon: Pokemon): boolean {
return pokemon.isOppositeGender(pokemon.scene.getPokemonById(this.sourceId));
if (this.sourceId) {
const pkm = pokemon.scene.getPokemonById(this.sourceId);
if (pkm) {
return pokemon.isOppositeGender(pkm);
} else {
console.warn("canAdd: this.sourceId is not a valid pokemon id!", this.sourceId);
return false;
}
} else {
console.warn("canAdd: this.sourceId is undefined");
return false;
}
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedOnAdd", {
i18next.t("battlerTags:infatuatedOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct?
})
);
}
@ -347,7 +397,7 @@ export class InfatuatedTag extends BattlerTag {
onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsInfatuatedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:infatuatedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -355,15 +405,15 @@ export class InfatuatedTag extends BattlerTag {
if (ret) {
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedLapse", {
i18next.t("battlerTags:infatuatedLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct?
})
);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT));
if (pokemon.randSeedInt(2)) {
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsInfatuatedLapseImmobilize", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:infatuatedLapseImmobilize", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
}
}
@ -374,7 +424,7 @@ export class InfatuatedTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsInfatuatedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:infatuatedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
isSourceLinked(): boolean {
@ -409,8 +459,8 @@ export class SeedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSeededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex();
pokemon.scene.queueMessage(i18next.t("battlerTags:seededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -429,7 +479,7 @@ export class SeedTag extends BattlerTag {
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(),
!reverseDrain ? damage : damage * -1,
!reverseDrain ? i18next.t("battle:battlerTagsSeededLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }) : i18next.t("battle:battlerTagsSeededLapseShed", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
!reverseDrain ? i18next.t("battlerTags:seededLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }) : i18next.t("battlerTags:seededLapseShed", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
false, true));
}
}
@ -451,20 +501,20 @@ export class NightmareTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsNightmareOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsNightmareOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) {
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsNightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
const cancelled = new Utils.BooleanHolder(false);
@ -552,15 +602,15 @@ export class EncoreTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsEncoreOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:encoreOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
const movePhase = pokemon.scene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon);
if (movePhase) {
const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId);
const movesetMove = pokemon.getMoveset().find(m => m!.moveId === this.moveId); // TODO: is this bang correct?
if (movesetMove) {
const lastMove = pokemon.getLastXMoves(1)[0];
pokemon.scene.tryReplacePhase((m => m instanceof MovePhase && m.pokemon === pokemon),
new MovePhase(pokemon.scene, pokemon, lastMove.targets, movesetMove));
new MovePhase(pokemon.scene, pokemon, lastMove.targets!, movesetMove)); // TODO: is this bang correct?
}
}
}
@ -568,7 +618,7 @@ export class EncoreTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsEncoreOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:encoreOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
@ -579,8 +629,8 @@ export class HelpingHandTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsHelpingHandOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)),
i18next.t("battlerTags:helpingHandOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
pokemonName: getPokemonNameWithAffix(pokemon)
})
);
@ -616,7 +666,7 @@ export class IngrainTag extends TrappedTag {
pokemon.scene,
pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16),
i18next.t("battle:battlerTagsIngrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
true
)
);
@ -626,7 +676,7 @@ export class IngrainTag extends TrappedTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsIngrainOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
return i18next.t("battlerTags:ingrainOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
getDescriptor(): string {
@ -667,7 +717,7 @@ export class AquaRingTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsAquaRingOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:aquaRingOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -679,7 +729,7 @@ export class AquaRingTag extends BattlerTag {
pokemon.scene,
pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16),
i18next.t("battle:battlerTagsAquaRingLapse", {
i18next.t("battlerTags:aquaRingLapse", {
moveName: this.getMoveName(),
pokemonName: getPokemonNameWithAffix(pokemon)
}),
@ -729,7 +779,7 @@ export class DrowsyTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsDrowsyOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:drowsyOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -773,7 +823,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
if (ret) {
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsDamagingTrapLapse", {
i18next.t("battlerTags:damagingTrapLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName()
})
@ -798,9 +848,9 @@ export class BindTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsBindOnTrap", {
return i18next.t("battlerTags:bindOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
moveName: this.getMoveName()
});
}
@ -812,9 +862,9 @@ export class WrapTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsWrapOnTrap", {
return i18next.t("battlerTags:wrapOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
});
}
}
@ -825,7 +875,7 @@ export abstract class VortexTrapTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsVortexOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
return i18next.t("battlerTags:vortexOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -847,8 +897,8 @@ export class ClampTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsClampOnTrap", {
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)),
return i18next.t("battlerTags:clampOnTrap", {
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
@ -860,7 +910,7 @@ export class SandTombTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsSandTombOnTrap", {
return i18next.t("battlerTags:sandTombOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName()
});
@ -873,7 +923,7 @@ export class MagmaStormTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsMagmaStormOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
return i18next.t("battlerTags:magmaStormOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -883,7 +933,7 @@ export class SnapTrapTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsSnapTrapOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
return i18next.t("battlerTags:snapTrapOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -893,9 +943,9 @@ export class ThunderCageTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsThunderCageOnTrap", {
return i18next.t("battlerTags:thunderCageOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
});
}
}
@ -906,9 +956,9 @@ export class InfestationTag extends DamagingTrapTag {
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsInfestationOnTrap", {
return i18next.t("battlerTags:infestationOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
});
}
}
@ -922,13 +972,13 @@ export class ProtectedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsProtectedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:protectedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
new CommonBattleAnim(CommonAnim.PROTECT, pokemon).play(pokemon.scene);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsProtectedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:protectedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
// Stop multi-hit moves early
const effectPhase = pokemon.scene.getCurrentPhase();
@ -1061,12 +1111,12 @@ export class EnduringTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsEnduringOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:enduringOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsEnduringLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:enduringLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return true;
}
@ -1081,7 +1131,7 @@ export class SturdyTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSturdyLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:sturdyLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
return true;
}
@ -1103,7 +1153,7 @@ export class PerishSongTag extends BattlerTag {
if (ret) {
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsPerishSongLapse", {
i18next.t("battlerTags:perishSongLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
turnCount: this.turnCount
})
@ -1139,7 +1189,7 @@ export class CenterOfAttentionTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsCenterOfAttentionOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:centerOfAttentionOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
@ -1178,7 +1228,7 @@ export class TruantTag extends AbilityBattlerTag {
if (lastMove && lastMove.move !== Moves.NONE) {
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, passive));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsTruantLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:truantLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
return true;
@ -1193,7 +1243,7 @@ export class SlowStartTag extends AbilityBattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSlowStartOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null, true);
pokemon.scene.queueMessage(i18next.t("battlerTags:slowStartOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null, true);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1207,7 +1257,7 @@ export class SlowStartTag extends AbilityBattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSlowStartOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null);
pokemon.scene.queueMessage(i18next.t("battlerTags:slowStartOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null);
}
}
@ -1242,6 +1292,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
return highestValue;
}, 0);
highestStat = highestStat!; // tell TS compiler it's defined!
this.stat = highestStat;
switch (this.stat) {
@ -1253,13 +1304,13 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
break;
}
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHighestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: getStatName(highestStat) }), null, false, null, true);
pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: getStatName(highestStat) }), null, false, null, true);
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHighestStatBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: allAbilities[this.ability].name }));
pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: allAbilities[this.ability].name }));
}
}
@ -1346,13 +1397,13 @@ export class MagnetRisenTag extends TypeImmuneTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsMagnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsMagnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
@ -1393,7 +1444,7 @@ export class CritBoostTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsCritBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:critBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1403,7 +1454,7 @@ export class CritBoostTag extends BattlerTag {
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsCritBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:critBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
@ -1426,8 +1477,8 @@ export class SaltCuredTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSaltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex();
pokemon.scene.queueMessage(i18next.t("battlerTags:saltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1444,7 +1495,7 @@ export class SaltCuredTag extends BattlerTag {
pokemon.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1));
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsSaltCuredLapse", {
i18next.t("battlerTags:saltCuredLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName()
})
@ -1474,7 +1525,7 @@ export class CursedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex();
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1488,7 +1539,7 @@ export class CursedTag extends BattlerTag {
if (!cancelled.value) {
pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 4), 1));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsCursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
@ -1507,36 +1558,25 @@ export class GroundedTag extends BattlerTag {
}
}
/**
* Provides the Ice Face ability's effects.
*/
export class IceFaceTag extends BattlerTag {
constructor(sourceMove: Moves) {
super(BattlerTagType.ICE_FACE, BattlerTagLapseType.CUSTOM, 1, sourceMove);
/** Common attributes of form change abilities that block damage */
export class FormBlockDamageTag extends BattlerTag {
constructor(tagType: BattlerTagType) {
super(tagType, BattlerTagLapseType.CUSTOM, 1);
}
/**
* Determines if the Ice Face tag can be added to the Pokémon.
* @param {Pokemon} pokemon - The Pokémon to which the tag might be added.
* @returns {boolean} - True if the tag can be added, false otherwise.
* Determines if the tag can be added to the Pokémon.
* @param {Pokemon} pokemon The Pokémon to which the tag might be added.
* @returns {boolean} True if the tag can be added, false otherwise.
*/
canAdd(pokemon: Pokemon): boolean {
const weatherType = pokemon.scene.arena.weather?.weatherType;
const isWeatherSnowOrHail = weatherType === WeatherType.HAIL || weatherType === WeatherType.SNOW;
const isFormIceFace = pokemon.formIndex === 0;
// Hard code Eiscue for now, this is to prevent the game from crashing if fused pokemon has Ice Face
if ((pokemon.species.speciesId === Species.EISCUE && isFormIceFace) || isWeatherSnowOrHail) {
return true;
}
return false;
return pokemon.formIndex === 0;
}
/**
* Applies the Ice Face tag to the Pokémon.
* Triggers a form change to Ice Face if the Pokémon is not in its Ice Face form.
* @param {Pokemon} pokemon - The Pokémon to which the tag is added.
* Applies the tag to the Pokémon.
* Triggers a form change if the Pokémon is not in its defense form.
* @param {Pokemon} pokemon The Pokémon to which the tag is added.
*/
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
@ -1547,9 +1587,9 @@ export class IceFaceTag extends BattlerTag {
}
/**
* Removes the Ice Face tag from the Pokémon.
* Triggers a form change to Noice when the tag is removed.
* @param {Pokemon} pokemon - The Pokémon from which the tag is removed.
* Removes the tag from the Pokémon.
* Triggers a form change when the tag is removed.
* @param {Pokemon} pokemon The Pokémon from which the tag is removed.
*/
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
@ -1558,6 +1598,24 @@ export class IceFaceTag extends BattlerTag {
}
}
/** Provides the additional weather-based effects of the Ice Face ability */
export class IceFaceBlockDamageTag extends FormBlockDamageTag {
constructor(tagType: BattlerTagType) {
super(tagType);
}
/**
* Determines if the tag can be added to the Pokémon.
* @param {Pokemon} pokemon The Pokémon to which the tag might be added.
* @returns {boolean} True if the tag can be added, false otherwise.
*/
canAdd(pokemon: Pokemon): boolean {
const weatherType = pokemon.scene.arena.weather?.weatherType;
const isWeatherSnowOrHail = weatherType === WeatherType.HAIL || weatherType === WeatherType.SNOW;
return super.canAdd(pokemon) || isWeatherSnowOrHail;
}
}
/**
* Battler tag enabling the Stockpile mechanic. This tag handles:
@ -1612,7 +1670,7 @@ export class StockpilingTag extends BattlerTag {
if (this.stockpiledCount < 3) {
this.stockpiledCount++;
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsStockpilingOnAdd", {
pokemon.scene.queueMessage(i18next.t("battlerTags:stockpilingOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
stockpiledCount: this.stockpiledCount
}));
@ -1647,10 +1705,86 @@ export class StockpilingTag extends BattlerTag {
}
}
/**
* Battler tag for Gulp Missile used by Cramorant.
* @extends BattlerTag
*/
export class GulpMissileTag extends BattlerTag {
constructor(tagType: BattlerTagType, sourceMove: Moves) {
super(tagType, BattlerTagLapseType.CUSTOM, 0, sourceMove);
}
/**
* Gulp Missile's initial form changes are triggered by using Surf and Dive.
* @param {Pokemon} pokemon The Pokemon with Gulp Missile ability.
* @returns Whether the BattlerTag can be added.
*/
canAdd(pokemon: Pokemon): boolean {
const isSurfOrDive = [ Moves.SURF, Moves.DIVE ].includes(this.sourceMove);
const isNormalForm = pokemon.formIndex === 0 && !pokemon.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA) && !pokemon.getTag(BattlerTagType.GULP_MISSILE_PIKACHU);
const isCramorant = pokemon.species.speciesId === Species.CRAMORANT;
return isSurfOrDive && isNormalForm && isCramorant;
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
}
/**
* Tag that makes the target drop all of it type immunities
* and all accuracy checks ignore its evasiveness stat.
*
* Applied by moves: {@linkcode Moves.ODOR_SLEUTH | Odor Sleuth},
* {@linkcode Moves.MIRACLE_EYE | Miracle Eye} and {@linkcode Moves.FORESIGHT | Foresight}.
*
* @extends BattlerTag
* @see {@linkcode ignoreImmunity}
*/
export class ExposedTag extends BattlerTag {
private defenderType: Type;
private allowedTypes: Type[];
constructor(tagType: BattlerTagType, sourceMove: Moves, defenderType: Type, allowedTypes: Type[]) {
super(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove);
this.defenderType = defenderType;
this.allowedTypes = allowedTypes;
}
/**
* When given a battler tag or json representing one, load the data for it.
* @param {BattlerTag | any} source A battler tag
*/
loadTag(source: BattlerTag | any): void {
super.loadTag(source);
this.defenderType = source.defenderType as Type;
this.allowedTypes = source.allowedTypes as Type[];
}
/**
* @param types {@linkcode Type} of the defending Pokemon
* @param moveType {@linkcode Type} of the move targetting it
* @returns `true` if the move should be allowed to target the defender.
*/
ignoreImmunity(type: Type, moveType: Type): boolean {
return type === this.defenderType && this.allowedTypes.includes(moveType);
}
}
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) {
case BattlerTagType.RECHARGING:
return new RechargingTag(sourceMove);
case BattlerTagType.BEAK_BLAST_CHARGING:
return new BeakBlastChargingTag();
case BattlerTagType.FLINCHED:
return new FlinchedTag(sourceMove);
case BattlerTagType.INTERRUPTED:
@ -1741,8 +1875,6 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
case BattlerTagType.NO_CRIT:
return new BattlerTag(tagType, BattlerTagLapseType.AFTER_MOVE, turnCount, sourceMove);
case BattlerTagType.ALWAYS_GET_HIT:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove);
@ -1765,11 +1897,20 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.DESTINY_BOND:
return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.ICE_FACE:
return new IceFaceTag(sourceMove);
return new IceFaceBlockDamageTag(tagType);
case BattlerTagType.DISGUISE:
return new FormBlockDamageTag(tagType);
case BattlerTagType.STOCKPILING:
return new StockpilingTag(sourceMove);
case BattlerTagType.OCTOLOCK:
return new OctolockTag(sourceId);
case BattlerTagType.IGNORE_GHOST:
return new ExposedTag(tagType, sourceMove, Type.GHOST, [Type.NORMAL, Type.FIGHTING]);
case BattlerTagType.IGNORE_DARK:
return new ExposedTag(tagType, sourceMove, Type.DARK, [Type.PSYCHIC]);
case BattlerTagType.GULP_MISSILE_ARROKUDA:
case BattlerTagType.GULP_MISSILE_PIKACHU:
return new GulpMissileTag(tagType, sourceMove);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -54,7 +54,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold);
return !!pokemon.getMoveset().find(m => !m.getPpRatio());
return !!pokemon.getMoveset().find(m => !m?.getPpRatio());
};
}
}
@ -120,10 +120,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const ppRestoreMove = pokemon.getMoveset().find(m => !m.getPpRatio()) ? pokemon.getMoveset().find(m => !m.getPpRatio()) : pokemon.getMoveset().find(m => m.getPpRatio() < 1);
const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct?
if (ppRestoreMove !== undefined) {
ppRestoreMove.ppUsed = Math.max(ppRestoreMove.ppUsed - 10, 0);
pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove.getName(), berryName: getBerryName(berryType) }));
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
}
};
}

View File

@ -7705,7 +7705,7 @@ export function initBiomes() {
? pokemonEvolutions[speciesId]
: [];
if (!biomeEntries.filter(b => b[0] !== Biome.END).length && !speciesEvolutions.filter(es => !!((pokemonBiomes.find(p => p[0] === es.speciesId))[3] as any[]).filter(b => b[0] !== Biome.END).length).length) {
if (!biomeEntries.filter(b => b[0] !== Biome.END).length && !speciesEvolutions.filter(es => !!((pokemonBiomes.find(p => p[0] === es.speciesId)!)[3] as any[]).filter(b => b[0] !== Biome.END).length).length) { // TODO: is the bang on the `find()` correct?
uncatchableSpecies.push(speciesId);
}

View File

@ -273,11 +273,9 @@ export abstract class Challenge {
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @param checkEvolutions {@link boolean} If true, check the pokemon's future evolutions
* @param checkForms {@link boolean} If true, check the pokemon's alternative forms
* @returns {@link boolean} Whether this function did anything.
*/
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false, checkEvolutions?: boolean, checkForms?: boolean): boolean {
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
return false;
}
@ -405,14 +403,13 @@ export class SingleGenerationChallenge extends Challenge {
super(Challenges.SINGLE_GENERATION, 9);
}
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false, checkEvolutions?: boolean): boolean {
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
const generations = [pokemon.generation];
const checkPokemonEvolutions = checkEvolutions ?? true as boolean;
if (soft) {
const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) {
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
generations.push(getPokemonSpecies(e.speciesId).generation);
@ -430,7 +427,7 @@ export class SingleGenerationChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
const baseGeneration = pokemon.species.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.species.speciesId).generation;
const fusionGeneration = pokemon.isFusion() ? pokemon.fusionSpecies.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.fusionSpecies.speciesId).generation : 0;
const fusionGeneration = pokemon.isFusion() ? pokemon.fusionSpecies?.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; // TODO: is the bang on fusionSpecies correct?
if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) {
valid.value = false;
return true;
@ -533,22 +530,20 @@ export class SingleTypeChallenge extends Challenge {
super(Challenges.SINGLE_TYPE, 18);
}
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false, checkEvolutions?: boolean, checkForms?: boolean): boolean {
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex);
const types = [speciesForm.type1, speciesForm.type2];
const checkPokemonEvolutions = checkEvolutions ?? true as boolean;
const checkPokemonForms = checkForms ?? true as boolean;
if (soft) {
const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) {
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2);
});
}
if (pokemonFormChanges.hasOwnProperty(checking) && checkPokemonForms) {
if (checking && pokemonFormChanges.hasOwnProperty(checking)) {
pokemonFormChanges[checking].forEach(f1 => {
getPokemonSpecies(checking).forms.forEach(f2 => {
if (f1.formKey === f2.formKey) {
@ -568,10 +563,11 @@ export class SingleTypeChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true)
&& !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies : pokemon.species).speciesId === o.species)) {
&& !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species)) { // TODO: is the bang on fusionSpecies correct?
valid.value = false;
return true;
}
return false;
}
/**
@ -634,8 +630,8 @@ export class FreshStartChallenge extends Challenge {
}
applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean {
if (defaultStarterSpecies.includes(species) && cost.value !== 3) {
cost.value = 3;
if (defaultStarterSpecies.includes(species)) {
cost.value = speciesStarters[species];
return true;
}
return false;
@ -745,7 +741,7 @@ export class LowerStarterPointsChallenge extends Challenge {
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean, checkEvolutions?: boolean, checkForms?: boolean): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean): boolean;
/**
* Apply all challenges that modify available total starter points.
* @param gameMode {@link GameMode} The current gameMode
@ -853,7 +849,7 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
if (c.value !== 0) {
switch (challengeType) {
case ChallengeType.STARTER_CHOICE:
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3], args[4], args[5]);
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]);

View File

@ -11,15 +11,15 @@ export interface DailyRunConfig {
starters: Starter;
}
export function fetchDailyRunSeed(): Promise<string> {
return new Promise<string>((resolve, reject) => {
export function fetchDailyRunSeed(): Promise<string | null> {
return new Promise<string | null>((resolve, reject) => {
Utils.apiFetch("daily/seed").then(response => {
if (!response.ok) {
resolve(null);
return;
}
return response.text();
}).then(seed => resolve(seed))
}).then(seed => resolve(seed!)) // TODO: is this bang correct?
.catch(err => reject(err));
});
}

View File

@ -452,144 +452,304 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
[TrainerType.ROCKET_GRUNT]: [
{
encounter: [
"dialogue:rocket_grunt.encounter.1"
"dialogue:rocket_grunt.encounter.1",
"dialogue:rocket_grunt.encounter.2",
"dialogue:rocket_grunt.encounter.3",
"dialogue:rocket_grunt.encounter.4",
"dialogue:rocket_grunt.encounter.5",
],
victory: [
"dialogue:rocket_grunt.victory.1"
"dialogue:rocket_grunt.victory.1",
"dialogue:rocket_grunt.victory.2",
"dialogue:rocket_grunt.victory.3",
"dialogue:rocket_grunt.victory.4",
"dialogue:rocket_grunt.victory.5",
]
}
],
[TrainerType.ROCKET_ADMIN]: [
[TrainerType.ARCHER]: [
{
encounter: [
"dialogue:rocket_admin.encounter.1",
"dialogue:rocket_admin.encounter.2",
"dialogue:rocket_admin.encounter.3",
"dialogue:archer.encounter.1",
"dialogue:archer.encounter.2",
"dialogue:archer.encounter.3",
],
victory: [
"dialogue:rocket_admin.victory.1",
"dialogue:rocket_admin.victory.2",
"dialogue:rocket_admin.victory.3",
"dialogue:archer.victory.1",
"dialogue:archer.victory.2",
"dialogue:archer.victory.3",
]
}
],
[TrainerType.ARIANA]: [
{
encounter: [
"dialogue:ariana.encounter.1",
"dialogue:ariana.encounter.2",
"dialogue:ariana.encounter.3",
],
victory: [
"dialogue:ariana.victory.1",
"dialogue:ariana.victory.2",
"dialogue:ariana.victory.3",
]
}
],
[TrainerType.PROTON]: [
{
encounter: [
"dialogue:proton.encounter.1",
"dialogue:proton.encounter.2",
"dialogue:proton.encounter.3",
],
victory: [
"dialogue:proton.victory.1",
"dialogue:proton.victory.2",
"dialogue:proton.victory.3",
]
}
],
[TrainerType.PETREL]: [
{
encounter: [
"dialogue:petrel.encounter.1",
"dialogue:petrel.encounter.2",
"dialogue:petrel.encounter.3",
],
victory: [
"dialogue:petrel.victory.1",
"dialogue:petrel.victory.2",
"dialogue:petrel.victory.3",
]
}
],
[TrainerType.MAGMA_GRUNT]: [
{
encounter: [
"dialogue:magma_grunt.encounter.1"
"dialogue:magma_grunt.encounter.1",
"dialogue:magma_grunt.encounter.2",
"dialogue:magma_grunt.encounter.3",
"dialogue:magma_grunt.encounter.4",
"dialogue:magma_grunt.encounter.5",
],
victory: [
"dialogue:magma_grunt.victory.1"
"dialogue:magma_grunt.victory.1",
"dialogue:magma_grunt.victory.2",
"dialogue:magma_grunt.victory.3",
"dialogue:magma_grunt.victory.4",
"dialogue:magma_grunt.victory.5",
]
}
],
[TrainerType.MAGMA_ADMIN]: [
[TrainerType.TABITHA]: [
{
encounter: [
"dialogue:magma_admin.encounter.1",
"dialogue:magma_admin.encounter.2",
"dialogue:magma_admin.encounter.3",
"dialogue:tabitha.encounter.1",
"dialogue:tabitha.encounter.2",
"dialogue:tabitha.encounter.3",
],
victory: [
"dialogue:magma_admin.victory.1",
"dialogue:magma_admin.victory.2",
"dialogue:magma_admin.victory.3",
"dialogue:tabitha.victory.1",
"dialogue:tabitha.victory.2",
"dialogue:tabitha.victory.3",
]
}
],
[TrainerType.COURTNEY]: [
{
encounter: [
"dialogue:courtney.encounter.1",
"dialogue:courtney.encounter.2",
"dialogue:courtney.encounter.3",
],
victory: [
"dialogue:courtney.victory.1",
"dialogue:courtney.victory.2",
"dialogue:courtney.victory.3",
]
}
],
[TrainerType.AQUA_GRUNT]: [
{
encounter: [
"dialogue:aqua_grunt.encounter.1"
"dialogue:aqua_grunt.encounter.1",
"dialogue:aqua_grunt.encounter.2",
"dialogue:aqua_grunt.encounter.3",
"dialogue:aqua_grunt.encounter.4",
"dialogue:aqua_grunt.encounter.5",
],
victory: [
"dialogue:aqua_grunt.victory.1"
"dialogue:aqua_grunt.victory.1",
"dialogue:aqua_grunt.victory.2",
"dialogue:aqua_grunt.victory.3",
"dialogue:aqua_grunt.victory.4",
"dialogue:aqua_grunt.victory.5",
]
}
],
[TrainerType.AQUA_ADMIN]: [
[TrainerType.MATT]: [
{
encounter: [
"dialogue:aqua_admin.encounter.1",
"dialogue:aqua_admin.encounter.2",
"dialogue:aqua_admin.encounter.3",
"dialogue:matt.encounter.1",
"dialogue:matt.encounter.2",
"dialogue:matt.encounter.3",
],
victory: [
"dialogue:aqua_admin.victory.1",
"dialogue:aqua_admin.victory.2",
"dialogue:aqua_admin.victory.3",
"dialogue:matt.victory.1",
"dialogue:matt.victory.2",
"dialogue:matt.victory.3",
]
}
],
[TrainerType.SHELLY]: [
{
encounter: [
"dialogue:shelly.encounter.1",
"dialogue:shelly.encounter.2",
"dialogue:shelly.encounter.3",
],
victory: [
"dialogue:shelly.victory.1",
"dialogue:shelly.victory.2",
"dialogue:shelly.victory.3",
]
}
],
[TrainerType.GALACTIC_GRUNT]: [
{
encounter: [
"dialogue:galactic_grunt.encounter.1"
"dialogue:galactic_grunt.encounter.1",
"dialogue:galactic_grunt.encounter.2",
"dialogue:galactic_grunt.encounter.3",
"dialogue:galactic_grunt.encounter.4",
"dialogue:galactic_grunt.encounter.5",
],
victory: [
"dialogue:galactic_grunt.victory.1"
"dialogue:galactic_grunt.victory.1",
"dialogue:galactic_grunt.victory.2",
"dialogue:galactic_grunt.victory.3",
"dialogue:galactic_grunt.victory.4",
"dialogue:galactic_grunt.victory.5",
]
}
],
[TrainerType.GALACTIC_ADMIN]: [
[TrainerType.JUPITER]: [
{
encounter: [
"dialogue:galactic_admin.encounter.1",
"dialogue:galactic_admin.encounter.2",
"dialogue:galactic_admin.encounter.3",
"dialogue:jupiter.encounter.1",
"dialogue:jupiter.encounter.2",
"dialogue:jupiter.encounter.3",
],
victory: [
"dialogue:galactic_admin.victory.1",
"dialogue:galactic_admin.victory.2",
"dialogue:galactic_admin.victory.3",
"dialogue:jupiter.victory.1",
"dialogue:jupiter.victory.2",
"dialogue:jupiter.victory.3",
]
}
],
[TrainerType.MARS]: [
{
encounter: [
"dialogue:mars.encounter.1",
"dialogue:mars.encounter.2",
"dialogue:mars.encounter.3",
],
victory: [
"dialogue:mars.victory.1",
"dialogue:mars.victory.2",
"dialogue:mars.victory.3",
]
}
],
[TrainerType.SATURN]: [
{
encounter: [
"dialogue:saturn.encounter.1",
"dialogue:saturn.encounter.2",
"dialogue:saturn.encounter.3",
],
victory: [
"dialogue:saturn.victory.1",
"dialogue:saturn.victory.2",
"dialogue:saturn.victory.3",
]
}
],
[TrainerType.PLASMA_GRUNT]: [
{
encounter: [
"dialogue:plasma_grunt.encounter.1"
"dialogue:plasma_grunt.encounter.1",
"dialogue:plasma_grunt.encounter.2",
"dialogue:plasma_grunt.encounter.3",
"dialogue:plasma_grunt.encounter.4",
"dialogue:plasma_grunt.encounter.5",
],
victory: [
"dialogue:plasma_grunt.victory.1"
"dialogue:plasma_grunt.victory.1",
"dialogue:plasma_grunt.victory.2",
"dialogue:plasma_grunt.victory.3",
"dialogue:plasma_grunt.victory.4",
"dialogue:plasma_grunt.victory.5",
]
}
],
[TrainerType.PLASMA_SAGE]: [
[TrainerType.ZINZOLIN]: [
{
encounter: [
"dialogue:plasma_sage.encounter.1",
"dialogue:plasma_sage.encounter.2",
"dialogue:plasma_sage.encounter.3",
"dialogue:zinzolin.encounter.1",
"dialogue:zinzolin.encounter.2",
"dialogue:zinzolin.encounter.3",
],
victory: [
"dialogue:plasma_sage.victory.1",
"dialogue:plasma_sage.victory.2",
"dialogue:plasma_sage.victory.3",
"dialogue:zinzolin.victory.1",
"dialogue:zinzolin.victory.2",
"dialogue:zinzolin.victory.3",
]
}
],
[TrainerType.FLARE_GRUNT]: [
{
encounter: [
"dialogue:flare_grunt.encounter.1"
"dialogue:flare_grunt.encounter.1",
"dialogue:flare_grunt.encounter.2",
"dialogue:flare_grunt.encounter.3",
"dialogue:flare_grunt.encounter.4",
"dialogue:flare_grunt.encounter.5",
],
victory: [
"dialogue:flare_grunt.victory.1"
"dialogue:flare_grunt.victory.1",
"dialogue:flare_grunt.victory.2",
"dialogue:flare_grunt.victory.3",
"dialogue:flare_grunt.victory.4",
"dialogue:flare_grunt.victory.5",
]
}
],
[TrainerType.FLARE_ADMIN]: [
[TrainerType.BRYONY]: [
{
encounter: [
"dialogue:flare_admin.encounter.1",
"dialogue:flare_admin.encounter.2",
"dialogue:flare_admin.encounter.3",
"dialogue:bryony.encounter.1",
"dialogue:bryony.encounter.2",
"dialogue:bryony.encounter.3",
],
victory: [
"dialogue:flare_admin.victory.1",
"dialogue:flare_admin.victory.2",
"dialogue:flare_admin.victory.3",
"dialogue:bryony.victory.1",
"dialogue:bryony.victory.2",
"dialogue:bryony.victory.3",
]
}
],
[TrainerType.XEROSIC]: [
{
encounter: [
"dialogue:xerosic.encounter.1",
"dialogue:xerosic.encounter.2",
"dialogue:xerosic.encounter.3",
],
victory: [
"dialogue:xerosic.victory.1",
"dialogue:xerosic.victory.2",
"dialogue:xerosic.victory.3",
]
}
],

View File

@ -140,30 +140,30 @@ export class Egg {
constructor(eggOptions?: IEggOptions) {
//if (eggOptions.tier && eggOptions.species) throw Error("Error egg can't have species and tier as option. only choose one of them.")
this._sourceType = eggOptions.sourceType ?? undefined;
this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
// Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced
this._tier = eggOptions.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
this._tier = eggOptions?.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
// If egg was pulled, check if egg pity needs to override the egg tier
if (eggOptions.pulled) {
if (eggOptions?.pulled) {
// Needs this._tier and this._sourceType to work
this.checkForPityTierOverrides(eggOptions.scene);
this.checkForPityTierOverrides(eggOptions.scene!); // TODO: is this bang correct?
}
this._id = eggOptions.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier);
this._id = eggOptions?.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier);
this._sourceType = eggOptions.sourceType ?? undefined;
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
this._timestamp = eggOptions.timestamp ?? new Date().getTime();
this._sourceType = eggOptions?.sourceType ?? undefined;
this._hatchWaves = eggOptions?.hatchWaves ?? this.getEggTierDefaultHatchWaves();
this._timestamp = eggOptions?.timestamp ?? new Date().getTime();
// First roll shiny and variant so we can filter if species with an variant exist
this._isShiny = eggOptions.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
this._variantTier = eggOptions.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
this._species = eggOptions.species ?? this.rollSpecies(eggOptions.scene);
this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
this._species = eggOptions?.species ?? this.rollSpecies(eggOptions!.scene!)!; // TODO: Are those bangs correct?
this._overrideHiddenAbility = eggOptions.overrideHiddenAbility ?? false;
this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false;
// Override egg tier and hatchwaves if species was given
if (eggOptions.species) {
if (eggOptions?.species) {
this._tier = this.getEggTierFromSpeciesStarterValue();
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
}
@ -174,10 +174,10 @@ export class Egg {
this._variantTier = VariantTier.COMMON;
}
// Needs this._tier so it needs to be generated afer the tier override if bought from same species
this._eggMoveIndex = eggOptions.eggMoveIndex ?? this.rollEggMoveIndex();
if (eggOptions.pulled) {
this.increasePullStatistic(eggOptions.scene);
this.addEggToGameData(eggOptions.scene);
this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
if (eggOptions?.pulled) {
this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
}
}
@ -202,14 +202,18 @@ export class Egg {
// Legacy egg wants to hatch. Generate missing properties
if (!this._species) {
this._isShiny = this.rollShiny();
this._species = this.rollSpecies(scene);
this._species = this.rollSpecies(scene!)!; // TODO: are these bangs correct?
}
const pokemonSpecies = getPokemonSpecies(this._species);
let pokemonSpecies = getPokemonSpecies(this._species);
// Special condition to have Phione eggs also have a chance of generating Manaphy
if (this._species === Species.PHIONE) {
pokemonSpecies = getPokemonSpecies(Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY);
}
// Sets the hidden ability if a hidden ability exists and the override is set
// or if the same species egg hits the chance
let abilityIndex = undefined;
let abilityIndex: number | undefined = undefined;
if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility
|| (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE)))) {
abilityIndex = 2;
@ -273,6 +277,9 @@ export class Egg {
return i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE:
return i18next.t("egg:gachaTypeMove");
default:
console.warn("getEggTypeDescriptor case not defined. Returning default empty string");
return "";
}
}
@ -322,9 +329,9 @@ export class Egg {
return tierValue >= 52 + tierValueOffset ? EggTier.COMMON : tierValue >= 8 + tierValueOffset ? EggTier.GREAT : tierValue >= 1 + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER;
}
private rollSpecies(scene: BattleScene): Species {
private rollSpecies(scene: BattleScene): Species | null {
if (!scene) {
return undefined;
return null;
}
/**
* Manaphy eggs have a 1/8 chance of being Manaphy and 7/8 chance of being Phione
@ -396,7 +403,7 @@ export class Egg {
* and being the same each time
*/
let totalWeight = 0;
const speciesWeights = [];
const speciesWeights : number[] = [];
for (const speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId);
@ -416,6 +423,7 @@ export class Egg {
break;
}
}
species = species!; // tell TS compiled it's defined now!
if (!!scene.gameData.dexData[species].caughtAttr || scene.gameData.eggs.some(e => e.species === species)) {
scene.gameData.unlockPity[this.tier] = Math.min(scene.gameData.unlockPity[this.tier] + 1, 10);
@ -513,6 +521,8 @@ export class Egg {
if (speciesStartValue >= 8) {
return EggTier.MASTER;
}
return EggTier.COMMON;
}
////
@ -537,6 +547,7 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta
scene.executeWithSeedOffset(() => {
ret = Phaser.Math.RND.shuffle(legendarySpecies)[index];
}, offset, EGG_SEED.toString());
ret = ret!; // tell TS compiler it's
return ret;
}

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,8 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
}
if (includeStatEffects) {
const stats = Utils.getEnumValues(Stat).slice(1);
let increasedStat: Stat = null;
let decreasedStat: Stat = null;
let increasedStat: Stat | null = null;
let decreasedStat: Stat | null = null;
for (const stat of stats) {
const multiplier = getNatureStatMultiplier(nature, stat);
if (multiplier > 1) {

View File

@ -59,26 +59,26 @@ export type EvolutionConditionEnforceFunc = (p: Pokemon) => void;
export class SpeciesFormEvolution {
public speciesId: Species;
public preFormKey: string;
public evoFormKey: string;
public preFormKey: string | null;
public evoFormKey: string | null;
public level: integer;
public item: EvolutionItem;
public condition: SpeciesEvolutionCondition;
public item: EvolutionItem | null;
public condition: SpeciesEvolutionCondition | null;
public wildDelay: SpeciesWildEvolutionDelay;
constructor(speciesId: Species, preFormKey: string, evoFormKey: string, level: integer, item: EvolutionItem, condition: SpeciesEvolutionCondition, wildDelay?: SpeciesWildEvolutionDelay) {
constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: integer, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
this.speciesId = speciesId;
this.preFormKey = preFormKey;
this.evoFormKey = evoFormKey;
this.level = level;
this.item = item || EvolutionItem.NONE;
this.condition = condition;
this.wildDelay = wildDelay || SpeciesWildEvolutionDelay.NONE;
this.wildDelay = wildDelay ?? SpeciesWildEvolutionDelay.NONE;
}
}
export class SpeciesEvolution extends SpeciesFormEvolution {
constructor(speciesId: Species, level: integer, item: EvolutionItem, condition: SpeciesEvolutionCondition, wildDelay?: SpeciesWildEvolutionDelay) {
constructor(speciesId: Species, level: integer, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
super(speciesId, null, null, level, item, condition, wildDelay);
}
}
@ -95,7 +95,7 @@ export class FusionSpeciesFormEvolution extends SpeciesFormEvolution {
export class SpeciesEvolutionCondition {
public predicate: EvolutionConditionPredicate;
public enforceFunc: EvolutionConditionEnforceFunc;
public enforceFunc: EvolutionConditionEnforceFunc | undefined;
constructor(predicate: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) {
this.predicate = predicate;
@ -400,8 +400,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.LINOONE, 20, null, null)
],
[Species.WURMPLE]: [
new SpeciesEvolution(Species.SILCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), null),
new SpeciesEvolution(Species.CASCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), null)
new SpeciesEvolution(Species.SILCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)),
new SpeciesEvolution(Species.CASCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
],
[Species.SILCOON]: [
new SpeciesEvolution(Species.BEAUTIFLY, 10, null, null)
@ -945,7 +945,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.SHIINOTIC, 24, null, null)
],
[Species.SALANDIT]: [
new SpeciesEvolution(Species.SALAZZLE, 33, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE), null)
new SpeciesEvolution(Species.SALAZZLE, 33, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE))
],
[Species.STUFFUL]: [
new SpeciesEvolution(Species.BEWEAR, 27, null, null)
@ -969,8 +969,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.COSMOEM, 43, null, null)
],
[Species.COSMOEM]: [
new SpeciesEvolution(Species.SOLGALEO, 53, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), null),
new SpeciesEvolution(Species.LUNALA, 53, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), null)
new SpeciesEvolution(Species.SOLGALEO, 53, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)),
new SpeciesEvolution(Species.LUNALA, 53, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
],
[Species.MELTAN]: [
new SpeciesEvolution(Species.MELMETAL, 48, null, null)
@ -1264,17 +1264,17 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.EXEGGUTOR, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.TANGELA]: [
new SpeciesEvolution(Species.TANGROWTH, 34, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.TANGROWTH, 34, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.LICKITUNG]: [
new SpeciesEvolution(Species.LICKILICKY, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.ROLLOUT).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.LICKILICKY, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ROLLOUT).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.STARYU]: [
new SpeciesEvolution(Species.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.EEVEE]: [
new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => !!p.getMoveset().find(m => m.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => !!p.getMoveset().find(m => m.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG),
@ -1294,13 +1294,13 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.TOGEKISS, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.AIPOM]: [
new SpeciesEvolution(Species.AMBIPOM, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.DOUBLE_HIT).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.AMBIPOM, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DOUBLE_HIT).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.SUNKERN]: [
new SpeciesEvolution(Species.SUNFLORA, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.YANMA]: [
new SpeciesEvolution(Species.YANMEGA, 33, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.YANMEGA, 33, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.MURKROW]: [
new SpeciesEvolution(Species.HONCHKROW, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
@ -1309,17 +1309,17 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.MISMAGIUS, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.GIRAFARIG]: [
new SpeciesEvolution(Species.FARIGIRAF, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.TWIN_BEAM).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.FARIGIRAF, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.TWIN_BEAM).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.DUNSPARCE]: [
new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new SpeciesEvolutionCondition(p => {
let ret = false;
if (p.moveset.filter(m => m.moveId === Moves.HYPER_DRILL).length > 0) {
if (p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0) {
p.scene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id);
}
return ret;
}), SpeciesWildEvolutionDelay.LONG),
new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.HYPER_DRILL).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.GLIGAR]: [
new SpeciesEvolution(Species.GLISCOR, 1, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.LONG)
@ -1331,10 +1331,10 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna
],
[Species.PILOSWINE]: [
new SpeciesEvolution(Species.MAMOSWINE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.MAMOSWINE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.STANTLER]: [
new SpeciesEvolution(Species.WYRDEER, 25, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.PSYSHIELD_BASH).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.WYRDEER, 25, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.PSYSHIELD_BASH).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.LOMBRE]: [
new SpeciesEvolution(Species.LUDICOLO, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG)
@ -1352,11 +1352,11 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.ROSERADE, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.BONSLY]: [
new SpeciesEvolution(Species.SUDOWOODO, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.SUDOWOODO, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.MIME_JR]: [
new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.MIMIC).length > 0 && (p.scene.arena.biomeType === Biome.ICE_CAVE || p.scene.arena.biomeType === Biome.SNOWY_FOREST)), SpeciesWildEvolutionDelay.MEDIUM),
new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (p.scene.arena.biomeType === Biome.ICE_CAVE || p.scene.arena.biomeType === Biome.SNOWY_FOREST)), SpeciesWildEvolutionDelay.MEDIUM),
new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.PANSAGE]: [
new SpeciesEvolution(Species.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
@ -1406,15 +1406,15 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.CRABOMINABLE, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.ROCKRUFF]: [
new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0)), null),
new SpeciesFormEvolution(Species.LYCANROC, "", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1), null),
new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0)), null)
new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0))),
new SpeciesFormEvolution(Species.LYCANROC, "", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1)),
new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0)))
],
[Species.STEENEE]: [
new SpeciesEvolution(Species.TSAREENA, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.STOMP).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.TSAREENA, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.STOMP).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.POIPOLE]: [
new SpeciesEvolution(Species.NAGANADEL, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.DRAGON_PULSE).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.NAGANADEL, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DRAGON_PULSE).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.ALOLA_SANDSHREW]: [
new SpeciesEvolution(Species.ALOLA_SANDSLASH, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG)
@ -1428,7 +1428,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.APPLETUN, 1, EvolutionItem.SWEET_APPLE, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.CLOBBOPUS]: [
new SpeciesEvolution(Species.GRAPPLOCT, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.TAUNT).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.GRAPPLOCT, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.TAUNT).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.SINISTEA]: [
new SpeciesFormEvolution(Species.POLTEAGEIST, "phony", "phony", 1, EvolutionItem.CRACKED_POT, null, SpeciesWildEvolutionDelay.LONG),
@ -1462,7 +1462,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.HISUI_ELECTRODE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.HISUI_QWILFISH]: [
new SpeciesEvolution(Species.OVERQWIL, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.BARB_BARRAGE).length > 0), SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(Species.OVERQWIL, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.BARB_BARRAGE).length > 0), SpeciesWildEvolutionDelay.LONG)
],
[Species.HISUI_SNEASEL]: [
new SpeciesEvolution(Species.SNEASLER, 1, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.LONG)
@ -1485,7 +1485,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesFormEvolution(Species.SINISTCHA, "artisan", "masterpiece", 1, EvolutionItem.MASTERPIECE_TEACUP, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.DIPPLIN]: [
new SpeciesEvolution(Species.HYDRAPPLE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.DRAGON_CHEER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.HYDRAPPLE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DRAGON_CHEER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.KADABRA]: [
new SpeciesEvolution(Species.ALAKAZAM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
@ -1501,7 +1501,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
],
[Species.ONIX]: [
new SpeciesEvolution(Species.STEELIX, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(
p => p.moveset.filter(m => m.getMove().type === Type.STEEL).length > 0),
p => p.moveset.filter(m => m?.getMove().type === Type.STEEL).length > 0),
SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.RHYDON]: [
@ -1512,7 +1512,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
],
[Species.SCYTHER]: [
new SpeciesEvolution(Species.SCIZOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(
p => p.moveset.filter(m => m.getMove().type === Type.STEEL).length > 0),
p => p.moveset.filter(m => m?.getMove().type === Type.STEEL).length > 0),
SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesEvolution(Species.KLEAVOR, 1, EvolutionItem.BLACK_AUGURITE, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
@ -1566,7 +1566,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.ALOLA_GOLEM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.PRIMEAPE]: [
new SpeciesEvolution(Species.ANNIHILAPE, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.RAGE_FIST).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.ANNIHILAPE, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.RAGE_FIST).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.GOLBAT]: [
new SpeciesEvolution(Species.CROBAT, 1, null, new SpeciesFriendshipEvolutionCondition(110), SpeciesWildEvolutionDelay.VERY_LONG)

View File

@ -181,7 +181,7 @@ export class SpeciesFormChange {
return true;
}
findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger {
findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | null {
if (!this.trigger.hasTriggerType(triggerType)) {
return null;
}
@ -189,7 +189,7 @@ export class SpeciesFormChange {
const trigger = this.trigger;
if (trigger instanceof SpeciesFormChangeCompoundTrigger) {
return trigger.triggers.find(t => t.hasTriggerType(triggerType));
return trigger.triggers.find(t => t.hasTriggerType(triggerType))!; // TODO: is this bang correct?
}
return trigger;
@ -198,11 +198,11 @@ export class SpeciesFormChange {
export class SpeciesFormChangeCondition {
public predicate: SpeciesFormChangeConditionPredicate;
public enforceFunc: SpeciesFormChangeConditionEnforceFunc;
public enforceFunc: SpeciesFormChangeConditionEnforceFunc | null;
constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) {
this.predicate = predicate;
this.enforceFunc = enforceFunc;
this.enforceFunc = enforceFunc!; // TODO: is this bang correct?
}
}
@ -314,7 +314,7 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
}
canChange(pokemon: Pokemon): boolean {
return (!!pokemon.moveset.filter(m => m.moveId === this.move).length) === this.known;
return (!!pokemon.moveset.filter(m => m?.moveId === this.move).length) === this.known;
}
}
@ -332,7 +332,7 @@ export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrig
export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger {
canChange(pokemon: Pokemon): boolean {
const command = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
return command?.move && this.movePredicate(command.move.move) === this.used;
return !!command?.move && this.movePredicate(command.move.move) === this.used;
}
}
@ -372,6 +372,9 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie
if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", { pokemonName: getPokemonNameWithAffix(pokemon) });
}
if (pokemon.getAbility().id === Abilities.DISGUISE) {
return i18next.t("battlePokemonForm:disguiseChange");
}
return i18next.t("battlePokemonForm:formChange", { preName });
}
@ -828,6 +831,12 @@ export const pokemonFormChanges: PokemonFormChanges = {
[Species.EISCUE]: [
new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.EISCUE, "no-ice", "", new SpeciesFormChangeManualTrigger(), true),
],
[Species.CRAMORANT]: [
new SpeciesFormChange(Species.CRAMORANT, "", "gulping", new SpeciesFormChangeManualTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() >= .5)),
new SpeciesFormChange(Species.CRAMORANT, "", "gorging", new SpeciesFormChangeManualTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() < .5)),
new SpeciesFormChange(Species.CRAMORANT, "gulping", "", new SpeciesFormChangeManualTrigger, true),
new SpeciesFormChange(Species.CRAMORANT, "gorging", "", new SpeciesFormChangeManualTrigger, true),
]
};

View File

@ -2631,7 +2631,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.CHIKORITA]: [
[ 1, Moves.TACKLE ],
[ 1, Moves.GROWL ],
[ 6, Moves.RAZOR_LEAF ],
[ 5, Moves.RAZOR_LEAF ], //Custom, moved from 6 to 5
[ 9, Moves.POISON_POWDER ],
[ 12, Moves.SYNTHESIS ],
[ 17, Moves.REFLECT ],
@ -2681,8 +2681,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.CYNDAQUIL]: [
[ 1, Moves.TACKLE ],
[ 1, Moves.LEER ],
[ 6, Moves.SMOKESCREEN ],
[ 10, Moves.EMBER ],
[ 5, Moves.EMBER ], //Custom, moved to 5
[ 10, Moves.SMOKESCREEN ], //Custom, moved to 10
[ 13, Moves.QUICK_ATTACK ],
[ 19, Moves.FLAME_WHEEL ],
[ 22, Moves.DEFENSE_CURL ],
@ -2736,7 +2736,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.TOTODILE]: [
[ 1, Moves.SCRATCH ],
[ 1, Moves.LEER ],
[ 6, Moves.WATER_GUN ],
[ 5, Moves.WATER_GUN ], //Custom, moved from 6 to 5
[ 9, Moves.BITE ],
[ 13, Moves.SCARY_FACE ],
[ 19, Moves.ICE_FANG ],
@ -6723,7 +6723,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.TURTWIG]: [
[ 1, Moves.TACKLE ],
[ 5, Moves.WITHDRAW ],
[ 9, Moves.ABSORB ],
[ 5, Moves.ABSORB ], //Custom, moved from 9 to 5
[ 13, Moves.RAZOR_LEAF ],
[ 17, Moves.CURSE ],
[ 21, Moves.BITE ],
@ -6768,7 +6768,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.CHIMCHAR]: [
[ 1, Moves.SCRATCH ],
[ 1, Moves.LEER ],
[ 7, Moves.EMBER ],
[ 5, Moves.EMBER ], //Custom, moved from 7 to 5
[ 9, Moves.TAUNT ],
[ 15, Moves.FURY_SWIPES ],
[ 17, Moves.FLAME_WHEEL ],
@ -6817,7 +6817,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.PIPLUP]: [
[ 1, Moves.POUND ],
[ 4, Moves.GROWL ],
[ 8, Moves.WATER_GUN ],
[ 5, Moves.WATER_GUN ], //Custom, moved from 8 to 5
[ 11, Moves.CHARM ],
[ 15, Moves.PECK ],
[ 18, Moves.BUBBLE_BEAM ],
@ -8591,7 +8591,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.SNIVY]: [
[ 1, Moves.TACKLE ],
[ 4, Moves.LEER ],
[ 7, Moves.VINE_WHIP ],
[ 5, Moves.VINE_WHIP ], //Custom, moved from 7 to 5
[ 10, Moves.WRAP ],
[ 13, Moves.GROWTH ],
[ 16, Moves.MAGICAL_LEAF ],
@ -8639,7 +8639,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.TEPIG]: [
[ 1, Moves.TACKLE ],
[ 3, Moves.TAIL_WHIP ],
[ 7, Moves.EMBER ],
[ 5, Moves.EMBER ], //Custom, moved from 7 to 5
[ 9, Moves.ENDURE ],
[ 13, Moves.DEFENSE_CURL ],
[ 15, Moves.FLAME_CHARGE ],
@ -8693,7 +8693,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.OSHAWOTT]: [
[ 1, Moves.TACKLE ],
[ 5, Moves.TAIL_WHIP ],
[ 7, Moves.WATER_GUN ],
[ 5, Moves.WATER_GUN ], //Custom, moved from 7 to 5
[ 11, Moves.SOAK ],
[ 13, Moves.FOCUS_ENERGY ],
[ 17, Moves.RAZOR_SHELL ],
@ -13850,11 +13850,11 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[ 99, Moves.CLOSE_COMBAT ],
],
[Species.POIPOLE]: [
[ RELEARN_MOVE, Moves.DRAGON_PULSE ], //Custom, made relearn
[ 1, Moves.GROWL ],
[ 1, Moves.ACID ],
[ 1, Moves.PECK ],
[ 1, Moves.HELPING_HAND ],
[ 1, Moves.DRAGON_PULSE ],
[ 7, Moves.FURY_ATTACK ],
[ 14, Moves.FELL_STINGER ],
[ 21, Moves.CHARM ],
@ -13969,7 +13969,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.GROOKEY]: [
[ 1, Moves.SCRATCH ],
[ 1, Moves.GROWL ],
[ 6, Moves.BRANCH_POKE ],
[ 5, Moves.BRANCH_POKE ], //Custom, moved from 6 to 5
[ 8, Moves.TAUNT ],
[ 12, Moves.RAZOR_LEAF ],
[ 17, Moves.SCREECH ],
@ -14014,7 +14014,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.SCORBUNNY]: [
[ 1, Moves.TACKLE ],
[ 1, Moves.GROWL ],
[ 6, Moves.EMBER ],
[ 5, Moves.EMBER ], //Custom, moved from 6 to 5
[ 8, Moves.QUICK_ATTACK ],
[ 12, Moves.DOUBLE_KICK ],
[ 17, Moves.FLAME_CHARGE ],
@ -14056,7 +14056,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.SOBBLE]: [
[ 1, Moves.POUND ],
[ 1, Moves.GROWL ],
[ 6, Moves.WATER_GUN ],
[ 5, Moves.WATER_GUN ], //Custom, moved from 6 to 5
[ 8, Moves.BIND ],
[ 12, Moves.WATER_PULSE ],
[ 17, Moves.TEARFUL_LOOK ],
@ -18666,7 +18666,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ],
],
3: [
[ EVOLVE_MOVE, Moves.METEOR_MASH ],
[ 1, Moves.METEOR_MASH ],
[ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ],
@ -18690,7 +18690,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ],
],
4: [
[ EVOLVE_MOVE, Moves.ICICLE_CRASH ],
[ 1, Moves.ICICLE_CRASH ],
[ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ],
@ -18714,7 +18714,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ],
],
5: [
[ EVOLVE_MOVE, Moves.DRAINING_KISS ],
[ 1, Moves.DRAINING_KISS ],
[ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ],
@ -18738,7 +18738,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ],
],
6: [
[ EVOLVE_MOVE, Moves.ELECTRIC_TERRAIN ],
[ 1, Moves.ELECTRIC_TERRAIN ],
[ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ],
@ -18762,7 +18762,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ],
],
7: [
[ EVOLVE_MOVE, Moves.FLYING_PRESS ],
[ 1, Moves.FLYING_PRESS ],
[ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ],
@ -18886,7 +18886,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
},
[Species.ROTOM]: {
1: [
[ EVOLVE_MOVE, Moves.OVERHEAT ],
[ 1, Moves.OVERHEAT ],
[ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ],
@ -18902,7 +18902,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 55, Moves.UPROAR ],
],
2: [
[ EVOLVE_MOVE, Moves.HYDRO_PUMP ],
[ 1, Moves.HYDRO_PUMP ],
[ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ],
@ -18918,7 +18918,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 55, Moves.UPROAR ],
],
3: [
[ EVOLVE_MOVE, Moves.BLIZZARD ],
[ 1, Moves.BLIZZARD ],
[ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ],
@ -18934,7 +18934,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 55, Moves.UPROAR ],
],
4: [
[ EVOLVE_MOVE, Moves.AIR_SLASH ],
[ 1, Moves.AIR_SLASH ],
[ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ],
@ -18950,7 +18950,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 55, Moves.UPROAR ],
],
5: [
[ EVOLVE_MOVE, Moves.LEAF_STORM ],
[ 1, Moves.LEAF_STORM ],
[ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ],

View File

@ -28,21 +28,21 @@ export enum Region {
PALDEA
}
export function getPokemonSpecies(species: Species): PokemonSpecies {
export function getPokemonSpecies(species: Species | Species[]): PokemonSpecies {
// If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block
if (Array.isArray(species)) {
// Pick a random species from the list
species = species[Math.floor(Math.random() * species.length)];
}
if (species >= 2000) {
return allSpecies.find(s => s.speciesId === species);
return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct?
}
return allSpecies[species - 1];
}
export function getPokemonSpeciesForm(species: Species, formIndex: integer): PokemonSpeciesForm {
const retSpecies: PokemonSpecies = species >= 2000
? allSpecies.find(s => s.speciesId === species)
? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct?
: allSpecies[species - 1];
if (formIndex < retSpecies.forms?.length) {
return retSpecies.forms[formIndex];
@ -97,7 +97,7 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
fragB = fragB.slice(1);
} else {
const newCharMatch = new RegExp(`[^${lastCharA}]`).exec(fragB);
if (newCharMatch?.index > 0) {
if (newCharMatch?.index !== undefined && newCharMatch.index > 0) {
fragB = fragB.slice(newCharMatch.index);
}
}
@ -125,7 +125,7 @@ export abstract class PokemonSpeciesForm {
public formIndex: integer;
public generation: integer;
public type1: Type;
public type2: Type;
public type2: Type | null;
public height: number;
public weight: number;
public ability1: Abilities;
@ -139,7 +139,7 @@ export abstract class PokemonSpeciesForm {
public genderDiffs: boolean;
public isStarterSelectable: boolean;
constructor(type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
constructor(type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs: boolean, isStarterSelectable: boolean) {
this.type1 = type1;
@ -267,7 +267,7 @@ export abstract class PokemonSpeciesForm {
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
}
getSpriteId(female: boolean, formIndex?: integer, shiny?: boolean, variant?: integer, back?: boolean): string {
getSpriteId(female: boolean, formIndex?: integer, shiny?: boolean, variant: integer = 0, back?: boolean): string {
if (formIndex === undefined || this instanceof PokemonForm) {
formIndex = this.formIndex;
}
@ -281,7 +281,7 @@ export abstract class PokemonSpeciesForm {
`${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => config ? config = config[p] : null);
const variantSet = config as VariantSet;
return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant || 0] === 2 ? `_${variant + 1}` : ""}`;
return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant] === 2 ? `_${variant + 1}` : ""}`;
}
getSpriteKey(female: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): string {
@ -297,10 +297,10 @@ export abstract class PokemonSpeciesForm {
* @returns species id if no additional forms, index with formkey if a pokemon with a form
*/
getVariantDataIndex(formIndex?: integer) {
let formkey = null;
let formkey: string | null = null;
let variantDataIndex: integer | string = this.speciesId;
const species = getPokemonSpecies(this.speciesId);
if (species.forms.length > 0) {
if (species.forms.length > 0 && formIndex !== undefined) {
formkey = species.forms[formIndex]?.formSpriteKey;
if (formkey) {
variantDataIndex = `${this.speciesId}-${formkey}`;
@ -311,7 +311,7 @@ export abstract class PokemonSpeciesForm {
getIconAtlasKey(formIndex?: integer, shiny?: boolean, variant?: integer): string {
const variantDataIndex = this.getVariantDataIndex(formIndex);
const isVariant = shiny && variantData[variantDataIndex] && variantData[variantDataIndex][variant];
const isVariant = shiny && variantData[variantDataIndex] && (variant !== undefined && variantData[variantDataIndex][variant]);
return `pokemon_icons_${this.generation}${isVariant ? "v" : ""}`;
}
@ -324,7 +324,7 @@ export abstract class PokemonSpeciesForm {
let ret = this.speciesId.toString();
const isVariant = shiny && variantData[variantDataIndex] && variantData[variantDataIndex][variant];
const isVariant = shiny && variantData[variantDataIndex] && (variant !== undefined && variantData[variantDataIndex][variant]);
if (shiny && !isVariant) {
ret += "s";
@ -382,7 +382,7 @@ export abstract class PokemonSpeciesForm {
let ret = speciesId.toString();
const forms = getPokemonSpecies(speciesId).forms;
if (forms.length) {
if (formIndex >= forms.length) {
if (formIndex !== undefined && formIndex >= forms.length) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${getPokemonSpecies(speciesId).getName()} with only ${forms.length || 0} forms`);
formIndex = Math.min(formIndex, forms.length - 1);
}
@ -478,7 +478,7 @@ export abstract class PokemonSpeciesForm {
let config = variantData;
spritePath.split("/").map(p => config ? config = config[p] : null);
const variantSet = config as VariantSet;
if (variantSet && variantSet[variant] === 1) {
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
const populateVariantColors = (key: string): Promise<void> => {
return new Promise(resolve => {
if (variantColorCache.hasOwnProperty(key)) {
@ -507,7 +507,7 @@ export abstract class PokemonSpeciesForm {
cry(scene: BattleScene, soundConfig?: Phaser.Types.Sound.SoundConfig, ignorePlay?: boolean): AnySound {
const cryKey = this.getCryKey(this.formIndex);
let cry = scene.sound.get(cryKey) as AnySound;
let cry: AnySound | null = scene.sound.get(cryKey) as AnySound;
if (cry?.pendingRemove) {
cry = null;
}
@ -532,10 +532,12 @@ export abstract class PokemonSpeciesForm {
const frame = sourceFrame;
canvas.width = frame.width;
canvas.height = frame.height;
context.drawImage(sourceImage, frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height);
const imageData = context.getImageData(frame.cutX, frame.cutY, frame.width, frame.height);
const pixelData = imageData.data;
context?.drawImage(sourceImage, frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height);
const imageData = context?.getImageData(frame.cutX, frame.cutY, frame.width, frame.height);
const pixelData = imageData?.data;
const pixelColors: number[] = [];
if (pixelData?.length !== undefined) {
for (let i = 0; i < pixelData.length; i += 4) {
if (pixelData[i + 3]) {
const pixel = pixelData.slice(i, i + 4);
@ -546,7 +548,6 @@ export abstract class PokemonSpeciesForm {
}
}
const pixelColors = [];
for (let i = 0; i < pixelData.length; i += 4) {
const total = pixelData.slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0);
if (!total) {
@ -554,8 +555,9 @@ export abstract class PokemonSpeciesForm {
}
pixelColors.push(argbFromRgba({ r: pixelData[i], g: pixelData[i + 1], b: pixelData[i + 2], a: pixelData[i + 3] }));
}
}
let paletteColors: Map<number, number>;
let paletteColors: Map<number, number> = new Map();
const originalRandom = Math.random;
Math.random = () => Phaser.Math.RND.realInRange(0, 1);
@ -577,15 +579,15 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
public mythical: boolean;
public species: string;
public growthRate: GrowthRate;
public malePercent: number;
public malePercent: number | null;
public genderDiffs: boolean;
public canChangeForm: boolean;
public forms: PokemonForm[];
constructor(id: Species, generation: integer, subLegendary: boolean, legendary: boolean, mythical: boolean, species: string,
type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, growthRate: GrowthRate, malePercent: number,
catchRate: integer, baseFriendship: integer, baseExp: integer, growthRate: GrowthRate, malePercent: number | null,
genderDiffs: boolean, canChangeForm?: boolean, ...forms: PokemonForm[]) {
super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd,
catchRate, baseFriendship, baseExp, genderDiffs, false);
@ -614,7 +616,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
getName(formIndex?: integer): string {
if (formIndex !== undefined && this.forms.length) {
const form = this.forms[formIndex];
let key: string;
let key: string | null;
switch (form.formKey) {
case SpeciesFormKey.MEGA:
case SpeciesFormKey.PRIMAL:
@ -626,6 +628,8 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
default:
if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) {
key = "gigantamax";
} else {
key = null;
}
}
@ -713,11 +717,11 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
evolutionChance = Math.min(minChance + easeInFunc(Math.min(level - ev.level, maxLevelDiff) / maxLevelDiff) * (1 - minChance), 1);
}
} else {
const preferredMinLevel = Math.max((ev.level - 1) + ev.wildDelay * this.getStrengthLevelDiff(strength), 1);
const preferredMinLevel = Math.max((ev.level - 1) + (ev.wildDelay!) * this.getStrengthLevelDiff(strength), 1); // TODO: is the bang correct?
let evolutionLevel = Math.max(ev.level > 1 ? ev.level : Math.floor(preferredMinLevel / 2), 1);
if (ev.level <= 1 && pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(ev => ev.speciesId === this.speciesId).level;
const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(ev => ev.speciesId === this.speciesId)!.level; // TODO: is the bang correct?
if (prevolutionLevel > 1) {
evolutionLevel = prevolutionLevel;
}
@ -750,15 +754,15 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
for (const weight of evolutionPool.keys()) {
if (randValue < weight) {
return getPokemonSpecies(evolutionPool.get(weight)).getSpeciesForLevel(level, true, forTrainer, strength);
return getPokemonSpecies(evolutionPool.get(weight)!).getSpeciesForLevel(level, true, forTrainer, strength); // TODO: is the bang correct?
}
}
return this.speciesId;
}
getEvolutionLevels() {
const evolutionLevels = [];
getEvolutionLevels(): [Species, integer][] {
const evolutionLevels: [Species, integer][] = [];
//console.log(Species[this.speciesId], pokemonEvolutions[this.speciesId])
@ -778,8 +782,8 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
return evolutionLevels;
}
getPrevolutionLevels() {
const prevolutionLevels = [];
getPrevolutionLevels(): [Species, integer][] {
const prevolutionLevels: [Species, integer][] = [];
const allEvolvingPokemon = Object.keys(pokemonEvolutions);
for (const p of allEvolvingPokemon) {
@ -801,18 +805,18 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
// This could definitely be written better and more accurate to the getSpeciesForLevel logic, but it is only for generating movesets for evolved Pokemon
getSimulatedEvolutionChain(currentLevel: integer, forTrainer: boolean = false, isBoss: boolean = false, player: boolean = false): [Species, integer][] {
const ret = [];
const ret: [Species, integer][] = [];
if (pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevels = this.getPrevolutionLevels().reverse();
const levelDiff = player ? 0 : forTrainer || isBoss ? forTrainer && isBoss ? 2.5 : 5 : 10;
ret.push([ prevolutionLevels[0][0], 1 ]);
for (let l = 1; l < prevolutionLevels.length; l++) {
const evolution = pokemonEvolutions[prevolutionLevels[l - 1][0]].find(e => e.speciesId === prevolutionLevels[l][0]);
ret.push([ prevolutionLevels[l][0], Math.min(Math.max(evolution.level + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution.wildDelay, 0.5) * 5) - 1, 2, evolution.level), currentLevel - 1) ]);
ret.push([ prevolutionLevels[l][0], Math.min(Math.max((evolution?.level!) + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max((evolution?.wildDelay!), 0.5) * 5) - 1, 2, (evolution?.level!)), currentLevel - 1) ]); // TODO: are those bangs correct?
}
const lastPrevolutionLevel = ret[prevolutionLevels.length - 1][1];
const evolution = pokemonEvolutions[prevolutionLevels[prevolutionLevels.length - 1][0]].find(e => e.speciesId === this.speciesId);
ret.push([ this.speciesId, Math.min(Math.max(lastPrevolutionLevel + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution.wildDelay, 0.5) * 5), lastPrevolutionLevel + 1, evolution.level), currentLevel) ]);
ret.push([ this.speciesId, Math.min(Math.max(lastPrevolutionLevel + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max((evolution?.wildDelay!), 0.5) * 5), lastPrevolutionLevel + 1, (evolution?.level!)), currentLevel) ]); // TODO: are those bangs correct?
} else {
ret.push([ this.speciesId, 1 ]);
}
@ -853,7 +857,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
}
getFormSpriteKey(formIndex?: integer) {
if (this.forms.length && formIndex >= this.forms.length) {
if (this.forms.length && (formIndex !== undefined && formIndex >= this.forms.length)) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${this.getName()} with only ${this.forms.length || 0} forms`);
formIndex = Math.min(formIndex, this.forms.length - 1);
}
@ -866,14 +870,14 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
export class PokemonForm extends PokemonSpeciesForm {
public formName: string;
public formKey: string;
public formSpriteKey: string;
public formSpriteKey: string | null;
// This is a collection of form keys that have in-run form changes, but should still be separately selectable from the start screen
private starterSelectableKeys: string[] = ["10", "50", "10-pc", "50-pc", "red", "orange", "yellow", "green", "blue", "indigo", "violet"];
constructor(formName: string, formKey: string, type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
constructor(formName: string, formKey: string, type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs?: boolean, formSpriteKey?: string, isStarterSelectable?: boolean, ) {
catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs?: boolean, formSpriteKey?: string | null, isStarterSelectable?: boolean, ) {
super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd,
catchRate, baseFriendship, baseExp, !!genderDiffs, (!!isStarterSelectable || !formKey));
this.formName = formName;
@ -2725,8 +2729,8 @@ export const speciesStarters = {
[Species.VOLTORB]: 2,
[Species.EXEGGCUTE]: 3,
[Species.CUBONE]: 3,
[Species.HITMONLEE]: 4,
[Species.HITMONCHAN]: 4,
[Species.HITMONLEE]: 5,
[Species.HITMONCHAN]: 5,
[Species.LICKITUNG]: 3,
[Species.KOFFING]: 2,
[Species.RHYHORN]: 3,
@ -2738,7 +2742,7 @@ export const speciesStarters = {
[Species.STARYU]: 3,
[Species.MR_MIME]: 3,
[Species.SCYTHER]: 5,
[Species.JYNX]: 3,
[Species.JYNX]: 4,
[Species.ELECTABUZZ]: 4,
[Species.MAGMAR]: 4,
[Species.PINSIR]: 4,
@ -2765,7 +2769,7 @@ export const speciesStarters = {
[Species.SENTRET]: 1,
[Species.HOOTHOOT]: 2,
[Species.LEDYBA]: 1,
[Species.SPINARAK]: 2,
[Species.SPINARAK]: 1,
[Species.CHINCHOU]: 2,
[Species.PICHU]: 2,
[Species.CLEFFA]: 2,
@ -2805,8 +2809,8 @@ export const speciesStarters = {
[Species.PHANPY]: 3,
[Species.STANTLER]: 3,
[Species.SMEARGLE]: 1,
[Species.TYROGUE]: 2,
[Species.SMOOCHUM]: 2,
[Species.TYROGUE]: 3,
[Species.SMOOCHUM]: 3,
[Species.ELEKID]: 3,
[Species.MAGBY]: 3,
[Species.MILTANK]: 4,
@ -2819,7 +2823,7 @@ export const speciesStarters = {
[Species.CELEBI]: 6,
[Species.TREECKO]: 3,
[Species.TORCHIC]: 3,
[Species.TORCHIC]: 4,
[Species.MUDKIP]: 3,
[Species.POOCHYENA]: 2,
[Species.ZIGZAGOON]: 2,
@ -2898,7 +2902,7 @@ export const speciesStarters = {
[Species.CHIMCHAR]: 3,
[Species.PIPLUP]: 3,
[Species.STARLY]: 3,
[Species.BIDOOF]: 3,
[Species.BIDOOF]: 2,
[Species.KRICKETOT]: 1,
[Species.SHINX]: 2,
[Species.BUDEW]: 3,
@ -2986,7 +2990,7 @@ export const speciesStarters = {
[Species.ZORUA]: 3,
[Species.MINCCINO]: 3,
[Species.GOTHITA]: 3,
[Species.SOLOSIS]: 4,
[Species.SOLOSIS]: 3,
[Species.DUCKLETT]: 2,
[Species.VANILLITE]: 3,
[Species.DEERLING]: 2,
@ -3205,7 +3209,7 @@ export const speciesStarters = {
[Species.LECHONK]: 2,
[Species.TAROUNTULA]: 1,
[Species.NYMBLE]: 3,
[Species.PAWMI]: 4,
[Species.PAWMI]: 3,
[Species.TANDEMAUS]: 4,
[Species.FIDOUGH]: 2,
[Species.SMOLIV]: 3,
@ -3319,14 +3323,14 @@ export const starterPassiveAbilities = {
[Species.SQUIRTLE]: Abilities.STURDY,
[Species.CATERPIE]: Abilities.MAGICIAN,
[Species.WEEDLE]: Abilities.TINTED_LENS,
[Species.PIDGEY]: Abilities.GALE_WINGS,
[Species.PIDGEY]: Abilities.FLARE_BOOST,
[Species.RATTATA]: Abilities.STRONG_JAW,
[Species.SPEAROW]: Abilities.MOXIE,
[Species.EKANS]: Abilities.REGENERATOR,
[Species.SANDSHREW]: Abilities.TOUGH_CLAWS,
[Species.NIDORAN_F]: Abilities.FLARE_BOOST,
[Species.NIDORAN_M]: Abilities.GUTS,
[Species.VULPIX]: Abilities.SOLAR_POWER,
[Species.VULPIX]: Abilities.FUR_COAT,
[Species.ZUBAT]: Abilities.INTIMIDATE,
[Species.ODDISH]: Abilities.TRIAGE,
[Species.PARAS]: Abilities.TRIAGE,
@ -3345,16 +3349,16 @@ export const starterPassiveAbilities = {
[Species.PONYTA]: Abilities.MAGIC_GUARD,
[Species.SLOWPOKE]: Abilities.UNAWARE,
[Species.MAGNEMITE]: Abilities.LEVITATE,
[Species.FARFETCHD]: Abilities.HUGE_POWER,
[Species.FARFETCHD]: Abilities.SNIPER,
[Species.DODUO]: Abilities.PARENTAL_BOND,
[Species.SEEL]: Abilities.WATER_BUBBLE,
[Species.GRIMER]: Abilities.WATER_ABSORB,
[Species.SHELLDER]: Abilities.ICE_SCALES,
[Species.GASTLY]: Abilities.SHADOW_SHIELD,
[Species.ONIX]: Abilities.ROCKY_PAYLOAD,
[Species.DROWZEE]: Abilities.BAD_DREAMS,
[Species.DROWZEE]: Abilities.MAGICIAN,
[Species.KRABBY]: Abilities.UNBURDEN,
[Species.VOLTORB]: Abilities.ELECTRIC_SURGE,
[Species.VOLTORB]: Abilities.TRANSISTOR,
[Species.EXEGGCUTE]: Abilities.RIPEN,
[Species.CUBONE]: Abilities.PARENTAL_BOND,
[Species.LICKITUNG]: Abilities.THICK_FAT,
@ -3374,7 +3378,7 @@ export const starterPassiveAbilities = {
[Species.EEVEE]: Abilities.SIMPLE,
[Species.PORYGON]: Abilities.PROTEAN,
[Species.OMANYTE]: Abilities.STURDY,
[Species.KABUTO]: Abilities.SHARPNESS,
[Species.KABUTO]: Abilities.TOUGH_CLAWS,
[Species.AERODACTYL]: Abilities.ORICHALCUM_PULSE,
[Species.ARTICUNO]: Abilities.SNOW_WARNING,
[Species.ZAPDOS]: Abilities.DRIZZLE,
@ -3476,7 +3480,7 @@ export const starterPassiveAbilities = {
[Species.CACNEA]: Abilities.SAND_RUSH,
[Species.SWABLU]: Abilities.ADAPTABILITY,
[Species.ZANGOOSE]: Abilities.POISON_HEAL,
[Species.SEVIPER]: Abilities.INTIMIDATE,
[Species.SEVIPER]: Abilities.MULTISCALE,
[Species.LUNATONE]: Abilities.SHADOW_SHIELD,
[Species.SOLROCK]: Abilities.DROUGHT,
[Species.BARBOACH]: Abilities.SIMPLE,
@ -3495,16 +3499,16 @@ export const starterPassiveAbilities = {
[Species.SNORUNT]: Abilities.SNOW_WARNING,
[Species.SPHEAL]: Abilities.UNAWARE,
[Species.CLAMPERL]: Abilities.DRIZZLE,
[Species.RELICANTH]: Abilities.SOLID_ROCK,
[Species.RELICANTH]: Abilities.PRIMORDIAL_SEA,
[Species.LUVDISC]: Abilities.MULTISCALE,
[Species.BAGON]: Abilities.ADAPTABILITY,
[Species.BAGON]: Abilities.DRAGONS_MAW,
[Species.BELDUM]: Abilities.LEVITATE,
[Species.REGIROCK]: Abilities.SAND_STREAM,
[Species.REGICE]: Abilities.SNOW_WARNING,
[Species.REGISTEEL]: Abilities.FILTER,
[Species.LATIAS]: Abilities.SOUL_HEART,
[Species.LATIAS]: Abilities.PRISM_ARMOR,
[Species.LATIOS]: Abilities.TINTED_LENS,
[Species.KYOGRE]: Abilities.RAIN_DISH,
[Species.KYOGRE]: Abilities.MOLD_BREAKER,
[Species.GROUDON]: Abilities.TURBOBLAZE,
[Species.RAYQUAZA]: Abilities.UNNERVE,
[Species.JIRACHI]: Abilities.COMATOSE,
@ -3523,7 +3527,7 @@ export const starterPassiveAbilities = {
[Species.COMBEE]: Abilities.INTIMIDATE,
[Species.PACHIRISU]: Abilities.HONEY_GATHER,
[Species.BUIZEL]: Abilities.MOXIE,
[Species.CHERUBI]: Abilities.DROUGHT,
[Species.CHERUBI]: Abilities.ORICHALCUM_PULSE,
[Species.SHELLOS]: Abilities.REGENERATOR,
[Species.DRIFLOON]: Abilities.MAGIC_GUARD,
[Species.BUNEARY]: Abilities.ADAPTABILITY,
@ -3537,13 +3541,13 @@ export const starterPassiveAbilities = {
[Species.CHATOT]: Abilities.PUNK_ROCK,
[Species.SPIRITOMB]: Abilities.VESSEL_OF_RUIN,
[Species.GIBLE]: Abilities.SAND_STREAM,
[Species.MUNCHLAX]: Abilities.RIPEN,
[Species.MUNCHLAX]: Abilities.HARVEST,
[Species.RIOLU]: Abilities.MINDS_EYE,
[Species.HIPPOPOTAS]: Abilities.UNAWARE,
[Species.SKORUPI]: Abilities.SUPER_LUCK,
[Species.CROAGUNK]: Abilities.MOXIE,
[Species.CARNIVINE]: Abilities.ARENA_TRAP,
[Species.FINNEON]: Abilities.DRIZZLE,
[Species.FINNEON]: Abilities.WATER_BUBBLE,
[Species.MANTYKE]: Abilities.UNAWARE,
[Species.SNOVER]: Abilities.THICK_FAT,
[Species.ROTOM]: Abilities.HADRON_ENGINE,
@ -3557,7 +3561,7 @@ export const starterPassiveAbilities = {
[Species.GIRATINA]: Abilities.SHADOW_SHIELD,
[Species.CRESSELIA]: Abilities.MAGIC_BOUNCE,
[Species.PHIONE]: Abilities.SIMPLE,
[Species.MANAPHY]: Abilities.SIMPLE,
[Species.MANAPHY]: Abilities.PRIMORDIAL_SEA,
[Species.DARKRAI]: Abilities.UNNERVE,
[Species.SHAYMIN]: Abilities.WIND_RIDER,
[Species.ARCEUS]: Abilities.ADAPTABILITY,
@ -3590,13 +3594,13 @@ export const starterPassiveAbilities = {
[Species.SANDILE]: Abilities.TOUGH_CLAWS,
[Species.DARUMAKA]: Abilities.GORILLA_TACTICS,
[Species.MARACTUS]: Abilities.WELL_BAKED_BODY,
[Species.DWEBBLE]: Abilities.ANGER_SHELL,
[Species.DWEBBLE]: Abilities.ROCKY_PAYLOAD,
[Species.SCRAGGY]: Abilities.PROTEAN,
[Species.SIGILYPH]: Abilities.MAGICIAN,
[Species.SIGILYPH]: Abilities.FLARE_BOOST,
[Species.YAMASK]: Abilities.PURIFYING_SALT,
[Species.TIRTOUGA]: Abilities.ANGER_SHELL,
[Species.TIRTOUGA]: Abilities.WATER_ABSORB,
[Species.ARCHEN]: Abilities.MULTISCALE,
[Species.TRUBBISH]: Abilities.TOXIC_DEBRIS,
[Species.TRUBBISH]: Abilities.NEUTRALIZING_GAS,
[Species.ZORUA]: Abilities.DARK_AURA,
[Species.MINCCINO]: Abilities.FUR_COAT,
[Species.GOTHITA]: Abilities.UNNERVE,
@ -3611,7 +3615,7 @@ export const starterPassiveAbilities = {
[Species.ALOMOMOLA]: Abilities.MULTISCALE,
[Species.JOLTIK]: Abilities.TRANSISTOR,
[Species.FERROSEED]: Abilities.ROUGH_SKIN,
[Species.KLINK]: Abilities.STEELWORKER,
[Species.KLINK]: Abilities.STEELY_SPIRIT,
[Species.TYNAMO]: Abilities.POISON_HEAL,
[Species.ELGYEM]: Abilities.PRISM_ARMOR,
[Species.LITWICK]: Abilities.SOUL_HEART,
@ -3625,7 +3629,7 @@ export const starterPassiveAbilities = {
[Species.GOLETT]: Abilities.SHADOW_SHIELD,
[Species.PAWNIARD]: Abilities.SWORD_OF_RUIN,
[Species.BOUFFALANT]: Abilities.ROCK_HEAD,
[Species.RUFFLET]: Abilities.GALE_WINGS,
[Species.RUFFLET]: Abilities.SPEED_BOOST,
[Species.VULLABY]: Abilities.THICK_FAT,
[Species.HEATMOR]: Abilities.CONTRARY,
[Species.DURANT]: Abilities.COMPOUND_EYES,
@ -3651,12 +3655,12 @@ export const starterPassiveAbilities = {
[Species.SCATTERBUG]: Abilities.PRANKSTER,
[Species.LITLEO]: Abilities.BEAST_BOOST,
[Species.FLABEBE]: Abilities.GRASSY_SURGE,
[Species.SKIDDO]: Abilities.GRASSY_SURGE,
[Species.SKIDDO]: Abilities.SEED_SOWER,
[Species.PANCHAM]: Abilities.FUR_COAT,
[Species.FURFROU]: Abilities.FLUFFY,
[Species.ESPURR]: Abilities.FUR_COAT,
[Species.HONEDGE]: Abilities.SHARPNESS,
[Species.SPRITZEE]: Abilities.MISTY_SURGE,
[Species.SPRITZEE]: Abilities.FUR_COAT,
[Species.SWIRLIX]: Abilities.WELL_BAKED_BODY,
[Species.INKAY]: Abilities.UNNERVE,
[Species.BINACLE]: Abilities.SAP_SIPPER,
@ -3670,17 +3674,17 @@ export const starterPassiveAbilities = {
[Species.CARBINK]: Abilities.SOLID_ROCK,
[Species.GOOMY]: Abilities.REGENERATOR,
[Species.KLEFKI]: Abilities.LEVITATE,
[Species.PHANTUMP]: Abilities.RIPEN,
[Species.PHANTUMP]: Abilities.SHADOW_TAG,
[Species.PUMPKABOO]: Abilities.WELL_BAKED_BODY,
[Species.BERGMITE]: Abilities.ICE_SCALES,
[Species.NOIBAT]: Abilities.PUNK_ROCK,
[Species.XERNEAS]: Abilities.MISTY_SURGE,
[Species.XERNEAS]: Abilities.HARVEST,
[Species.YVELTAL]: Abilities.SOUL_HEART,
[Species.ZYGARDE]: Abilities.HUGE_POWER,
[Species.DIANCIE]: Abilities.LEVITATE,
[Species.HOOPA]: Abilities.OPPORTUNIST,
[Species.VOLCANION]: Abilities.FILTER,
[Species.ROWLET]: Abilities.UNBURDEN,
[Species.ROWLET]: Abilities.SNIPER,
[Species.LITTEN]: Abilities.FUR_COAT,
[Species.POPPLIO]: Abilities.PUNK_ROCK,
[Species.PIKIPEK]: Abilities.TECHNICIAN,
@ -3714,7 +3718,7 @@ export const starterPassiveAbilities = {
[Species.BRUXISH]: Abilities.MULTISCALE,
[Species.DRAMPA]: Abilities.THICK_FAT,
[Species.DHELMISE]: Abilities.WATER_BUBBLE,
[Species.JANGMO_O]: Abilities.PUNK_ROCK,
[Species.JANGMO_O]: Abilities.DAUNTLESS_SHIELD,
[Species.TAPU_KOKO]: Abilities.TRANSISTOR,
[Species.TAPU_LELE]: Abilities.SHEER_FORCE,
[Species.TAPU_BULU]: Abilities.TRIAGE,
@ -3726,7 +3730,7 @@ export const starterPassiveAbilities = {
[Species.XURKITREE]: Abilities.TRANSISTOR,
[Species.CELESTEELA]: Abilities.HEATPROOF,
[Species.KARTANA]: Abilities.SHARPNESS,
[Species.GUZZLORD]: Abilities.INNARDS_OUT,
[Species.GUZZLORD]: Abilities.POISON_HEAL,
[Species.NECROZMA]: Abilities.BEAST_BOOST,
[Species.MAGEARNA]: Abilities.STEELY_SPIRIT,
[Species.MARSHADOW]: Abilities.IRON_FIST,
@ -3738,13 +3742,13 @@ export const starterPassiveAbilities = {
[Species.GROOKEY]: Abilities.GRASS_PELT,
[Species.SCORBUNNY]: Abilities.NO_GUARD,
[Species.SOBBLE]: Abilities.SUPER_LUCK,
[Species.SKWOVET]: Abilities.RIPEN,
[Species.SKWOVET]: Abilities.HARVEST,
[Species.ROOKIDEE]: Abilities.IRON_BARBS,
[Species.BLIPBUG]: Abilities.PSYCHIC_SURGE,
[Species.NICKIT]: Abilities.MAGICIAN,
[Species.GOSSIFLEUR]: Abilities.GRASSY_SURGE,
[Species.WOOLOO]: Abilities.SIMPLE,
[Species.CHEWTLE]: Abilities.ROCK_HEAD,
[Species.CHEWTLE]: Abilities.ROCKY_PAYLOAD,
[Species.YAMPER]: Abilities.SHEER_FORCE,
[Species.ROLYCOLY]: Abilities.SOLID_ROCK,
[Species.APPLIN]: Abilities.DRAGONS_MAW,
@ -3757,7 +3761,7 @@ export const starterPassiveAbilities = {
[Species.SINISTEA]: Abilities.SHADOW_SHIELD,
[Species.HATENNA]: Abilities.FAIRY_AURA,
[Species.IMPIDIMP]: Abilities.FUR_COAT,
[Species.MILCERY]: Abilities.MISTY_SURGE,
[Species.MILCERY]: Abilities.REGENERATOR,
[Species.FALINKS]: Abilities.PARENTAL_BOND,
[Species.PINCURCHIN]: Abilities.ELECTROMORPHOSIS,
[Species.SNOM]: Abilities.SNOW_WARNING,
@ -3776,7 +3780,7 @@ export const starterPassiveAbilities = {
[Species.ZAMAZENTA]: Abilities.STAMINA,
[Species.ETERNATUS]: Abilities.SUPREME_OVERLORD,
[Species.KUBFU]: Abilities.IRON_FIST,
[Species.ZARUDE]: Abilities.GRASSY_SURGE,
[Species.ZARUDE]: Abilities.TOUGH_CLAWS,
[Species.REGIELEKI]: Abilities.ELECTRIC_SURGE,
[Species.REGIDRAGO]: Abilities.MULTISCALE,
[Species.GLASTRIER]: Abilities.FILTER,
@ -3785,7 +3789,7 @@ export const starterPassiveAbilities = {
[Species.ENAMORUS]: Abilities.FAIRY_AURA,
[Species.SPRIGATITO]: Abilities.MAGICIAN,
[Species.FUECOCO]: Abilities.PUNK_ROCK,
[Species.QUAXLY]: Abilities.DEFIANT,
[Species.QUAXLY]: Abilities.OPPORTUNIST,
[Species.LECHONK]: Abilities.SIMPLE,
[Species.TAROUNTULA]: Abilities.HONEY_GATHER,
[Species.NYMBLE]: Abilities.GUTS,
@ -3833,7 +3837,7 @@ export const starterPassiveAbilities = {
[Species.IRON_MOTH]: Abilities.LEVITATE,
[Species.IRON_THORNS]: Abilities.SAND_STREAM,
[Species.FRIGIBAX]: Abilities.SNOW_WARNING,
[Species.GIMMIGHOUL]: Abilities.CONTRARY,
[Species.GIMMIGHOUL]: Abilities.HONEY_GATHER,
[Species.WO_CHIEN]: Abilities.VESSEL_OF_RUIN,
[Species.CHIEN_PAO]: Abilities.INTREPID_SWORD,
[Species.TING_LU]: Abilities.STAMINA,
@ -3864,7 +3868,7 @@ export const starterPassiveAbilities = {
[Species.ALOLA_GRIMER]: Abilities.TOXIC_DEBRIS,
[Species.ETERNAL_FLOETTE]: Abilities.MAGIC_GUARD,
[Species.GALAR_MEOWTH]: Abilities.STEELWORKER,
[Species.GALAR_PONYTA]: Abilities.PIXILATE,
[Species.GALAR_PONYTA]: Abilities.MOXIE,
[Species.GALAR_SLOWPOKE]: Abilities.UNAWARE,
[Species.GALAR_FARFETCHD]: Abilities.INTREPID_SWORD,
[Species.GALAR_ARTICUNO]: Abilities.SERENE_GRACE,
@ -3876,7 +3880,7 @@ export const starterPassiveAbilities = {
[Species.GALAR_YAMASK]: Abilities.TABLETS_OF_RUIN,
[Species.GALAR_STUNFISK]: Abilities.ARENA_TRAP,
[Species.HISUI_GROWLITHE]: Abilities.RECKLESS,
[Species.HISUI_VOLTORB]: Abilities.ELECTRIC_SURGE,
[Species.HISUI_VOLTORB]: Abilities.TRANSISTOR,
[Species.HISUI_QWILFISH]: Abilities.MERCILESS,
[Species.HISUI_SNEASEL]: Abilities.SCRAPPY,
[Species.HISUI_ZORUA]: Abilities.ADAPTABILITY,

View File

@ -7,12 +7,12 @@ export { StatusEffect };
export class Status {
public effect: StatusEffect;
public turnCount: integer;
public cureTurn: integer;
public cureTurn: integer | null;
constructor(effect: StatusEffect, turnCount: integer = 0, cureTurn?: integer) {
this.effect = effect;
this.turnCount = turnCount === undefined ? 0 : turnCount;
this.cureTurn = cureTurn;
this.cureTurn = cureTurn!; // TODO: is this bang correct?
}
incrementTurn(): void {
@ -24,7 +24,7 @@ export class Status {
}
}
function getStatusEffectMessageKey(statusEffect: StatusEffect): string {
function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): string {
switch (statusEffect) {
case StatusEffect.POISON:
return "statusEffect:poison";
@ -43,7 +43,7 @@ function getStatusEffectMessageKey(statusEffect: StatusEffect): string {
}
}
export function getStatusEffectObtainText(statusEffect: StatusEffect, pokemonNameWithAffix: string, sourceText?: string): string {
export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined, pokemonNameWithAffix: string, sourceText?: string): string {
if (!sourceText) {
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtain`as ParseKeys;
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });
@ -115,11 +115,11 @@ export function getRandomStatusEffect(statusEffectA: StatusEffect, statusEffectB
* @param statusA The first Status
* @param statusB The second Status
*/
export function getRandomStatus(statusA: Status, statusB: Status): Status {
if (statusA === undefined || statusA.effect === StatusEffect.NONE || statusA.effect === StatusEffect.FAINT) {
export function getRandomStatus(statusA: Status | null, statusB: Status | null): Status | null {
if (!statusA || statusA.effect === StatusEffect.NONE || statusA.effect === StatusEffect.FAINT) {
return statusB;
}
if (statusB === undefined || statusB.effect === StatusEffect.NONE || statusB.effect === StatusEffect.FAINT) {
if (!statusB || statusB.effect === StatusEffect.NONE || statusB.effect === StatusEffect.FAINT) {
return statusA;
}

View File

@ -201,6 +201,7 @@ export class TrainerConfig {
public speciesPools: TrainerTierPools;
public speciesFilter: PokemonSpeciesFilter;
public specialtyTypes: Type[] = [];
public hasVoucher: boolean = false;
public encounterMessages: string[] = [];
public victoryMessages: string[] = [];
@ -257,6 +258,14 @@ export class TrainerConfig {
return this;
}
/**
* Sets if a boss trainer will have a voucher or not.
* @param hasVoucher - If the boss trainer will have a voucher.
*/
setHasVoucher(hasVoucher: boolean): void {
this.hasVoucher = hasVoucher;
}
setTitle(title: string): TrainerConfig {
// First check if i18n is initialized
if (!getIsInitialized()) {
@ -279,7 +288,7 @@ export class TrainerConfig {
* @param trainerTypeToDeriveFrom - The trainer type to derive from. (If null, the this.trainerType property will be used.)
* @returns {TrainerType} - The derived trainer type.
*/
getDerivedType(trainerTypeToDeriveFrom: TrainerType = null): TrainerType {
getDerivedType(trainerTypeToDeriveFrom: TrainerType | null = null): TrainerType {
let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType;
switch (trainerType) {
case TrainerType.RIVAL_2:
@ -352,7 +361,7 @@ export class TrainerConfig {
this.nameFemale = i18next.t("trainerNames:rival_female");
} else {
// Otherwise, assign the provided female name.
this.nameFemale = nameFemale;
this.nameFemale = nameFemale!; // TODO: is this bang correct?
}
// Indicate that this trainer configuration includes genders.
@ -527,6 +536,98 @@ export class TrainerConfig {
return this;
}
/**
* Returns the pool of species for an evil team admin
* @param team - The evil team the admin belongs to.
* @returns {TrainerTierPools}
*/
speciesPoolPerEvilTeamAdmin(team): TrainerTierPools {
team = team.toLowerCase();
switch (team) {
case "rocket": {
return {
[TrainerPoolTier.COMMON]: [Species.RATTATA, Species.KOFFING, Species.EKANS, Species.GYARADOS, Species.TAUROS, Species.SCYTHER, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB],
[TrainerPoolTier.UNCOMMON]: [Species.PORYGON, Species.ALOLA_RATTATA, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE],
[TrainerPoolTier.RARE]: [Species.DRATINI, Species.LARVITAR]
};
}
case "magma": {
return {
[TrainerPoolTier.COMMON]: [Species.NUMEL, Species.POOCHYENA, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.SWINUB, Species.GLIGAR],
[TrainerPoolTier.UNCOMMON]: [Species.TRAPINCH, Species.HEATMOR],
[TrainerPoolTier.RARE]: [Species.TURTONATOR, Species.CHARCADET]
};
}
case "aqua": {
return {
[TrainerPoolTier.COMMON]: [Species.CARVANHA, Species.CORPHISH, Species.ZIGZAGOON, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.QWILFISH],
[TrainerPoolTier.UNCOMMON]: [Species.MANTINE, Species.BASCULEGION, Species.REMORAID, Species.ARROKUDA],
[TrainerPoolTier.RARE]: [Species.DONDOZO]
};
}
case "galactic": {
return {
[TrainerPoolTier.COMMON]: [Species.GLAMEOW, Species.STUNKY, Species.BRONZOR, Species.CARNIVINE, Species.GROWLITHE, Species.QWILFISH, Species.SNEASEL],
[TrainerPoolTier.UNCOMMON]: [Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.HISUI_SNEASEL],
[TrainerPoolTier.RARE]: [Species.HISUI_ZORUA, Species.HISUI_SLIGGOO]
};
}
case "plasma": {
return {
[TrainerPoolTier.COMMON]: [Species.SCRAFTY, Species.LILLIPUP, Species.PURRLOIN, Species.FRILLISH, Species.VENIPEDE, Species.GOLETT, Species.TIMBURR, Species.DARUMAKA, Species.AMOONGUSS],
[TrainerPoolTier.UNCOMMON]: [Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK],
[TrainerPoolTier.RARE]: [Species.DRUDDIGON, Species.BOUFFALANT, Species.AXEW, Species.DEINO, Species.DURANT]
};
}
case "flare": {
return {
[TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.GULPIN, Species.PURRLOIN, Species.POOCHYENA, Species.SCATTERBUG],
[TrainerPoolTier.UNCOMMON]: [Species.LITWICK, Species.SNEASEL, Species.PANCHAM, Species.PAWNIARD],
[TrainerPoolTier.RARE]: [Species.NOIVERN, Species.DRUDDIGON]
};
}
}
console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`);
return [];
}
/**
* Initializes the trainer configuration for an evil team admin.
* @param title - The title of the evil team admin.
* @param poolName - The evil team the admin belongs to.
* @param {Species | Species[]} signatureSpecies - The signature species for the evil team leader.
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* **/
initForEvilTeamAdmin(title: string, poolName: string, signatureSpecies: (Species | Species[])[],): TrainerConfig {
if (!getIsInitialized()) {
initI18n();
}
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
// Set the species pools for the evil team admin.
this.speciesPools = this.speciesPoolPerEvilTeamAdmin(poolName);
signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
});
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
this.name = i18next.t(`trainerNames:${nameForCall}`);
this.setHasVoucher(false);
this.setTitle(title);
this.setMoneyMultiplier(1.5);
this.setBoss();
this.setStaticParty();
this.setBattleBgm("battle_plasma_boss");
this.setVictoryBgm("victory_team_plasma");
return this;
}
/**
* Initializes the trainer configuration for an evil team leader. Temporarily hardcoding evil leader teams though.
* @param {Species | Species[]} signatureSpecies - The signature species for the evil team leader.
@ -559,6 +660,7 @@ export class TrainerConfig {
this.setMoneyMultiplier(2.5);
this.setBoss();
this.setStaticParty();
this.setHasVoucher(true);
this.setBattleBgm("battle_plasma_boss");
this.setVictoryBgm("victory_team_plasma");
@ -611,11 +713,12 @@ export class TrainerConfig {
this.setMoneyMultiplier(2.5);
this.setBoss();
this.setStaticParty();
this.setHasVoucher(true);
this.setBattleBgm("battle_unova_gym");
this.setVictoryBgm("victory_gym");
this.setGenModifiersFunc(party => {
const waveIndex = party[0].scene.currentBattle.waveIndex;
return getRandomTeraModifiers(party, waveIndex >= 100 ? 1 : 0, specialtyTypes.length ? specialtyTypes : null);
return getRandomTeraModifiers(party, waveIndex >= 100 ? 1 : 0, specialtyTypes.length ? specialtyTypes : undefined);
});
return this;
@ -649,7 +752,7 @@ export class TrainerConfig {
// Set species filter and specialty types if provided, otherwise filter by base total.
if (specialtyTypes.length) {
this.setSpeciesFilter(p => specialtyTypes.find(t => p.isOfType(t)) && p.baseTotal >= 450);
this.setSpeciesFilter(p => specialtyTypes.some(t => p.isOfType(t)) && p.baseTotal >= 450);
this.setSpecialtyTypes(...specialtyTypes);
} else {
this.setSpeciesFilter(p => p.baseTotal >= 450);
@ -669,9 +772,10 @@ export class TrainerConfig {
this.setMoneyMultiplier(3.25);
this.setBoss();
this.setStaticParty();
this.setHasVoucher(true);
this.setBattleBgm("battle_unova_elite");
this.setVictoryBgm("victory_gym");
this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 2, specialtyTypes.length ? specialtyTypes : null));
this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 2, specialtyTypes.length ? specialtyTypes : undefined));
return this;
}
@ -719,6 +823,7 @@ export class TrainerConfig {
this.setMoneyMultiplier(10);
this.setBoss();
this.setStaticParty();
this.setHasVoucher(true);
this.setBattleBgm("battle_champion_alder");
this.setVictoryBgm("victory_champion");
this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 3));
@ -782,10 +887,20 @@ export class TrainerConfig {
// Ignore warnings for missing frames, because there will be a lot
console.warn = () => {
};
const frameNames = scene.anims.generateFrameNames(trainerKey, {zeroPad: 4,suffix: ".png",start: 1,end: 128});
const frameNames = scene.anims.generateFrameNames(trainerKey, {
zeroPad: 4,
suffix: ".png",
start: 1,
end: 128
});
const partnerFrameNames = isDouble
? scene.anims.generateFrameNames(partnerTrainerKey, {zeroPad: 4,suffix: ".png",start: 1,end: 128})
: null;
? scene.anims.generateFrameNames(partnerTrainerKey, {
zeroPad: 4,
suffix: ".png",
start: 1,
end: 128
})
: "";
console.warn = originalWarn;
if (!(scene.anims.exists(trainerKey))) {
scene.anims.create({
@ -871,7 +986,7 @@ function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: T
for (let t = 0; t < Math.min(count, party.length); t++) {
const randomIndex = Utils.randSeedItem(partyMemberIndexes);
partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
ret.push(modifierTypes.TERA_SHARD().generateType(null, [Utils.randSeedItem(types ? types : party[randomIndex].getTypes())]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier);
ret.push(modifierTypes.TERA_SHARD().generateType([], [Utils.randSeedItem(types ? types : party[randomIndex].getTypes())])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct?
}
return ret;
}
@ -1187,12 +1302,10 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [Species.PORYGON, Species.ALOLA_RATTATA, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE],
[TrainerPoolTier.SUPER_RARE]: [Species.DRATINI, Species.LARVITAR]
}),
[TrainerType.ROCKET_ADMIN]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.GYARADOS, Species.TAUROS, Species.SCYTHER, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB],
[TrainerPoolTier.UNCOMMON]: [Species.PORYGON, Species.ALOLA_RATTATA, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE],
[TrainerPoolTier.RARE]: [Species.DRATINI, Species.LARVITAR]
}),
[TrainerType.ARCHER]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [Species.HOUNDOOM]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.ARIANA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin_female", "rocket", [Species.ARBOK]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.PROTON]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [Species.CROBAT]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.PETREL]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [Species.WEEZING]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.MAGMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Magma Grunt Female").setHasDouble("Magma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY, Species.TORKOAL, Species.BALTOY, Species.BARBOACH],
@ -1200,12 +1313,8 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [Species.TRAPINCH, Species.HEATMOR],
[TrainerPoolTier.SUPER_RARE]: [Species.TURTONATOR, Species.CHARCADET]
}),
[TrainerType.MAGMA_ADMIN]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.NUMEL, Species.POOCHYENA, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.SWINUB, Species.GLIGAR],
[TrainerPoolTier.UNCOMMON]: [Species.TRAPINCH, Species.HEATMOR],
[TrainerPoolTier.RARE]: [Species.TURTONATOR, Species.CHARCADET]
}),
[TrainerType.TABITHA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin", "magma", [Species.CAMERUPT]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.COURTNEY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin_female", "magma", [Species.CAMERUPT]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.AQUA_GRUNT]: new TrainerConfig(++t).setHasGenders("Aqua Grunt Female").setHasDouble("Aqua Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.CARVANHA, Species.WAILMER, Species.ZIGZAGOON, Species.LOTAD, Species.CORPHISH, Species.SPHEAL],
@ -1213,12 +1322,8 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [Species.MANTINE, Species.BASCULEGION, Species.REMORAID, Species.ARROKUDA],
[TrainerPoolTier.SUPER_RARE]: [Species.DONDOZO]
}),
[TrainerType.AQUA_ADMIN]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.CARVANHA, Species.CORPHISH, Species.ZIGZAGOON, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.QWILFISH ],
[TrainerPoolTier.UNCOMMON]: [Species.MANTINE, Species.BASCULEGION, Species.REMORAID, Species.ARROKUDA],
[TrainerPoolTier.RARE]: [Species.DONDOZO]
}),
[TrainerType.MATT]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin", "aqua", [Species.SHARPEDO]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.SHELLY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin_female", "aqua", [Species.SHARPEDO]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.GALACTIC_GRUNT]: new TrainerConfig(++t).setHasGenders("Galactic Grunt Female").setHasDouble("Galactic Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.GLAMEOW, Species.STUNKY, Species.CROAGUNK, Species.SHINX, Species.WURMPLE, Species.BRONZOR, Species.DRIFLOON, Species.BURMY],
@ -1226,12 +1331,10 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.HISUI_SNEASEL],
[TrainerPoolTier.SUPER_RARE]: [Species.HISUI_ZORUA, Species.HISUI_SLIGGOO]
}),
[TrainerType.GALACTIC_ADMIN]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.GLAMEOW, Species.STUNKY, Species.BRONZOR, Species.CARNIVINE, Species.GROWLITHE, Species.QWILFISH, Species.SNEASEL ],
[TrainerPoolTier.UNCOMMON]: [Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.HISUI_SNEASEL],
[TrainerPoolTier.RARE]: [Species.HISUI_ZORUA, Species.HISUI_SLIGGOO]
}),
[TrainerType.JUPITER]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander_female", "galactic", [Species.SKUNTANK]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.MARS]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander_female", "galactic", [Species.PURUGLY]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.SATURN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander", "galactic", [Species.TOXICROAK]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.PLASMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Plasma Grunt Female").setHasDouble("Plasma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.PATRAT, Species.LILLIPUP, Species.PURRLOIN, Species.SCRAFTY, Species.WOOBAT, Species.VANILLITE, Species.SANDILE, Species.TRUBBISH],
@ -1239,12 +1342,9 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK],
[TrainerPoolTier.SUPER_RARE]: [Species.DRUDDIGON, Species.BOUFFALANT, Species.AXEW, Species.DEINO, Species.DURANT]
}),
[TrainerType.PLASMA_SAGE]: new TrainerConfig(++t).setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.SCRAFTY, Species.LILLIPUP, Species.PURRLOIN, Species.FRILLISH, Species.VENIPEDE, Species.GOLETT, Species.TIMBURR, Species.DARUMAKA, Species.AMOONGUSS],
[TrainerPoolTier.UNCOMMON]: [Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK],
[TrainerPoolTier.RARE]: [Species.DRUDDIGON, Species.BOUFFALANT, Species.AXEW, Species.DEINO, Species.DURANT]
}),
[TrainerType.ZINZOLIN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [Species.CRYOGONAL]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.ROOD]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [Species.SWOOBAT]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK],
@ -1252,12 +1352,8 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [Species.LITWICK, Species.SNEASEL, Species.PANCHAM, Species.PAWNIARD],
[TrainerPoolTier.SUPER_RARE]: [Species.NOIVERN, Species.DRUDDIGON]
}),
[TrainerType.FLARE_ADMIN]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.GULPIN, Species.PURRLOIN, Species.POOCHYENA, Species.SCATTERBUG],
[TrainerPoolTier.UNCOMMON]: [Species.LITWICK, Species.SNEASEL, Species.PANCHAM, Species.PAWNIARD],
[TrainerPoolTier.RARE]: [Species.NOIVERN, Species.DRUDDIGON]
}),
[TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [Species.LIEPARD]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.XEROSIC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin", "flare", [Species.MALAMAR]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.BROCK]: new TrainerConfig((t = TrainerType.BROCK)).initForGymLeader(signatureSpecies["BROCK"], true, Type.ROCK).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.MISTY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["MISTY"], false, Type.WATER).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.LT_SURGE]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["LT_SURGE"], true, Type.ELECTRIC).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"),
@ -1501,7 +1597,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesFilter(species => species.baseTotal >= 540)
.setGenModifiersFunc(party => {
const starter = party[0];
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier];
return [modifierTypes.TERA_SHARD().generateType([], [starter.species.type1])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; // TODO: is the bang correct?
}),
[TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true,
@ -1517,7 +1613,7 @@ export const trainerConfigs: TrainerConfigs = {
}))
.setGenModifiersFunc(party => {
const starter = party[0];
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier];
return [modifierTypes.TERA_SHARD().generateType([], [starter.species.type1])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; //TODO: is the bang correct?
}),
[TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true,
@ -1543,7 +1639,7 @@ export const trainerConfigs: TrainerConfigs = {
}))
.setGenModifiersFunc(party => {
const starter = party[0];
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier];
return [modifierTypes.TERA_SHARD().generateType([], [starter.species.type1])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; // TODO: is the bang correct?
}),
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")

View File

@ -3,7 +3,7 @@ import * as Utils from "../utils";
class TrainerNameConfig {
public urls: string[];
public femaleUrls: string[];
public femaleUrls: string[] | null;
constructor(type: TrainerType, ...urls: string[]) {
this.urls = urls.length ? urls : [ Utils.toReadableString(TrainerType[type]).replace(/ /g, "_") ];
@ -136,8 +136,11 @@ function fetchAndPopulateTrainerNames(url: string, parser: DOMParser, trainerNam
.then(html => {
console.log(url);
const htmlDoc = parser.parseFromString(html, "text/html");
const trainerListHeader = htmlDoc.querySelector("#Trainer_list").parentElement;
const elements = [...trainerListHeader.parentElement.childNodes];
const trainerListHeader = htmlDoc.querySelector("#Trainer_list")?.parentElement;
if (!trainerListHeader) {
return [];
}
const elements = [...(trainerListHeader?.parentElement?.childNodes ?? [])];
const startChildIndex = elements.indexOf(trainerListHeader);
const endChildIndex = elements.findIndex(h => h.nodeName === "H2" && elements.indexOf(h) > startChildIndex);
const tables = elements.filter(t => {
@ -152,6 +155,9 @@ function fetchAndPopulateTrainerNames(url: string, parser: DOMParser, trainerNam
const trainerRows = [...table.querySelectorAll("tr:not(:first-child)")].filter(r => r.children.length === 9);
for (const row of trainerRows) {
const nameCell = row.firstElementChild;
if (!nameCell) {
continue;
}
const content = nameCell.innerHTML;
if (content.indexOf(" <a ") > -1) {
const female = /♀/.test(content);

View File

@ -499,6 +499,8 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.STELLAR:
return 1;
}
return 0;
}
/**

View File

@ -103,7 +103,7 @@ export class Weather {
const field = scene.getField(true);
for (const pokemon of field) {
let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0];
let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0];
if (!suppressWeatherEffectAbAttr) {
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null;
}
@ -116,7 +116,7 @@ export class Weather {
}
}
export function getWeatherStartMessage(weatherType: WeatherType): string {
export function getWeatherStartMessage(weatherType: WeatherType): string | null {
switch (weatherType) {
case WeatherType.SUNNY:
return i18next.t("weather:sunnyStartMessage");
@ -141,7 +141,7 @@ export function getWeatherStartMessage(weatherType: WeatherType): string {
return null;
}
export function getWeatherLapseMessage(weatherType: WeatherType): string {
export function getWeatherLapseMessage(weatherType: WeatherType): string | null {
switch (weatherType) {
case WeatherType.SUNNY:
return i18next.t("weather:sunnyLapseMessage");
@ -166,7 +166,7 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string {
return null;
}
export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string {
export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null {
switch (weatherType) {
case WeatherType.SANDSTORM:
return i18next.t("weather:sandstormDamageMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)});
@ -177,7 +177,7 @@ export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokem
return null;
}
export function getWeatherClearMessage(weatherType: WeatherType): string {
export function getWeatherClearMessage(weatherType: WeatherType): string | null {
switch (weatherType) {
case WeatherType.SUNNY:
return i18next.t("weather:sunnyClearMessage");
@ -202,7 +202,7 @@ export function getWeatherClearMessage(weatherType: WeatherType): string {
return null;
}
export function getTerrainStartMessage(terrainType: TerrainType): string {
export function getTerrainStartMessage(terrainType: TerrainType): string | null {
switch (terrainType) {
case TerrainType.MISTY:
return i18next.t("terrain:mistyStartMessage");
@ -212,10 +212,13 @@ export function getTerrainStartMessage(terrainType: TerrainType): string {
return i18next.t("terrain:grassyStartMessage");
case TerrainType.PSYCHIC:
return i18next.t("terrain:psychicStartMessage");
default:
console.warn("getTerrainStartMessage not defined. Using default null");
return null;
}
}
export function getTerrainClearMessage(terrainType: TerrainType): string {
export function getTerrainClearMessage(terrainType: TerrainType): string | null {
switch (terrainType) {
case TerrainType.MISTY:
return i18next.t("terrain:mistyClearMessage");
@ -225,6 +228,9 @@ export function getTerrainClearMessage(terrainType: TerrainType): string {
return i18next.t("terrain:grassyClearMessage");
case TerrainType.PSYCHIC:
return i18next.t("terrain:psychicClearMessage");
default:
console.warn("getTerrainClearMessage not defined. Using default null");
return null;
}
}

View File

@ -84,7 +84,7 @@ export class EggHatchPhase extends Phase {
this.scene.gameData.eggs.splice(eggIndex, 1);
this.scene.fadeOutBgm(null, false);
this.scene.fadeOutBgm(undefined, false);
this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
@ -234,8 +234,8 @@ export class EggHatchPhase extends Phase {
ease: "Sine.easeInOut",
duration: 250,
onComplete: () => {
count++;
if (count < repeatCount) {
count!++;
if (count! < repeatCount!) { // we know they are defined
return this.doEggShake(intensity, repeatCount, count).then(() => resolve());
}
this.scene.tweens.add({
@ -347,7 +347,7 @@ export class EggHatchPhase extends Phase {
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => {
this.scene.ui.showText(null, 0);
this.scene.ui.showText("", 0);
this.end();
});
});
@ -447,6 +447,6 @@ export class EggHatchPhase extends Phase {
}, this.egg.id, EGG_SEED.toString());
return ret;
return ret!;
}
}

View File

@ -21,5 +21,6 @@ export enum ArenaTagType {
MAT_BLOCK = "MAT_BLOCK",
CRAFTY_SHIELD = "CRAFTY_SHIELD",
TAILWIND = "TAILWIND",
HAPPY_HOUR = "HAPPY_HOUR"
HAPPY_HOUR = "HAPPY_HOUR",
NO_CRIT = "NO_CRIT"
}

View File

@ -48,7 +48,6 @@ export enum BattlerTagType {
FIRE_BOOST = "FIRE_BOOST",
CRIT_BOOST = "CRIT_BOOST",
ALWAYS_CRIT = "ALWAYS_CRIT",
NO_CRIT = "NO_CRIT",
IGNORE_ACCURACY = "IGNORE_ACCURACY",
BYPASS_SLEEP = "BYPASS_SLEEP",
IGNORE_FLYING = "IGNORE_FLYING",
@ -61,7 +60,13 @@ export enum BattlerTagType {
DESTINY_BOND = "DESTINY_BOND",
CENTER_OF_ATTENTION = "CENTER_OF_ATTENTION",
ICE_FACE = "ICE_FACE",
DISGUISE = "DISGUISE",
STOCKPILING = "STOCKPILING",
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
ALWAYS_GET_HIT = "ALWAYS_GET_HIT"
ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
IGNORE_GHOST = "IGNORE_GHOST",
IGNORE_DARK = "IGNORE_DARK",
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU",
BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING"
}

View File

@ -1,4 +1,3 @@
export enum TrainerType {
UNKNOWN,
@ -53,17 +52,26 @@ export enum TrainerType {
WORKER,
YOUNGSTER,
ROCKET_GRUNT,
ROCKET_ADMIN,
ARCHER,
ARIANA,
PROTON,
PETREL,
MAGMA_GRUNT,
MAGMA_ADMIN,
TABITHA,
COURTNEY,
AQUA_GRUNT,
AQUA_ADMIN,
MATT,
SHELLY,
GALACTIC_GRUNT,
GALACTIC_ADMIN,
JUPITER,
MARS,
SATURN,
PLASMA_GRUNT,
PLASMA_SAGE,
ZINZOLIN,
ROOD,
FLARE_GRUNT,
FLARE_ADMIN,
BRYONY,
XEROSIC,
ROCKET_BOSS_GIOVANNI_1,
ROCKET_BOSS_GIOVANNI_2,
MAXIE,

View File

@ -81,8 +81,8 @@ export class TagAddedEvent extends ArenaEvent {
this.arenaTagType = arenaTagType;
this.arenaTagSide = arenaTagSide;
this.arenaTagLayers = arenaTagLayers;
this.arenaTagMaxLayers = arenaTagMaxLayers;
this.arenaTagLayers = arenaTagLayers!; // TODO: is this bang correct?
this.arenaTagMaxLayers = arenaTagMaxLayers!; // TODO: is this bang correct?
}
}
/**

View File

@ -16,7 +16,7 @@ export class EvolutionPhase extends Phase {
protected pokemon: PlayerPokemon;
protected lastLevel: integer;
private evolution: SpeciesFormEvolution;
private evolution: SpeciesFormEvolution | null;
protected evolutionContainer: Phaser.GameObjects.Container;
protected evolutionBaseBg: Phaser.GameObjects.Image;
@ -28,7 +28,7 @@ export class EvolutionPhase extends Phase {
protected pokemonEvoSprite: Phaser.GameObjects.Sprite;
protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
constructor(scene: BattleScene, pokemon: PlayerPokemon, evolution: SpeciesFormEvolution, lastLevel: integer) {
constructor(scene: BattleScene, pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: integer) {
super(scene);
this.pokemon = pokemon;
@ -53,7 +53,7 @@ export class EvolutionPhase extends Phase {
return this.end();
}
this.scene.fadeOutBgm(null, false);
this.scene.fadeOutBgm(undefined, false);
const evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler;
@ -195,7 +195,7 @@ export class EvolutionPhase extends Phase {
this.scene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: preName }), null, () => {
this.scene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: preName }), null, () => {
const end = () => {
this.scene.ui.showText(null, 0);
this.scene.ui.showText("", 0);
this.scene.playBgm();
evolvedPokemon.destroy();
this.end();

View File

@ -25,8 +25,8 @@ import { TrainerType } from "#enums/trainer-type";
export class Arena {
public scene: BattleScene;
public biomeType: Biome;
public weather: Weather;
public terrain: Terrain;
public weather: Weather | null;
public terrain: Terrain | null;
public tags: ArenaTag[];
public bgm: string;
public ignoreAbilities: boolean;
@ -121,7 +121,7 @@ export class Arena {
}
}
ret = getPokemonSpecies(species);
ret = getPokemonSpecies(species!);
if (ret.subLegendary || ret.legendary || ret.mythical) {
switch (true) {
@ -292,7 +292,7 @@ export class Arena {
trySetWeatherOverride(weather: WeatherType): boolean {
this.weather = new Weather(weather, 0);
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather));
this.scene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct?
return true;
}
@ -314,13 +314,13 @@ export class Arena {
const oldWeatherType = this.weather?.weatherType || WeatherType.NONE;
this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null;
this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType, this.weather?.turnsLeft));
this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!)); // TODO: is this bang correct?
if (this.weather) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather));
this.scene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct?
} else {
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType));
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct?
}
this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
@ -339,15 +339,15 @@ export class Arena {
const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE;
this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null;
this.eventTarget.dispatchEvent(new TerrainChangedEvent(oldTerrainType,this.terrain?.terrainType, this.terrain?.turnsLeft));
this.eventTarget.dispatchEvent(new TerrainChangedEvent(oldTerrainType,this.terrain?.terrainType!, this.terrain?.turnsLeft!)); // TODO: are those bangs correct?
if (this.terrain) {
if (!ignoreAnim) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1)));
}
this.scene.queueMessage(getTerrainStartMessage(terrain));
this.scene.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct?
} else {
this.scene.queueMessage(getTerrainClearMessage(oldTerrainType));
this.scene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct?
}
this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
@ -554,7 +554,7 @@ export class Arena {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args);
}
addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean {
addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean {
const existingTag = this.getTagOnSide(tagType, side);
if (existingTag) {
existingTag.onOverlap(this);
@ -568,21 +568,23 @@ export class Arena {
}
const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
if (newTag) {
this.tags.push(newTag);
newTag.onAdd(this, quiet);
const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {};
this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers));
}
return true;
}
getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag {
getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag | undefined {
return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
}
getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag {
getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag | undefined {
return typeof(tagType) === "string"
? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side))
: this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side));
@ -724,6 +726,9 @@ export class Arena {
return 0.000;
case Biome.SNOWY_FOREST:
return 3.047;
default:
console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`);
return 0;
}
}
}
@ -777,12 +782,12 @@ export class ArenaBase extends Phaser.GameObjects.Container {
this.player = player;
this.base = scene.addFieldSprite(0, 0, "plains_a", null, 1);
this.base = scene.addFieldSprite(0, 0, "plains_a", undefined, 1);
this.base.setOrigin(0, 0);
this.props = !player ?
new Array(3).fill(null).map(() => {
const ret = scene.addFieldSprite(0, 0, "plains_b", null, 1);
const ret = scene.addFieldSprite(0, 0, "plains_b", undefined, 1);
ret.setOrigin(0, 0);
ret.setVisible(false);
return ret;

View File

@ -3,6 +3,8 @@ import Pokemon, { DamageResult, HitResult } from "./pokemon";
import * as Utils from "../utils";
import { BattlerIndex } from "../battle";
type TextAndShadowArr = [ string | null, string | null ];
export default class DamageNumberHandler {
private damageNumbers: Map<BattlerIndex, Phaser.GameObjects.Text[]>;
@ -24,7 +26,7 @@ export default class DamageNumberHandler {
damageNumber.setOrigin(0.5, 1);
damageNumber.setScale(baseScale);
let [ textColor, shadowColor ] = [ null, null ];
let [ textColor, shadowColor ] : TextAndShadowArr = [ null, null ];
switch (result) {
case HitResult.SUPER_EFFECTIVE:
@ -62,12 +64,12 @@ export default class DamageNumberHandler {
this.damageNumbers.set(battlerIndex, []);
}
const yOffset = this.damageNumbers.get(battlerIndex).length * -10;
const yOffset = this.damageNumbers.get(battlerIndex)!.length * -10;
if (yOffset) {
damageNumber.y += yOffset;
}
this.damageNumbers.get(battlerIndex).push(damageNumber);
this.damageNumbers.get(battlerIndex)!.push(damageNumber);
if (scene.damageNumbersMode === 1) {
scene.tweens.add({
@ -83,7 +85,7 @@ export default class DamageNumberHandler {
alpha: 0,
ease: "Sine.easeIn",
onComplete: () => {
this.damageNumbers.get(battlerIndex).splice(this.damageNumbers.get(battlerIndex).indexOf(damageNumber), 1);
this.damageNumbers.get(battlerIndex)!.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1);
damageNumber.destroy(true);
}
});
@ -167,7 +169,7 @@ export default class DamageNumberHandler {
delay: Utils.fixedInt(500),
alpha: 0,
onComplete: () => {
this.damageNumbers.get(battlerIndex).splice(this.damageNumbers.get(battlerIndex).indexOf(damageNumber), 1);
this.damageNumbers.get(battlerIndex)!.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1);
damageNumber.destroy(true);
}
}

View File

@ -35,7 +35,7 @@ export default class PokemonSpriteSparkleHandler {
const ratioX = s.width / width;
const ratioY = s.height / height;
const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE");
if (pixel.alpha) {
if (pixel?.alpha) {
const [ xOffset, yOffset ] = [ -s.originX * s.width, -s.originY * s.height];
const sparkle = (s.scene as BattleScene).addFieldSprite(((pokemon?.x || 0) + s.x + pixelX * ratioX + xOffset), ((pokemon?.y || 0) + s.y + pixelY * ratioY + yOffset), "tera_sparkle");
sparkle.pipelineData["ignoreTimeTint"] = s.pipelineData["ignoreTimeTint"];

View File

@ -19,10 +19,10 @@ import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEv
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase, MoveEndPhase } from "../phases";
import { BattleStat } from "../data/battle-stat";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag } from "../data/battler-tags";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags";
import { WeatherType } from "../data/weather";
import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, WeakenMoveScreenTag } from "../data/arena-tag";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability";
import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle";
@ -79,8 +79,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public ivs: integer[];
public nature: Nature;
public natureOverride: Nature | -1;
public moveset: PokemonMove[];
public status: Status;
public moveset: (PokemonMove | null)[];
public status: Status | null;
public friendship: integer;
public metLevel: integer;
public metBiome: Biome | -1;
@ -88,8 +88,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public luck: integer;
public pauseEvolutions: boolean;
public pokerus: boolean;
public wildFlee: boolean;
public fusionSpecies: PokemonSpecies;
public fusionSpecies: PokemonSpecies | null;
public fusionFormIndex: integer;
public fusionAbilityIndex: integer;
public fusionShiny: boolean;
@ -97,7 +98,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fusionGender: Gender;
public fusionLuck: integer;
private summonDataPrimer: PokemonSummonData;
private summonDataPrimer: PokemonSummonData | null;
public summonData: PokemonSummonData;
public battleData: PokemonBattleData;
@ -107,7 +108,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fieldPosition: FieldPosition;
public maskEnabled: boolean;
public maskSprite: Phaser.GameObjects.Sprite;
public maskSprite: Phaser.GameObjects.Sprite | null;
private shinySparkle: Phaser.GameObjects.Sprite;
@ -129,6 +130,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.species = species;
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
this.level = level;
this.wildFlee = false;
// Determine the ability index
if (abilityIndex !== undefined) {
this.abilityIndex = abilityIndex; // Use the provided ability index if it is defined
@ -169,7 +172,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.nickname = dataSource.nickname;
this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1;
this.moveset = dataSource.moveset;
this.status = dataSource.status;
this.status = dataSource.status!; // TODO: is this bang correct?
this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship;
this.metLevel = dataSource.metLevel || 5;
this.luck = dataSource.luck;
@ -177,7 +180,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.metSpecies = dataSource.metSpecies ?? (this.metBiome !== -1 ? this.species.speciesId : this.species.getRootSpeciesId(true));
this.pauseEvolutions = dataSource.pauseEvolutions;
this.pokerus = !!dataSource.pokerus;
this.fusionSpecies = dataSource.fusionSpecies instanceof PokemonSpecies ? dataSource.fusionSpecies : getPokemonSpecies(dataSource.fusionSpecies);
this.fusionSpecies = dataSource.fusionSpecies instanceof PokemonSpecies ? dataSource.fusionSpecies : dataSource.fusionSpecies ? getPokemonSpecies(dataSource.fusionSpecies) : null;
this.fusionFormIndex = dataSource.fusionFormIndex;
this.fusionAbilityIndex = dataSource.fusionAbilityIndex;
this.fusionShiny = dataSource.fusionShiny;
@ -298,14 +301,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
/**
* Check if this pokemon is both not fainted and allowed to be in battle.
* Check if this pokemon is both not fainted (or a fled wild pokemon) and allowed to be in battle.
* This is frequently a better alternative to {@link isFainted}
* @returns {boolean} True if pokemon is allowed in battle
*/
isAllowedInBattle(): boolean {
const challengeAllowed = new Utils.BooleanHolder(true);
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed);
return !this.isFainted() && challengeAllowed.value;
return !this.isFainted() && !this.wildFlee && challengeAllowed.value;
}
isActive(onField?: boolean): boolean {
@ -350,7 +353,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
loadAssets(ignoreOverride: boolean = true): Promise<void> {
return new Promise(resolve => {
const moveIds = this.getMoveset().map(m => m.getMove().id);
const moveIds = this.getMoveset().map(m => m!.getMove().id); // TODO: is this bang correct?
Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m)))
.then(() => {
loadMoveAnimAssets(this.scene, moveIds);
@ -438,7 +441,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.species.forms[this.formIndex].formKey;
}
getFusionFormKey(): string {
getFusionFormKey(): string | null {
if (!this.fusionSpecies) {
return null;
}
@ -527,7 +530,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.summonData.fusionSpeciesForm;
}
if (!this.fusionSpecies?.forms?.length || this.fusionFormIndex >= this.fusionSpecies?.forms.length) {
return this.fusionSpecies;
//@ts-ignore
return this.fusionSpecies; // TODO: I don't even know how to fix this... A complete cluster of classes involved + null
}
return this.fusionSpecies?.forms[this.fusionFormIndex];
}
@ -536,7 +540,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.getAt(0) as Phaser.GameObjects.Sprite;
}
getTintSprite(): Phaser.GameObjects.Sprite {
getTintSprite(): Phaser.GameObjects.Sprite | null {
return !this.maskEnabled
? this.getAt(1) as Phaser.GameObjects.Sprite
: this.maskSprite;
@ -562,7 +566,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
updateSpritePipelineData(): void {
[ this.getSprite(), this.getTintSprite() ].map(s => s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType()));
[ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType()));
this.updateInfo(true);
}
@ -610,7 +614,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
playAnim(): void {
this.tryPlaySprite(this.getSprite(), this.getTintSprite(), this.getBattleSpriteKey());
this.tryPlaySprite(this.getSprite(), this.getTintSprite()!, this.getBattleSpriteKey()); // TODO: is the bag correct?
}
getFieldPositionOffset(): [ number, number ] {
@ -870,7 +874,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
abstract isBoss(): boolean;
getMoveset(ignoreOverride?: boolean): PokemonMove[] {
getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] {
const ret = !ignoreOverride && this.summonData?.moveset
? this.summonData.moveset
: this.moveset;
@ -912,16 +916,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* excluding any moves already known.
*
* Available egg moves are only included if the {@linkcode Pokemon} was
* in the starting party of the run.
* in the starting party of the run and if Fresh Start is not active.
* @returns an array of {@linkcode Moves}, the length of which is determined
* by how many learnable moves there are for the {@linkcode Pokemon}.
*/
getLearnableLevelMoves(): Moves[] {
let levelMoves = this.getLevelMoves(1, true).map(lm => lm[1]);
if (this.metBiome === -1) {
if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge()) {
levelMoves = this.getUnlockedEggMoves().concat(levelMoves);
}
return levelMoves.filter(lm => !this.moveset.some(m => m.moveId === lm));
return levelMoves.filter(lm => !this.moveset.some(m => m?.moveId === lm));
}
/**
@ -932,7 +936,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns array of {@linkcode Type}
*/
getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean): Type[] {
const types = [];
const types : Type[] = [];
if (includeTeraType) {
const teraType = this.getTeraType();
@ -942,7 +946,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (!types.length || !includeTeraType) {
if (!ignoreOverride && this.summonData?.types) {
if (!ignoreOverride && this.summonData?.types && this.summonData.types.length !== 0) {
this.summonData.types.forEach(t => types.push(t));
} else {
const speciesForm = this.getSpeciesForm(ignoreOverride);
@ -1077,6 +1081,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
(Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) {
return true;
}
// Classic Final boss and Endless Minor/Major bosses do not have passive
const { currentBattle, gameMode } = this.scene;
const waveIndex = currentBattle?.waveIndex;
if (this instanceof EnemyPokemon &&
(currentBattle?.battleSpec === BattleSpec.FINAL_BOSS ||
gameMode.isEndlessMinorBoss(waveIndex) ||
gameMode.isEndlessMajorBoss(waveIndex))) {
return false;
}
return this.passive || this.isBoss();
}
@ -1115,7 +1130,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false;
}
}
return (this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this));
return (!!this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this));
}
/**
@ -1242,7 +1257,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
? moveOrType
: undefined;
const moveType = (moveOrType instanceof Move)
? move.type
? move!.type // TODO: is this bang correct?
: moveOrType;
if (moveType === Type.STELLAR) {
@ -1259,6 +1274,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (ignoreImmunity.value) {
return 1;
}
const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[];
if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) {
return 1;
}
}
return getTypeDamageMultiplier(moveType, defType);
@ -1280,7 +1300,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
return multiplier;
return multiplier as TypeDamageMultiplier;
}
/**
@ -1322,7 +1342,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return (atkScore + defScore) * hpDiffRatio;
}
getEvolution(): SpeciesFormEvolution {
getEvolution(): SpeciesFormEvolution | null {
if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) {
const evolutions = pokemonEvolutions[this.species.speciesId];
for (const e of evolutions) {
@ -1334,7 +1354,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
if (this.isFusion() && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) {
if (this.isFusion() && this.fusionSpecies && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) {
const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map(e => new FusionSpeciesFormEvolution(this.species.speciesId, e));
for (const fe of fusionEvolutions) {
if (!fe.item && this.level >= fe.level && (!fe.preFormKey || this.getFusionFormKey() === fe.preFormKey)) {
@ -1544,7 +1564,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
clearFusionSpecies(): void {
this.fusionSpecies = undefined;
this.fusionSpecies = null;
this.fusionFormIndex = 0;
this.fusionAbilityIndex = 0;
this.fusionShiny = false;
@ -1704,9 +1724,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Sqrt the weight of any damaging moves with overlapping types. This is about a 0.05 - 0.1 multiplier.
// Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights double if STAB.
// Status moves remain unchanged on weight, this encourages 1-2
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo.moveId)).map(m => [m[0], this.moveset.some(mo => mo.getMove().category !== MoveCategory.STATUS && mo.getMove().type === allMoves[m[0]].type) ? Math.ceil(Math.sqrt(m[1])) : allMoves[m[0]].category !== MoveCategory.STATUS ? Math.ceil(m[1]/Math.max(Math.pow(4, this.moveset.filter(mo => mo.getMove().power > 1).length)/8,0.5) * (this.isOfType(allMoves[m[0]].type) ? 2 : 1)) : m[1]]);
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo?.moveId)).map(m => [m[0], this.moveset.some(mo => mo?.getMove().category !== MoveCategory.STATUS && mo?.getMove().type === allMoves[m[0]].type) ? Math.ceil(Math.sqrt(m[1])) : allMoves[m[0]].category !== MoveCategory.STATUS ? Math.ceil(m[1]/Math.max(Math.pow(4, this.moveset.filter(mo => (mo?.getMove().power!) > 1).length)/8,0.5) * (this.isOfType(allMoves[m[0]].type) ? 2 : 1)) : m[1]]); // TODO: is this bang correct?
} else { // Non-trainer pokemon just use normal weights
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo.moveId));
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo?.moveId));
}
const totalWeight = movePool.reduce((v, m) => v + m[1], 0);
let rand = Utils.randSeedInt(totalWeight);
@ -1724,7 +1744,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const move = this.getMoveset().length > moveIndex
? this.getMoveset()[moveIndex]
: null;
return move?.isUsable(this, ignorePp);
return move?.isUsable(this, ignorePp)!; // TODO: is this bang correct?
}
showInfo(): void {
@ -1773,6 +1793,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
});
}
/**
* sets if the pokemon has fled (implies it's a wild pokemon)
* @param status - boolean
*/
setWildFlee(status: boolean): void {
this.wildFlee = status;
}
updateInfo(instant?: boolean): Promise<void> {
return this.battleInfo.updateInfo(this, instant);
}
@ -1807,7 +1835,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate);
}
getOpponent(targetIndex: integer): Pokemon {
getOpponent(targetIndex: integer): Pokemon | null {
const ret = this.getOpponents()[targetIndex];
if (ret.summonData) {
return ret;
@ -1865,6 +1893,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvasionLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), TempBattleStat.ACC, userAccuracyLevel);
if (target.findTag(t => t instanceof ExposedTag)) {
targetEvasionLevel.value = Math.min(0, targetEvasionLevel.value);
}
const accuracyMultiplier = new Utils.NumberHolder(1);
if (userAccuracyLevel.value !== targetEvasionLevel.value) {
accuracyMultiplier.value = userAccuracyLevel.value > targetEvasionLevel.value
@ -1885,6 +1917,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
apply(source: Pokemon, move: Move): HitResult {
let result: HitResult;
const damage = new Utils.NumberHolder(0);
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
const variableCategory = new Utils.IntegerHolder(move.category);
@ -1911,7 +1944,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Apply arena tags for conditional protection
if (!move.checkFlag(MoveFlags.IGNORE_PROTECT, source, this) && !move.isAllyTarget()) {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
this.scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority);
this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget);
this.scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category);
@ -1968,7 +2000,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
const bonusCrit = new Utils.BooleanHolder(false);
if (applyAbAttrs(BonusCritAbAttr, source, null, bonusCrit)) {
//@ts-ignore
if (applyAbAttrs(BonusCritAbAttr, source, null, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus.
if (bonusCrit.value) {
critLevel.value += 1;
}
@ -1978,25 +2011,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
console.log(`crit stage: +${critLevel.value}`);
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance));
isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance);
if (Overrides.NEVER_CRIT_OVERRIDE) {
isCritical = false;
}
}
if (isCritical) {
const noCritTag = this.scene.arena.getTagOnSide(NoCritTag, defendingSide);
const blockCrit = new Utils.BooleanHolder(false);
applyAbAttrs(BlockCritAbAttr, this, null, blockCrit);
if (blockCrit.value) {
if (noCritTag || blockCrit.value) {
isCritical = false;
}
}
const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, null, isCritical));
const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));
const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier);
const screenMultiplier = new Utils.NumberHolder(1);
if (!isCritical) {
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, this.scene.currentBattle.double, screenMultiplier);
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier);
}
const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0;
const sourceTypes = source.getTypes();
@ -2078,6 +2112,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical = false;
result = HitResult.EFFECTIVE;
}
result = result!; // telling TS compiler that result is defined!
if (!result) {
if (!typeMultiplier.value) {
@ -2182,7 +2217,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (damage) {
const attacker = this.scene.getPokemonById(source.id);
const attacker = this.scene.getPokemonById(source.id)!; // TODO: is this bang correct?
destinyTag?.lapse(attacker, BattlerTagLapseType.CUSTOM);
}
}
@ -2282,7 +2317,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isMax(): boolean {
const maxForms = [SpeciesFormKey.GIGANTAMAX, SpeciesFormKey.GIGANTAMAX_RAPID, SpeciesFormKey.GIGANTAMAX_SINGLE, SpeciesFormKey.ETERNAMAX] as string[];
return maxForms.includes(this.getFormKey()) || maxForms.includes(this.getFusionFormKey());
return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!));
}
addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean {
@ -2292,7 +2327,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false;
}
const newTag = getBattlerTag(tagType, turnCount, sourceMove, sourceId);
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
const cancelled = new Utils.BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled);
@ -2311,18 +2346,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
/** @overload */
getTag(tagType: BattlerTagType): BattlerTag;
getTag(tagType: BattlerTagType): BattlerTag | null;
/** @overload */
getTag<T extends BattlerTag>(tagType: Constructor<T>): T;
getTag<T extends BattlerTag>(tagType: Constructor<T>): T | null;
getTag(tagType: BattlerTagType | Constructor<BattlerTag>): BattlerTag {
getTag(tagType: BattlerTagType | Constructor<BattlerTag>): BattlerTag | null {
if (!this.summonData) {
return null;
}
return tagType instanceof Function
return (tagType instanceof Function
? this.summonData.tags.find(t => t instanceof tagType)
: this.summonData.tags.find(t => t.tagType === tagType);
: this.summonData.tags.find(t => t.tagType === tagType)
)!; // TODO: is this bang correct?
}
findTag(tagFilter: ((tag: BattlerTag) => boolean)) {
@ -2404,8 +2440,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
for (const tag of source.summonData.tags) {
// bypass yawn, and infatuation as those can not be passed via Baton Pass
if (tag.sourceMove === Moves.YAWN || tag.tagType === BattlerTagType.INFATUATED) {
// bypass those can not be passed via Baton Pass
const excludeTagTypes = new Set([BattlerTagType.DROWSY, BattlerTagType.INFATUATED, BattlerTagType.FIRE_BOOST]);
if (excludeTagTypes.has(tag.tagType)) {
continue;
}
@ -2426,7 +2464,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.getMoveHistory().push(turnMove);
}
getLastXMoves(turnCount?: integer): TurnMove[] {
getLastXMoves(turnCount: integer = 0): TurnMove[] {
const moveHistory = this.getMoveHistory();
return moveHistory.slice(turnCount >= 0 ? Math.max(moveHistory.length - (turnCount || 1), 0) : 0, moveHistory.length).reverse();
}
@ -2504,9 +2542,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let frameThreshold: number;
sprite.anims.pause();
tintSprite.anims.pause();
tintSprite?.anims.pause();
let faintCryTimer = this.scene.time.addEvent({
let faintCryTimer : Phaser.Time.TimerEvent | null = this.scene.time.addEvent({
delay: Utils.fixedInt(delay),
repeat: -1,
callback: () => {
@ -2516,7 +2554,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
while (frameProgress > frameThreshold) {
if (sprite.anims.duration) {
sprite.anims.nextFrame();
tintSprite.anims.nextFrame();
tintSprite?.anims.nextFrame();
}
frameProgress -= frameThreshold;
}
@ -2524,7 +2562,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
rate *= 0.99;
cry.setRate(rate);
} else {
faintCryTimer.destroy();
faintCryTimer?.destroy();
faintCryTimer = null;
if (callback) {
callback();
@ -2583,9 +2621,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let frameThreshold: number;
sprite.anims.pause();
tintSprite.anims.pause();
tintSprite?.anims.pause();
let faintCryTimer = this.scene.time.addEvent({
let faintCryTimer: Phaser.Time.TimerEvent | null = this.scene.time.addEvent({
delay: Utils.fixedInt(delay),
repeat: -1,
callback: () => {
@ -2595,7 +2633,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
while (frameProgress > frameThreshold) {
if (sprite.anims.duration) {
sprite.anims.nextFrame();
tintSprite.anims.nextFrame();
tintSprite?.anims.nextFrame();
}
frameProgress -= frameThreshold;
}
@ -2612,7 +2650,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
fusionCry.setRate(rate);
}
if ((!cry || cry.pendingRemove) && (!fusionCry || fusionCry.pendingRemove)) {
faintCryTimer.destroy();
faintCryTimer?.destroy();
faintCryTimer = null;
if (callback) {
callback();
@ -2643,7 +2681,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE);
}
canSetStatus(effect: StatusEffect, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon = null): boolean {
canSetStatus(effect: StatusEffect | undefined, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon | null = null): boolean {
if (effect !== StatusEffect.FAINT) {
if (overrideStatus ? this.status?.effect === effect : this.status) {
return false;
@ -2694,7 +2732,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
break;
case StatusEffect.FREEZE:
if (this.isOfType(Type.ICE) || [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(this.scene?.arena.weather?.weatherType)) {
if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType &&[WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(this.scene.arena.weather.weatherType))) {
return false;
}
break;
@ -2718,7 +2756,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return true;
}
trySetStatus(effect: StatusEffect, asPhase: boolean = false, sourcePokemon: Pokemon = null, cureTurn: integer = 0, sourceText: string = null): boolean {
trySetStatus(effect: StatusEffect | undefined, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, cureTurn: integer | null = 0, sourceText: string | null = null): boolean {
if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) {
return false;
}
@ -2732,7 +2770,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (asPhase) {
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon));
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText!, sourcePokemon!)); // TODO: are these bangs correct?
return true;
}
@ -2760,6 +2798,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
statusCureTurn = statusCureTurn!; // tell TS compiler it's defined
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
this.status = new Status(effect, 0, statusCureTurn?.value);
if (effect !== StatusEffect.FAINT) {
@ -2780,7 +2820,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!revive && lastStatus === StatusEffect.FAINT) {
return;
}
this.status = undefined;
this.status = null;
if (lastStatus === StatusEffect.SLEEP) {
this.setFrameRate(12);
if (this.getTag(BattlerTagType.NIGHTMARE)) {
@ -2848,16 +2888,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
setFrameRate(frameRate: integer) {
this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate;
this.getSprite().play(this.getBattleSpriteKey());
this.getTintSprite().play(this.getBattleSpriteKey());
this.getTintSprite()?.play(this.getBattleSpriteKey());
}
tint(color: number, alpha?: number, duration?: integer, ease?: string) {
const tintSprite = this.getTintSprite();
tintSprite.setTintFill(color);
tintSprite.setVisible(true);
tintSprite?.setTintFill(color);
tintSprite?.setVisible(true);
if (duration) {
tintSprite.setAlpha(0);
tintSprite?.setAlpha(0);
this.scene.tweens.add({
targets: tintSprite,
@ -2866,7 +2906,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
ease: ease || "Linear"
});
} else {
tintSprite.setAlpha(alpha);
tintSprite?.setAlpha(alpha);
}
}
@ -2880,32 +2920,32 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
duration: duration,
ease: ease || "Linear",
onComplete: () => {
tintSprite.setVisible(false);
tintSprite.setAlpha(1);
tintSprite?.setVisible(false);
tintSprite?.setAlpha(1);
}
});
} else {
tintSprite.setVisible(false);
tintSprite.setAlpha(1);
tintSprite?.setVisible(false);
tintSprite?.setAlpha(1);
}
}
enableMask() {
if (!this.maskEnabled) {
this.maskSprite = this.getTintSprite();
this.maskSprite.setVisible(true);
this.maskSprite.setPosition(this.x * this.parentContainer.scale + this.parentContainer.x,
this.maskSprite?.setVisible(true);
this.maskSprite?.setPosition(this.x * this.parentContainer.scale + this.parentContainer.x,
this.y * this.parentContainer.scale + this.parentContainer.y);
this.maskSprite.setScale(this.getSpriteScale() * this.parentContainer.scale);
this.maskSprite?.setScale(this.getSpriteScale() * this.parentContainer.scale);
this.maskEnabled = true;
}
}
disableMask() {
if (this.maskEnabled) {
this.maskSprite.setVisible(false);
this.maskSprite.setPosition(0, 0);
this.maskSprite.setScale(this.getSpriteScale());
this.maskSprite?.setVisible(false);
this.maskSprite?.setPosition(0, 0);
this.maskSprite?.setScale(this.getSpriteScale());
this.maskSprite = null;
this.maskEnabled = false;
}
@ -2920,7 +2960,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
updateFusionPalette(ignoreOveride?: boolean): void {
if (!this.getFusionSpeciesForm(ignoreOveride)) {
[ this.getSprite(), this.getTintSprite() ].map(s => {
[ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => {
s.pipelineData[`spriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = [];
s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = [];
});
@ -2956,9 +2996,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const frame = [ sourceFrame, sourceBackFrame, fusionFrame, fusionBackFrame ][c];
canv.width = frame.width;
canv.height = frame.height;
if (context) {
context.drawImage([ sourceImage, sourceBackImage, fusionImage, fusionBackImage ][c], frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height);
const imageData = context.getImageData(frame.cutX, frame.cutY, frame.width, frame.height);
pixelData.push(imageData.data);
}
});
for (let f = 0; f < 2; f++) {
@ -2978,9 +3021,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const color = Utils.rgbaToInt([r, g, b, a]);
if (variantColorSet.has(color)) {
const mappedPixel = variantColorSet.get(color);
if (mappedPixel) {
[ r, g, b, a ] = mappedPixel;
}
}
}
if (!spriteColors.find(c => c[0] === r && c[1] === g && c[2] === b)) {
spriteColors.push([ r, g, b, a ]);
}
@ -2990,7 +3035,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const fusionSpriteColors = JSON.parse(JSON.stringify(spriteColors));
const pixelColors = [];
const pixelColors: number[] = [];
for (let f = 0; f < 2; f++) {
for (let i = 0; i < pixelData[f].length; i += 4) {
const total = pixelData[f].slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0);
@ -3001,7 +3046,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
const fusionPixelColors = [];
const fusionPixelColors : number[] = [];
for (let f = 0; f < 2; f++) {
const variantColors = variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey];
const variantColorSet = new Map<integer, integer[]>();
@ -3020,9 +3065,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const color = Utils.rgbaToInt([r, g, b, a]);
if (variantColorSet.has(color)) {
const mappedPixel = variantColorSet.get(color);
if (mappedPixel) {
[ r, g, b, a ] = mappedPixel;
}
}
}
fusionPixelColors.push(argbFromRgba({ r, g, b, a }));
}
}
@ -3040,9 +3087,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
Math.random = originalRandom;
paletteColors = paletteColors!; // tell TS compiler that paletteColors is defined!
fusionPaletteColors = fusionPaletteColors!; // TS compiler that fusionPaletteColors is defined!
const [ palette, fusionPalette ] = [ paletteColors, fusionPaletteColors ]
.map(paletteColors => {
let keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a) < paletteColors.get(b) ? 1 : -1);
let keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1);
let rgbaColors: Map<number, integer[]>;
let hsvColors: Map<number, number[]>;
@ -3055,19 +3104,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
map.set(k, Object.values(rgbaFromArgb(k))); return map;
}, new Map<number, integer[]>());
hsvColors = Array.from(rgbaColors.keys()).reduce((map: Map<number, number[]>, k: number) => {
const rgb = rgbaColors.get(k).slice(0, 3);
const rgb = rgbaColors.get(k)!.slice(0, 3);
map.set(k, Utils.rgbToHsv(rgb[0], rgb[1], rgb[2]));
return map;
}, new Map<number, number[]>());
for (let c = keys.length - 1; c >= 0; c--) {
const hsv = hsvColors.get(keys[c]);
const hsv = hsvColors.get(keys[c])!;
for (let c2 = 0; c2 < c; c2++) {
const hsv2 = hsvColors.get(keys[c2]);
const hsv2 = hsvColors.get(keys[c2])!;
const diff = Math.abs(hsv[0] - hsv2[0]);
if (diff < 30 || diff >= 330) {
if (mappedColors.has(keys[c])) {
mappedColors.get(keys[c]).push(keys[c2]);
mappedColors.get(keys[c])!.push(keys[c2]);
} else {
mappedColors.set(keys[c], [ keys[c2] ]);
}
@ -3077,10 +3126,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
mappedColors.forEach((values: integer[], key: integer) => {
const keyColor = rgbaColors.get(key);
const valueColors = values.map(v => rgbaColors.get(v));
const keyColor = rgbaColors.get(key)!;
const valueColors = values.map(v => rgbaColors.get(v)!);
const color = keyColor.slice(0);
let count = paletteColors.get(key);
let count = paletteColors.get(key)!;
for (const value of values) {
const valueCount = paletteColors.get(value);
if (!valueCount) {
@ -3090,10 +3139,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
for (let c = 0; c < 3; c++) {
color[c] *= (paletteColors.get(key) / count);
color[c] *= (paletteColors.get(key)! / count);
values.forEach((value: integer, i: integer) => {
if (paletteColors.has(value)) {
const valueCount = paletteColors.get(value);
const valueCount = paletteColors.get(value)!;
color[c] += valueColors[i][c] * (valueCount / count);
}
});
@ -3111,7 +3160,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
paletteColors.set(argbFromRgba({ r: color[0], g: color[1], b: color[2], a: color[3] }), count);
});
keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a) < paletteColors.get(b) ? 1 : -1);
keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1);
} while (mappedColors.size);
return keys.map(c => Object.values(rgbaFromArgb(c)));
@ -3142,7 +3191,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
[ this.getSprite(), this.getTintSprite() ].map(s => {
[ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => {
s.pipelineData[`spriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = spriteColors;
s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = fusionSpriteColors;
});
@ -3206,7 +3255,7 @@ export default interface Pokemon {
export class PlayerPokemon extends Pokemon {
public compatibleTms: Moves[];
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender: Gender, shiny: boolean, variant: Variant, ivs: integer[], nature: Nature, dataSource: Pokemon | PokemonData) {
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) {
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (Overrides.STATUS_OVERRIDE) {
@ -3310,11 +3359,11 @@ export class PlayerPokemon extends Pokemon {
addFriendship(friendship: integer): void {
const starterSpeciesId = this.species.getRootSpeciesId();
const fusionStarterSpeciesId = this.isFusion() ? this.fusionSpecies.getRootSpeciesId() : 0;
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
const starterData = [
this.scene.gameData.starterData[starterSpeciesId],
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
].filter(d => d);
].filter(d => !!d);
const amount = new Utils.IntegerHolder(friendship);
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1)));
if (amount.value > 0) {
@ -3378,7 +3427,10 @@ export class PlayerPokemon extends Pokemon {
});
}
getPossibleEvolution(evolution: SpeciesFormEvolution): Promise<Pokemon> {
getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise<Pokemon> {
if (!evolution) {
return new Promise(resolve => resolve(this));
}
return new Promise(resolve => {
const evolutionSpecies = getPokemonSpecies(evolution.speciesId);
const isFusion = evolution instanceof FusionSpeciesFormEvolution;
@ -3399,7 +3451,10 @@ export class PlayerPokemon extends Pokemon {
});
}
evolve(evolution: SpeciesFormEvolution, preEvolution: PokemonSpeciesForm): Promise<void> {
evolve(evolution: SpeciesFormEvolution | null, preEvolution: PokemonSpeciesForm): Promise<void> {
if (!evolution) {
return new Promise(resolve => resolve());
}
return new Promise(resolve => {
this.pauseEvolutions = false;
// Handles Nincada evolving into Ninjask + Shedinja
@ -3411,7 +3466,7 @@ export class PlayerPokemon extends Pokemon {
this.fusionSpecies = getPokemonSpecies(evolution.speciesId);
}
if (evolution.preFormKey !== null) {
const formIndex = Math.max((!isFusion ? this.species : this.fusionSpecies).forms.findIndex(f => f.formKey === evolution.evoFormKey), 0);
const formIndex = Math.max((!isFusion || !this.fusionSpecies ? this.species : this.fusionSpecies).forms.findIndex(f => f.formKey === evolution.evoFormKey), 0);
if (!isFusion) {
this.formIndex = formIndex;
} else {
@ -3467,10 +3522,10 @@ export class PlayerPokemon extends Pokemon {
const isFusion = evolution instanceof FusionSpeciesFormEvolution;
const evoSpecies = (!isFusion ? this.species : this.fusionSpecies);
if (evoSpecies.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) {
if (evoSpecies?.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) {
const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1];
if (newEvolution.condition.predicate(this)) {
if (newEvolution.condition?.predicate(this)) {
const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature);
newPokemon.natureOverride = this.natureOverride;
newPokemon.passive = this.passive;
@ -3591,7 +3646,7 @@ export class PlayerPokemon extends Pokemon {
this.scene.removePartyMemberModifiers(fusedPartyMemberIndex);
this.scene.getParty().splice(fusedPartyMemberIndex, 1)[0];
const newPartyMemberIndex = this.scene.getParty().indexOf(this);
pokemon.getMoveset(true).map(m => this.scene.unshiftPhase(new LearnMovePhase(this.scene, newPartyMemberIndex, m.getMove().id)));
pokemon.getMoveset(true).map(m => this.scene.unshiftPhase(new LearnMovePhase(this.scene, newPartyMemberIndex, m!.getMove().id))); // TODO: is the bang correct?
pokemon.destroy();
this.updateFusionPalette();
resolve();
@ -3611,9 +3666,9 @@ export class PlayerPokemon extends Pokemon {
/** Returns a deep copy of this Pokemon's moveset array */
copyMoveset(): PokemonMove[] {
const newMoveset = [];
const newMoveset : PokemonMove[] = [];
this.moveset.forEach(move =>
newMoveset.push(new PokemonMove(move.moveId, 0, move.ppUp, move.virtual)));
newMoveset.push(new PokemonMove(move!.moveId, 0, move!.ppUp, move!.virtual))); // TODO: are those bangs correct?
return newMoveset;
}
@ -3627,9 +3682,9 @@ export class EnemyPokemon extends Pokemon {
/** To indicate of the instance was populated with a dataSource -> e.g. loaded & populated from session data */
public readonly isPopulatedFromDataSource: boolean;
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource: PokemonData) {
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource?: PokemonData) {
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex,
dataSource?.gender, dataSource ? dataSource.shiny : false, dataSource ? dataSource.variant : undefined, null, dataSource ? dataSource.nature : undefined, dataSource);
dataSource?.gender, dataSource ? dataSource.shiny : false, dataSource ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, dataSource);
this.trainerSlot = trainerSlot;
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
@ -3662,7 +3717,7 @@ export class EnemyPokemon extends Pokemon {
let speciesId = species.speciesId;
while ((prevolution = pokemonPrevolutions[speciesId])) {
const evolution = pokemonEvolutions[prevolution].find(pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()));
if (evolution.condition?.enforceFunc) {
if (evolution?.condition?.enforceFunc) {
evolution.condition.enforceFunc(this);
}
speciesId = prevolution;
@ -3738,7 +3793,7 @@ export class EnemyPokemon extends Pokemon {
getNextMove(): QueuedMove {
// If this Pokemon has a move already queued, return it.
const queuedMove = this.getMoveQueue().length
? this.getMoveset().find(m => m.moveId === this.getMoveQueue()[0].move)
? this.getMoveset().find(m => m?.moveId === this.getMoveQueue()[0].move)
: null;
if (queuedMove) {
if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP)) {
@ -3750,24 +3805,24 @@ export class EnemyPokemon extends Pokemon {
}
// Filter out any moves this Pokemon cannot use
const movePool = this.getMoveset().filter(m => m.isUsable(this));
const movePool = this.getMoveset().filter(m => m?.isUsable(this));
// If no moves are left, use Struggle. Otherwise, continue with move selection
if (movePool.length) {
// If there's only 1 move in the move pool, use it.
if (movePool.length === 1) {
return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
return { move: movePool[0]!.moveId, targets: this.getNextTargets(movePool[0]!.moveId) }; // TODO: are the bangs correct?
}
// If a move is forced because of Encore, use it.
const encoreTag = this.getTag(EncoreTag) as EncoreTag;
if (encoreTag) {
const encoreMove = movePool.find(m => m.moveId === encoreTag.moveId);
const encoreMove = movePool.find(m => m?.moveId === encoreTag.moveId);
if (encoreMove) {
return { move: encoreMove.moveId, targets: this.getNextTargets(encoreMove.moveId) };
}
}
switch (this.aiType) {
case AiType.RANDOM: // No enemy should spawn with this AI type in-game
const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)].moveId;
const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct?
return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM:
case AiType.SMART:
@ -3777,9 +3832,9 @@ export class EnemyPokemon extends Pokemon {
* For more information on how benefit scores are calculated, see `docs/enemy-ai.md`.
*/
const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ]));
const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct?
for (const m in movePool) {
const pokemonMove = movePool[m];
const pokemonMove = movePool[m]!; // TODO: is the bang correct?
const move = pokemonMove.getMove();
let moveScore = moveScores[m];
@ -3860,8 +3915,8 @@ export class EnemyPokemon extends Pokemon {
r++;
}
}
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName()));
return { move: sortedMovePool[r].moveId, targets: moveTargets[sortedMovePool[r].moveId] };
console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct?
return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] };
}
}
@ -3924,7 +3979,7 @@ export class EnemyPokemon extends Pokemon {
}
const thresholds: integer[] = [];
let totalWeight: integer;
let totalWeight: integer = 0;
targetWeights.reduce((total: integer, w: integer) => {
total += w;
thresholds.push(total);
@ -3938,7 +3993,7 @@ export class EnemyPokemon extends Pokemon {
* is greater than that random number.
*/
const randValue = this.scene.randBattleSeedInt(totalWeight);
let targetIndex: integer;
let targetIndex: integer = 0;
thresholds.every((t, i) => {
if (randValue >= t) {
@ -4114,7 +4169,7 @@ export class EnemyPokemon extends Pokemon {
addToParty(pokeballType: PokeballType) {
const party = this.scene.getParty();
let ret: PlayerPokemon = null;
let ret: PlayerPokemon | null = null;
if (party.length < 6) {
this.pokeball = pokeballType;
@ -4163,15 +4218,15 @@ export class PokemonSummonData {
public abilitySuppressed: boolean = false;
public abilitiesApplied: Abilities[] = [];
public speciesForm: PokemonSpeciesForm;
public speciesForm: PokemonSpeciesForm | null;
public fusionSpeciesForm: PokemonSpeciesForm;
public ability: Abilities = Abilities.NONE;
public gender: Gender;
public fusionGender: Gender;
public stats: integer[];
public moveset: PokemonMove[];
public moveset: (PokemonMove | null)[];
// If not initialized this value will not be populated from save data.
public types: Type[] = null;
public types: Type[] = [];
}
export class PokemonBattleData {

View File

@ -121,7 +121,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
// Determine the title to include based on the configuration and includeTitle flag.
let title = includeTitle && this.config.title ? this.config.title : null;
const evilTeamTitles = ["grunt", "admin", "sage"];
const evilTeamTitles = ["grunt"];
if (this.name === "" && evilTeamTitles.some(t => name.toLocaleLowerCase().includes(t))) {
// This is a evil team grunt so we localize it by only using the "name" as the title
title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`);
@ -165,6 +165,8 @@ export default class Trainer extends Phaser.GameObjects.Container {
name = i18next.t(`trainerNames:${this.config.nameDouble.toLowerCase().replace(/\s/g, "_")}`);
}
console.log(title ? `${title} ${name}` : name);
// Return the formatted name, including the title if it is set.
return title ? `${title} ${name}` : name;
}
@ -206,7 +208,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
}
getPartyLevels(waveIndex: integer): integer[] {
const ret = [];
const ret: number[] = [];
const partyTemplate = this.getPartyTemplate();
const difficultyWaveIndex = this.scene.gameMode.getWaveForDifficulty(waveIndex);
@ -255,7 +257,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
genPartyMember(index: integer): EnemyPokemon {
const battle = this.scene.currentBattle;
const level = battle.enemyLevels[index];
const level = battle.enemyLevels?.[index]!; // TODO: is this bang correct?
let ret: EnemyPokemon;
@ -288,7 +290,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
}
// Create an empty species pool (which will be set to one of the species pools based on the index)
let newSpeciesPool = [];
let newSpeciesPool: Species[] = [];
let useNewSpeciesPool = false;
// If we are in a double battle of named trainers, we need to use alternate species pools (generate half the party from each trainer)
@ -313,7 +315,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
return !species.some(s => AlreadyUsedSpecies.includes(s));
}
return !AlreadyUsedSpecies.includes(species);
});
}).flat();
// Filter out the species that are already in the enemy party from the partner trainer species pool
const speciesPoolPartnerFiltered = speciesPoolPartner.filter(species => {
@ -322,7 +324,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
return !species.some(s => AlreadyUsedSpecies.includes(s));
}
return !AlreadyUsedSpecies.includes(species);
});
}).flat();
// If the index is even, use the species pool for the main trainer (that way he only uses his own pokemon in battle)
@ -368,7 +370,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
ret = this.scene.addEnemyPokemon(species, level, !this.isDouble() || !(index % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER);
}, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : this.scene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8));
return ret;
return ret!; // TODO: is this bang correct?
}
@ -479,7 +481,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
if (maxScorePartyMemberIndexes.length > 1) {
let rand: integer;
this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2);
return maxScorePartyMemberIndexes[rand];
return maxScorePartyMemberIndexes[rand!];
}
return maxScorePartyMemberIndexes[0];
@ -497,6 +499,9 @@ export default class Trainer extends Phaser.GameObjects.Container {
return 0.45;
case PartyMemberStrength.STRONGER:
return 0.375;
default:
console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0");
return 0;
}
}

View File

@ -11,6 +11,7 @@ import { BattleSpec } from "#enums/battle-spec";
import { BattlePhase, MovePhase, PokemonHealPhase } from "./phases";
import { getTypeRgb } from "./data/type";
import { getPokemonNameWithAffix } from "./messages";
import { SemiInvulnerableTag } from "./data/battler-tags";
export class FormChangePhase extends EvolutionPhase {
private formChange: SpeciesFormChange;
@ -194,7 +195,7 @@ export class QuietFormChangePhase extends BattlePhase {
const preName = getPokemonNameWithAffix(this.pokemon);
if (!this.pokemon.isOnField()) {
if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) {
this.pokemon.changeForm(this.formChange).then(() => {
this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500);
});

Some files were not shown because too many files have changed in this diff Show More