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 - type: textarea
id: session-file id: session-file
attributes: 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/)). 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) placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
validations: validations:
@ -29,7 +29,7 @@ body:
- type: textarea - type: textarea
id: data-file id: data-file
attributes: 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/)). 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) placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
validations: validations:

View File

@ -2,10 +2,10 @@
<!-- Make sure that this PR is not overlapping with someone else's work --> <!-- Make sure that this PR is not overlapping with someone else's work -->
<!-- Please try to keep the PR self-contained (and small) --> <!-- 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 --> <!-- 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 --> <!-- Explain why you decided to introduce these changes -->
<!-- Does it come from an issue or another PR? Please link it --> <!-- Does it come from an issue or another PR? Please link it -->
<!-- Explain why you believe this can enhance user experience --> <!-- 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 - kyledove
- Brumirage - Brumirage
- pkmn_realidea (Paid Commissions) - pkmn_realidea (Paid Commissions)
- IceJkai
### 🎨 Trainer Portraits ### 🎨 Trainer Portraits
- pkmn_realidea (Paid Commissions) - pkmn_realidea (Paid Commissions)

310
index.css
View File

@ -1,16 +1,8 @@
/* Global */
:root { :root {
--color-base: hsl(0, 0%, 55%); --color-base: hsl(0, 0%, 55%);
--color-light: hsl(0, 0%, 90%); --color-light: hsl(0, 0%, 90%);
--color-dark: hsl(0, 0%, 10%); --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 { html {
@ -43,33 +35,181 @@ body {
transform-origin: top !important; 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 */ /* Need adjust input font-size */
input { input {
font-size: 3.2rem; 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) { #touchControls:not(.visible) {
display: none; display: none;
} }
#dpad, #apad { #touchControls .active {
opacity: var(--touch-control-opacity);
}
.control-group {
position: fixed; 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; 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) { #configToolbar .button:active {
#dpad, #apad { opacity: var(--touch-control-opacity)
bottom: calc(1rem + env(safe-area-inset-bottom));
}
} }
#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 { #dpad {
left: 1rem; z-index: 3;
} opacity: 0.8;
#apad {
right: 1rem;
} }
#dpad svg { #dpad svg {
@ -78,114 +218,84 @@ input {
fill: var(--color-base); fill: var(--color-base);
} }
#dpad svg rect { /* apad buttons */
opacity: 0.6;
}
#apad > * { .apad-button {
width: var(--controls-size);
height: var(--controls-size);
}
#apad .apadBtn {
width: var(--controls-size);
height: var(--controls-size);
background-color: var(--color-base); background-color: var(--color-base);
border-radius: 50%; 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 { .apad-small {
font-family: 'emerald'; width: var(--small-control-size);
font-size: var(--controls-size); height: var(--small-control-size);
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size); }
color: var(--color-light);
.apad-label {
user-select: none; user-select: none;
height: 100%;
margin-right: -2px;
} }
#apad .apadLabelSmall { .apad-small > .apad-label {
font-size: calc(var(--controls-size) / 3); font-size: var(--small-control-size);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3); text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
} }
#apad #apadLabelAction, #apad #apadLabelCancel { .apad-rectangle {
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;
text-align: center; text-align: center;
padding-right: 10%; width: var(--rect-control-size);
border-radius: 10%; height: var(--small-control-size);
bottom: calc(var(--controls-size) * 0.05);
width: calc(var(--controls-size) * 0.6);
height: calc(var(--controls-size) * 0.3);
} }
#apad .apadSqBtn { .apad-square {
border-radius: 10%; width: var(--small-control-size);
width: calc(var(--controls-size) * 0.3); height: var(--small-control-size);
height: calc(var(--controls-size) * 0.3);
} }
#apad .apadBtnContainer { .apad-circle {
position: relative; width: var(--controls-size);
display: flex; height: var(--controls-size);
border-radius: 50%;
} }
#apad .apadRectBtnContainer { /* Defaults:*/
flex-wrap: wrap;
margin-top: calc(var(--controls-size) * -0.8); #control-group-dpad {
left: calc(var(--controls-size) * 0.175); left: var(--controls-padding);
height: calc(var(--controls-size) * 0.8); bottom: var(--controls-padding);
} }
#apad .apadSqBtnContainer { #control-group-action {
flex-wrap: wrap; right: var(--controls-padding);
justify-content: space-evenly; bottom: var(--controls-size-with-padding);
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);
} }
#apad .apadRectBtnContainer > #apadMenu { #control-group-cancel {
align-self: flex-end; right: var(--controls-size-with-wide-padding);
bottom: var(--controls-padding);
} }
#apad .apadRectBtnContainer > .apadSqBtn:not(:first-child) { #control-group-extra-1 {
margin-left: 10%; 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), #control-group-extra-2 {
#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) width: var(--control-group-extra-wide-size);
{ right: var(--control-group-extra-2-offset);
display: none; 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 { /* Layout */
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:fullscreen #dpad, #layout:fullscreen #apad { #layout:fullscreen #dpad, #layout:fullscreen #apad {
bottom: 6rem; bottom: 6rem;

View File

@ -64,54 +64,84 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<div id="touchControls"> <div id="touchControls">
<div id="dpad" class="unselectable"> <div class="left">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"> <div id="control-group-dpad" class="control-group control-group-dpad">
<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" /> <div id="dpad" data-control-key="DPAD">
<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" /> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
<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="dpadUp" data-key="UP"
<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" /> d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
<rect id="dpadCenter" x="24" y="24" width="24" height="24" /> <path id="dpadRight" data-key="RIGHT"
</svg> 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>
<div id="apad" class="unselectable"> <div class="right">
<div id="apadAction" class="apadCircBtn apadBtn" data-key="ACTION"> <div id="control-group-action" class="control-group">
<text id="apadLabelAction" class="apadLabel">A</text> <div id="apadAction" class="apad-button apad-circle" data-key="ACTION">
</div> <span class="apad-label">A</span>
<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> </div>
</div> </div>
<div class="apadBtnContainer apadSqBtnContainer">
<div id="apadCycleForm" class="apadSqBtn apadBtn" data-key="CYCLE_FORM"> <div id="control-group-cancel" class="control-group">
<text class="apadLabel apadLabelSmall">F</text> <div id="apadCancel" class="apad-button apad-circle" data-key="CANCEL">
</div> <span class="apad-label">B</span>
<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> </div>
</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> </div>
<div id="tnc-links"> <div id="tnc-links">

View File

@ -13,6 +13,7 @@
"test:cov": "vitest run --project pre && vitest run --project main --coverage", "test:cov": "vitest run --project pre && vitest run --project main --coverage",
"test:watch": "vitest run --project pre && vitest watch --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", "test:silent": "vitest run --project pre && vitest run --project main --silent",
"typecheck": "tsc --noEmit",
"eslint": "eslint --fix .", "eslint": "eslint --fix .",
"eslint-ci": "eslint .", "eslint-ci": "eslint .",
"docs": "typedoc", "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": [ "textures": [
{ {
"image": "aqua_admin_m.png", "image": "archer.png",
"format": "RGBA8888", "format": "RGBA8888",
"size": { "size": {
"w": 80, "w": 80,

View File

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 645 B

View File

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

View File

Before

Width:  |  Height:  |  Size: 736 B

After

Width:  |  Height:  |  Size: 736 B

View File

@ -1,7 +1,7 @@
{ {
"textures": [ "textures": [
{ {
"image": "magma_admin_f.png", "image": "bryony.png",
"format": "RGBA8888", "format": "RGBA8888",
"size": { "size": {
"w": 80, "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": [ "textures": [
{ {
"image": "flare_admin_f.png", "image": "jupiter.png",
"format": "RGBA8888", "format": "RGBA8888",
"size": { "size": {
"w": 80, "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, "w": 12,
"h": 6 "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, "w": 9,
"h": 8 "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, "w": 13,
"h": 7 "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, "w": 9,
"h": 8 "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; 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 // 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); export const clientSessionId = Utils.randomString(32);
@ -30,11 +30,13 @@ export function updateUserInfo(): Promise<[boolean, integer]> {
loggedInUser.lastSessionSlot = lastSessionSlot; loggedInUser.lastSessionSlot = lastSessionSlot;
// Migrate old data from before the username was appended // Migrate old data from before the username was appended
[ "data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4" ].map(d => { [ "data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4" ].map(d => {
if (localStorage.hasOwnProperty(d)) { const lsItem = localStorage.getItem(d);
if (localStorage.hasOwnProperty(`${d}_${loggedInUser.username}`)) { if (lsItem && !!loggedInUser?.username) {
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, localStorage.getItem(`${d}_${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); localStorage.removeItem(d);
} }
}); });

View File

@ -55,7 +55,6 @@ import {UiInputs} from "./ui-inputs";
import { NewArenaEvent } from "./events/battle-scene"; import { NewArenaEvent } from "./events/battle-scene";
import { ArenaFlyout } from "./ui/arena-flyout"; import { ArenaFlyout } from "./ui/arena-flyout";
import { EaseType } from "#enums/ease-type"; import { EaseType } from "#enums/ease-type";
import { Abilities } from "#enums/abilities";
import { BattleSpec } from "#enums/battle-spec"; import { BattleSpec } from "#enums/battle-spec";
import { BattleStyle } from "#enums/battle-style"; import { BattleStyle } from "#enums/battle-style";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
@ -106,8 +105,8 @@ export default class BattleScene extends SceneBase {
public inputController: InputsController; public inputController: InputsController;
public uiInputs: UiInputs; public uiInputs: UiInputs;
public sessionPlayTime: integer = null; public sessionPlayTime: integer | null = null;
public lastSavePlayTime: integer = null; public lastSavePlayTime: integer | null = null;
public masterVolume: number = 0.5; public masterVolume: number = 0.5;
public bgmVolume: number = 1; public bgmVolume: number = 1;
public seVolume: 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 enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1";
public enableMoveInfo: boolean = true; public enableMoveInfo: boolean = true;
public enableRetries: boolean = false; public enableRetries: boolean = false;
public hideIvs: boolean = false;
/** /**
* Determines the condition for a notification should be shown for Candy Upgrades * Determines the condition for a notification should be shown for Candy Upgrades
* - 0 = 'Off' * - 0 = 'Off'
@ -192,8 +192,8 @@ export default class BattleScene extends SceneBase {
private phaseQueuePrependSpliceIndex: integer; private phaseQueuePrependSpliceIndex: integer;
private nextCommandPhaseQueue: Phase[]; private nextCommandPhaseQueue: Phase[];
private currentPhase: Phase; private currentPhase: Phase | null;
private standbyPhase: Phase; private standbyPhase: Phase | null;
public field: Phaser.GameObjects.Container; public field: Phaser.GameObjects.Container;
public fieldUI: Phaser.GameObjects.Container; public fieldUI: Phaser.GameObjects.Container;
public charSprite: CharSprite; public charSprite: CharSprite;
@ -213,7 +213,7 @@ export default class BattleScene extends SceneBase {
public score: integer; public score: integer;
public lockModifierTiers: boolean; public lockModifierTiers: boolean;
public trainer: Phaser.GameObjects.Sprite; public trainer: Phaser.GameObjects.Sprite;
public lastEnemyTrainer: Trainer; public lastEnemyTrainer: Trainer | null;
public currentBattle: Battle; public currentBattle: Battle;
public pokeballCounts: PokeballCounts; public pokeballCounts: PokeballCounts;
public money: integer; public money: integer;
@ -251,7 +251,7 @@ export default class BattleScene extends SceneBase {
public spritePipeline: SpritePipeline; public spritePipeline: SpritePipeline;
private bgm: AnySound; private bgm: AnySound;
private bgmResumeTimer: Phaser.Time.TimerEvent; private bgmResumeTimer: Phaser.Time.TimerEvent | null;
private bgmCache: Set<string> = new Set(); private bgmCache: Set<string> = new Set();
private playTimeTimer: Phaser.Time.TimerEvent; private playTimeTimer: Phaser.Time.TimerEvent;
@ -700,7 +700,11 @@ export default class BattleScene extends SceneBase {
hasExpSprite(key: string): boolean { hasExpSprite(key: string): boolean {
const keyMatch = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/g.exec(key); 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]) { if (keyMatch[2]) {
k += "s"; k += "s";
} }
@ -723,7 +727,7 @@ export default class BattleScene extends SceneBase {
return this.party; return this.party;
} }
getPlayerPokemon(): PlayerPokemon { getPlayerPokemon(): PlayerPokemon | undefined {
return this.getPlayerField().find(p => p.isActive()); return this.getPlayerField().find(p => p.isActive());
} }
@ -740,7 +744,7 @@ export default class BattleScene extends SceneBase {
return this.currentBattle?.enemyParty || []; return this.currentBattle?.enemyParty || [];
} }
getEnemyPokemon(): EnemyPokemon { getEnemyPokemon(): EnemyPokemon | undefined {
return this.getEnemyField().find(p => p.isActive()); return this.getEnemyField().find(p => p.isActive());
} }
@ -764,6 +768,27 @@ export default class BattleScene extends SceneBase {
: ret; : 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 the ModifierBar of this scene, which is declared private and therefore not accessible elsewhere
* @returns {ModifierBar} * @returns {ModifierBar}
@ -782,12 +807,12 @@ export default class BattleScene extends SceneBase {
return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles; 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); 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); const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (postProcess) { if (postProcess) {
postProcess(pokemon); postProcess(pokemon);
@ -957,7 +982,8 @@ export default class BattleScene extends SceneBase {
p.destroy(); 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.setText(startingWave.toString());
this.biomeWaveText.setVisible(false); 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 _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1); const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
let newDouble: boolean; let newDouble: boolean | undefined;
let newBattleType: BattleType; let newBattleType: BattleType;
let newTrainer: Trainer; let newTrainer: Trainer | undefined;
let battleConfig: FixedBattleConfig = null; let battleConfig: FixedBattleConfig | null = null;
this.resetSeed(newWaveIndex); this.resetSeed(newWaveIndex);
@ -1038,7 +1064,7 @@ export default class BattleScene extends SceneBase {
battleConfig = this.gameMode.getFixedBattle(newWaveIndex); battleConfig = this.gameMode.getFixedBattle(newWaveIndex);
newDouble = battleConfig.double; newDouble = battleConfig.double;
newBattleType = battleConfig.battleType; 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) { if (newTrainer) {
this.field.add(newTrainer); this.field.add(newTrainer);
} }
@ -1078,7 +1104,7 @@ export default class BattleScene extends SceneBase {
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance)); playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance));
newDouble = !Utils.randSeedInt(doubleChance.value); newDouble = !Utils.randSeedInt(doubleChance.value);
} else if (newBattleType === BattleType.TRAINER) { } else if (newBattleType === BattleType.TRAINER) {
newDouble = newTrainer.variant === TrainerVariant.DOUBLE; newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
} }
} else if (!battleConfig) { } else if (!battleConfig) {
newDouble = !!double; 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)); //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) { if (!waveIndex && lastBattle) {
let isNewBiome = !(lastBattle.waveIndex % 10) || ((this.gameMode.hasShortBiomes || this.gameMode.isDaily) && (lastBattle.waveIndex % 50) === 49); const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10);
if (!isNewBiome && this.gameMode.hasShortBiomes && (lastBattle.waveIndex % 10) < 9) { const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily;
let w = lastBattle.waveIndex - ((lastBattle.waveIndex % 10) - 1); const isEndlessFifthWave = this.gameMode.hasShortBiomes && (lastBattle.waveIndex % 5) === 0;
let biomeWaves = 1; const isWaveIndexMultipleOfFiftyMinusOne = (lastBattle.waveIndex % 50) === 49;
while (w < lastBattle.waveIndex) { const isNewBiome = isWaveIndexMultipleOfTen || isEndlessFifthWave || (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne);
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 resetArenaState = isNewBiome || this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS; const resetArenaState = isNewBiome || this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS;
this.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy()); this.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy());
this.trySpreadPokerus(); this.trySpreadPokerus();
@ -1142,12 +1152,6 @@ export default class BattleScene extends SceneBase {
playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p))); playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p)));
for (const pokemon of this.getParty()) { 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(); pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
} }
@ -1309,7 +1313,7 @@ export default class BattleScene extends SceneBase {
return 5; return 5;
} }
let isBoss: boolean; let isBoss: boolean | undefined;
if (forceBoss || (species && (species.subLegendary || species.legendary || species.mythical))) { if (forceBoss || (species && (species.subLegendary || species.legendary || species.mythical))) {
isBoss = true; isBoss = true;
} else { } else {
@ -1608,7 +1612,7 @@ export default class BattleScene extends SceneBase {
randomSpecies(waveIndex: integer, level: integer, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies { randomSpecies(waveIndex: integer, level: integer, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies {
if (fromArenaPool) { 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 => { const filteredSpecies = speciesFilter ? [...new Set(allSpecies.filter(s => s.isCatchable()).filter(speciesFilter).map(s => {
if (!filterAllEvolutions) { if (!filterAllEvolutions) {
@ -1966,11 +1970,11 @@ export default class BattleScene extends SceneBase {
} }
/* Phase Functions */ /* Phase Functions */
getCurrentPhase(): Phase { getCurrentPhase(): Phase | null {
return this.currentPhase; return this.currentPhase;
} }
getStandbyPhase(): Phase { getStandbyPhase(): Phase | null {
return this.standbyPhase; return this.standbyPhase;
} }
@ -2048,7 +2052,10 @@ export default class BattleScene extends SceneBase {
} }
if (this.phaseQueuePrepend.length) { if (this.phaseQueuePrepend.length) {
while (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) { 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 // Clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = []; this.conditionalQueue = [];
} }
this.currentPhase = this.phaseQueue.shift();
this.currentPhase = this.phaseQueue.shift() ?? null;
// Check if there are any conditional phases queued // Check if there are any conditional phases queued
if (this.conditionalQueue?.length) { if (this.conditionalQueue?.length) {
// Retrieve the first conditional phase from the queue // Retrieve the first conditional phase from the queue
const conditionalPhase = this.conditionalQueue.shift(); const conditionalPhase = this.conditionalQueue.shift();
// Evaluate the condition associated with the phase // 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 // If the condition is met, add the phase to the phase queue
this.pushPhase(conditionalPhase[1]); 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 // If the condition is not met, re-add the phase back to the front of the conditional queue
this.conditionalQueue.unshift(conditionalPhase); this.conditionalQueue.unshift(conditionalPhase);
} else {
console.warn("condition phase is undefined/null!", conditionalPhase);
} }
} }
this.currentPhase.start(); this.currentPhase?.start();
} }
overridePhase(phase: Phase): boolean { overridePhase(phase: Phase): boolean {
@ -2087,7 +2097,7 @@ export default class BattleScene extends SceneBase {
return true; return true;
} }
findPhase(phaseFilter: (phase: Phase) => boolean): Phase { findPhase(phaseFilter: (phase: Phase) => boolean): Phase | undefined {
return this.phaseQueue.find(phaseFilter); return this.phaseQueue.find(phaseFilter);
} }
@ -2146,7 +2156,7 @@ export default class BattleScene extends SceneBase {
* @param promptDelay optional param for MessagePhase constructor * @param promptDelay optional param for MessagePhase constructor
* @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue * @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); const phase = new MessagePhase(this, message, callbackDelay, prompt, promptDelay);
if (!defer) { if (!defer) {
// adds to the end of PhaseQueuePrepend // adds to the end of PhaseQueuePrepend
@ -2182,7 +2192,10 @@ export default class BattleScene extends SceneBase {
return Math.floor(moneyValue / 10) * 10; 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 => { return new Promise(resolve => {
let success = false; let success = false;
const soundName = modifier.type.soundName; const soundName = modifier.type.soundName;
@ -2202,7 +2215,7 @@ export default class BattleScene extends SceneBase {
} }
} else if (!virtual) { } else if (!virtual) {
const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); 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)); 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 => { return new Promise(resolve => {
const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null; const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null;
const cancelled = new Utils.BooleanHolder(false); 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) { if (cancelled.value) {
return resolve(false); return resolve(false);
} }
@ -2383,7 +2396,7 @@ export default class BattleScene extends SceneBase {
} }
party.forEach((enemyPokemon: EnemyPokemon, i: integer) => { 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; let upgradeChance = 32;
if (isBoss) { if (isBoss) {
upgradeChance /= 2; upgradeChance /= 2;
@ -2391,9 +2404,9 @@ export default class BattleScene extends SceneBase {
if (isFinalBoss) { if (isFinalBoss) {
upgradeChance /= 8; upgradeChance /= 8;
} }
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss); const modifierChance = this.gameMode.getEnemyModifierChance(isBoss!); // TODO: is this bang correct?
let pokemonModifierChance = modifierChance; 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 pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line
let count = 0; let count = 0;
for (let c = 0; c < chances; c++) { 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)); 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)); return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m));
} }
@ -2548,7 +2561,7 @@ export default class BattleScene extends SceneBase {
return appliedModifiers; 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)); const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args));
for (const modifier of modifiers) { for (const modifier of modifiers) {
if (modifier.apply(args)) { if (modifier.apply(args)) {
@ -2635,7 +2648,7 @@ export default class BattleScene extends SceneBase {
initFinalBossPhaseTwo(pokemon: Pokemon): void { initFinalBossPhaseTwo(pokemon: Pokemon): void {
if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
this.fadeOutBgm(Utils.fixedInt(2000), false); 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); this.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true);
pokemon.generateAndPopulateMoveset(1); pokemon.generateAndPopulateMoveset(1);
this.setFieldScale(0.75); this.setFieldScale(0.75);

View File

@ -39,7 +39,7 @@ export interface TurnCommand {
} }
interface TurnCommands { interface TurnCommands {
[key: integer]: TurnCommand [key: integer]: TurnCommand | null
} }
export default class Battle { export default class Battle {
@ -47,8 +47,8 @@ export default class Battle {
public waveIndex: integer; public waveIndex: integer;
public battleType: BattleType; public battleType: BattleType;
public battleSpec: BattleSpec; public battleSpec: BattleSpec;
public trainer: Trainer; public trainer: Trainer | null;
public enemyLevels: integer[]; public enemyLevels: integer[] | undefined;
public enemyParty: EnemyPokemon[]; public enemyParty: EnemyPokemon[];
public seenEnemyPartyMemberIds: Set<integer>; public seenEnemyPartyMemberIds: Set<integer>;
public double: boolean; public double: boolean;
@ -62,26 +62,26 @@ export default class Battle {
public escapeAttempts: integer; public escapeAttempts: integer;
public lastMove: Moves; public lastMove: Moves;
public battleSeed: string; public battleSeed: string;
private battleSeedState: string; private battleSeedState: string | null;
public moneyScattered: number; 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 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 public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
private rngCounter: integer = 0; 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.gameMode = gameMode;
this.waveIndex = waveIndex; this.waveIndex = waveIndex;
this.battleType = battleType; this.battleType = battleType;
this.trainer = trainer; this.trainer = trainer!; //TODO: is this bang correct?
this.initBattleSpec(); this.initBattleSpec();
this.enemyLevels = battleType !== BattleType.TRAINER this.enemyLevels = battleType !== BattleType.TRAINER
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave()) ? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
: trainer.getPartyLevels(this.waveIndex); : trainer?.getPartyLevels(this.waveIndex);
this.enemyParty = []; this.enemyParty = [];
this.seenEnemyPartyMemberIds = new Set<integer>(); this.seenEnemyPartyMemberIds = new Set<integer>();
this.double = double; this.double = double!; //TODO: is this bang correct?
this.enemySwitchCounter = 0; this.enemySwitchCounter = 0;
this.turn = 0; this.turn = 0;
this.playerParticipantIds = new Set<integer>(); this.playerParticipantIds = new Set<integer>();
@ -159,6 +159,7 @@ export default class Battle {
addPostBattleLoot(enemyPokemon: EnemyPokemon): void { addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferrable, false).map(i => { this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferrable, false).map(i => {
const ret = i as PokemonHeldItemModifier; const ret = i as PokemonHeldItemModifier;
//@ts-ignore - this is awful to fix/change
ret.pokemonId = null; ret.pokemonId = null;
return ret; return ret;
})); }));
@ -177,7 +178,7 @@ export default class Battle {
const userLocale = navigator.language || "en-US"; const userLocale = navigator.language || "en-US";
const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale);
const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount }); const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount });
scene.queueMessage(message, null, true); scene.queueMessage(message, undefined, true);
scene.currentBattle.moneyScattered = 0; scene.currentBattle.moneyScattered = 0;
} }
@ -200,16 +201,16 @@ export default class Battle {
scene.updateScoreText(); scene.updateScoreText();
} }
getBgmOverride(scene: BattleScene): string { getBgmOverride(scene: BattleScene): string | null {
const battlers = this.enemyParty.slice(0, this.getBattlerCount()); const battlers = this.enemyParty.slice(0, this.getBattlerCount());
if (this.battleType === BattleType.TRAINER) { if (this.battleType === BattleType.TRAINER) {
if (!this.started && this.trainer.config.encounterBgm && this.trainer.getEncounterMessages()?.length) { if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer.getEncounterBgm()}`; return `encounter_${this.trainer?.getEncounterBgm()}`;
} }
if (scene.musicPreference === 0) { if (scene.musicPreference === 0) {
return this.trainer.getBattleBgm(); return this.trainer?.getBattleBgm()!; // TODO: is this bang correct?
} else { } 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) { } else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
return "end_summit"; return "end_summit";
@ -382,7 +383,7 @@ export default class Battle {
export class FixedBattle extends Battle { export class FixedBattle extends Battle {
constructor(scene: BattleScene, waveIndex: integer, config: FixedBattleConfig) { 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) { if (config.getEnemyParty) {
this.enemyParty = config.getEnemyParty(scene); 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 * 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 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 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 * @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) => { return (scene: BattleScene) => {
const rand = Utils.randSeedInt(trainerPool.length); const rand = Utils.randSeedInt(trainerPool.length);
const trainerTypes: TrainerType[] = []; const trainerTypes: TrainerType[] = [];
for (const trainerPoolEntry of trainerPool) {
const trainerType = Array.isArray(trainerPoolEntry) scene.executeWithSeedOffset(() => {
? Utils.randSeedItem(trainerPoolEntry) for (const trainerPoolEntry of trainerPool) {
: trainerPoolEntry; const trainerType = Array.isArray(trainerPoolEntry)
trainerTypes.push(trainerType); ? Utils.randSeedItem(trainerPoolEntry)
} : trainerPoolEntry;
trainerTypes.push(trainerType);
}
}, seedOffset);
let trainerGender = TrainerVariant.DEFAULT; let trainerGender = TrainerVariant.DEFAULT;
if (randomGender) { if (randomGender) {
trainerGender = (Utils.randInt(2) === 0) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT; 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) [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)), .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) [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) [95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), .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) [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)), .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) [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) [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 ])), .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE ])),
[145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) [145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)

View File

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

View File

@ -6,7 +6,7 @@ import { BattleStat, getBattleStatName } from "./battle-stat";
import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import { Weather, WeatherType } from "./weather"; 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 { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender"; 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"; 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 AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>;
type AbAttrCondition = (pokemon: Pokemon) => 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 PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
type PokemonStatChangeCondition = (target: Pokemon, statsChanged: BattleStat[], levels: integer) => boolean; type PokemonStatChangeCondition = (target: Pokemon, statsChanged: BattleStat[], levels: integer) => boolean;
@ -132,11 +132,11 @@ export abstract class AbAttr {
this.showAbility = showAbility; 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; return false;
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
return null; return null;
} }
@ -226,7 +226,7 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
} }
for (const statChangePhase of statChangePhases) { for (const statChangePhase of statChangePhases) {
if (!this.selfTarget && !statChangePhase.getPokemon().summonData) { if (!this.selfTarget && !statChangePhase.getPokemon()?.summonData) {
pokemon.scene.pushPhase(statChangePhase); pokemon.scene.pushPhase(statChangePhase);
} else { // TODO: This causes the ability bar to be shown at the wrong time } else { // TODO: This causes the ability bar to be shown at the wrong time
pokemon.scene.unshiftPhase(statChangePhase); pokemon.scene.unshiftPhase(statChangePhase);
@ -240,30 +240,11 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean; type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean;
export class PreDefendAbAttr extends AbAttr { 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; 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 { export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (pokemon.isFullHp() && 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. * Determines whether a Pokemon is immune to a move because of an ability.
* @extends PreDefendAbAttr * @extends PreDefendAbAttr
@ -352,14 +318,14 @@ export class PreDefendMoveDamageToOneAbAttr extends ReceivedMoveDamageMultiplier
* @see {@linkcode getCondition} * @see {@linkcode getCondition}
*/ */
export class TypeImmunityAbAttr extends PreDefendAbAttr { export class TypeImmunityAbAttr extends PreDefendAbAttr {
private immuneType: Type; private immuneType: Type | null;
private condition: AbAttrCondition; private condition: AbAttrCondition | null;
constructor(immuneType: Type, condition?: AbAttrCondition) { constructor(immuneType: Type | null, condition?: AbAttrCondition) {
super(); super();
this.immuneType = immuneType; 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)) { if ([ MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE ].includes(move.moveTarget)) {
return false; return false;
} }
if (attacker !== pokemon && move.type === this.immuneType) { if (attacker !== pokemon && move.type === this.immuneType) {
(args[0] as Utils.NumberHolder).value = 0; (args[0] as Utils.NumberHolder).value = 0;
return true; return true;
} }
return false; return false;
} }
getCondition(): AbAttrCondition { override getCondition(): AbAttrCondition | null {
return this.condition; 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 { export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
constructor(immuneType: Type) { constructor(immuneType: Type) {
super(immuneType); super(immuneType);
@ -491,47 +473,51 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
} }
export class PostDefendAbAttr extends AbAttr { 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; return false;
} }
} }
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr { /**
* Applies the effects of Gulp Missile when the user is hit by an attack.
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { * @extends PostDefendAbAttr
if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.category === MoveCategory.SPECIAL || move.category === MoveCategory.PHYSICAL)) { */
export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr {
const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt); constructor() {
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)) {
super(true); super(true);
this.formFunc = formFunc;
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { /**
const formIndex = this.formFunc(pokemon); * Damages the attacker and triggers the secondary effect based on the form or the BattlerTagType.
if (formIndex !== pokemon.formIndex) { * @param {Pokemon} pokemon - The defending Pokemon.
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); * @param passive - n/a
return true; * @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;
} }
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;
} }
} }
@ -838,7 +824,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr
export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
private chance: integer; private chance: integer;
private tagType: BattlerTagType; private tagType: BattlerTagType;
private turnCount: integer; private turnCount: integer | undefined;
constructor(chance: integer, tagType: BattlerTagType, turnCount?: integer) { constructor(chance: integer, tagType: BattlerTagType, turnCount?: integer) {
super(); super();
@ -875,7 +861,7 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr {
} }
getCondition(): AbAttrCondition { 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 { 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; return false;
} }
} }
@ -1079,7 +1065,7 @@ export class PreAttackAbAttr extends AbAttr {
export class MoveEffectChanceMultiplierAbAttr extends AbAttr { export class MoveEffectChanceMultiplierAbAttr extends AbAttr {
private chanceMultiplier: number; private chanceMultiplier: number;
constructor(chanceMultiplier?: number) { constructor(chanceMultiplier: number) {
super(true); super(true);
this.chanceMultiplier = chanceMultiplier; this.chanceMultiplier = chanceMultiplier;
} }
@ -1460,7 +1446,7 @@ export class FieldMovePowerBoostAbAttr extends AbAttr {
this.powerMultiplier = powerMultiplier; 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)) { if (this.condition(pokemon, defender, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
@ -1514,14 +1500,14 @@ export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr
export class BattleStatMultiplierAbAttr extends AbAttr { export class BattleStatMultiplierAbAttr extends AbAttr {
private battleStat: BattleStat; private battleStat: BattleStat;
private multiplier: number; private multiplier: number;
private condition: PokemonAttackCondition; private condition: PokemonAttackCondition | null;
constructor(battleStat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) { constructor(battleStat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) {
super(false); super(false);
this.battleStat = battleStat; this.battleStat = battleStat;
this.multiplier = multiplier; 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> { 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} * 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. * 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. // 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 attackRequired is false, we always defer to the secondary requirements.
if (this.attackCondition(pokemon, defender, move)) { 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. * 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; return false;
} }
} }
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
private stealCondition: PokemonAttackCondition; private stealCondition: PokemonAttackCondition | null;
constructor(stealCondition?: PokemonAttackCondition) { constructor(stealCondition?: PokemonAttackCondition) {
super(); 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> { 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 { export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
private condition: PokemonDefendCondition; private condition: PokemonDefendCondition | null;
constructor(condition?: PokemonDefendCondition) { constructor(condition?: PokemonDefendCondition) {
super(); 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> { 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 ( if (
target.getAbility().hasAttr(UncopiableAbilityAbAttr) && target!.getAbility().hasAttr(UncopiableAbilityAbAttr) &&
// Wonder Guard is normally uncopiable so has the attribute, but Trace specifically can copy it // 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; return false;
} }
this.target = target; this.target = target!;
this.targetAbilityName = allAbilities[target.getAbility().id].name; this.targetAbilityName = allAbilities[target!.getAbility().id].name;
pokemon.summonData.ability = target.getAbility().id; pokemon.summonData.ability = target!.getAbility().id;
setAbilityRevealed(target); setAbilityRevealed(target!);
pokemon.updateInfo(); pokemon.updateInfo();
return true; return true;
@ -2211,7 +2197,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt
} }
for (const pokemon of allowedParty) { 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.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
pokemon.resetStatus(false); pokemon.resetStatus(false);
pokemon.updateInfo(); pokemon.updateInfo();
@ -2267,6 +2253,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
target = targets[0]; target = targets[0];
} }
target = target!; // compiler doesn't know its guranteed to be defined
pokemon.summonData.speciesForm = target.getSpeciesForm(); pokemon.summonData.speciesForm = target.getSpeciesForm();
pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm(); pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm();
pokemon.summonData.ability = target.getAbility().id; pokemon.summonData.ability = target.getAbility().id;
@ -2274,7 +2261,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
pokemon.summonData.fusionGender = target.getFusionGender(); pokemon.summonData.fusionGender = target.getFusionGender();
pokemon.summonData.stats = [ pokemon.stats[Stat.HP] ].concat(target.stats.slice(1)); pokemon.summonData.stats = [ pokemon.stats[Stat.HP] ].concat(target.stats.slice(1));
pokemon.summonData.battleStats = target.summonData.battleStats.slice(0); 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.summonData.types = target.getTypes();
pokemon.scene.playSound("PRSFX- Transform"); pokemon.scene.playSound("PRSFX- Transform");
@ -2321,7 +2308,7 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
* @returns {boolean} Returns true if the weather clears, otherwise false. * @returns {boolean} Returns true if the weather clears, otherwise false.
*/ */
applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> { 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; let turnOffWeather = false;
// Clear weather only if user's ability matches the weather and no other pokemon has the ability. // 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 { 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; return false;
} }
} }
export class ProtectStatAbAttr extends PreStatChangeAbAttr { export class ProtectStatAbAttr extends PreStatChangeAbAttr {
private protectedStat: BattleStat; private protectedStat: BattleStat | null;
constructor(protectedStat?: BattleStat) { constructor(protectedStat?: BattleStat) {
super(); 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 { 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", { return i18next.t("abilityTriggers:protectStat", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName, 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 { 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; return false;
} }
} }
@ -2680,7 +2667,7 @@ export class BlockStatusDamageAbAttr extends AbAttr {
* @returns Returns true if status damage is blocked * @returns Returns true if status damage is blocked
*/ */
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { 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; cancelled.value = true;
return true; return true;
} }
@ -2719,7 +2706,7 @@ export class IncrementMovePriorityAbAttr extends AbAttr {
export class IgnoreContactAbAttr extends AbAttr { } export class IgnoreContactAbAttr extends AbAttr { }
export class PreWeatherEffectAbAttr 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; return false;
} }
} }
@ -2750,7 +2737,7 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr {
constructor(affectsImmutable?: boolean) { constructor(affectsImmutable?: boolean) {
super(); 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 { applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -2801,7 +2788,7 @@ function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
return false; return false;
} }
const weatherType = pokemon.scene.arena.weather?.weatherType; 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 opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) { for (const move of opponent.moveset) {
// move is super effective // 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; return true;
} }
// move is a OHKO // move is a OHKO
if (move.getMove().hasAttr(OneHitKOAttr)) { if (move!.getMove().hasAttr(OneHitKOAttr)) { // TODO: is this bang correct?
return true; return true;
} }
// edge case for hidden power, type is computed // 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) const iv_val = Math.floor(((opponent.ivs[Stat.HP] & 1)
+(opponent.ivs[Stat.ATK] & 1) * 2 +(opponent.ivs[Stat.ATK] & 1) * 2
+(opponent.ivs[Stat.DEF] & 1) * 4 +(opponent.ivs[Stat.DEF] & 1) * 4
@ -2866,21 +2853,21 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
let movePower = 0; let movePower = 0;
for (const opponent of pokemon.getOpponents()) { for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) { for (const move of opponent.moveset) {
if (move.getMove() instanceof StatusMove) { if (move!.getMove() instanceof StatusMove) { // TODO: is this bang correct?
movePower = 1; movePower = 1;
} else if (move.getMove().hasAttr(OneHitKOAttr)) { } else if (move!.getMove().hasAttr(OneHitKOAttr)) { // TODO: is this bang correct?
movePower = 150; 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; movePower = 120;
} else if (move.getMove().power === -1) { } else if (move!.getMove().power === -1) { // TODO: is this bang correct?
movePower = 80; movePower = 80;
} else { } else {
movePower = move.getMove().power; movePower = move!.getMove().power; // TODO: is this bang correct?
} }
if (movePower > maxPowerSeen) { if (movePower > maxPowerSeen) {
maxPowerSeen = movePower; 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; 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; return false;
} }
@ -3024,7 +3011,7 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr
function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition {
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const terrainType = pokemon.scene.arena.terrain?.terrainType; 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 * @returns Returns true if healed from status, false if not
*/ */
applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> { 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()) { if (!pokemon.isFullHp()) {
const scene = pokemon.scene; const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; 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 { applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
const lastUsed = pokemon.scene.currentBattle.lastUsedPokeball; const lastUsed = pokemon.scene.currentBattle.lastUsedPokeball;
if (lastUsed !== null && pokemon.isPlayer) { if (lastUsed !== null && !!pokemon.isPlayer) {
pokemon.scene.pokeballCounts[lastUsed]++; pokemon.scene.pokeballCounts[lastUsed]++;
pokemon.scene.currentBattle.lastUsedPokeball = null; pokemon.scene.currentBattle.lastUsedPokeball = null;
pokemon.scene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed) })); 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; const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot;
if (postBattleLoot.length) { if (postBattleLoot.length) {
const randItem = Utils.randSeedItem(postBattleLoot); 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); postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name })); pokemon.scene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name }));
return true; return true;
@ -3605,7 +3593,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
* @returns {boolean} Returns true if the weather clears, otherwise false. * @returns {boolean} Returns true if the weather clears, otherwise false.
*/ */
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { 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; let turnOffWeather = false;
// Clear weather only if user's ability matches the weather and no other pokemon has the ability. // 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. * Takes no damage from the first hit of a damaging move.
* This is used in Ice Face ability. * 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 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); super(condition, multiplier);
this.multiplier = multiplier; this.multiplier = multiplier;
this.tagType = tagType;
this.recoilDamageFunc = recoilDamageFunc;
this.triggerMessageFunc = triggerMessageFunc;
} }
/** /**
* Applies the Ice Face pre-defense ability to the Pokémon. * Applies the pre-defense ability to the Pokémon.
* Removes BattlerTagType.ICE_FACE when hit by physical attack and is in Ice Face form. * 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 {Pokemon} pokemon The Pokémon with the ability.
* @param {boolean} passive - Whether the ability is passive. * @param {boolean} passive n/a
* @param {Pokemon} attacker - The attacking Pokémon. * @param {Pokemon} attacker The attacking Pokémon.
* @param {PokemonMove} move - The move being used. * @param {PokemonMove} move The move being used.
* @param {Utils.BooleanHolder} cancelled - A holder for whether the move was cancelled. * @param {Utils.BooleanHolder} cancelled n/a
* @param {any[]} args - Additional arguments. * @param {any[]} args Additional arguments.
* @returns {boolean} - Whether the immunity was applied. * @returns {boolean} Whether the immunity was applied.
*/ */
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) { if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = this.multiplier; (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; 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. * Gets the message triggered when the Pokémon avoids damage using the form-changing ability.
* @param {Pokemon} pokemon - The Pokémon with the Ice Face ability. * @param {Pokemon} pokemon The Pokémon with the ability.
* @param {string} abilityName - The name of the ability. * @param {string} abilityName The name of the ability.
* @param {...any} args - Additional arguments. * @param {...any} args n/a
* @returns {string} - The trigger message. * @returns {string} The trigger message.
*/ */
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { 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 = const turnCommand =
pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
const isCommandFight = turnCommand?.command === Command.FIGHT; 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; const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
if (isCommandFight && isDamageMove) { if (isCommandFight && isDamageMove) {
@ -4096,14 +4094,14 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
async function applyAbAttrsInternal<TAttr extends AbAttr>( async function applyAbAttrsInternal<TAttr extends AbAttr>(
attrType: Constructor<TAttr>, attrType: Constructor<TAttr>,
pokemon: Pokemon, pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<TAttr>, applyFunc: AbAttrApplyFunc<TAttr>,
args: any[], args: any[],
showAbilityInstant: boolean = false, showAbilityInstant: boolean = false,
quiet: boolean = false, quiet: boolean = false,
) { ) {
for (const passive of [false, true]) { for (const passive of [false, true]) {
if (!pokemon.canApplyAbility(passive)) { if (!pokemon?.canApplyAbility(passive)) {
continue; 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); 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>, 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]; 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); return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, simulated);
} }
export function applyPostDefendAbAttrs(attrType: Constructor<PostDefendAbAttr>, 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); 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>, 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); return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args);
} }
export function applyPostAttackAbAttrs(attrType: Constructor<PostAttackAbAttr>, 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); 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>, 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); 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>, 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]; const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, effect, cancelled, args), args, false, !simulated); 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>, 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); 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>, 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); 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) .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(), .ignorable(),
new Ability(Abilities.FLASH_FIRE, 3) 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(), .ignorable(),
new Ability(Abilities.SHIELD_DUST, 3) new Ability(Abilities.SHIELD_DUST, 3)
.attr(IgnoreMoveEffectsAbAttr) .attr(IgnoreMoveEffectsAbAttr)
@ -4393,7 +4391,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.LEVITATE, 3) 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(), .ignorable(),
new Ability(Abilities.EFFECT_SPORE, 3) new Ability(Abilities.EFFECT_SPORE, 3)
.attr(EffectSporeAbAttr), .attr(EffectSporeAbAttr),
@ -4552,8 +4550,8 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1) .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1)
.ignorable(), .ignorable(),
new Ability(Abilities.RIVALRY, 4) 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, 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, 0.75),
new Ability(Abilities.STEADFAST, 4) new Ability(Abilities.STEADFAST, 4)
.attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1), .attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1),
new Ability(Abilities.SNOW_CLOAK, 4) new Ability(Abilities.SNOW_CLOAK, 4)
@ -4643,7 +4641,8 @@ export function initAbilities() {
.attr(IgnoreOpponentStatChangesAbAttr) .attr(IgnoreOpponentStatChangesAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.TINTED_LENS, 4) 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) new Ability(Abilities.FILTER, 4)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75) .attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
.ignorable(), .ignorable(),
@ -4725,9 +4724,9 @@ export function initAbilities() {
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.isFullHp(), 0.5) .attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.isFullHp(), 0.5)
.ignorable(), .ignorable(),
new Ability(Abilities.TOXIC_BOOST, 5) 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) 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) new Ability(Abilities.HARVEST, 5)
.attr( .attr(
PostTurnLootAbAttr, PostTurnLootAbAttr,
@ -4760,7 +4759,8 @@ export function initAbilities() {
.attr(WonderSkinAbAttr) .attr(WonderSkinAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.ANALYTIC, 5) 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) new Ability(Abilities.ILLUSION, 5)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
@ -4910,7 +4910,7 @@ export function initAbilities() {
new Ability(Abilities.WATER_COMPACTION, 7) new Ability(Abilities.WATER_COMPACTION, 7)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2), .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2),
new Ability(Abilities.MERCILESS, 7) 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) new Ability(Abilities.SHIELDS_DOWN, 7)
.attr(PostBattleInitFormChangeAbAttr, () => 0) .attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) .attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
@ -4922,7 +4922,8 @@ export function initAbilities() {
.bypassFaint() .bypassFaint()
.partial(), .partial(),
new Ability(Abilities.STAKEOUT, 7) 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) new Ability(Abilities.WATER_BUBBLE, 7)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2) .attr(MoveTypePowerBoostAbAttr, Type.WATER, 2)
@ -4956,20 +4957,18 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.bypassFaint(), .bypassFaint(),
new Ability(Abilities.DISGUISE, 7) 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(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.bypassFaint() // Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
.ignorable() .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
.partial(), .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) new Ability(Abilities.BATTLE_BOND, 7)
.attr(PostVictoryFormChangeAbAttr, () => 2) .attr(PostVictoryFormChangeAbAttr, () => 2)
.attr(PostBattleInitFormChangeAbAttr, () => 1) .attr(PostBattleInitFormChangeAbAttr, () => 1)
@ -5062,7 +5061,8 @@ export function initAbilities() {
new Ability(Abilities.PRISM_ARMOR, 7) new Ability(Abilities.PRISM_ARMOR, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75), .attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75),
new Ability(Abilities.NEUROFORCE, 7) 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) new Ability(Abilities.INTREPID_SWORD, 8)
.attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true) .attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true)
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)), .condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
@ -5087,7 +5087,9 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.unimplemented(), .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(PostDefendGulpMissileAbAttr),
new Ability(Abilities.STALWART, 8) new Ability(Abilities.STALWART, 8)
.attr(BlockRedirectAbAttr), .attr(BlockRedirectAbAttr),
new Ability(Abilities.STEAM_ENGINE, 8) new Ability(Abilities.STEAM_ENGINE, 8)
@ -5115,7 +5117,9 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0) .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 // 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(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(), .ignorable(),
new Ability(Abilities.POWER_SPOT, 8) new Ability(Abilities.POWER_SPOT, 8)
.attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL], 1.3), .attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL], 1.3),
@ -5145,8 +5149,10 @@ export function initAbilities() {
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(), .ignorable(),
new Ability(Abilities.HUNGER_SWITCH, 8) new Ability(Abilities.HUNGER_SWITCH, 8)
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) //@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0) .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(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)

View File

@ -25,12 +25,12 @@ export enum ArenaTagSide {
export abstract class ArenaTag { export abstract class ArenaTag {
public tagType: ArenaTagType; public tagType: ArenaTagType;
public turnCount: integer; public turnCount: integer;
public sourceMove: Moves; public sourceMove?: Moves;
public sourceId: integer; public sourceId?: integer;
public side: ArenaTagSide; 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.tagType = tagType;
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove; this.sourceMove = sourceMove;
@ -56,7 +56,7 @@ export abstract class ArenaTag {
return this.turnCount < 1 || !!(--this.turnCount); return this.turnCount < 1 || !!(--this.turnCount);
} }
getMoveName(): string { getMoveName(): string | null {
return this.sourceMove return this.sourceMove
? allMoves[this.sourceMove].name ? allMoves[this.sourceMove].name
: null; : null;
@ -75,9 +75,14 @@ export class MistTag extends ArenaTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); if (this.sourceId) {
if (!quiet) { const source = arena.scene.getPokemonById(this.sourceId);
arena.scene.queueMessage(i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
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) { onAdd(arena: Arena) {
const source = arena.scene.getPokemonById(this.sourceId); if (this.sourceId) {
arena.scene.queueMessage(i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); 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}. * 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. * 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 { onAdd(arena: Arena): void {
const user = arena.scene.getPokemonById(this.sourceId); if (this.sourceId) {
this.battlerIndex = user.getBattlerIndex(); const user = arena.scene.getPokemonById(this.sourceId);
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) }); if (user) {
this.healHp = Math.max(Math.floor(user.getMaxHp() / 2), 1); 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 { onRemove(arena: Arena): void {
@ -461,8 +511,8 @@ class SpikesTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet) { if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); 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 { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet) { if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); 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 { class DelayedAttackTag extends ArenaTag {
public targetIndex: BattlerIndex; 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); super(tagType, 3, sourceMove, sourceId);
this.targetIndex = targetIndex; this.targetIndex = targetIndex;
@ -566,7 +616,7 @@ class DelayedAttackTag extends ArenaTag {
const ret = super.lapse(arena); const ret = super.lapse(arena);
if (!ret) { 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; return ret;
@ -588,8 +638,8 @@ class StealthRockTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet) { if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() })); arena.scene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() }));
} }
} }
@ -597,7 +647,7 @@ class StealthRockTag extends ArenaTrapTag {
getDamageHpRatio(pokemon: Pokemon): number { getDamageHpRatio(pokemon: Pokemon): number {
const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK, undefined, true); const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK, undefined, true);
let damageHpRatio: number; let damageHpRatio: number = 0;
switch (effectiveness) { switch (effectiveness) {
case 0: case 0:
@ -663,8 +713,8 @@ class StickyWebTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet) { if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); 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 { 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 { 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" : ""}`)); 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 source = arena.scene.getPokemonById(this.sourceId!); //TODO: this bang is questionable!
const party = source.isPlayer() ? source.scene.getPlayerField() : source.scene.getEnemyField(); const party = (source?.isPlayer() ? source.scene.getPlayerField() : source?.scene.getEnemyField()) ?? [];
for (const pokemon of party) { for (const pokemon of party) {
// Apply the CHARGED tag to party members with the WIND_POWER ability // 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) { switch (tagType) {
case ArenaTagType.MIST: case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side); return new MistTag(turnCount, sourceId, side);
@ -803,6 +856,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new MatBlockTag(sourceId, side); return new MatBlockTag(sourceId, side);
case ArenaTagType.CRAFTY_SHIELD: case ArenaTagType.CRAFTY_SHIELD:
return new CraftyShieldTag(sourceId, side); 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: case ArenaTagType.MUD_SPORT:
return new MudSportTag(turnCount, sourceId); return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT: case ArenaTagType.WATER_SPORT:
@ -813,7 +868,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new ToxicSpikesTag(sourceId, side); return new ToxicSpikesTag(sourceId, side);
case ArenaTagType.FUTURE_SIGHT: case ArenaTagType.FUTURE_SIGHT:
case ArenaTagType.DOOM_DESIRE: case ArenaTagType.DOOM_DESIRE:
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex); return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang
case ArenaTagType.WISH: case ArenaTagType.WISH:
return new WishTag(turnCount, sourceId, side); return new WishTag(turnCount, sourceId, side);
case ArenaTagType.STEALTH_ROCK: case ArenaTagType.STEALTH_ROCK:
@ -834,5 +889,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new TailwindTag(turnCount, sourceId, side); return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR: case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side); return new HappyHourTag(turnCount, sourceId, side);
default:
return null;
} }
} }

View File

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

View File

@ -8,7 +8,8 @@ export enum BattleStat {
SPD, SPD,
ACC, ACC,
EVA, EVA,
RAND RAND,
HP
} }
export function getBattleStatName(stat: BattleStat) { export function getBattleStatName(stat: BattleStat) {
@ -27,6 +28,8 @@ export function getBattleStatName(stat: BattleStat) {
return i18next.t("pokemonInfo:Stat.ACC"); return i18next.t("pokemonInfo:Stat.ACC");
case BattleStat.EVA: case BattleStat.EVA:
return i18next.t("pokemonInfo:Stat.EVA"); return i18next.t("pokemonInfo:Stat.EVA");
case BattleStat.HP:
return i18next.t("pokemonInfo:Stat.HPStat");
default: default:
return "???"; 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 { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangeCallback, StatChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
@ -36,11 +36,11 @@ export class BattlerTag {
public sourceMove: Moves; public sourceMove: Moves;
public sourceId?: number; 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.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ]; this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove; this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId; this.sourceId = sourceId;
} }
@ -66,7 +66,7 @@ export class BattlerTag {
return false; return false;
} }
getMoveName(): string { getMoveName(): string | null {
return this.sourceMove return this.sourceMove
? allMoves[this.sourceMove].name ? allMoves[this.sourceMove].name
: null; : null;
@ -110,7 +110,7 @@ export class RechargingTag extends BattlerTag {
/** Cancels the source's move this turn and queues a "__ must recharge!" message */ /** Cancels the source's move this turn and queues a "__ must recharge!" message */
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) { 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.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.getMoveQueue().shift(); 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 { export class TrappedTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) { constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
super(tagType, lapseType, turnCount, sourceMove, sourceId); super(tagType, lapseType, turnCount, sourceMove, sourceId);
@ -139,7 +177,7 @@ export class TrappedTag extends BattlerTag {
onRemove(pokemon: Pokemon): void { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsTrappedOnRemove", { pokemon.scene.queueMessage(i18next.t("battlerTags:trappedOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName() moveName: this.getMoveName()
})); }));
@ -154,7 +192,7 @@ export class TrappedTag extends BattlerTag {
} }
getTrapMessage(pokemon: Pokemon): string { 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) { if (lapseType === BattlerTagLapseType.PRE_MOVE) {
(pokemon.scene.getCurrentPhase() as MovePhase).cancel(); (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); return super.lapse(pokemon, lapseType);
@ -234,26 +272,26 @@ export class ConfusedTag extends BattlerTag {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); 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 { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); 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 { onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM && super.lapse(pokemon, lapseType); const ret = lapseType !== BattlerTagLapseType.CUSTOM && super.lapse(pokemon, lapseType);
if (ret) { 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)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
// 1/3 chance of hitting self with a 40 base power move // 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 atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF); 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)); 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.damageAndUpdate(damage);
pokemon.battleData.hitCount++; pokemon.battleData.hitCount++;
(pokemon.scene.getCurrentPhase() as MovePhase).cancel(); (pokemon.scene.getCurrentPhase() as MovePhase).cancel();
@ -299,22 +337,22 @@ export class DestinyBondTag extends BattlerTag {
if (lapseType !== BattlerTagLapseType.CUSTOM) { if (lapseType !== BattlerTagLapseType.CUSTOM) {
return super.lapse(pokemon, lapseType); return super.lapse(pokemon, lapseType);
} }
const source = pokemon.scene.getPokemonById(this.sourceId); const source = this.sourceId ? pokemon.scene.getPokemonById(this.sourceId) : null;
if (!source.isFainted()) { if (!source?.isFainted()) {
return true; return true;
} }
if (source.getAlly() === pokemon) { if (source?.getAlly() === pokemon) {
return false; return false;
} }
if (pokemon.isBossImmune()) { 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; return false;
} }
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsDestinyBondLapse", { i18next.t("battlerTags:destinyBondLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
pokemonNameWithAffix2: getPokemonNameWithAffix(pokemon) pokemonNameWithAffix2: getPokemonNameWithAffix(pokemon)
}) })
@ -330,16 +368,28 @@ export class InfatuatedTag extends BattlerTag {
} }
canAdd(pokemon: Pokemon): boolean { 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 { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedOnAdd", { i18next.t("battlerTags:infatuatedOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), 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 { onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -355,15 +405,15 @@ export class InfatuatedTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedLapse", { i18next.t("battlerTags:infatuatedLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), 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)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT));
if (pokemon.randSeedInt(2)) { 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(); (pokemon.scene.getCurrentPhase() as MovePhase).cancel();
} }
} }
@ -374,7 +424,7 @@ export class InfatuatedTag extends BattlerTag {
onRemove(pokemon: Pokemon): void { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); 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 { isSourceLinked(): boolean {
@ -409,8 +459,8 @@ export class SeedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSeededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("battlerTags:seededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -429,7 +479,7 @@ export class SeedTag extends BattlerTag {
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(),
!reverseDrain ? damage : damage * -1, !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)); false, true));
} }
} }
@ -451,20 +501,20 @@ export class NightmareTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) { 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 pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
@ -552,15 +602,15 @@ export class EncoreTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onRemove(pokemon); 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); const movePhase = pokemon.scene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon);
if (movePhase) { 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) { if (movesetMove) {
const lastMove = pokemon.getLastXMoves(1)[0]; const lastMove = pokemon.getLastXMoves(1)[0];
pokemon.scene.tryReplacePhase((m => m instanceof MovePhase && m.pokemon === pokemon), 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 { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); 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 { onAdd(pokemon: Pokemon): void {
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsHelpingHandOnAdd", { i18next.t("battlerTags:helpingHandOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
pokemonName: getPokemonNameWithAffix(pokemon) pokemonName: getPokemonNameWithAffix(pokemon)
}) })
); );
@ -616,7 +666,7 @@ export class IngrainTag extends TrappedTag {
pokemon.scene, pokemon.scene,
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16), Math.floor(pokemon.getMaxHp() / 16),
i18next.t("battle:battlerTagsIngrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
true true
) )
); );
@ -626,7 +676,7 @@ export class IngrainTag extends TrappedTag {
} }
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsIngrainOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); return i18next.t("battlerTags:ingrainOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
} }
getDescriptor(): string { getDescriptor(): string {
@ -667,7 +717,7 @@ export class AquaRingTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -679,7 +729,7 @@ export class AquaRingTag extends BattlerTag {
pokemon.scene, pokemon.scene,
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16), Math.floor(pokemon.getMaxHp() / 16),
i18next.t("battle:battlerTagsAquaRingLapse", { i18next.t("battlerTags:aquaRingLapse", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
pokemonName: getPokemonNameWithAffix(pokemon) pokemonName: getPokemonNameWithAffix(pokemon)
}), }),
@ -729,7 +779,7 @@ export class DrowsyTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -773,7 +823,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsDamagingTrapLapse", { i18next.t("battlerTags:damagingTrapLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName() moveName: this.getMoveName()
}) })
@ -798,9 +848,9 @@ export class BindTag extends DamagingTrapTag {
} }
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsBindOnTrap", { return i18next.t("battlerTags:bindOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), 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() moveName: this.getMoveName()
}); });
} }
@ -812,9 +862,9 @@ export class WrapTag extends DamagingTrapTag {
} }
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsWrapOnTrap", { return i18next.t("battlerTags:wrapOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), 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 { 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 { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsClampOnTrap", { return i18next.t("battlerTags:clampOnTrap", {
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)), sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
}); });
} }
@ -860,7 +910,7 @@ export class SandTombTag extends DamagingTrapTag {
} }
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsSandTombOnTrap", { return i18next.t("battlerTags:sandTombOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName() moveName: this.getMoveName()
}); });
@ -873,7 +923,7 @@ export class MagmaStormTag extends DamagingTrapTag {
} }
getTrapMessage(pokemon: Pokemon): string { 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 { 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 { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsThunderCageOnTrap", { return i18next.t("battlerTags:thunderCageOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), 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 { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsInfestationOnTrap", { return i18next.t("battlerTags:infestationOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), 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 { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) { if (lapseType === BattlerTagLapseType.CUSTOM) {
new CommonBattleAnim(CommonAnim.PROTECT, pokemon).play(pokemon.scene); 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 // Stop multi-hit moves early
const effectPhase = pokemon.scene.getCurrentPhase(); const effectPhase = pokemon.scene.getCurrentPhase();
@ -1061,12 +1111,12 @@ export class EnduringTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) { 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; return true;
} }
@ -1081,7 +1131,7 @@ export class SturdyTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) { 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; return true;
} }
@ -1103,7 +1153,7 @@ export class PerishSongTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsPerishSongLapse", { i18next.t("battlerTags:perishSongLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
turnCount: this.turnCount turnCount: this.turnCount
}) })
@ -1139,7 +1189,7 @@ export class CenterOfAttentionTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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) { if (lastMove && lastMove.move !== Moves.NONE) {
(pokemon.scene.getCurrentPhase() as MovePhase).cancel(); (pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, passive)); 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; return true;
@ -1193,7 +1243,7 @@ export class SlowStartTag extends AbilityBattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1207,7 +1257,7 @@ export class SlowStartTag extends AbilityBattlerTag {
onRemove(pokemon: Pokemon): void { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); 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; return highestValue;
}, 0); }, 0);
highestStat = highestStat!; // tell TS compiler it's defined!
this.stat = highestStat; this.stat = highestStat;
switch (this.stat) { switch (this.stat) {
@ -1253,13 +1304,13 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
break; 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 { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); 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 { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); 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 { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1403,7 +1454,7 @@ export class CritBoostTag extends BattlerTag {
onRemove(pokemon: Pokemon): void { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); 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 { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSaltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("battlerTags:saltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(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 { 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.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1));
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsSaltCuredLapse", { i18next.t("battlerTags:saltCuredLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName() moveName: this.getMoveName()
}) })
@ -1474,7 +1525,7 @@ export class CursedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); 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 { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1488,7 +1539,7 @@ export class CursedTag extends BattlerTag {
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 4), 1)); 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 {
} }
} }
/** /** Common attributes of form change abilities that block damage */
* Provides the Ice Face ability's effects. export class FormBlockDamageTag extends BattlerTag {
*/ constructor(tagType: BattlerTagType) {
export class IceFaceTag extends BattlerTag { super(tagType, BattlerTagLapseType.CUSTOM, 1);
constructor(sourceMove: Moves) {
super(BattlerTagType.ICE_FACE, BattlerTagLapseType.CUSTOM, 1, sourceMove);
} }
/** /**
* Determines if the Ice Face tag can be added to the Pokémon. * Determines if the tag can be added to the Pokémon.
* @param {Pokemon} pokemon - The Pokémon to which the tag might be added. * @param {Pokemon} pokemon The Pokémon to which the tag might be added.
* @returns {boolean} - True if the tag can be added, false otherwise. * @returns {boolean} True if the tag can be added, false otherwise.
*/ */
canAdd(pokemon: Pokemon): boolean { canAdd(pokemon: Pokemon): boolean {
const weatherType = pokemon.scene.arena.weather?.weatherType; return pokemon.formIndex === 0;
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;
} }
/** /**
* Applies the Ice Face tag to the Pokémon. * Applies the tag to the Pokémon.
* Triggers a form change to Ice Face if the Pokémon is not in its Ice Face form. * 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. * @param {Pokemon} pokemon The Pokémon to which the tag is added.
*/ */
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
@ -1547,9 +1587,9 @@ export class IceFaceTag extends BattlerTag {
} }
/** /**
* Removes the Ice Face tag from the Pokémon. * Removes the tag from the Pokémon.
* Triggers a form change to Noice when the tag is removed. * Triggers a form change when the tag is removed.
* @param {Pokemon} pokemon - The Pokémon from which the tag is removed. * @param {Pokemon} pokemon The Pokémon from which the tag is removed.
*/ */
onRemove(pokemon: Pokemon): void { onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon); 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: * Battler tag enabling the Stockpile mechanic. This tag handles:
@ -1612,7 +1670,7 @@ export class StockpilingTag extends BattlerTag {
if (this.stockpiledCount < 3) { if (this.stockpiledCount < 3) {
this.stockpiledCount++; this.stockpiledCount++;
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsStockpilingOnAdd", { pokemon.scene.queueMessage(i18next.t("battlerTags:stockpilingOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
stockpiledCount: this.stockpiledCount 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 { export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) { switch (tagType) {
case BattlerTagType.RECHARGING: case BattlerTagType.RECHARGING:
return new RechargingTag(sourceMove); return new RechargingTag(sourceMove);
case BattlerTagType.BEAK_BLAST_CHARGING:
return new BeakBlastChargingTag();
case BattlerTagType.FLINCHED: case BattlerTagType.FLINCHED:
return new FlinchedTag(sourceMove); return new FlinchedTag(sourceMove);
case BattlerTagType.INTERRUPTED: case BattlerTagType.INTERRUPTED:
@ -1741,8 +1875,6 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.ALWAYS_CRIT: case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY: case BattlerTagType.IGNORE_ACCURACY:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); 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.ALWAYS_GET_HIT:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); 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: case BattlerTagType.DESTINY_BOND:
return new DestinyBondTag(sourceMove, sourceId); return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.ICE_FACE: case BattlerTagType.ICE_FACE:
return new IceFaceTag(sourceMove); return new IceFaceBlockDamageTag(tagType);
case BattlerTagType.DISGUISE:
return new FormBlockDamageTag(tagType);
case BattlerTagType.STOCKPILING: case BattlerTagType.STOCKPILING:
return new StockpilingTag(sourceMove); return new StockpilingTag(sourceMove);
case BattlerTagType.OCTOLOCK: case BattlerTagType.OCTOLOCK:
return new OctolockTag(sourceId); 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: case BattlerTagType.NONE:
default: default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -54,7 +54,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25); const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); 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) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); 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) { if (ppRestoreMove !== undefined) {
ppRestoreMove.ppUsed = Math.max(ppRestoreMove.ppUsed - 10, 0); ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove.getName(), berryName: getBerryName(berryType) })); 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] ? 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); 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 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 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 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. * @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; return false;
} }
@ -405,14 +403,13 @@ export class SingleGenerationChallenge extends Challenge {
super(Challenges.SINGLE_GENERATION, 9); 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 generations = [pokemon.generation];
const checkPokemonEvolutions = checkEvolutions ?? true as boolean;
if (soft) { if (soft) {
const speciesToCheck = [pokemon.speciesId]; const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) { while (speciesToCheck.length) {
const checking = speciesToCheck.pop(); const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) { if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => { pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId); speciesToCheck.push(e.speciesId);
generations.push(getPokemonSpecies(e.speciesId).generation); generations.push(getPokemonSpecies(e.speciesId).generation);
@ -430,7 +427,7 @@ export class SingleGenerationChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
const baseGeneration = pokemon.species.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.species.speciesId).generation; 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))) { if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) {
valid.value = false; valid.value = false;
return true; return true;
@ -533,22 +530,20 @@ export class SingleTypeChallenge extends Challenge {
super(Challenges.SINGLE_TYPE, 18); 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 speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex);
const types = [speciesForm.type1, speciesForm.type2]; const types = [speciesForm.type1, speciesForm.type2];
const checkPokemonEvolutions = checkEvolutions ?? true as boolean;
const checkPokemonForms = checkForms ?? true as boolean;
if (soft) { if (soft) {
const speciesToCheck = [pokemon.speciesId]; const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) { while (speciesToCheck.length) {
const checking = speciesToCheck.pop(); const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) { if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => { pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId); speciesToCheck.push(e.speciesId);
types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2); types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2);
}); });
} }
if (pokemonFormChanges.hasOwnProperty(checking) && checkPokemonForms) { if (checking && pokemonFormChanges.hasOwnProperty(checking)) {
pokemonFormChanges[checking].forEach(f1 => { pokemonFormChanges[checking].forEach(f1 => {
getPokemonSpecies(checking).forms.forEach(f2 => { getPokemonSpecies(checking).forms.forEach(f2 => {
if (f1.formKey === f2.formKey) { if (f1.formKey === f2.formKey) {
@ -568,10 +563,11 @@ export class SingleTypeChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true) 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; valid.value = false;
return true; return true;
} }
return false;
} }
/** /**
@ -634,8 +630,8 @@ export class FreshStartChallenge extends Challenge {
} }
applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean { applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean {
if (defaultStarterSpecies.includes(species) && cost.value !== 3) { if (defaultStarterSpecies.includes(species)) {
cost.value = 3; cost.value = speciesStarters[species];
return true; return true;
} }
return false; 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. * @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns True if any challenge was successfully applied. * @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. * Apply all challenges that modify available total starter points.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -853,7 +849,7 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
if (c.value !== 0) { if (c.value !== 0) {
switch (challengeType) { switch (challengeType) {
case ChallengeType.STARTER_CHOICE: 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; break;
case ChallengeType.STARTER_POINTS: case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]); ret ||= c.applyStarterPoints(args[0]);

View File

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

View File

@ -452,144 +452,304 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
[TrainerType.ROCKET_GRUNT]: [ [TrainerType.ROCKET_GRUNT]: [
{ {
encounter: [ 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: [ 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: [ encounter: [
"dialogue:rocket_admin.encounter.1", "dialogue:archer.encounter.1",
"dialogue:rocket_admin.encounter.2", "dialogue:archer.encounter.2",
"dialogue:rocket_admin.encounter.3", "dialogue:archer.encounter.3",
], ],
victory: [ victory: [
"dialogue:rocket_admin.victory.1", "dialogue:archer.victory.1",
"dialogue:rocket_admin.victory.2", "dialogue:archer.victory.2",
"dialogue:rocket_admin.victory.3", "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]: [ [TrainerType.MAGMA_GRUNT]: [
{ {
encounter: [ 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: [ 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: [ encounter: [
"dialogue:magma_admin.encounter.1", "dialogue:tabitha.encounter.1",
"dialogue:magma_admin.encounter.2", "dialogue:tabitha.encounter.2",
"dialogue:magma_admin.encounter.3", "dialogue:tabitha.encounter.3",
], ],
victory: [ victory: [
"dialogue:magma_admin.victory.1", "dialogue:tabitha.victory.1",
"dialogue:magma_admin.victory.2", "dialogue:tabitha.victory.2",
"dialogue:magma_admin.victory.3", "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]: [ [TrainerType.AQUA_GRUNT]: [
{ {
encounter: [ 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: [ 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: [ encounter: [
"dialogue:aqua_admin.encounter.1", "dialogue:matt.encounter.1",
"dialogue:aqua_admin.encounter.2", "dialogue:matt.encounter.2",
"dialogue:aqua_admin.encounter.3", "dialogue:matt.encounter.3",
], ],
victory: [ victory: [
"dialogue:aqua_admin.victory.1", "dialogue:matt.victory.1",
"dialogue:aqua_admin.victory.2", "dialogue:matt.victory.2",
"dialogue:aqua_admin.victory.3", "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]: [ [TrainerType.GALACTIC_GRUNT]: [
{ {
encounter: [ 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: [ 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: [ encounter: [
"dialogue:galactic_admin.encounter.1", "dialogue:jupiter.encounter.1",
"dialogue:galactic_admin.encounter.2", "dialogue:jupiter.encounter.2",
"dialogue:galactic_admin.encounter.3", "dialogue:jupiter.encounter.3",
], ],
victory: [ victory: [
"dialogue:galactic_admin.victory.1", "dialogue:jupiter.victory.1",
"dialogue:galactic_admin.victory.2", "dialogue:jupiter.victory.2",
"dialogue:galactic_admin.victory.3", "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]: [ [TrainerType.PLASMA_GRUNT]: [
{ {
encounter: [ 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: [ 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: [ encounter: [
"dialogue:plasma_sage.encounter.1", "dialogue:zinzolin.encounter.1",
"dialogue:plasma_sage.encounter.2", "dialogue:zinzolin.encounter.2",
"dialogue:plasma_sage.encounter.3", "dialogue:zinzolin.encounter.3",
], ],
victory: [ victory: [
"dialogue:plasma_sage.victory.1", "dialogue:zinzolin.victory.1",
"dialogue:plasma_sage.victory.2", "dialogue:zinzolin.victory.2",
"dialogue:plasma_sage.victory.3", "dialogue:zinzolin.victory.3",
] ]
} }
], ],
[TrainerType.FLARE_GRUNT]: [ [TrainerType.FLARE_GRUNT]: [
{ {
encounter: [ 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: [ 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: [ encounter: [
"dialogue:flare_admin.encounter.1", "dialogue:bryony.encounter.1",
"dialogue:flare_admin.encounter.2", "dialogue:bryony.encounter.2",
"dialogue:flare_admin.encounter.3", "dialogue:bryony.encounter.3",
], ],
victory: [ victory: [
"dialogue:flare_admin.victory.1", "dialogue:bryony.victory.1",
"dialogue:flare_admin.victory.2", "dialogue:bryony.victory.2",
"dialogue:flare_admin.victory.3", "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) { 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.") //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 // 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 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 // 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._sourceType = eggOptions?.sourceType ?? undefined;
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves(); this._hatchWaves = eggOptions?.hatchWaves ?? this.getEggTierDefaultHatchWaves();
this._timestamp = eggOptions.timestamp ?? new Date().getTime(); this._timestamp = eggOptions?.timestamp ?? new Date().getTime();
// First roll shiny and variant so we can filter if species with an variant exist // 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._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
this._variantTier = eggOptions.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant()); this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
this._species = eggOptions.species ?? this.rollSpecies(eggOptions.scene); 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 // Override egg tier and hatchwaves if species was given
if (eggOptions.species) { if (eggOptions?.species) {
this._tier = this.getEggTierFromSpeciesStarterValue(); this._tier = this.getEggTierFromSpeciesStarterValue();
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves(); this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
} }
@ -174,10 +174,10 @@ export class Egg {
this._variantTier = VariantTier.COMMON; this._variantTier = VariantTier.COMMON;
} }
// Needs this._tier so it needs to be generated afer the tier override if bought from same species // Needs this._tier so it needs to be generated afer the tier override if bought from same species
this._eggMoveIndex = eggOptions.eggMoveIndex ?? this.rollEggMoveIndex(); this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
if (eggOptions.pulled) { if (eggOptions?.pulled) {
this.increasePullStatistic(eggOptions.scene); this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
this.addEggToGameData(eggOptions.scene); this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
} }
} }
@ -202,14 +202,18 @@ export class Egg {
// Legacy egg wants to hatch. Generate missing properties // Legacy egg wants to hatch. Generate missing properties
if (!this._species) { if (!this._species) {
this._isShiny = this.rollShiny(); 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 // Sets the hidden ability if a hidden ability exists and the override is set
// or if the same species egg hits the chance // or if the same species egg hits the chance
let abilityIndex = undefined; let abilityIndex: number | undefined = undefined;
if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility
|| (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE)))) { || (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE)))) {
abilityIndex = 2; abilityIndex = 2;
@ -273,6 +277,9 @@ export class Egg {
return i18next.t("egg:gachaTypeShiny"); return i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE: case EggSourceType.GACHA_MOVE:
return i18next.t("egg:gachaTypeMove"); 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; 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) { if (!scene) {
return undefined; return null;
} }
/** /**
* Manaphy eggs have a 1/8 chance of being Manaphy and 7/8 chance of being Phione * 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 * and being the same each time
*/ */
let totalWeight = 0; let totalWeight = 0;
const speciesWeights = []; const speciesWeights : number[] = [];
for (const speciesId of speciesPool) { for (const speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId); const species = getPokemonSpecies(speciesId);
@ -416,6 +423,7 @@ export class Egg {
break; break;
} }
} }
species = species!; // tell TS compiled it's defined now!
if (!!scene.gameData.dexData[species].caughtAttr || scene.gameData.eggs.some(e => e.species === species)) { 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); scene.gameData.unlockPity[this.tier] = Math.min(scene.gameData.unlockPity[this.tier] + 1, 10);
@ -513,6 +521,8 @@ export class Egg {
if (speciesStartValue >= 8) { if (speciesStartValue >= 8) {
return EggTier.MASTER; return EggTier.MASTER;
} }
return EggTier.COMMON;
} }
//// ////
@ -537,6 +547,7 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta
scene.executeWithSeedOffset(() => { scene.executeWithSeedOffset(() => {
ret = Phaser.Math.RND.shuffle(legendarySpecies)[index]; ret = Phaser.Math.RND.shuffle(legendarySpecies)[index];
}, offset, EGG_SEED.toString()); }, offset, EGG_SEED.toString());
ret = ret!; // tell TS compiler it's
return ret; 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) { if (includeStatEffects) {
const stats = Utils.getEnumValues(Stat).slice(1); const stats = Utils.getEnumValues(Stat).slice(1);
let increasedStat: Stat = null; let increasedStat: Stat | null = null;
let decreasedStat: Stat = null; let decreasedStat: Stat | null = null;
for (const stat of stats) { for (const stat of stats) {
const multiplier = getNatureStatMultiplier(nature, stat); const multiplier = getNatureStatMultiplier(nature, stat);
if (multiplier > 1) { if (multiplier > 1) {

View File

@ -59,26 +59,26 @@ export type EvolutionConditionEnforceFunc = (p: Pokemon) => void;
export class SpeciesFormEvolution { export class SpeciesFormEvolution {
public speciesId: Species; public speciesId: Species;
public preFormKey: string; public preFormKey: string | null;
public evoFormKey: string; public evoFormKey: string | null;
public level: integer; public level: integer;
public item: EvolutionItem; public item: EvolutionItem | null;
public condition: SpeciesEvolutionCondition; public condition: SpeciesEvolutionCondition | null;
public wildDelay: SpeciesWildEvolutionDelay; 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.speciesId = speciesId;
this.preFormKey = preFormKey; this.preFormKey = preFormKey;
this.evoFormKey = evoFormKey; this.evoFormKey = evoFormKey;
this.level = level; this.level = level;
this.item = item || EvolutionItem.NONE; this.item = item || EvolutionItem.NONE;
this.condition = condition; this.condition = condition;
this.wildDelay = wildDelay || SpeciesWildEvolutionDelay.NONE; this.wildDelay = wildDelay ?? SpeciesWildEvolutionDelay.NONE;
} }
} }
export class SpeciesEvolution extends SpeciesFormEvolution { 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); super(speciesId, null, null, level, item, condition, wildDelay);
} }
} }
@ -95,7 +95,7 @@ export class FusionSpeciesFormEvolution extends SpeciesFormEvolution {
export class SpeciesEvolutionCondition { export class SpeciesEvolutionCondition {
public predicate: EvolutionConditionPredicate; public predicate: EvolutionConditionPredicate;
public enforceFunc: EvolutionConditionEnforceFunc; public enforceFunc: EvolutionConditionEnforceFunc | undefined;
constructor(predicate: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) { constructor(predicate: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) {
this.predicate = predicate; this.predicate = predicate;
@ -400,8 +400,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.LINOONE, 20, null, null) new SpeciesEvolution(Species.LINOONE, 20, null, null)
], ],
[Species.WURMPLE]: [ [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.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), null) new SpeciesEvolution(Species.CASCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
], ],
[Species.SILCOON]: [ [Species.SILCOON]: [
new SpeciesEvolution(Species.BEAUTIFLY, 10, null, null) new SpeciesEvolution(Species.BEAUTIFLY, 10, null, null)
@ -945,7 +945,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.SHIINOTIC, 24, null, null) new SpeciesEvolution(Species.SHIINOTIC, 24, null, null)
], ],
[Species.SALANDIT]: [ [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]: [ [Species.STUFFUL]: [
new SpeciesEvolution(Species.BEWEAR, 27, null, null) new SpeciesEvolution(Species.BEWEAR, 27, null, null)
@ -969,8 +969,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.COSMOEM, 43, null, null) new SpeciesEvolution(Species.COSMOEM, 43, null, null)
], ],
[Species.COSMOEM]: [ [Species.COSMOEM]: [
new SpeciesEvolution(Species.SOLGALEO, 53, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), 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), null) new SpeciesEvolution(Species.LUNALA, 53, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT))
], ],
[Species.MELTAN]: [ [Species.MELTAN]: [
new SpeciesEvolution(Species.MELMETAL, 48, null, null) 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) new SpeciesEvolution(Species.EXEGGUTOR, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.TANGELA]: [ [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]: [ [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]: [ [Species.STARYU]: [
new SpeciesEvolution(Species.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.EEVEE]: [ [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, "", "", 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, "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, "", "", 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.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), 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) new SpeciesEvolution(Species.TOGEKISS, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.AIPOM]: [ [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]: [ [Species.SUNKERN]: [
new SpeciesEvolution(Species.SUNFLORA, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.SUNFLORA, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.YANMA]: [ [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]: [ [Species.MURKROW]: [
new SpeciesEvolution(Species.HONCHKROW, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) 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) new SpeciesEvolution(Species.MISMAGIUS, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.GIRAFARIG]: [ [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]: [ [Species.DUNSPARCE]: [
new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new SpeciesEvolutionCondition(p => { new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new SpeciesEvolutionCondition(p => {
let ret = false; 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); p.scene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id);
} }
return ret; return ret;
}), SpeciesWildEvolutionDelay.LONG), }), 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]: [ [Species.GLIGAR]: [
new SpeciesEvolution(Species.GLISCOR, 1, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.LONG) 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 new SpeciesEvolution(Species.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna
], ],
[Species.PILOSWINE]: [ [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]: [ [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]: [ [Species.LOMBRE]: [
new SpeciesEvolution(Species.LUDICOLO, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) 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) new SpeciesEvolution(Species.ROSERADE, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.BONSLY]: [ [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]: [ [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.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.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
], ],
[Species.PANSAGE]: [ [Species.PANSAGE]: [
new SpeciesEvolution(Species.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) 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) new SpeciesEvolution(Species.CRABOMINABLE, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.ROCKRUFF]: [ [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, "", "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), null), 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)), 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)))
], ],
[Species.STEENEE]: [ [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]: [ [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]: [ [Species.ALOLA_SANDSHREW]: [
new SpeciesEvolution(Species.ALOLA_SANDSLASH, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG) 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) new SpeciesEvolution(Species.APPLETUN, 1, EvolutionItem.SWEET_APPLE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.CLOBBOPUS]: [ [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]: [ [Species.SINISTEA]: [
new SpeciesFormEvolution(Species.POLTEAGEIST, "phony", "phony", 1, EvolutionItem.CRACKED_POT, null, SpeciesWildEvolutionDelay.LONG), 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) new SpeciesEvolution(Species.HISUI_ELECTRODE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.HISUI_QWILFISH]: [ [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]: [ [Species.HISUI_SNEASEL]: [
new SpeciesEvolution(Species.SNEASLER, 1, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.LONG) 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) new SpeciesFormEvolution(Species.SINISTCHA, "artisan", "masterpiece", 1, EvolutionItem.MASTERPIECE_TEACUP, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.DIPPLIN]: [ [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]: [ [Species.KADABRA]: [
new SpeciesEvolution(Species.ALAKAZAM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.ALAKAZAM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
@ -1501,7 +1501,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
], ],
[Species.ONIX]: [ [Species.ONIX]: [
new SpeciesEvolution(Species.STEELIX, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition( 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) SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.RHYDON]: [ [Species.RHYDON]: [
@ -1512,7 +1512,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
], ],
[Species.SCYTHER]: [ [Species.SCYTHER]: [
new SpeciesEvolution(Species.SCIZOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition( 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), SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesEvolution(Species.KLEAVOR, 1, EvolutionItem.BLACK_AUGURITE, null, 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) new SpeciesEvolution(Species.ALOLA_GOLEM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.PRIMEAPE]: [ [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]: [ [Species.GOLBAT]: [
new SpeciesEvolution(Species.CROBAT, 1, null, new SpeciesFriendshipEvolutionCondition(110), SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.CROBAT, 1, null, new SpeciesFriendshipEvolutionCondition(110), SpeciesWildEvolutionDelay.VERY_LONG)

View File

@ -181,7 +181,7 @@ export class SpeciesFormChange {
return true; return true;
} }
findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger { findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | null {
if (!this.trigger.hasTriggerType(triggerType)) { if (!this.trigger.hasTriggerType(triggerType)) {
return null; return null;
} }
@ -189,7 +189,7 @@ export class SpeciesFormChange {
const trigger = this.trigger; const trigger = this.trigger;
if (trigger instanceof SpeciesFormChangeCompoundTrigger) { 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; return trigger;
@ -198,11 +198,11 @@ export class SpeciesFormChange {
export class SpeciesFormChangeCondition { export class SpeciesFormChangeCondition {
public predicate: SpeciesFormChangeConditionPredicate; public predicate: SpeciesFormChangeConditionPredicate;
public enforceFunc: SpeciesFormChangeConditionEnforceFunc; public enforceFunc: SpeciesFormChangeConditionEnforceFunc | null;
constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) { constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) {
this.predicate = predicate; 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 { 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 { export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger {
canChange(pokemon: Pokemon): boolean { canChange(pokemon: Pokemon): boolean {
const command = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; 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) { if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", { pokemonName: getPokemonNameWithAffix(pokemon) }); 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 }); return i18next.t("battlePokemonForm:formChange", { preName });
} }
@ -828,6 +831,12 @@ export const pokemonFormChanges: PokemonFormChanges = {
[Species.EISCUE]: [ [Species.EISCUE]: [
new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeManualTrigger(), true),
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]: [ [Species.CHIKORITA]: [
[ 1, Moves.TACKLE ], [ 1, Moves.TACKLE ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 6, Moves.RAZOR_LEAF ], [ 5, Moves.RAZOR_LEAF ], //Custom, moved from 6 to 5
[ 9, Moves.POISON_POWDER ], [ 9, Moves.POISON_POWDER ],
[ 12, Moves.SYNTHESIS ], [ 12, Moves.SYNTHESIS ],
[ 17, Moves.REFLECT ], [ 17, Moves.REFLECT ],
@ -2681,8 +2681,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.CYNDAQUIL]: [ [Species.CYNDAQUIL]: [
[ 1, Moves.TACKLE ], [ 1, Moves.TACKLE ],
[ 1, Moves.LEER ], [ 1, Moves.LEER ],
[ 6, Moves.SMOKESCREEN ], [ 5, Moves.EMBER ], //Custom, moved to 5
[ 10, Moves.EMBER ], [ 10, Moves.SMOKESCREEN ], //Custom, moved to 10
[ 13, Moves.QUICK_ATTACK ], [ 13, Moves.QUICK_ATTACK ],
[ 19, Moves.FLAME_WHEEL ], [ 19, Moves.FLAME_WHEEL ],
[ 22, Moves.DEFENSE_CURL ], [ 22, Moves.DEFENSE_CURL ],
@ -2736,7 +2736,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.TOTODILE]: [ [Species.TOTODILE]: [
[ 1, Moves.SCRATCH ], [ 1, Moves.SCRATCH ],
[ 1, Moves.LEER ], [ 1, Moves.LEER ],
[ 6, Moves.WATER_GUN ], [ 5, Moves.WATER_GUN ], //Custom, moved from 6 to 5
[ 9, Moves.BITE ], [ 9, Moves.BITE ],
[ 13, Moves.SCARY_FACE ], [ 13, Moves.SCARY_FACE ],
[ 19, Moves.ICE_FANG ], [ 19, Moves.ICE_FANG ],
@ -6723,7 +6723,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.TURTWIG]: [ [Species.TURTWIG]: [
[ 1, Moves.TACKLE ], [ 1, Moves.TACKLE ],
[ 5, Moves.WITHDRAW ], [ 5, Moves.WITHDRAW ],
[ 9, Moves.ABSORB ], [ 5, Moves.ABSORB ], //Custom, moved from 9 to 5
[ 13, Moves.RAZOR_LEAF ], [ 13, Moves.RAZOR_LEAF ],
[ 17, Moves.CURSE ], [ 17, Moves.CURSE ],
[ 21, Moves.BITE ], [ 21, Moves.BITE ],
@ -6768,7 +6768,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.CHIMCHAR]: [ [Species.CHIMCHAR]: [
[ 1, Moves.SCRATCH ], [ 1, Moves.SCRATCH ],
[ 1, Moves.LEER ], [ 1, Moves.LEER ],
[ 7, Moves.EMBER ], [ 5, Moves.EMBER ], //Custom, moved from 7 to 5
[ 9, Moves.TAUNT ], [ 9, Moves.TAUNT ],
[ 15, Moves.FURY_SWIPES ], [ 15, Moves.FURY_SWIPES ],
[ 17, Moves.FLAME_WHEEL ], [ 17, Moves.FLAME_WHEEL ],
@ -6817,7 +6817,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.PIPLUP]: [ [Species.PIPLUP]: [
[ 1, Moves.POUND ], [ 1, Moves.POUND ],
[ 4, Moves.GROWL ], [ 4, Moves.GROWL ],
[ 8, Moves.WATER_GUN ], [ 5, Moves.WATER_GUN ], //Custom, moved from 8 to 5
[ 11, Moves.CHARM ], [ 11, Moves.CHARM ],
[ 15, Moves.PECK ], [ 15, Moves.PECK ],
[ 18, Moves.BUBBLE_BEAM ], [ 18, Moves.BUBBLE_BEAM ],
@ -8591,7 +8591,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.SNIVY]: [ [Species.SNIVY]: [
[ 1, Moves.TACKLE ], [ 1, Moves.TACKLE ],
[ 4, Moves.LEER ], [ 4, Moves.LEER ],
[ 7, Moves.VINE_WHIP ], [ 5, Moves.VINE_WHIP ], //Custom, moved from 7 to 5
[ 10, Moves.WRAP ], [ 10, Moves.WRAP ],
[ 13, Moves.GROWTH ], [ 13, Moves.GROWTH ],
[ 16, Moves.MAGICAL_LEAF ], [ 16, Moves.MAGICAL_LEAF ],
@ -8639,7 +8639,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.TEPIG]: [ [Species.TEPIG]: [
[ 1, Moves.TACKLE ], [ 1, Moves.TACKLE ],
[ 3, Moves.TAIL_WHIP ], [ 3, Moves.TAIL_WHIP ],
[ 7, Moves.EMBER ], [ 5, Moves.EMBER ], //Custom, moved from 7 to 5
[ 9, Moves.ENDURE ], [ 9, Moves.ENDURE ],
[ 13, Moves.DEFENSE_CURL ], [ 13, Moves.DEFENSE_CURL ],
[ 15, Moves.FLAME_CHARGE ], [ 15, Moves.FLAME_CHARGE ],
@ -8693,7 +8693,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.OSHAWOTT]: [ [Species.OSHAWOTT]: [
[ 1, Moves.TACKLE ], [ 1, Moves.TACKLE ],
[ 5, Moves.TAIL_WHIP ], [ 5, Moves.TAIL_WHIP ],
[ 7, Moves.WATER_GUN ], [ 5, Moves.WATER_GUN ], //Custom, moved from 7 to 5
[ 11, Moves.SOAK ], [ 11, Moves.SOAK ],
[ 13, Moves.FOCUS_ENERGY ], [ 13, Moves.FOCUS_ENERGY ],
[ 17, Moves.RAZOR_SHELL ], [ 17, Moves.RAZOR_SHELL ],
@ -13850,11 +13850,11 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[ 99, Moves.CLOSE_COMBAT ], [ 99, Moves.CLOSE_COMBAT ],
], ],
[Species.POIPOLE]: [ [Species.POIPOLE]: [
[ RELEARN_MOVE, Moves.DRAGON_PULSE ], //Custom, made relearn
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 1, Moves.ACID ], [ 1, Moves.ACID ],
[ 1, Moves.PECK ], [ 1, Moves.PECK ],
[ 1, Moves.HELPING_HAND ], [ 1, Moves.HELPING_HAND ],
[ 1, Moves.DRAGON_PULSE ],
[ 7, Moves.FURY_ATTACK ], [ 7, Moves.FURY_ATTACK ],
[ 14, Moves.FELL_STINGER ], [ 14, Moves.FELL_STINGER ],
[ 21, Moves.CHARM ], [ 21, Moves.CHARM ],
@ -13969,7 +13969,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.GROOKEY]: [ [Species.GROOKEY]: [
[ 1, Moves.SCRATCH ], [ 1, Moves.SCRATCH ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 6, Moves.BRANCH_POKE ], [ 5, Moves.BRANCH_POKE ], //Custom, moved from 6 to 5
[ 8, Moves.TAUNT ], [ 8, Moves.TAUNT ],
[ 12, Moves.RAZOR_LEAF ], [ 12, Moves.RAZOR_LEAF ],
[ 17, Moves.SCREECH ], [ 17, Moves.SCREECH ],
@ -14014,7 +14014,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.SCORBUNNY]: [ [Species.SCORBUNNY]: [
[ 1, Moves.TACKLE ], [ 1, Moves.TACKLE ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 6, Moves.EMBER ], [ 5, Moves.EMBER ], //Custom, moved from 6 to 5
[ 8, Moves.QUICK_ATTACK ], [ 8, Moves.QUICK_ATTACK ],
[ 12, Moves.DOUBLE_KICK ], [ 12, Moves.DOUBLE_KICK ],
[ 17, Moves.FLAME_CHARGE ], [ 17, Moves.FLAME_CHARGE ],
@ -14056,7 +14056,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[Species.SOBBLE]: [ [Species.SOBBLE]: [
[ 1, Moves.POUND ], [ 1, Moves.POUND ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 6, Moves.WATER_GUN ], [ 5, Moves.WATER_GUN ], //Custom, moved from 6 to 5
[ 8, Moves.BIND ], [ 8, Moves.BIND ],
[ 12, Moves.WATER_PULSE ], [ 12, Moves.WATER_PULSE ],
[ 17, Moves.TEARFUL_LOOK ], [ 17, Moves.TEARFUL_LOOK ],
@ -18666,7 +18666,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ], [ 48, Moves.PIKA_PAPOW ],
], ],
3: [ 3: [
[ EVOLVE_MOVE, Moves.METEOR_MASH ], [ 1, Moves.METEOR_MASH ],
[ 1, Moves.TAIL_WHIP ], [ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ], [ 1, Moves.THUNDER_SHOCK ],
@ -18690,7 +18690,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ], [ 48, Moves.PIKA_PAPOW ],
], ],
4: [ 4: [
[ EVOLVE_MOVE, Moves.ICICLE_CRASH ], [ 1, Moves.ICICLE_CRASH ],
[ 1, Moves.TAIL_WHIP ], [ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ], [ 1, Moves.THUNDER_SHOCK ],
@ -18714,7 +18714,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ], [ 48, Moves.PIKA_PAPOW ],
], ],
5: [ 5: [
[ EVOLVE_MOVE, Moves.DRAINING_KISS ], [ 1, Moves.DRAINING_KISS ],
[ 1, Moves.TAIL_WHIP ], [ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ], [ 1, Moves.THUNDER_SHOCK ],
@ -18738,7 +18738,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ], [ 48, Moves.PIKA_PAPOW ],
], ],
6: [ 6: [
[ EVOLVE_MOVE, Moves.ELECTRIC_TERRAIN ], [ 1, Moves.ELECTRIC_TERRAIN ],
[ 1, Moves.TAIL_WHIP ], [ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ], [ 1, Moves.THUNDER_SHOCK ],
@ -18762,7 +18762,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 48, Moves.PIKA_PAPOW ], [ 48, Moves.PIKA_PAPOW ],
], ],
7: [ 7: [
[ EVOLVE_MOVE, Moves.FLYING_PRESS ], [ 1, Moves.FLYING_PRESS ],
[ 1, Moves.TAIL_WHIP ], [ 1, Moves.TAIL_WHIP ],
[ 1, Moves.GROWL ], [ 1, Moves.GROWL ],
[ 1, Moves.THUNDER_SHOCK ], [ 1, Moves.THUNDER_SHOCK ],
@ -18886,7 +18886,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
}, },
[Species.ROTOM]: { [Species.ROTOM]: {
1: [ 1: [
[ EVOLVE_MOVE, Moves.OVERHEAT ], [ 1, Moves.OVERHEAT ],
[ 1, Moves.DOUBLE_TEAM ], [ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ], [ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ], [ 5, Moves.THUNDER_SHOCK ],
@ -18902,7 +18902,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 55, Moves.UPROAR ], [ 55, Moves.UPROAR ],
], ],
2: [ 2: [
[ EVOLVE_MOVE, Moves.HYDRO_PUMP ], [ 1, Moves.HYDRO_PUMP ],
[ 1, Moves.DOUBLE_TEAM ], [ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ], [ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ], [ 5, Moves.THUNDER_SHOCK ],
@ -18918,7 +18918,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 55, Moves.UPROAR ], [ 55, Moves.UPROAR ],
], ],
3: [ 3: [
[ EVOLVE_MOVE, Moves.BLIZZARD ], [ 1, Moves.BLIZZARD ],
[ 1, Moves.DOUBLE_TEAM ], [ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ], [ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ], [ 5, Moves.THUNDER_SHOCK ],
@ -18934,7 +18934,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 55, Moves.UPROAR ], [ 55, Moves.UPROAR ],
], ],
4: [ 4: [
[ EVOLVE_MOVE, Moves.AIR_SLASH ], [ 1, Moves.AIR_SLASH ],
[ 1, Moves.DOUBLE_TEAM ], [ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ], [ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ], [ 5, Moves.THUNDER_SHOCK ],
@ -18950,7 +18950,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 55, Moves.UPROAR ], [ 55, Moves.UPROAR ],
], ],
5: [ 5: [
[ EVOLVE_MOVE, Moves.LEAF_STORM ], [ 1, Moves.LEAF_STORM ],
[ 1, Moves.DOUBLE_TEAM ], [ 1, Moves.DOUBLE_TEAM ],
[ 1, Moves.ASTONISH ], [ 1, Moves.ASTONISH ],
[ 5, Moves.THUNDER_SHOCK ], [ 5, Moves.THUNDER_SHOCK ],

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -103,7 +103,7 @@ export class Weather {
const field = scene.getField(true); const field = scene.getField(true);
for (const pokemon of field) { for (const pokemon of field) {
let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]; let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0];
if (!suppressWeatherEffectAbAttr) { if (!suppressWeatherEffectAbAttr) {
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null; 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) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyStartMessage"); return i18next.t("weather:sunnyStartMessage");
@ -141,7 +141,7 @@ export function getWeatherStartMessage(weatherType: WeatherType): string {
return null; return null;
} }
export function getWeatherLapseMessage(weatherType: WeatherType): string { export function getWeatherLapseMessage(weatherType: WeatherType): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyLapseMessage"); return i18next.t("weather:sunnyLapseMessage");
@ -166,7 +166,7 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string {
return null; return null;
} }
export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string { export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
return i18next.t("weather:sandstormDamageMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)}); return i18next.t("weather:sandstormDamageMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)});
@ -177,7 +177,7 @@ export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokem
return null; return null;
} }
export function getWeatherClearMessage(weatherType: WeatherType): string { export function getWeatherClearMessage(weatherType: WeatherType): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyClearMessage"); return i18next.t("weather:sunnyClearMessage");
@ -202,7 +202,7 @@ export function getWeatherClearMessage(weatherType: WeatherType): string {
return null; return null;
} }
export function getTerrainStartMessage(terrainType: TerrainType): string { export function getTerrainStartMessage(terrainType: TerrainType): string | null {
switch (terrainType) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:
return i18next.t("terrain:mistyStartMessage"); return i18next.t("terrain:mistyStartMessage");
@ -212,10 +212,13 @@ export function getTerrainStartMessage(terrainType: TerrainType): string {
return i18next.t("terrain:grassyStartMessage"); return i18next.t("terrain:grassyStartMessage");
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
return i18next.t("terrain:psychicStartMessage"); 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) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:
return i18next.t("terrain:mistyClearMessage"); return i18next.t("terrain:mistyClearMessage");
@ -225,6 +228,9 @@ export function getTerrainClearMessage(terrainType: TerrainType): string {
return i18next.t("terrain:grassyClearMessage"); return i18next.t("terrain:grassyClearMessage");
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
return i18next.t("terrain:psychicClearMessage"); 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.gameData.eggs.splice(eggIndex, 1);
this.scene.fadeOutBgm(null, false); this.scene.fadeOutBgm(undefined, false);
this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler; this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
@ -234,8 +234,8 @@ export class EggHatchPhase extends Phase {
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
duration: 250, duration: 250,
onComplete: () => { onComplete: () => {
count++; count!++;
if (count < repeatCount) { if (count! < repeatCount!) { // we know they are defined
return this.doEggShake(intensity, repeatCount, count).then(() => resolve()); return this.doEggShake(intensity, repeatCount, count).then(() => resolve());
} }
this.scene.tweens.add({ 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.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => { this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => { this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => {
this.scene.ui.showText(null, 0); this.scene.ui.showText("", 0);
this.end(); this.end();
}); });
}); });
@ -447,6 +447,6 @@ export class EggHatchPhase extends Phase {
}, this.egg.id, EGG_SEED.toString()); }, this.egg.id, EGG_SEED.toString());
return ret; return ret!;
} }
} }

View File

@ -21,5 +21,6 @@ export enum ArenaTagType {
MAT_BLOCK = "MAT_BLOCK", MAT_BLOCK = "MAT_BLOCK",
CRAFTY_SHIELD = "CRAFTY_SHIELD", CRAFTY_SHIELD = "CRAFTY_SHIELD",
TAILWIND = "TAILWIND", 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", FIRE_BOOST = "FIRE_BOOST",
CRIT_BOOST = "CRIT_BOOST", CRIT_BOOST = "CRIT_BOOST",
ALWAYS_CRIT = "ALWAYS_CRIT", ALWAYS_CRIT = "ALWAYS_CRIT",
NO_CRIT = "NO_CRIT",
IGNORE_ACCURACY = "IGNORE_ACCURACY", IGNORE_ACCURACY = "IGNORE_ACCURACY",
BYPASS_SLEEP = "BYPASS_SLEEP", BYPASS_SLEEP = "BYPASS_SLEEP",
IGNORE_FLYING = "IGNORE_FLYING", IGNORE_FLYING = "IGNORE_FLYING",
@ -61,7 +60,13 @@ export enum BattlerTagType {
DESTINY_BOND = "DESTINY_BOND", DESTINY_BOND = "DESTINY_BOND",
CENTER_OF_ATTENTION = "CENTER_OF_ATTENTION", CENTER_OF_ATTENTION = "CENTER_OF_ATTENTION",
ICE_FACE = "ICE_FACE", ICE_FACE = "ICE_FACE",
DISGUISE = "DISGUISE",
STOCKPILING = "STOCKPILING", STOCKPILING = "STOCKPILING",
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE", 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,215 +1,223 @@
export enum TrainerType { export enum TrainerType {
UNKNOWN, UNKNOWN,
ACE_TRAINER, ACE_TRAINER,
ARTIST, ARTIST,
BACKERS, BACKERS,
BACKPACKER, BACKPACKER,
BAKER, BAKER,
BEAUTY, BEAUTY,
BIKER, BIKER,
BLACK_BELT, BLACK_BELT,
BREEDER, BREEDER,
CLERK, CLERK,
CYCLIST, CYCLIST,
DANCER, DANCER,
DEPOT_AGENT, DEPOT_AGENT,
DOCTOR, DOCTOR,
FIREBREATHER, FIREBREATHER,
FISHERMAN, FISHERMAN,
GUITARIST, GUITARIST,
HARLEQUIN, HARLEQUIN,
HIKER, HIKER,
HOOLIGANS, HOOLIGANS,
HOOPSTER, HOOPSTER,
INFIELDER, INFIELDER,
JANITOR, JANITOR,
LINEBACKER, LINEBACKER,
MAID, MAID,
MUSICIAN, MUSICIAN,
HEX_MANIAC, HEX_MANIAC,
NURSERY_AIDE, NURSERY_AIDE,
OFFICER, OFFICER,
PARASOL_LADY, PARASOL_LADY,
PILOT, PILOT,
POKEFAN, POKEFAN,
PRESCHOOLER, PRESCHOOLER,
PSYCHIC, PSYCHIC,
RANGER, RANGER,
RICH, RICH,
RICH_KID, RICH_KID,
ROUGHNECK, ROUGHNECK,
SAILOR, SAILOR,
SCIENTIST, SCIENTIST,
SMASHER, SMASHER,
SNOW_WORKER, SNOW_WORKER,
STRIKER, STRIKER,
SCHOOL_KID, SCHOOL_KID,
SWIMMER, SWIMMER,
TWINS, TWINS,
VETERAN, VETERAN,
WAITER, WAITER,
WORKER, WORKER,
YOUNGSTER, YOUNGSTER,
ROCKET_GRUNT, ROCKET_GRUNT,
ROCKET_ADMIN, ARCHER,
MAGMA_GRUNT, ARIANA,
MAGMA_ADMIN, PROTON,
AQUA_GRUNT, PETREL,
AQUA_ADMIN, MAGMA_GRUNT,
GALACTIC_GRUNT, TABITHA,
GALACTIC_ADMIN, COURTNEY,
PLASMA_GRUNT, AQUA_GRUNT,
PLASMA_SAGE, MATT,
FLARE_GRUNT, SHELLY,
FLARE_ADMIN, GALACTIC_GRUNT,
ROCKET_BOSS_GIOVANNI_1, JUPITER,
ROCKET_BOSS_GIOVANNI_2, MARS,
MAXIE, SATURN,
MAXIE_2, PLASMA_GRUNT,
ARCHIE, ZINZOLIN,
ARCHIE_2, ROOD,
CYRUS, FLARE_GRUNT,
CYRUS_2, BRYONY,
GHETSIS, XEROSIC,
GHETSIS_2, ROCKET_BOSS_GIOVANNI_1,
LYSANDRE, ROCKET_BOSS_GIOVANNI_2,
LYSANDRE_2, MAXIE,
MAXIE_2,
ARCHIE,
ARCHIE_2,
CYRUS,
CYRUS_2,
GHETSIS,
GHETSIS_2,
LYSANDRE,
LYSANDRE_2,
BROCK = 200, BROCK = 200,
MISTY, MISTY,
LT_SURGE, LT_SURGE,
ERIKA, ERIKA,
JANINE, JANINE,
SABRINA, SABRINA,
BLAINE, BLAINE,
GIOVANNI, GIOVANNI,
FALKNER, FALKNER,
BUGSY, BUGSY,
WHITNEY, WHITNEY,
MORTY, MORTY,
CHUCK, CHUCK,
JASMINE, JASMINE,
PRYCE, PRYCE,
CLAIR, CLAIR,
ROXANNE, ROXANNE,
BRAWLY, BRAWLY,
WATTSON, WATTSON,
FLANNERY, FLANNERY,
NORMAN, NORMAN,
WINONA, WINONA,
TATE, TATE,
LIZA, LIZA,
JUAN, JUAN,
ROARK, ROARK,
GARDENIA, GARDENIA,
MAYLENE, MAYLENE,
CRASHER_WAKE, CRASHER_WAKE,
FANTINA, FANTINA,
BYRON, BYRON,
CANDICE, CANDICE,
VOLKNER, VOLKNER,
CILAN, CILAN,
CHILI, CHILI,
CRESS, CRESS,
CHEREN, CHEREN,
LENORA, LENORA,
ROXIE, ROXIE,
BURGH, BURGH,
ELESA, ELESA,
CLAY, CLAY,
SKYLA, SKYLA,
BRYCEN, BRYCEN,
DRAYDEN, DRAYDEN,
MARLON, MARLON,
VIOLA, VIOLA,
GRANT, GRANT,
KORRINA, KORRINA,
RAMOS, RAMOS,
CLEMONT, CLEMONT,
VALERIE, VALERIE,
OLYMPIA, OLYMPIA,
WULFRIC, WULFRIC,
MILO, MILO,
NESSA, NESSA,
KABU, KABU,
BEA, BEA,
ALLISTER, ALLISTER,
OPAL, OPAL,
BEDE, BEDE,
GORDIE, GORDIE,
MELONY, MELONY,
PIERS, PIERS,
MARNIE, MARNIE,
RAIHAN, RAIHAN,
KATY, KATY,
BRASSIUS, BRASSIUS,
IONO, IONO,
KOFU, KOFU,
LARRY, LARRY,
RYME, RYME,
TULIP, TULIP,
GRUSHA, GRUSHA,
LORELEI = 300, LORELEI = 300,
BRUNO, BRUNO,
AGATHA, AGATHA,
LANCE, LANCE,
WILL, WILL,
KOGA, KOGA,
KAREN, KAREN,
SIDNEY, SIDNEY,
PHOEBE, PHOEBE,
GLACIA, GLACIA,
DRAKE, DRAKE,
AARON, AARON,
BERTHA, BERTHA,
FLINT, FLINT,
LUCIAN, LUCIAN,
SHAUNTAL, SHAUNTAL,
MARSHAL, MARSHAL,
GRIMSLEY, GRIMSLEY,
CAITLIN, CAITLIN,
MALVA, MALVA,
SIEBOLD, SIEBOLD,
WIKSTROM, WIKSTROM,
DRASNA, DRASNA,
HALA, HALA,
MOLAYNE, MOLAYNE,
OLIVIA, OLIVIA,
ACEROLA, ACEROLA,
KAHILI, KAHILI,
MARNIE_ELITE, MARNIE_ELITE,
NESSA_ELITE, NESSA_ELITE,
BEA_ELITE, BEA_ELITE,
ALLISTER_ELITE, ALLISTER_ELITE,
RAIHAN_ELITE, RAIHAN_ELITE,
RIKA, RIKA,
POPPY, POPPY,
LARRY_ELITE, LARRY_ELITE,
HASSEL, HASSEL,
CRISPIN, CRISPIN,
AMARYS, AMARYS,
LACEY, LACEY,
DRAYTON, DRAYTON,
BLUE = 350, BLUE = 350,
RED, RED,
LANCE_CHAMPION, LANCE_CHAMPION,
STEVEN, STEVEN,
WALLACE, WALLACE,
CYNTHIA, CYNTHIA,
ALDER, ALDER,
IRIS, IRIS,
DIANTHA, DIANTHA,
HAU, HAU,
LEON, LEON,
GEETA, GEETA,
NEMONA, NEMONA,
KIERAN, KIERAN,
RIVAL = 375, RIVAL = 375,
RIVAL_2, RIVAL_2,
RIVAL_3, RIVAL_3,
RIVAL_4, RIVAL_4,
RIVAL_5, RIVAL_5,
RIVAL_6 RIVAL_6
} }

View File

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

View File

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

View File

@ -25,8 +25,8 @@ import { TrainerType } from "#enums/trainer-type";
export class Arena { export class Arena {
public scene: BattleScene; public scene: BattleScene;
public biomeType: Biome; public biomeType: Biome;
public weather: Weather; public weather: Weather | null;
public terrain: Terrain; public terrain: Terrain | null;
public tags: ArenaTag[]; public tags: ArenaTag[];
public bgm: string; public bgm: string;
public ignoreAbilities: boolean; public ignoreAbilities: boolean;
@ -121,7 +121,7 @@ export class Arena {
} }
} }
ret = getPokemonSpecies(species); ret = getPokemonSpecies(species!);
if (ret.subLegendary || ret.legendary || ret.mythical) { if (ret.subLegendary || ret.legendary || ret.mythical) {
switch (true) { switch (true) {
@ -292,7 +292,7 @@ export class Arena {
trySetWeatherOverride(weather: WeatherType): boolean { trySetWeatherOverride(weather: WeatherType): boolean {
this.weather = new Weather(weather, 0); this.weather = new Weather(weather, 0);
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); 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; return true;
} }
@ -314,13 +314,13 @@ export class Arena {
const oldWeatherType = this.weather?.weatherType || WeatherType.NONE; const oldWeatherType = this.weather?.weatherType || WeatherType.NONE;
this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null; 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) { if (this.weather) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); 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 { } 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 => { this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
@ -339,15 +339,15 @@ export class Arena {
const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE; const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE;
this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null; 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 (this.terrain) {
if (!ignoreAnim) { if (!ignoreAnim) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); 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 { } 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 => { this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
@ -554,7 +554,7 @@ export class Arena {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); 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); const existingTag = this.getTagOnSide(tagType, side);
if (existingTag) { if (existingTag) {
existingTag.onOverlap(this); existingTag.onOverlap(this);
@ -568,21 +568,23 @@ export class Arena {
} }
const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
this.tags.push(newTag); if (newTag) {
newTag.onAdd(this, quiet); this.tags.push(newTag);
newTag.onAdd(this, quiet);
const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {}; const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {};
this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers)); this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers));
}
return true; return true;
} }
getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag { getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag | undefined {
return this.getTagOnSide(tagType, ArenaTagSide.BOTH); 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" 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.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)); : 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; return 0.000;
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
return 3.047; 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.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.base.setOrigin(0, 0);
this.props = !player ? this.props = !player ?
new Array(3).fill(null).map(() => { 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.setOrigin(0, 0);
ret.setVisible(false); ret.setVisible(false);
return ret; return ret;

View File

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

View File

@ -35,7 +35,7 @@ export default class PokemonSpriteSparkleHandler {
const ratioX = s.width / width; const ratioX = s.width / width;
const ratioY = s.height / height; const ratioY = s.height / height;
const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE"); 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 [ 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"); 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"]; 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 { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase, MoveEndPhase } from "../phases"; import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase, MoveEndPhase } from "../phases";
import { BattleStat } from "../data/battle-stat"; 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 { WeatherType } from "../data/weather";
import { TempBattleStat } from "../data/temp-battle-stat"; 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 { 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 PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
@ -79,8 +79,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public ivs: integer[]; public ivs: integer[];
public nature: Nature; public nature: Nature;
public natureOverride: Nature | -1; public natureOverride: Nature | -1;
public moveset: PokemonMove[]; public moveset: (PokemonMove | null)[];
public status: Status; public status: Status | null;
public friendship: integer; public friendship: integer;
public metLevel: integer; public metLevel: integer;
public metBiome: Biome | -1; public metBiome: Biome | -1;
@ -88,8 +88,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public luck: integer; public luck: integer;
public pauseEvolutions: boolean; public pauseEvolutions: boolean;
public pokerus: boolean; public pokerus: boolean;
public wildFlee: boolean;
public fusionSpecies: PokemonSpecies; public fusionSpecies: PokemonSpecies | null;
public fusionFormIndex: integer; public fusionFormIndex: integer;
public fusionAbilityIndex: integer; public fusionAbilityIndex: integer;
public fusionShiny: boolean; public fusionShiny: boolean;
@ -97,7 +98,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fusionGender: Gender; public fusionGender: Gender;
public fusionLuck: integer; public fusionLuck: integer;
private summonDataPrimer: PokemonSummonData; private summonDataPrimer: PokemonSummonData | null;
public summonData: PokemonSummonData; public summonData: PokemonSummonData;
public battleData: PokemonBattleData; public battleData: PokemonBattleData;
@ -107,7 +108,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fieldPosition: FieldPosition; public fieldPosition: FieldPosition;
public maskEnabled: boolean; public maskEnabled: boolean;
public maskSprite: Phaser.GameObjects.Sprite; public maskSprite: Phaser.GameObjects.Sprite | null;
private shinySparkle: Phaser.GameObjects.Sprite; private shinySparkle: Phaser.GameObjects.Sprite;
@ -129,6 +130,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.species = species; this.species = species;
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL; this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
this.level = level; this.level = level;
this.wildFlee = false;
// Determine the ability index // Determine the ability index
if (abilityIndex !== undefined) { if (abilityIndex !== undefined) {
this.abilityIndex = abilityIndex; // Use the provided ability index if it is defined 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.nickname = dataSource.nickname;
this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1; this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1;
this.moveset = dataSource.moveset; 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.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship;
this.metLevel = dataSource.metLevel || 5; this.metLevel = dataSource.metLevel || 5;
this.luck = dataSource.luck; 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.metSpecies = dataSource.metSpecies ?? (this.metBiome !== -1 ? this.species.speciesId : this.species.getRootSpeciesId(true));
this.pauseEvolutions = dataSource.pauseEvolutions; this.pauseEvolutions = dataSource.pauseEvolutions;
this.pokerus = !!dataSource.pokerus; 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.fusionFormIndex = dataSource.fusionFormIndex;
this.fusionAbilityIndex = dataSource.fusionAbilityIndex; this.fusionAbilityIndex = dataSource.fusionAbilityIndex;
this.fusionShiny = dataSource.fusionShiny; 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} * This is frequently a better alternative to {@link isFainted}
* @returns {boolean} True if pokemon is allowed in battle * @returns {boolean} True if pokemon is allowed in battle
*/ */
isAllowedInBattle(): boolean { isAllowedInBattle(): boolean {
const challengeAllowed = new Utils.BooleanHolder(true); const challengeAllowed = new Utils.BooleanHolder(true);
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed); 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 { isActive(onField?: boolean): boolean {
@ -350,7 +353,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
loadAssets(ignoreOverride: boolean = true): Promise<void> { loadAssets(ignoreOverride: boolean = true): Promise<void> {
return new Promise(resolve => { 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))) Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m)))
.then(() => { .then(() => {
loadMoveAnimAssets(this.scene, moveIds); loadMoveAnimAssets(this.scene, moveIds);
@ -438,7 +441,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.species.forms[this.formIndex].formKey; return this.species.forms[this.formIndex].formKey;
} }
getFusionFormKey(): string { getFusionFormKey(): string | null {
if (!this.fusionSpecies) { if (!this.fusionSpecies) {
return null; return null;
} }
@ -527,7 +530,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.summonData.fusionSpeciesForm; return this.summonData.fusionSpeciesForm;
} }
if (!this.fusionSpecies?.forms?.length || this.fusionFormIndex >= this.fusionSpecies?.forms.length) { 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]; 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; return this.getAt(0) as Phaser.GameObjects.Sprite;
} }
getTintSprite(): Phaser.GameObjects.Sprite { getTintSprite(): Phaser.GameObjects.Sprite | null {
return !this.maskEnabled return !this.maskEnabled
? this.getAt(1) as Phaser.GameObjects.Sprite ? this.getAt(1) as Phaser.GameObjects.Sprite
: this.maskSprite; : this.maskSprite;
@ -562,7 +566,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
updateSpritePipelineData(): void { 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); this.updateInfo(true);
} }
@ -610,7 +614,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
playAnim(): void { 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 ] { getFieldPositionOffset(): [ number, number ] {
@ -870,7 +874,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
abstract isBoss(): boolean; abstract isBoss(): boolean;
getMoveset(ignoreOverride?: boolean): PokemonMove[] { getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] {
const ret = !ignoreOverride && this.summonData?.moveset const ret = !ignoreOverride && this.summonData?.moveset
? this.summonData.moveset ? this.summonData.moveset
: this.moveset; : this.moveset;
@ -912,16 +916,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* excluding any moves already known. * excluding any moves already known.
* *
* Available egg moves are only included if the {@linkcode Pokemon} was * 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 * @returns an array of {@linkcode Moves}, the length of which is determined
* by how many learnable moves there are for the {@linkcode Pokemon}. * by how many learnable moves there are for the {@linkcode Pokemon}.
*/ */
getLearnableLevelMoves(): Moves[] { getLearnableLevelMoves(): Moves[] {
let levelMoves = this.getLevelMoves(1, true).map(lm => lm[1]); 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); 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} * @returns array of {@linkcode Type}
*/ */
getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean): Type[] { getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean): Type[] {
const types = []; const types : Type[] = [];
if (includeTeraType) { if (includeTeraType) {
const teraType = this.getTeraType(); const teraType = this.getTeraType();
@ -942,7 +946,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (!types.length || !includeTeraType) { 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)); this.summonData.types.forEach(t => types.push(t));
} else { } else {
const speciesForm = this.getSpeciesForm(ignoreOverride); 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())) { (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) {
return true; 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(); return this.passive || this.isBoss();
} }
@ -1115,7 +1130,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; 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 ? moveOrType
: undefined; : undefined;
const moveType = (moveOrType instanceof Move) const moveType = (moveOrType instanceof Move)
? move.type ? move!.type // TODO: is this bang correct?
: moveOrType; : moveOrType;
if (moveType === Type.STELLAR) { if (moveType === Type.STELLAR) {
@ -1259,6 +1274,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (ignoreImmunity.value) { if (ignoreImmunity.value) {
return 1; 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); 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; return (atkScore + defScore) * hpDiffRatio;
} }
getEvolution(): SpeciesFormEvolution { getEvolution(): SpeciesFormEvolution | null {
if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) { if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) {
const evolutions = pokemonEvolutions[this.species.speciesId]; const evolutions = pokemonEvolutions[this.species.speciesId];
for (const e of evolutions) { 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)); const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map(e => new FusionSpeciesFormEvolution(this.species.speciesId, e));
for (const fe of fusionEvolutions) { for (const fe of fusionEvolutions) {
if (!fe.item && this.level >= fe.level && (!fe.preFormKey || this.getFusionFormKey() === fe.preFormKey)) { 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 { clearFusionSpecies(): void {
this.fusionSpecies = undefined; this.fusionSpecies = null;
this.fusionFormIndex = 0; this.fusionFormIndex = 0;
this.fusionAbilityIndex = 0; this.fusionAbilityIndex = 0;
this.fusionShiny = false; 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. // 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. // 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 // 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 } 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); const totalWeight = movePool.reduce((v, m) => v + m[1], 0);
let rand = Utils.randSeedInt(totalWeight); let rand = Utils.randSeedInt(totalWeight);
@ -1724,7 +1744,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const move = this.getMoveset().length > moveIndex const move = this.getMoveset().length > moveIndex
? this.getMoveset()[moveIndex] ? this.getMoveset()[moveIndex]
: null; : null;
return move?.isUsable(this, ignorePp); return move?.isUsable(this, ignorePp)!; // TODO: is this bang correct?
} }
showInfo(): void { 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> { updateInfo(instant?: boolean): Promise<void> {
return this.battleInfo.updateInfo(this, instant); 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); this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate);
} }
getOpponent(targetIndex: integer): Pokemon { getOpponent(targetIndex: integer): Pokemon | null {
const ret = this.getOpponents()[targetIndex]; const ret = this.getOpponents()[targetIndex];
if (ret.summonData) { if (ret.summonData) {
return ret; return ret;
@ -1865,6 +1893,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvasionLevel); applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvasionLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), TempBattleStat.ACC, userAccuracyLevel); 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); const accuracyMultiplier = new Utils.NumberHolder(1);
if (userAccuracyLevel.value !== targetEvasionLevel.value) { if (userAccuracyLevel.value !== targetEvasionLevel.value) {
accuracyMultiplier.value = 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 { apply(source: Pokemon, move: Move): HitResult {
let result: HitResult; let result: HitResult;
const damage = new Utils.NumberHolder(0); 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 defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
const variableCategory = new Utils.IntegerHolder(move.category); 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 // Apply arena tags for conditional protection
if (!move.checkFlag(MoveFlags.IGNORE_PROTECT, source, this) && !move.isAllyTarget()) { 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.QUICK_GUARD, defendingSide, cancelled, this, move.priority);
this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget); this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget);
this.scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category); 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(CritBoosterModifier, source.isPlayer(), source, critLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel); this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
const bonusCrit = new Utils.BooleanHolder(false); 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) { if (bonusCrit.value) {
critLevel.value += 1; critLevel.value += 1;
} }
@ -1978,25 +2011,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
console.log(`crit stage: +${critLevel.value}`); console.log(`crit stage: +${critLevel.value}`);
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))]; 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) { if (Overrides.NEVER_CRIT_OVERRIDE) {
isCritical = false; isCritical = false;
} }
} }
if (isCritical) { if (isCritical) {
const noCritTag = this.scene.arena.getTagOnSide(NoCritTag, defendingSide);
const blockCrit = new Utils.BooleanHolder(false); const blockCrit = new Utils.BooleanHolder(false);
applyAbAttrs(BlockCritAbAttr, this, null, blockCrit); applyAbAttrs(BlockCritAbAttr, this, null, blockCrit);
if (blockCrit.value) { if (noCritTag || blockCrit.value) {
isCritical = false; 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 targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));
const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier); applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier);
const screenMultiplier = new Utils.NumberHolder(1); const screenMultiplier = new Utils.NumberHolder(1);
if (!isCritical) { 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 isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0;
const sourceTypes = source.getTypes(); const sourceTypes = source.getTypes();
@ -2078,6 +2112,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical = false; isCritical = false;
result = HitResult.EFFECTIVE; result = HitResult.EFFECTIVE;
} }
result = result!; // telling TS compiler that result is defined!
if (!result) { if (!result) {
if (!typeMultiplier.value) { if (!typeMultiplier.value) {
@ -2182,7 +2217,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (damage) { 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); destinyTag?.lapse(attacker, BattlerTagLapseType.CUSTOM);
} }
} }
@ -2282,7 +2317,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isMax(): boolean { isMax(): boolean {
const maxForms = [SpeciesFormKey.GIGANTAMAX, SpeciesFormKey.GIGANTAMAX_RAPID, SpeciesFormKey.GIGANTAMAX_SINGLE, SpeciesFormKey.ETERNAMAX] as string[]; 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 { 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; 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); const cancelled = new Utils.BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled); applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled);
@ -2311,18 +2346,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** @overload */ /** @overload */
getTag(tagType: BattlerTagType): BattlerTag; getTag(tagType: BattlerTagType): BattlerTag | null;
/** @overload */ /** @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) { if (!this.summonData) {
return null; return null;
} }
return tagType instanceof Function return (tagType instanceof Function
? this.summonData.tags.find(t => t instanceof tagType) ? 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)) { 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) { for (const tag of source.summonData.tags) {
// bypass yawn, and infatuation as those can not be passed via Baton Pass // bypass those can not be passed via Baton Pass
if (tag.sourceMove === Moves.YAWN || tag.tagType === BattlerTagType.INFATUATED) { const excludeTagTypes = new Set([BattlerTagType.DROWSY, BattlerTagType.INFATUATED, BattlerTagType.FIRE_BOOST]);
if (excludeTagTypes.has(tag.tagType)) {
continue; continue;
} }
@ -2426,7 +2464,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.getMoveHistory().push(turnMove); this.getMoveHistory().push(turnMove);
} }
getLastXMoves(turnCount?: integer): TurnMove[] { getLastXMoves(turnCount: integer = 0): TurnMove[] {
const moveHistory = this.getMoveHistory(); const moveHistory = this.getMoveHistory();
return moveHistory.slice(turnCount >= 0 ? Math.max(moveHistory.length - (turnCount || 1), 0) : 0, moveHistory.length).reverse(); 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; let frameThreshold: number;
sprite.anims.pause(); 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), delay: Utils.fixedInt(delay),
repeat: -1, repeat: -1,
callback: () => { callback: () => {
@ -2516,7 +2554,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
while (frameProgress > frameThreshold) { while (frameProgress > frameThreshold) {
if (sprite.anims.duration) { if (sprite.anims.duration) {
sprite.anims.nextFrame(); sprite.anims.nextFrame();
tintSprite.anims.nextFrame(); tintSprite?.anims.nextFrame();
} }
frameProgress -= frameThreshold; frameProgress -= frameThreshold;
} }
@ -2524,7 +2562,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
rate *= 0.99; rate *= 0.99;
cry.setRate(rate); cry.setRate(rate);
} else { } else {
faintCryTimer.destroy(); faintCryTimer?.destroy();
faintCryTimer = null; faintCryTimer = null;
if (callback) { if (callback) {
callback(); callback();
@ -2583,9 +2621,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let frameThreshold: number; let frameThreshold: number;
sprite.anims.pause(); 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), delay: Utils.fixedInt(delay),
repeat: -1, repeat: -1,
callback: () => { callback: () => {
@ -2595,7 +2633,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
while (frameProgress > frameThreshold) { while (frameProgress > frameThreshold) {
if (sprite.anims.duration) { if (sprite.anims.duration) {
sprite.anims.nextFrame(); sprite.anims.nextFrame();
tintSprite.anims.nextFrame(); tintSprite?.anims.nextFrame();
} }
frameProgress -= frameThreshold; frameProgress -= frameThreshold;
} }
@ -2612,7 +2650,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
fusionCry.setRate(rate); fusionCry.setRate(rate);
} }
if ((!cry || cry.pendingRemove) && (!fusionCry || fusionCry.pendingRemove)) { if ((!cry || cry.pendingRemove) && (!fusionCry || fusionCry.pendingRemove)) {
faintCryTimer.destroy(); faintCryTimer?.destroy();
faintCryTimer = null; faintCryTimer = null;
if (callback) { if (callback) {
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); 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 (effect !== StatusEffect.FAINT) {
if (overrideStatus ? this.status?.effect === effect : this.status) { if (overrideStatus ? this.status?.effect === effect : this.status) {
return false; return false;
@ -2694,7 +2732,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
break; break;
case StatusEffect.FREEZE: 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; return false;
} }
break; break;
@ -2718,7 +2756,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return true; 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)) { if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) {
return false; return false;
} }
@ -2732,7 +2770,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (asPhase) { 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; 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); this.status = new Status(effect, 0, statusCureTurn?.value);
if (effect !== StatusEffect.FAINT) { if (effect !== StatusEffect.FAINT) {
@ -2780,7 +2820,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!revive && lastStatus === StatusEffect.FAINT) { if (!revive && lastStatus === StatusEffect.FAINT) {
return; return;
} }
this.status = undefined; this.status = null;
if (lastStatus === StatusEffect.SLEEP) { if (lastStatus === StatusEffect.SLEEP) {
this.setFrameRate(12); this.setFrameRate(12);
if (this.getTag(BattlerTagType.NIGHTMARE)) { if (this.getTag(BattlerTagType.NIGHTMARE)) {
@ -2848,16 +2888,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
setFrameRate(frameRate: integer) { setFrameRate(frameRate: integer) {
this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate; this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate;
this.getSprite().play(this.getBattleSpriteKey()); this.getSprite().play(this.getBattleSpriteKey());
this.getTintSprite().play(this.getBattleSpriteKey()); this.getTintSprite()?.play(this.getBattleSpriteKey());
} }
tint(color: number, alpha?: number, duration?: integer, ease?: string) { tint(color: number, alpha?: number, duration?: integer, ease?: string) {
const tintSprite = this.getTintSprite(); const tintSprite = this.getTintSprite();
tintSprite.setTintFill(color); tintSprite?.setTintFill(color);
tintSprite.setVisible(true); tintSprite?.setVisible(true);
if (duration) { if (duration) {
tintSprite.setAlpha(0); tintSprite?.setAlpha(0);
this.scene.tweens.add({ this.scene.tweens.add({
targets: tintSprite, targets: tintSprite,
@ -2866,7 +2906,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
ease: ease || "Linear" ease: ease || "Linear"
}); });
} else { } else {
tintSprite.setAlpha(alpha); tintSprite?.setAlpha(alpha);
} }
} }
@ -2880,32 +2920,32 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
duration: duration, duration: duration,
ease: ease || "Linear", ease: ease || "Linear",
onComplete: () => { onComplete: () => {
tintSprite.setVisible(false); tintSprite?.setVisible(false);
tintSprite.setAlpha(1); tintSprite?.setAlpha(1);
} }
}); });
} else { } else {
tintSprite.setVisible(false); tintSprite?.setVisible(false);
tintSprite.setAlpha(1); tintSprite?.setAlpha(1);
} }
} }
enableMask() { enableMask() {
if (!this.maskEnabled) { if (!this.maskEnabled) {
this.maskSprite = this.getTintSprite(); this.maskSprite = this.getTintSprite();
this.maskSprite.setVisible(true); this.maskSprite?.setVisible(true);
this.maskSprite.setPosition(this.x * this.parentContainer.scale + this.parentContainer.x, this.maskSprite?.setPosition(this.x * this.parentContainer.scale + this.parentContainer.x,
this.y * this.parentContainer.scale + this.parentContainer.y); 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; this.maskEnabled = true;
} }
} }
disableMask() { disableMask() {
if (this.maskEnabled) { if (this.maskEnabled) {
this.maskSprite.setVisible(false); this.maskSprite?.setVisible(false);
this.maskSprite.setPosition(0, 0); this.maskSprite?.setPosition(0, 0);
this.maskSprite.setScale(this.getSpriteScale()); this.maskSprite?.setScale(this.getSpriteScale());
this.maskSprite = null; this.maskSprite = null;
this.maskEnabled = false; this.maskEnabled = false;
} }
@ -2920,7 +2960,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
updateFusionPalette(ignoreOveride?: boolean): void { updateFusionPalette(ignoreOveride?: boolean): void {
if (!this.getFusionSpeciesForm(ignoreOveride)) { 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[`spriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = [];
s.pipelineData[`fusionSpriteColors${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]; const frame = [ sourceFrame, sourceBackFrame, fusionFrame, fusionBackFrame ][c];
canv.width = frame.width; canv.width = frame.width;
canv.height = frame.height; canv.height = frame.height;
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); if (context) {
pixelData.push(imageData.data); 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++) { for (let f = 0; f < 2; f++) {
@ -2978,7 +3021,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const color = Utils.rgbaToInt([r, g, b, a]); const color = Utils.rgbaToInt([r, g, b, a]);
if (variantColorSet.has(color)) { if (variantColorSet.has(color)) {
const mappedPixel = variantColorSet.get(color); const mappedPixel = variantColorSet.get(color);
[ r, g, b, a ] = mappedPixel; if (mappedPixel) {
[ r, g, b, a ] = mappedPixel;
}
} }
} }
if (!spriteColors.find(c => c[0] === r && c[1] === g && c[2] === b)) { if (!spriteColors.find(c => c[0] === r && c[1] === g && c[2] === b)) {
@ -2990,7 +3035,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const fusionSpriteColors = JSON.parse(JSON.stringify(spriteColors)); const fusionSpriteColors = JSON.parse(JSON.stringify(spriteColors));
const pixelColors = []; const pixelColors: number[] = [];
for (let f = 0; f < 2; f++) { for (let f = 0; f < 2; f++) {
for (let i = 0; i < pixelData[f].length; i += 4) { 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); 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++) { for (let f = 0; f < 2; f++) {
const variantColors = variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey]; const variantColors = variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey];
const variantColorSet = new Map<integer, integer[]>(); const variantColorSet = new Map<integer, integer[]>();
@ -3020,7 +3065,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const color = Utils.rgbaToInt([r, g, b, a]); const color = Utils.rgbaToInt([r, g, b, a]);
if (variantColorSet.has(color)) { if (variantColorSet.has(color)) {
const mappedPixel = variantColorSet.get(color); const mappedPixel = variantColorSet.get(color);
[ r, g, b, a ] = mappedPixel; if (mappedPixel) {
[ r, g, b, a ] = mappedPixel;
}
} }
} }
fusionPixelColors.push(argbFromRgba({ r, g, b, a })); fusionPixelColors.push(argbFromRgba({ r, g, b, a }));
@ -3040,9 +3087,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
Math.random = originalRandom; 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 ] const [ palette, fusionPalette ] = [ paletteColors, fusionPaletteColors ]
.map(paletteColors => { .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 rgbaColors: Map<number, integer[]>;
let hsvColors: Map<number, number[]>; 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; map.set(k, Object.values(rgbaFromArgb(k))); return map;
}, new Map<number, integer[]>()); }, new Map<number, integer[]>());
hsvColors = Array.from(rgbaColors.keys()).reduce((map: Map<number, number[]>, k: number) => { 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])); map.set(k, Utils.rgbToHsv(rgb[0], rgb[1], rgb[2]));
return map; return map;
}, new Map<number, number[]>()); }, new Map<number, number[]>());
for (let c = keys.length - 1; c >= 0; c--) { 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++) { 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]); const diff = Math.abs(hsv[0] - hsv2[0]);
if (diff < 30 || diff >= 330) { if (diff < 30 || diff >= 330) {
if (mappedColors.has(keys[c])) { if (mappedColors.has(keys[c])) {
mappedColors.get(keys[c]).push(keys[c2]); mappedColors.get(keys[c])!.push(keys[c2]);
} else { } else {
mappedColors.set(keys[c], [ keys[c2] ]); 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) => { mappedColors.forEach((values: integer[], key: integer) => {
const keyColor = rgbaColors.get(key); const keyColor = rgbaColors.get(key)!;
const valueColors = values.map(v => rgbaColors.get(v)); const valueColors = values.map(v => rgbaColors.get(v)!);
const color = keyColor.slice(0); const color = keyColor.slice(0);
let count = paletteColors.get(key); let count = paletteColors.get(key)!;
for (const value of values) { for (const value of values) {
const valueCount = paletteColors.get(value); const valueCount = paletteColors.get(value);
if (!valueCount) { if (!valueCount) {
@ -3090,10 +3139,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
for (let c = 0; c < 3; c++) { 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) => { values.forEach((value: integer, i: integer) => {
if (paletteColors.has(value)) { if (paletteColors.has(value)) {
const valueCount = paletteColors.get(value); const valueCount = paletteColors.get(value)!;
color[c] += valueColors[i][c] * (valueCount / count); 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); 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); } while (mappedColors.size);
return keys.map(c => Object.values(rgbaFromArgb(c))); 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[`spriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = spriteColors;
s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = fusionSpriteColors; s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = fusionSpriteColors;
}); });
@ -3206,7 +3255,7 @@ export default interface Pokemon {
export class PlayerPokemon extends Pokemon { export class PlayerPokemon extends Pokemon {
public compatibleTms: Moves[]; 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); super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (Overrides.STATUS_OVERRIDE) { if (Overrides.STATUS_OVERRIDE) {
@ -3310,11 +3359,11 @@ export class PlayerPokemon extends Pokemon {
addFriendship(friendship: integer): void { addFriendship(friendship: integer): void {
const starterSpeciesId = this.species.getRootSpeciesId(); 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 = [ const starterData = [
this.scene.gameData.starterData[starterSpeciesId], this.scene.gameData.starterData[starterSpeciesId],
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
].filter(d => d); ].filter(d => !!d);
const amount = new Utils.IntegerHolder(friendship); 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))); const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1)));
if (amount.value > 0) { 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 => { return new Promise(resolve => {
const evolutionSpecies = getPokemonSpecies(evolution.speciesId); const evolutionSpecies = getPokemonSpecies(evolution.speciesId);
const isFusion = evolution instanceof FusionSpeciesFormEvolution; 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 => { return new Promise(resolve => {
this.pauseEvolutions = false; this.pauseEvolutions = false;
// Handles Nincada evolving into Ninjask + Shedinja // Handles Nincada evolving into Ninjask + Shedinja
@ -3411,7 +3466,7 @@ export class PlayerPokemon extends Pokemon {
this.fusionSpecies = getPokemonSpecies(evolution.speciesId); this.fusionSpecies = getPokemonSpecies(evolution.speciesId);
} }
if (evolution.preFormKey !== null) { 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) { if (!isFusion) {
this.formIndex = formIndex; this.formIndex = formIndex;
} else { } else {
@ -3467,10 +3522,10 @@ export class PlayerPokemon extends Pokemon {
const isFusion = evolution instanceof FusionSpeciesFormEvolution; const isFusion = evolution instanceof FusionSpeciesFormEvolution;
const evoSpecies = (!isFusion ? this.species : this.fusionSpecies); 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]; 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); 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.natureOverride = this.natureOverride;
newPokemon.passive = this.passive; newPokemon.passive = this.passive;
@ -3591,7 +3646,7 @@ export class PlayerPokemon extends Pokemon {
this.scene.removePartyMemberModifiers(fusedPartyMemberIndex); this.scene.removePartyMemberModifiers(fusedPartyMemberIndex);
this.scene.getParty().splice(fusedPartyMemberIndex, 1)[0]; this.scene.getParty().splice(fusedPartyMemberIndex, 1)[0];
const newPartyMemberIndex = this.scene.getParty().indexOf(this); 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(); pokemon.destroy();
this.updateFusionPalette(); this.updateFusionPalette();
resolve(); resolve();
@ -3611,9 +3666,9 @@ export class PlayerPokemon extends Pokemon {
/** Returns a deep copy of this Pokemon's moveset array */ /** Returns a deep copy of this Pokemon's moveset array */
copyMoveset(): PokemonMove[] { copyMoveset(): PokemonMove[] {
const newMoveset = []; const newMoveset : PokemonMove[] = [];
this.moveset.forEach(move => 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; 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 */ /** To indicate of the instance was populated with a dataSource -> e.g. loaded & populated from session data */
public readonly isPopulatedFromDataSource: boolean; 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, 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.trainerSlot = trainerSlot;
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource 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; let speciesId = species.speciesId;
while ((prevolution = pokemonPrevolutions[speciesId])) { while ((prevolution = pokemonPrevolutions[speciesId])) {
const evolution = pokemonEvolutions[prevolution].find(pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey())); 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); evolution.condition.enforceFunc(this);
} }
speciesId = prevolution; speciesId = prevolution;
@ -3738,7 +3793,7 @@ export class EnemyPokemon extends Pokemon {
getNextMove(): QueuedMove { getNextMove(): QueuedMove {
// If this Pokemon has a move already queued, return it. // If this Pokemon has a move already queued, return it.
const queuedMove = this.getMoveQueue().length 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; : null;
if (queuedMove) { if (queuedMove) {
if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP)) { 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 // 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 no moves are left, use Struggle. Otherwise, continue with move selection
if (movePool.length) { if (movePool.length) {
// If there's only 1 move in the move pool, use it. // If there's only 1 move in the move pool, use it.
if (movePool.length === 1) { 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. // If a move is forced because of Encore, use it.
const encoreTag = this.getTag(EncoreTag) as EncoreTag; const encoreTag = this.getTag(EncoreTag) as EncoreTag;
if (encoreTag) { if (encoreTag) {
const encoreMove = movePool.find(m => m.moveId === encoreTag.moveId); const encoreMove = movePool.find(m => m?.moveId === encoreTag.moveId);
if (encoreMove) { if (encoreMove) {
return { move: encoreMove.moveId, targets: this.getNextTargets(encoreMove.moveId) }; return { move: encoreMove.moveId, targets: this.getNextTargets(encoreMove.moveId) };
} }
} }
switch (this.aiType) { switch (this.aiType) {
case AiType.RANDOM: // No enemy should spawn with this AI type in-game 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) }; return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM: case AiType.SMART_RANDOM:
case AiType.SMART: 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`. * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`.
*/ */
const moveScores = movePool.map(() => 0); 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) { for (const m in movePool) {
const pokemonMove = movePool[m]; const pokemonMove = movePool[m]!; // TODO: is the bang correct?
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
let moveScore = moveScores[m]; let moveScore = moveScores[m];
@ -3860,8 +3915,8 @@ export class EnemyPokemon extends Pokemon {
r++; r++;
} }
} }
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); 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] }; return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] };
} }
} }
@ -3924,7 +3979,7 @@ export class EnemyPokemon extends Pokemon {
} }
const thresholds: integer[] = []; const thresholds: integer[] = [];
let totalWeight: integer; let totalWeight: integer = 0;
targetWeights.reduce((total: integer, w: integer) => { targetWeights.reduce((total: integer, w: integer) => {
total += w; total += w;
thresholds.push(total); thresholds.push(total);
@ -3938,7 +3993,7 @@ export class EnemyPokemon extends Pokemon {
* is greater than that random number. * is greater than that random number.
*/ */
const randValue = this.scene.randBattleSeedInt(totalWeight); const randValue = this.scene.randBattleSeedInt(totalWeight);
let targetIndex: integer; let targetIndex: integer = 0;
thresholds.every((t, i) => { thresholds.every((t, i) => {
if (randValue >= t) { if (randValue >= t) {
@ -4114,7 +4169,7 @@ export class EnemyPokemon extends Pokemon {
addToParty(pokeballType: PokeballType) { addToParty(pokeballType: PokeballType) {
const party = this.scene.getParty(); const party = this.scene.getParty();
let ret: PlayerPokemon = null; let ret: PlayerPokemon | null = null;
if (party.length < 6) { if (party.length < 6) {
this.pokeball = pokeballType; this.pokeball = pokeballType;
@ -4163,15 +4218,15 @@ export class PokemonSummonData {
public abilitySuppressed: boolean = false; public abilitySuppressed: boolean = false;
public abilitiesApplied: Abilities[] = []; public abilitiesApplied: Abilities[] = [];
public speciesForm: PokemonSpeciesForm; public speciesForm: PokemonSpeciesForm | null;
public fusionSpeciesForm: PokemonSpeciesForm; public fusionSpeciesForm: PokemonSpeciesForm;
public ability: Abilities = Abilities.NONE; public ability: Abilities = Abilities.NONE;
public gender: Gender; public gender: Gender;
public fusionGender: Gender; public fusionGender: Gender;
public stats: integer[]; public stats: integer[];
public moveset: PokemonMove[]; public moveset: (PokemonMove | null)[];
// If not initialized this value will not be populated from save data. // If not initialized this value will not be populated from save data.
public types: Type[] = null; public types: Type[] = [];
} }
export class PokemonBattleData { 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. // Determine the title to include based on the configuration and includeTitle flag.
let title = includeTitle && this.config.title ? this.config.title : null; 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))) { 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 // 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, "_")}`); 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, "_")}`); 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 the formatted name, including the title if it is set.
return title ? `${title} ${name}` : name; return title ? `${title} ${name}` : name;
} }
@ -206,7 +208,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
} }
getPartyLevels(waveIndex: integer): integer[] { getPartyLevels(waveIndex: integer): integer[] {
const ret = []; const ret: number[] = [];
const partyTemplate = this.getPartyTemplate(); const partyTemplate = this.getPartyTemplate();
const difficultyWaveIndex = this.scene.gameMode.getWaveForDifficulty(waveIndex); const difficultyWaveIndex = this.scene.gameMode.getWaveForDifficulty(waveIndex);
@ -255,7 +257,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
genPartyMember(index: integer): EnemyPokemon { genPartyMember(index: integer): EnemyPokemon {
const battle = this.scene.currentBattle; const battle = this.scene.currentBattle;
const level = battle.enemyLevels[index]; const level = battle.enemyLevels?.[index]!; // TODO: is this bang correct?
let ret: EnemyPokemon; 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) // 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; 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) // 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 !species.some(s => AlreadyUsedSpecies.includes(s));
} }
return !AlreadyUsedSpecies.includes(species); return !AlreadyUsedSpecies.includes(species);
}); }).flat();
// Filter out the species that are already in the enemy party from the partner trainer species pool // Filter out the species that are already in the enemy party from the partner trainer species pool
const speciesPoolPartnerFiltered = speciesPoolPartner.filter(species => { 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 !species.some(s => AlreadyUsedSpecies.includes(s));
} }
return !AlreadyUsedSpecies.includes(species); 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) // 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); 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)); }, 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) { if (maxScorePartyMemberIndexes.length > 1) {
let rand: integer; let rand: integer;
this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2); this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2);
return maxScorePartyMemberIndexes[rand]; return maxScorePartyMemberIndexes[rand!];
} }
return maxScorePartyMemberIndexes[0]; return maxScorePartyMemberIndexes[0];
@ -497,6 +499,9 @@ export default class Trainer extends Phaser.GameObjects.Container {
return 0.45; return 0.45;
case PartyMemberStrength.STRONGER: case PartyMemberStrength.STRONGER:
return 0.375; 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 { BattlePhase, MovePhase, PokemonHealPhase } from "./phases";
import { getTypeRgb } from "./data/type"; import { getTypeRgb } from "./data/type";
import { getPokemonNameWithAffix } from "./messages"; import { getPokemonNameWithAffix } from "./messages";
import { SemiInvulnerableTag } from "./data/battler-tags";
export class FormChangePhase extends EvolutionPhase { export class FormChangePhase extends EvolutionPhase {
private formChange: SpeciesFormChange; private formChange: SpeciesFormChange;
@ -194,7 +195,7 @@ export class QuietFormChangePhase extends BattlePhase {
const preName = getPokemonNameWithAffix(this.pokemon); const preName = getPokemonNameWithAffix(this.pokemon);
if (!this.pokemon.isOnField()) { if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) {
this.pokemon.changeForm(this.formChange).then(() => { this.pokemon.changeForm(this.formChange).then(() => {
this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); 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