Merge branch 'refs/heads/beta' into beta_fusion_names

# Conflicts:
#	src/data/pokemon-species.ts
This commit is contained in:
mercurius-00 2024-08-09 11:31:19 +08:00
commit c2f711313d
503 changed files with 33887 additions and 8349 deletions

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

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

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "aqua_admin_m.png",
"image": "archer.png",
"format": "RGBA8888",
"size": {
"w": 80,
@ -38,4 +38,4 @@
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}
}

View File

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 645 B

View File

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

View File

Before

Width:  |  Height:  |  Size: 736 B

After

Width:  |  Height:  |  Size: 736 B

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "magma_admin_f.png",
"image": "bryony.png",
"format": "RGBA8888",
"size": {
"w": 80,
@ -38,4 +38,4 @@
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}
}

View File

Before

Width:  |  Height:  |  Size: 671 B

After

Width:  |  Height:  |  Size: 671 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
{
"textures": [
{
"image": "flare_admin_f.png",
"image": "jupiter.png",
"format": "RGBA8888",
"size": {
"w": 80,
@ -38,4 +38,4 @@
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:831f5748dad92911b10a1cb358ee2dae:a3bf81bbaa3b49cad5e0e549cf94563b:bb6befc9383c9c08837183ae2a7a80c1$"
}
}
}

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 451 B

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 435 B

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 496 B

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 B

After

Width:  |  Height:  |  Size: 499 B

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import { BattleStat, getBattleStatName } from "./battle-stat";
import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages";
import { Weather, WeatherType } from "./weather";
import { BattlerTag, GroundedTag } from "./battler-tags";
import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit } from "./move";
@ -120,7 +120,7 @@ export class Ability implements Localizable {
type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>;
type AbAttrCondition = (pokemon: Pokemon) => boolean;
type PokemonAttackCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
type PokemonStatChangeCondition = (target: Pokemon, statsChanged: BattleStat[], levels: integer) => boolean;
@ -132,11 +132,11 @@ export abstract class AbAttr {
this.showAbility = showAbility;
}
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
return null;
}
@ -226,7 +226,7 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
}
for (const statChangePhase of statChangePhases) {
if (!this.selfTarget && !statChangePhase.getPokemon().summonData) {
if (!this.selfTarget && !statChangePhase.getPokemon()?.summonData) {
pokemon.scene.pushPhase(statChangePhase);
} else { // TODO: This causes the ability bar to be shown at the wrong time
pokemon.scene.unshiftPhase(statChangePhase);
@ -240,7 +240,7 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean;
export class PreDefendAbAttr extends AbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -352,14 +352,14 @@ export class PreDefendMoveDamageToOneAbAttr extends ReceivedMoveDamageMultiplier
* @see {@linkcode getCondition}
*/
export class TypeImmunityAbAttr extends PreDefendAbAttr {
private immuneType: Type;
private condition: AbAttrCondition;
private immuneType: Type | null;
private condition: AbAttrCondition | null;
constructor(immuneType: Type, condition?: AbAttrCondition) {
constructor(immuneType: Type | null, condition?: AbAttrCondition) {
super();
this.immuneType = immuneType;
this.condition = condition;
this.condition = condition!; // TODO: is this bang correct?
}
/**
@ -386,7 +386,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
return false;
}
getCondition(): AbAttrCondition {
override getCondition(): AbAttrCondition | null {
return this.condition;
}
}
@ -491,11 +491,54 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
}
export class PostDefendAbAttr extends AbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
return false;
}
}
/**
* Applies the effects of Gulp Missile when the user is hit by an attack.
* @extends PostDefendAbAttr
*/
export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr {
constructor() {
super(true);
}
/**
* Damages the attacker and triggers the secondary effect based on the form or the BattlerTagType.
* @param {Pokemon} pokemon - The defending Pokemon.
* @param passive - n/a
* @param {Pokemon} attacker - The attacking Pokemon.
* @param {Move} move - The move being used.
* @param {HitResult} hitResult - n/a
* @param {any[]} args - n/a
* @returns Whether the effects of the ability are applied.
*/
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
const battlerTag = pokemon.getTag(GulpMissileTag);
if (!battlerTag || move.category === MoveCategory.STATUS || pokemon.getTag(SemiInvulnerableTag)) {
return false;
}
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
if (!cancelled.value) {
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), HitResult.OTHER);
}
if (battlerTag.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ BattleStat.DEF ], -1));
} else {
attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon);
}
pokemon.removeTag(battlerTag.tagType);
return true;
}
}
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
@ -838,7 +881,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr
export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
private chance: integer;
private tagType: BattlerTagType;
private turnCount: integer;
private turnCount: integer | undefined;
constructor(chance: integer, tagType: BattlerTagType, turnCount?: integer) {
super();
@ -875,7 +918,7 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr {
}
getCondition(): AbAttrCondition {
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
}
}
@ -1066,7 +1109,7 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr {
}
export class PreAttackAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -1079,7 +1122,7 @@ export class PreAttackAbAttr extends AbAttr {
export class MoveEffectChanceMultiplierAbAttr extends AbAttr {
private chanceMultiplier: number;
constructor(chanceMultiplier?: number) {
constructor(chanceMultiplier: number) {
super(true);
this.chanceMultiplier = chanceMultiplier;
}
@ -1460,7 +1503,7 @@ export class FieldMovePowerBoostAbAttr extends AbAttr {
this.powerMultiplier = powerMultiplier;
}
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
applyPreAttack(pokemon: Pokemon | null, passive: boolean | null, defender: Pokemon | null, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
@ -1514,14 +1557,14 @@ export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr
export class BattleStatMultiplierAbAttr extends AbAttr {
private battleStat: BattleStat;
private multiplier: number;
private condition: PokemonAttackCondition;
private condition: PokemonAttackCondition | null;
constructor(battleStat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) {
super(false);
this.battleStat = battleStat;
this.multiplier = multiplier;
this.condition = condition;
this.condition = condition!; // TODO: is this bang correct?
}
applyBattleStat(pokemon: Pokemon, passive: boolean, battleStat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise<boolean> {
@ -1550,7 +1593,7 @@ export class PostAttackAbAttr extends AbAttr {
* applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr}
* for an example of an effect that does not require a damaging move.
*/
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
// When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements.
// If attackRequired is false, we always defer to the secondary requirements.
if (this.attackCondition(pokemon, defender, move)) {
@ -1563,18 +1606,18 @@ export class PostAttackAbAttr extends AbAttr {
/**
* This method is only called after {@link applyPostAttack} has already been applied. Use this for handling checks specific to the ability in question.
*/
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
return false;
}
}
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
private stealCondition: PokemonAttackCondition;
private stealCondition: PokemonAttackCondition | null;
constructor(stealCondition?: PokemonAttackCondition) {
super();
this.stealCondition = stealCondition;
this.stealCondition = stealCondition!; // TODO: is this bang correct?
}
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
@ -1658,12 +1701,12 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
}
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
private condition: PokemonDefendCondition;
private condition: PokemonDefendCondition | null;
constructor(condition?: PokemonDefendCondition) {
super();
this.condition = condition;
this.condition = condition!; // TODO: is this bang correct?
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
@ -2154,17 +2197,17 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
}
if (
target.getAbility().hasAttr(UncopiableAbilityAbAttr) &&
target!.getAbility().hasAttr(UncopiableAbilityAbAttr) &&
// Wonder Guard is normally uncopiable so has the attribute, but Trace specifically can copy it
!(pokemon.hasAbility(Abilities.TRACE) && target.getAbility().id === Abilities.WONDER_GUARD)
!(pokemon.hasAbility(Abilities.TRACE) && target!.getAbility().id === Abilities.WONDER_GUARD)
) {
return false;
}
this.target = target;
this.targetAbilityName = allAbilities[target.getAbility().id].name;
pokemon.summonData.ability = target.getAbility().id;
setAbilityRevealed(target);
this.target = target!;
this.targetAbilityName = allAbilities[target!.getAbility().id].name;
pokemon.summonData.ability = target!.getAbility().id;
setAbilityRevealed(target!);
pokemon.updateInfo();
return true;
@ -2211,7 +2254,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt
}
for (const pokemon of allowedParty) {
if (this.statusEffect.includes(pokemon.status?.effect)) {
if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) {
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
pokemon.resetStatus(false);
pokemon.updateInfo();
@ -2267,6 +2310,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
target = targets[0];
}
target = target!; // compiler doesn't know its guranteed to be defined
pokemon.summonData.speciesForm = target.getSpeciesForm();
pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm();
pokemon.summonData.ability = target.getAbility().id;
@ -2274,7 +2318,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
pokemon.summonData.fusionGender = target.getFusionGender();
pokemon.summonData.stats = [ pokemon.stats[Stat.HP] ].concat(target.stats.slice(1));
pokemon.summonData.battleStats = target.summonData.battleStats.slice(0);
pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m.moveId, m.ppUsed, m.ppUp));
pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m!.moveId, m!.ppUsed, m!.ppUp)); // TODO: are those bangs correct?
pokemon.summonData.types = target.getTypes();
pokemon.scene.playSound("PRSFX- Transform");
@ -2321,7 +2365,7 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
* @returns {boolean} Returns true if the weather clears, otherwise false.
*/
applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
const weatherType = pokemon.scene.arena.weather.weatherType;
const weatherType = pokemon.scene.arena.weather?.weatherType;
let turnOffWeather = false;
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
@ -2402,18 +2446,18 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr {
}
export class PreStatChangeAbAttr extends AbAttr {
applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreStatChange(pokemon: Pokemon | null, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false;
}
}
export class ProtectStatAbAttr extends PreStatChangeAbAttr {
private protectedStat: BattleStat;
private protectedStat: BattleStat | null;
constructor(protectedStat?: BattleStat) {
super();
this.protectedStat = protectedStat;
this.protectedStat = protectedStat!; // TODO: is this bang correct?
}
applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -2429,7 +2473,7 @@ export class ProtectStatAbAttr extends PreStatChangeAbAttr {
return i18next.t("abilityTriggers:protectStat", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName,
statName: this.protectedStat !== undefined ? getBattleStatName(this.protectedStat) : i18next.t("battle:stats")
statName: this.protectedStat ? getBattleStatName(this.protectedStat) : i18next.t("battle:stats")
});
}
}
@ -2469,7 +2513,7 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
}
export class PreSetStatusAbAttr extends AbAttr {
applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -2680,7 +2724,7 @@ export class BlockStatusDamageAbAttr extends AbAttr {
* @returns Returns true if status damage is blocked
*/
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.effects.includes(pokemon.status?.effect)) {
if (pokemon.status && this.effects.includes(pokemon.status.effect)) {
cancelled.value = true;
return true;
}
@ -2719,7 +2763,7 @@ export class IncrementMovePriorityAbAttr extends AbAttr {
export class IgnoreContactAbAttr extends AbAttr { }
export class PreWeatherEffectAbAttr extends AbAttr {
applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather | null, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -2750,7 +2794,7 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr {
constructor(affectsImmutable?: boolean) {
super();
this.affectsImmutable = affectsImmutable;
this.affectsImmutable = affectsImmutable!; // TODO: is this bang correct?
}
applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -2801,7 +2845,7 @@ function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
return false;
}
const weatherType = pokemon.scene.arena.weather?.weatherType;
return weatherType && weatherTypes.indexOf(weatherType) > -1;
return !!weatherType && weatherTypes.indexOf(weatherType) > -1;
};
}
@ -2810,15 +2854,15 @@ function getAnticipationCondition(): AbAttrCondition {
for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) {
// move is super effective
if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true) >= 2) {
if (move!.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move!.getMove().type, opponent, true) >= 2) { // TODO: is this bang correct?
return true;
}
// move is a OHKO
if (move.getMove().hasAttr(OneHitKOAttr)) {
if (move!.getMove().hasAttr(OneHitKOAttr)) { // TODO: is this bang correct?
return true;
}
// edge case for hidden power, type is computed
if (move.getMove().id === Moves.HIDDEN_POWER) {
if (move!.getMove().id === Moves.HIDDEN_POWER) { // TODO: is this bang correct?
const iv_val = Math.floor(((opponent.ivs[Stat.HP] & 1)
+(opponent.ivs[Stat.ATK] & 1) * 2
+(opponent.ivs[Stat.DEF] & 1) * 4
@ -2866,21 +2910,21 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
let movePower = 0;
for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) {
if (move.getMove() instanceof StatusMove) {
if (move!.getMove() instanceof StatusMove) { // TODO: is this bang correct?
movePower = 1;
} else if (move.getMove().hasAttr(OneHitKOAttr)) {
} else if (move!.getMove().hasAttr(OneHitKOAttr)) { // TODO: is this bang correct?
movePower = 150;
} else if (move.getMove().id === Moves.COUNTER || move.getMove().id === Moves.MIRROR_COAT || move.getMove().id === Moves.METAL_BURST) {
} else if (move!.getMove().id === Moves.COUNTER || move!.getMove().id === Moves.MIRROR_COAT || move!.getMove().id === Moves.METAL_BURST) { // TODO: are those bangs correct?
movePower = 120;
} else if (move.getMove().power === -1) {
} else if (move!.getMove().power === -1) { // TODO: is this bang correct?
movePower = 80;
} else {
movePower = move.getMove().power;
movePower = move!.getMove().power; // TODO: is this bang correct?
}
if (movePower > maxPowerSeen) {
maxPowerSeen = movePower;
maxMove = move.getName();
maxMove = move!.getName(); // TODO: is this bang correct?
}
}
}
@ -2941,7 +2985,7 @@ export class PostWeatherLapseAbAttr extends AbAttr {
this.weatherTypes = weatherTypes;
}
applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather, args: any[]): boolean | Promise<boolean> {
applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather | null, args: any[]): boolean | Promise<boolean> {
return false;
}
@ -3024,7 +3068,7 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr
function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition {
return (pokemon: Pokemon) => {
const terrainType = pokemon.scene.arena.terrain?.terrainType;
return terrainType && terrainTypes.indexOf(terrainType) > -1;
return !!terrainType && terrainTypes.indexOf(terrainType) > -1;
};
}
@ -3056,7 +3100,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
* @returns Returns true if healed from status, false if not
*/
applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
if (this.effects.includes(pokemon.status?.effect)) {
if (pokemon.status && this.effects.includes(pokemon.status.effect)) {
if (!pokemon.isFullHp()) {
const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
@ -3292,7 +3336,7 @@ export class FetchBallAbAttr extends PostTurnAbAttr {
*/
applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
const lastUsed = pokemon.scene.currentBattle.lastUsedPokeball;
if (lastUsed !== null && pokemon.isPlayer) {
if (lastUsed !== null && !!pokemon.isPlayer) {
pokemon.scene.pokeballCounts[lastUsed]++;
pokemon.scene.currentBattle.lastUsedPokeball = null;
pokemon.scene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed) }));
@ -3573,7 +3617,8 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr {
const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot;
if (postBattleLoot.length) {
const randItem = Utils.randSeedItem(postBattleLoot);
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) {
//@ts-ignore - TODO see below
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) { // TODO: fix. This is a promise!?
postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name }));
return true;
@ -3605,7 +3650,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
* @returns {boolean} Returns true if the weather clears, otherwise false.
*/
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const weatherType = pokemon.scene.arena.weather.weatherType;
const weatherType = pokemon.scene.arena.weather?.weatherType;
let turnOffWeather = false;
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
@ -4077,7 +4122,7 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
const turnCommand =
pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
const isCommandFight = turnCommand?.command === Command.FIGHT;
const move = allMoves[turnCommand.move?.move];
const move = turnCommand?.move?.move ?allMoves[turnCommand.move.move] : null;
const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
if (isCommandFight && isDamageMove) {
@ -4096,14 +4141,14 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
async function applyAbAttrsInternal<TAttr extends AbAttr>(
attrType: Constructor<TAttr>,
pokemon: Pokemon,
pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<TAttr>,
args: any[],
showAbilityInstant: boolean = false,
quiet: boolean = false,
) {
for (const passive of [false, true]) {
if (!pokemon.canApplyAbility(passive)) {
if (!pokemon?.canApplyAbility(passive)) {
continue;
}
@ -4151,7 +4196,7 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
}
}
export function applyAbAttrs(attrType: Constructor<AbAttr>, pokemon: Pokemon, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
export function applyAbAttrs(attrType: Constructor<AbAttr>, pokemon: Pokemon, cancelled: Utils.BooleanHolder | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<AbAttr>(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, cancelled, args), args);
}
@ -4161,13 +4206,13 @@ export function applyPostBattleInitAbAttrs(attrType: Constructor<PostBattleInitA
}
export function applyPreDefendAbAttrs(attrType: Constructor<PreDefendAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, simulated);
}
export function applyPostDefendAbAttrs(attrType: Constructor<PostDefendAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args);
}
@ -4197,12 +4242,12 @@ export function applyFieldBattleStatMultiplierAbAttrs(attrType: Constructor<Fiel
}
export function applyPreAttackAbAttrs(attrType: Constructor<PreAttackAbAttr>,
pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise<void> {
pokemon: Pokemon, defender: Pokemon | null, move: Move, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args);
}
export function applyPostAttackAbAttrs(attrType: Constructor<PostAttackAbAttr>,
pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args);
}
@ -4227,7 +4272,7 @@ export function applyPreSwitchOutAbAttrs(attrType: Constructor<PreSwitchOutAbAtt
}
export function applyPreStatChangeAbAttrs(attrType: Constructor<PreStatChangeAbAttr>,
pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreStatChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, stat, cancelled, args), args);
}
@ -4237,7 +4282,7 @@ export function applyPostStatChangeAbAttrs(attrType: Constructor<PostStatChangeA
}
export function applyPreSetStatusAbAttrs(attrType: Constructor<PreSetStatusAbAttr>,
pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, effect, cancelled, args), args, false, !simulated);
}
@ -4248,7 +4293,7 @@ export function applyPreApplyBattlerTagAbAttrs(attrType: Constructor<PreApplyBat
}
export function applyPreWeatherEffectAbAttrs(attrType: Constructor<PreWeatherEffectAbAttr>,
pokemon: Pokemon, weather: Weather, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon, weather: Weather | null, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreWeatherDamageAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, weather, cancelled, args), args, true);
}
@ -4263,7 +4308,7 @@ export function applyPostWeatherChangeAbAttrs(attrType: Constructor<PostWeatherC
}
export function applyPostWeatherLapseAbAttrs(attrType: Constructor<PostWeatherLapseAbAttr>,
pokemon: Pokemon, weather: Weather, ...args: any[]): Promise<void> {
pokemon: Pokemon, weather: Weather | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostWeatherLapseAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, weather, args), args);
}
@ -4552,8 +4597,8 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1)
.ignorable(),
new Ability(Abilities.RIVALRY, 4)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.gender !== Gender.GENDERLESS && target.gender !== Gender.GENDERLESS && user.gender === target.gender, 1.25, true)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.gender !== Gender.GENDERLESS && target.gender !== Gender.GENDERLESS && user.gender !== target.gender, 0.75),
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true)
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75),
new Ability(Abilities.STEADFAST, 4)
.attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1),
new Ability(Abilities.SNOW_CLOAK, 4)
@ -4643,7 +4688,8 @@ export function initAbilities() {
.attr(IgnoreOpponentStatChangesAbAttr)
.ignorable(),
new Ability(Abilities.TINTED_LENS, 4)
.attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5),
//@ts-ignore
.attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5), // TODO: fix TS issues
new Ability(Abilities.FILTER, 4)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
.ignorable(),
@ -4725,9 +4771,9 @@ export function initAbilities() {
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.isFullHp(), 0.5)
.ignorable(),
new Ability(Abilities.TOXIC_BOOST, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.PHYSICAL && (user.status?.effect === StatusEffect.POISON || user.status?.effect === StatusEffect.TOXIC), 1.5),
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC), 1.5),
new Ability(Abilities.FLARE_BOOST, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user.status?.effect === StatusEffect.BURN, 1.5),
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN, 1.5),
new Ability(Abilities.HARVEST, 5)
.attr(
PostTurnLootAbAttr,
@ -4760,7 +4806,8 @@ export function initAbilities() {
.attr(WonderSkinAbAttr)
.ignorable(),
new Ability(Abilities.ANALYTIC, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command !== Command.FIGHT, 1.3),
//@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command !== Command.FIGHT, 1.3), // TODO fix TS issues
new Ability(Abilities.ILLUSION, 5)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
@ -4910,7 +4957,7 @@ export function initAbilities() {
new Ability(Abilities.WATER_COMPACTION, 7)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2),
new Ability(Abilities.MERCILESS, 7)
.attr(ConditionalCritAbAttr, (user, target, move) => target.status?.effect === StatusEffect.TOXIC || target.status?.effect === StatusEffect.POISON),
.attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
new Ability(Abilities.SHIELDS_DOWN, 7)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
@ -4922,7 +4969,8 @@ export function initAbilities() {
.bypassFaint()
.partial(),
new Ability(Abilities.STAKEOUT, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2),
//@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2), // TODO: fix TS issues
new Ability(Abilities.WATER_BUBBLE, 7)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2)
@ -5062,7 +5110,8 @@ export function initAbilities() {
new Ability(Abilities.PRISM_ARMOR, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75),
new Ability(Abilities.NEUROFORCE, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25),
//@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25), // TODO: fix TS issues
new Ability(Abilities.INTREPID_SWORD, 8)
.attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true)
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
@ -5087,7 +5136,9 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.unimplemented(),
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(PostDefendGulpMissileAbAttr),
new Ability(Abilities.STALWART, 8)
.attr(BlockRedirectAbAttr),
new Ability(Abilities.STEAM_ENGINE, 8)
@ -5145,8 +5196,10 @@ export function initAbilities() {
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(),
new Ability(Abilities.HUNGER_SWITCH, 8)
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1)
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0)
//@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) // TODO: fix ts-ignore
//@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0) // TODO: fix ts-ignore
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims";
import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangeCallback, StatChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
@ -36,11 +36,11 @@ export class BattlerTag {
public sourceMove: Moves;
public sourceId?: number;
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove: Moves, sourceId?: number) {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number) {
this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
this.turnCount = turnCount;
this.sourceMove = sourceMove;
this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId;
}
@ -66,7 +66,7 @@ export class BattlerTag {
return false;
}
getMoveName(): string {
getMoveName(): string | null {
return this.sourceMove
? allMoves[this.sourceMove].name
: null;
@ -118,6 +118,44 @@ export class RechargingTag extends BattlerTag {
}
}
/**
* BattlerTag representing the "charge phase" of Beak Blast
* Pokemon with this tag will inflict BURN status on any attacker that makes contact.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Beak_Blast_(move) | Beak Blast}
*/
export class BeakBlastChargingTag extends BattlerTag {
constructor() {
super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1, Moves.BEAK_BLAST);
}
onAdd(pokemon: Pokemon): void {
// Play Beak Blast's charging animation
new MoveChargeAnim(ChargeAnim.BEAK_BLAST_CHARGING, this.sourceMove, pokemon).play(pokemon.scene);
// Queue Beak Blast's header message
pokemon.scene.queueMessage(i18next.t("moveTriggers:startedHeatingUpBeak", { pokemonName: getPokemonNameWithAffix(pokemon) }));
}
/**
* Inflicts `BURN` status on attackers that make contact, and causes this tag
* to be removed after the source makes a move (or the turn ends, whichever comes first)
* @param pokemon {@linkcode Pokemon} the owner of this tag
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
* @returns `true` if invoked with the CUSTOM lapse type; `false` otherwise
*/
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
}
return true;
}
return super.lapse(pokemon, lapseType);
}
}
export class TrappedTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
super(tagType, lapseType, turnCount, sourceMove, sourceId);
@ -299,12 +337,12 @@ export class DestinyBondTag extends BattlerTag {
if (lapseType !== BattlerTagLapseType.CUSTOM) {
return super.lapse(pokemon, lapseType);
}
const source = pokemon.scene.getPokemonById(this.sourceId);
if (!source.isFainted()) {
const source = this.sourceId ? pokemon.scene.getPokemonById(this.sourceId) : null;
if (!source?.isFainted()) {
return true;
}
if (source.getAlly() === pokemon) {
if (source?.getAlly() === pokemon) {
return false;
}
@ -330,7 +368,19 @@ export class InfatuatedTag extends BattlerTag {
}
canAdd(pokemon: Pokemon): boolean {
return pokemon.isOppositeGender(pokemon.scene.getPokemonById(this.sourceId));
if (this.sourceId) {
const pkm = pokemon.scene.getPokemonById(this.sourceId);
if (pkm) {
return pokemon.isOppositeGender(pkm);
} else {
console.warn("canAdd: this.sourceId is not a valid pokemon id!", this.sourceId);
return false;
}
} else {
console.warn("canAdd: this.sourceId is undefined");
return false;
}
}
onAdd(pokemon: Pokemon): void {
@ -339,7 +389,7 @@ export class InfatuatedTag extends BattlerTag {
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct?
})
);
}
@ -357,7 +407,7 @@ export class InfatuatedTag extends BattlerTag {
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedLapse", {
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));
@ -410,7 +460,7 @@ export class SeedTag extends BattlerTag {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSeededOnAdd", { 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 {
@ -556,11 +606,11 @@ export class EncoreTag extends BattlerTag {
const movePhase = pokemon.scene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon);
if (movePhase) {
const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId);
const movesetMove = pokemon.getMoveset().find(m => m!.moveId === this.moveId); // TODO: is this bang correct?
if (movesetMove) {
const lastMove = pokemon.getLastXMoves(1)[0];
pokemon.scene.tryReplacePhase((m => m instanceof MovePhase && m.pokemon === pokemon),
new MovePhase(pokemon.scene, pokemon, lastMove.targets, movesetMove));
new MovePhase(pokemon.scene, pokemon, lastMove.targets!, movesetMove)); // TODO: is this bang correct?
}
}
}
@ -580,7 +630,7 @@ export class HelpingHandTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsHelpingHandOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)),
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
pokemonName: getPokemonNameWithAffix(pokemon)
})
);
@ -800,7 +850,7 @@ export class BindTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsBindOnTrap", {
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()
});
}
@ -814,7 +864,7 @@ export class WrapTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsWrapOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
});
}
}
@ -848,7 +898,7 @@ export class ClampTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsClampOnTrap", {
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
@ -895,7 +945,7 @@ export class ThunderCageTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsThunderCageOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
});
}
}
@ -908,7 +958,7 @@ export class InfestationTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsInfestationOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId))
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
});
}
}
@ -1242,6 +1292,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
return highestValue;
}, 0);
highestStat = highestStat!; // tell TS compiler it's defined!
this.stat = highestStat;
switch (this.stat) {
@ -1427,7 +1478,7 @@ export class SaltCuredTag extends BattlerTag {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSaltCuredOnAdd", { 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 {
@ -1474,7 +1525,7 @@ export class CursedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex();
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1647,10 +1698,86 @@ export class StockpilingTag extends BattlerTag {
}
}
/**
* Battler tag for Gulp Missile used by Cramorant.
* @extends BattlerTag
*/
export class GulpMissileTag extends BattlerTag {
constructor(tagType: BattlerTagType, sourceMove: Moves) {
super(tagType, BattlerTagLapseType.CUSTOM, 0, sourceMove);
}
/**
* Gulp Missile's initial form changes are triggered by using Surf and Dive.
* @param {Pokemon} pokemon The Pokemon with Gulp Missile ability.
* @returns Whether the BattlerTag can be added.
*/
canAdd(pokemon: Pokemon): boolean {
const isSurfOrDive = [ Moves.SURF, Moves.DIVE ].includes(this.sourceMove);
const isNormalForm = pokemon.formIndex === 0 && !pokemon.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA) && !pokemon.getTag(BattlerTagType.GULP_MISSILE_PIKACHU);
const isCramorant = pokemon.species.speciesId === Species.CRAMORANT;
return isSurfOrDive && isNormalForm && isCramorant;
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
}
/**
* Tag that makes the target drop all of it type immunities
* and all accuracy checks ignore its evasiveness stat.
*
* Applied by moves: {@linkcode Moves.ODOR_SLEUTH | Odor Sleuth},
* {@linkcode Moves.MIRACLE_EYE | Miracle Eye} and {@linkcode Moves.FORESIGHT | Foresight}.
*
* @extends BattlerTag
* @see {@linkcode ignoreImmunity}
*/
export class ExposedTag extends BattlerTag {
private defenderType: Type;
private allowedTypes: Type[];
constructor(tagType: BattlerTagType, sourceMove: Moves, defenderType: Type, allowedTypes: Type[]) {
super(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove);
this.defenderType = defenderType;
this.allowedTypes = allowedTypes;
}
/**
* When given a battler tag or json representing one, load the data for it.
* @param {BattlerTag | any} source A battler tag
*/
loadTag(source: BattlerTag | any): void {
super.loadTag(source);
this.defenderType = source.defenderType as Type;
this.allowedTypes = source.allowedTypes as Type[];
}
/**
* @param types {@linkcode Type} of the defending Pokemon
* @param moveType {@linkcode Type} of the move targetting it
* @returns `true` if the move should be allowed to target the defender.
*/
ignoreImmunity(type: Type, moveType: Type): boolean {
return type === this.defenderType && this.allowedTypes.includes(moveType);
}
}
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) {
case BattlerTagType.RECHARGING:
return new RechargingTag(sourceMove);
case BattlerTagType.BEAK_BLAST_CHARGING:
return new BeakBlastChargingTag();
case BattlerTagType.FLINCHED:
return new FlinchedTag(sourceMove);
case BattlerTagType.INTERRUPTED:
@ -1741,8 +1868,6 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
case BattlerTagType.NO_CRIT:
return new BattlerTag(tagType, BattlerTagLapseType.AFTER_MOVE, turnCount, sourceMove);
case BattlerTagType.ALWAYS_GET_HIT:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove);
@ -1770,6 +1895,13 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new StockpilingTag(sourceMove);
case BattlerTagType.OCTOLOCK:
return new OctolockTag(sourceId);
case BattlerTagType.IGNORE_GHOST:
return new ExposedTag(tagType, sourceMove, Type.GHOST, [Type.NORMAL, Type.FIGHTING]);
case BattlerTagType.IGNORE_DARK:
return new ExposedTag(tagType, sourceMove, Type.DARK, [Type.PSYCHIC]);
case BattlerTagType.GULP_MISSILE_ARROKUDA:
case BattlerTagType.GULP_MISSILE_PIKACHU:
return new GulpMissileTag(tagType, sourceMove);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -28,21 +28,21 @@ export enum Region {
PALDEA
}
export function getPokemonSpecies(species: Species): PokemonSpecies {
export function getPokemonSpecies(species: Species | Species[]): PokemonSpecies {
// If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block
if (Array.isArray(species)) {
// Pick a random species from the list
species = species[Math.floor(Math.random() * species.length)];
}
if (species >= 2000) {
return allSpecies.find(s => s.speciesId === species);
return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct?
}
return allSpecies[species - 1];
}
export function getPokemonSpeciesForm(species: Species, formIndex: integer): PokemonSpeciesForm {
const retSpecies: PokemonSpecies = species >= 2000
? allSpecies.find(s => s.speciesId === species)
? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct?
: allSpecies[species - 1];
if (formIndex < retSpecies.forms?.length) {
return retSpecies.forms[formIndex];
@ -77,7 +77,7 @@ export abstract class PokemonSpeciesForm {
public formIndex: integer;
public generation: integer;
public type1: Type;
public type2: Type;
public type2: Type | null;
public height: number;
public weight: number;
public ability1: Abilities;
@ -91,7 +91,7 @@ export abstract class PokemonSpeciesForm {
public genderDiffs: boolean;
public isStarterSelectable: boolean;
constructor(type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
constructor(type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs: boolean, isStarterSelectable: boolean) {
this.type1 = type1;
@ -219,7 +219,7 @@ export abstract class PokemonSpeciesForm {
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
}
getSpriteId(female: boolean, formIndex?: integer, shiny?: boolean, variant?: integer, back?: boolean): string {
getSpriteId(female: boolean, formIndex?: integer, shiny?: boolean, variant: integer = 0, back?: boolean): string {
if (formIndex === undefined || this instanceof PokemonForm) {
formIndex = this.formIndex;
}
@ -233,7 +233,7 @@ export abstract class PokemonSpeciesForm {
`${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => config ? config = config[p] : null);
const variantSet = config as VariantSet;
return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant || 0] === 2 ? `_${variant + 1}` : ""}`;
return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant] === 2 ? `_${variant + 1}` : ""}`;
}
getSpriteKey(female: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): string {
@ -249,10 +249,10 @@ export abstract class PokemonSpeciesForm {
* @returns species id if no additional forms, index with formkey if a pokemon with a form
*/
getVariantDataIndex(formIndex?: integer) {
let formkey = null;
let variantDataIndex: integer|string = this.speciesId;
let formkey: string | null = null;
let variantDataIndex: integer | string = this.speciesId;
const species = getPokemonSpecies(this.speciesId);
if (species.forms.length > 0) {
if (species.forms.length > 0 && formIndex !== undefined) {
formkey = species.forms[formIndex]?.formSpriteKey;
if (formkey) {
variantDataIndex = `${this.speciesId}-${formkey}`;
@ -263,7 +263,7 @@ export abstract class PokemonSpeciesForm {
getIconAtlasKey(formIndex?: integer, shiny?: boolean, variant?: integer): string {
const variantDataIndex = this.getVariantDataIndex(formIndex);
const isVariant = shiny && variantData[variantDataIndex] && variantData[variantDataIndex][variant];
const isVariant = shiny && variantData[variantDataIndex] && (variant !== undefined && variantData[variantDataIndex][variant]);
return `pokemon_icons_${this.generation}${isVariant ? "v" : ""}`;
}
@ -276,7 +276,7 @@ export abstract class PokemonSpeciesForm {
let ret = this.speciesId.toString();
const isVariant = shiny && variantData[variantDataIndex] && variantData[variantDataIndex][variant];
const isVariant = shiny && variantData[variantDataIndex] && (variant !== undefined && variantData[variantDataIndex][variant]);
if (shiny && !isVariant) {
ret += "s";
@ -334,7 +334,7 @@ export abstract class PokemonSpeciesForm {
let ret = speciesId.toString();
const forms = getPokemonSpecies(speciesId).forms;
if (forms.length) {
if (formIndex >= forms.length) {
if (formIndex !== undefined && formIndex >= forms.length) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${getPokemonSpecies(speciesId).getName()} with only ${forms.length || 0} forms`);
formIndex = Math.min(formIndex, forms.length - 1);
}
@ -430,7 +430,7 @@ export abstract class PokemonSpeciesForm {
let config = variantData;
spritePath.split("/").map(p => config ? config = config[p] : null);
const variantSet = config as VariantSet;
if (variantSet && variantSet[variant] === 1) {
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
const populateVariantColors = (key: string): Promise<void> => {
return new Promise(resolve => {
if (variantColorCache.hasOwnProperty(key)) {
@ -459,7 +459,7 @@ export abstract class PokemonSpeciesForm {
cry(scene: BattleScene, soundConfig?: Phaser.Types.Sound.SoundConfig, ignorePlay?: boolean): AnySound {
const cryKey = this.getCryKey(this.formIndex);
let cry = scene.sound.get(cryKey) as AnySound;
let cry: AnySound | null = scene.sound.get(cryKey) as AnySound;
if (cry?.pendingRemove) {
cry = null;
}
@ -484,30 +484,32 @@ export abstract class PokemonSpeciesForm {
const frame = sourceFrame;
canvas.width = frame.width;
canvas.height = frame.height;
context.drawImage(sourceImage, frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height);
const imageData = context.getImageData(frame.cutX, frame.cutY, frame.width, frame.height);
const pixelData = imageData.data;
context?.drawImage(sourceImage, frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height);
const imageData = context?.getImageData(frame.cutX, frame.cutY, frame.width, frame.height);
const pixelData = imageData?.data;
const pixelColors: number[] = [];
for (let i = 0; i < pixelData.length; i += 4) {
if (pixelData[i + 3]) {
const pixel = pixelData.slice(i, i + 4);
const [ r, g, b, a ] = pixel;
if (!spriteColors.find(c => c[0] === r && c[1] === g && c[2] === b)) {
spriteColors.push([ r, g, b, a ]);
if (pixelData?.length !== undefined) {
for (let i = 0; i < pixelData.length; i += 4) {
if (pixelData[i + 3]) {
const pixel = pixelData.slice(i, i + 4);
const [ r, g, b, a ] = pixel;
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) {
const total = pixelData.slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0);
if (!total) {
continue;
for (let i = 0; i < pixelData.length; i += 4) {
const total = pixelData.slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0);
if (!total) {
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;
Math.random = () => Phaser.Math.RND.realInRange(0, 1);
@ -529,15 +531,15 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
public mythical: boolean;
public species: string;
public growthRate: GrowthRate;
public malePercent: number;
public malePercent: number | null;
public genderDiffs: boolean;
public canChangeForm: boolean;
public forms: PokemonForm[];
constructor(id: Species, generation: integer, subLegendary: boolean, legendary: boolean, mythical: boolean, species: string,
type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, growthRate: GrowthRate, malePercent: number,
catchRate: integer, baseFriendship: integer, baseExp: integer, growthRate: GrowthRate, malePercent: number | null,
genderDiffs: boolean, canChangeForm?: boolean, ...forms: PokemonForm[]) {
super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd,
catchRate, baseFriendship, baseExp, genderDiffs, false);
@ -566,7 +568,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
getName(formIndex?: integer): string {
if (formIndex !== undefined && this.forms.length) {
const form = this.forms[formIndex];
let key: string;
let key: string | null;
switch (form.formKey) {
case SpeciesFormKey.MEGA:
case SpeciesFormKey.PRIMAL:
@ -578,6 +580,8 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
default:
if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) {
key = "gigantamax";
} else {
key = null;
}
}
@ -665,11 +669,11 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
evolutionChance = Math.min(minChance + easeInFunc(Math.min(level - ev.level, maxLevelDiff) / maxLevelDiff) * (1 - minChance), 1);
}
} else {
const preferredMinLevel = Math.max((ev.level - 1) + ev.wildDelay * this.getStrengthLevelDiff(strength), 1);
const preferredMinLevel = Math.max((ev.level - 1) + (ev.wildDelay!) * this.getStrengthLevelDiff(strength), 1); // TODO: is the bang correct?
let evolutionLevel = Math.max(ev.level > 1 ? ev.level : Math.floor(preferredMinLevel / 2), 1);
if (ev.level <= 1 && pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(ev => ev.speciesId === this.speciesId).level;
const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(ev => ev.speciesId === this.speciesId)!.level; // TODO: is the bang correct?
if (prevolutionLevel > 1) {
evolutionLevel = prevolutionLevel;
}
@ -702,15 +706,15 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
for (const weight of evolutionPool.keys()) {
if (randValue < weight) {
return getPokemonSpecies(evolutionPool.get(weight)).getSpeciesForLevel(level, true, forTrainer, strength);
return getPokemonSpecies(evolutionPool.get(weight)!).getSpeciesForLevel(level, true, forTrainer, strength); // TODO: is the bang correct?
}
}
return this.speciesId;
}
getEvolutionLevels() {
const evolutionLevels = [];
getEvolutionLevels(): [Species, integer][] {
const evolutionLevels: [Species, integer][] = [];
//console.log(Species[this.speciesId], pokemonEvolutions[this.speciesId])
@ -730,8 +734,8 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
return evolutionLevels;
}
getPrevolutionLevels() {
const prevolutionLevels = [];
getPrevolutionLevels(): [Species, integer][] {
const prevolutionLevels: [Species, integer][] = [];
const allEvolvingPokemon = Object.keys(pokemonEvolutions);
for (const p of allEvolvingPokemon) {
@ -753,18 +757,18 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
// This could definitely be written better and more accurate to the getSpeciesForLevel logic, but it is only for generating movesets for evolved Pokemon
getSimulatedEvolutionChain(currentLevel: integer, forTrainer: boolean = false, isBoss: boolean = false, player: boolean = false): [Species, integer][] {
const ret = [];
const ret: [Species, integer][] = [];
if (pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevels = this.getPrevolutionLevels().reverse();
const levelDiff = player ? 0 : forTrainer || isBoss ? forTrainer && isBoss ? 2.5 : 5 : 10;
ret.push([ prevolutionLevels[0][0], 1 ]);
for (let l = 1; l < prevolutionLevels.length; l++) {
const evolution = pokemonEvolutions[prevolutionLevels[l - 1][0]].find(e => e.speciesId === prevolutionLevels[l][0]);
ret.push([ prevolutionLevels[l][0], Math.min(Math.max(evolution.level + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution.wildDelay, 0.5) * 5) - 1, 2, evolution.level), currentLevel - 1) ]);
ret.push([ prevolutionLevels[l][0], Math.min(Math.max((evolution?.level!) + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max((evolution?.wildDelay!), 0.5) * 5) - 1, 2, (evolution?.level!)), currentLevel - 1) ]); // TODO: are those bangs correct?
}
const lastPrevolutionLevel = ret[prevolutionLevels.length - 1][1];
const evolution = pokemonEvolutions[prevolutionLevels[prevolutionLevels.length - 1][0]].find(e => e.speciesId === this.speciesId);
ret.push([ this.speciesId, Math.min(Math.max(lastPrevolutionLevel + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution.wildDelay, 0.5) * 5), lastPrevolutionLevel + 1, evolution.level), currentLevel) ]);
ret.push([ this.speciesId, Math.min(Math.max(lastPrevolutionLevel + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max((evolution?.wildDelay!), 0.5) * 5), lastPrevolutionLevel + 1, (evolution?.level!)), currentLevel) ]); // TODO: are those bangs correct?
} else {
ret.push([ this.speciesId, 1 ]);
}
@ -805,7 +809,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
}
getFormSpriteKey(formIndex?: integer) {
if (this.forms.length && formIndex >= this.forms.length) {
if (this.forms.length && (formIndex !== undefined && formIndex >= this.forms.length)) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${this.getName()} with only ${this.forms.length || 0} forms`);
formIndex = Math.min(formIndex, this.forms.length - 1);
}
@ -818,14 +822,14 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
export class PokemonForm extends PokemonSpeciesForm {
public formName: string;
public formKey: string;
public formSpriteKey: string;
public formSpriteKey: string | null;
// This is a collection of form keys that have in-run form changes, but should still be separately selectable from the start screen
private starterSelectableKeys: string[] = ["10", "50", "10-pc", "50-pc", "red", "orange", "yellow", "green", "blue", "indigo", "violet"];
constructor(formName: string, formKey: string, type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
constructor(formName: string, formKey: string, type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs?: boolean, formSpriteKey?: string, isStarterSelectable?: boolean, ) {
catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs?: boolean, formSpriteKey?: string | null, isStarterSelectable?: boolean, ) {
super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd,
catchRate, baseFriendship, baseExp, !!genderDiffs, (!!isStarterSelectable || !formKey));
this.formName = formName;
@ -2677,8 +2681,8 @@ export const speciesStarters = {
[Species.VOLTORB]: 2,
[Species.EXEGGCUTE]: 3,
[Species.CUBONE]: 3,
[Species.HITMONLEE]: 4,
[Species.HITMONCHAN]: 4,
[Species.HITMONLEE]: 5,
[Species.HITMONCHAN]: 5,
[Species.LICKITUNG]: 3,
[Species.KOFFING]: 2,
[Species.RHYHORN]: 3,
@ -2690,7 +2694,7 @@ export const speciesStarters = {
[Species.STARYU]: 3,
[Species.MR_MIME]: 3,
[Species.SCYTHER]: 5,
[Species.JYNX]: 3,
[Species.JYNX]: 4,
[Species.ELECTABUZZ]: 4,
[Species.MAGMAR]: 4,
[Species.PINSIR]: 4,
@ -2757,8 +2761,8 @@ export const speciesStarters = {
[Species.PHANPY]: 3,
[Species.STANTLER]: 3,
[Species.SMEARGLE]: 1,
[Species.TYROGUE]: 2,
[Species.SMOOCHUM]: 2,
[Species.TYROGUE]: 3,
[Species.SMOOCHUM]: 3,
[Species.ELEKID]: 3,
[Species.MAGBY]: 3,
[Species.MILTANK]: 4,
@ -2850,7 +2854,7 @@ export const speciesStarters = {
[Species.CHIMCHAR]: 3,
[Species.PIPLUP]: 3,
[Species.STARLY]: 3,
[Species.BIDOOF]: 3,
[Species.BIDOOF]: 2,
[Species.KRICKETOT]: 1,
[Species.SHINX]: 2,
[Species.BUDEW]: 3,

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -48,7 +48,6 @@ export enum BattlerTagType {
FIRE_BOOST = "FIRE_BOOST",
CRIT_BOOST = "CRIT_BOOST",
ALWAYS_CRIT = "ALWAYS_CRIT",
NO_CRIT = "NO_CRIT",
IGNORE_ACCURACY = "IGNORE_ACCURACY",
BYPASS_SLEEP = "BYPASS_SLEEP",
IGNORE_FLYING = "IGNORE_FLYING",
@ -63,5 +62,10 @@ export enum BattlerTagType {
ICE_FACE = "ICE_FACE",
STOCKPILING = "STOCKPILING",
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
ALWAYS_GET_HIT = "ALWAYS_GET_HIT"
ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
IGNORE_GHOST = "IGNORE_GHOST",
IGNORE_DARK = "IGNORE_DARK",
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU",
BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -167,7 +167,7 @@ export class GameMode implements GameModeConfig {
}
}
getOverrideSpecies(waveIndex: integer): PokemonSpecies {
getOverrideSpecies(waveIndex: integer): PokemonSpecies | null {
if (this.isDaily && this.isWaveFinal(waveIndex)) {
const allFinalBossSpecies = allSpecies.filter(s => (s.subLegendary || s.legendary || s.mythical)
&& s.baseTotal >= 600 && s.speciesId !== Species.ETERNATUS && s.speciesId !== Species.ARCEUS);
@ -210,7 +210,7 @@ export class GameMode implements GameModeConfig {
* @returns true if waveIndex is a multiple of 50 in Endless
*/
isEndlessBoss(waveIndex: integer): boolean {
return waveIndex % 50 &&
return !!(waveIndex % 50) &&
(this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS);
}
@ -267,6 +267,8 @@ export class GameMode implements GameModeConfig {
return 5000;
case GameModes.DAILY:
return 2500;
default:
return 0;
}
}

View File

@ -154,7 +154,7 @@ export class InputsController {
});
if (typeof this.scene.input.gamepad !== "undefined") {
this.scene.input.gamepad.on("connected", function (thisGamepad) {
this.scene.input.gamepad?.on("connected", function (thisGamepad) {
if (!thisGamepad) {
return;
}
@ -163,23 +163,23 @@ export class InputsController {
this.onReconnect(thisGamepad);
}, this);
this.scene.input.gamepad.on("disconnected", function (thisGamepad) {
this.scene.input.gamepad?.on("disconnected", function (thisGamepad) {
this.onDisconnect(thisGamepad); // when a gamepad is disconnected
}, this);
// Check to see if the gamepad has already been setup by the browser
this.scene.input.gamepad.refreshPads();
if (this.scene.input.gamepad.total) {
this.scene.input.gamepad?.refreshPads();
if (this.scene.input.gamepad?.total) {
this.refreshGamepads();
for (const thisGamepad of this.gamepads) {
this.scene.input.gamepad.emit("connected", thisGamepad);
}
}
this.scene.input.gamepad.on("down", this.gamepadButtonDown, this);
this.scene.input.gamepad.on("up", this.gamepadButtonUp, this);
this.scene.input.keyboard.on("keydown", this.keyboardKeyDown, this);
this.scene.input.keyboard.on("keyup", this.keyboardKeyUp, this);
this.scene.input.gamepad?.on("down", this.gamepadButtonDown, this);
this.scene.input.gamepad?.on("up", this.gamepadButtonUp, this);
this.scene.input.keyboard?.on("keydown", this.keyboardKeyDown, this);
this.scene.input.keyboard?.on("keyup", this.keyboardKeyUp, this);
}
this.touchControls = new TouchControl(this.scene);
}
@ -338,9 +338,9 @@ export class InputsController {
*/
refreshGamepads(): void {
// Sometimes, gamepads are undefined. For some reason.
this.gamepads = this.scene.input.gamepad.gamepads.filter(function (el) {
this.gamepads = this.scene.input.gamepad?.gamepads.filter(function (el) {
return el !== null;
});
})!; // TODO: is this bang correct?
for (const [index, thisGamepad] of this.gamepads.entries()) {
thisGamepad.index = index; // Overwrite the gamepad index, in case we had undefined gamepads earlier

View File

@ -453,8 +453,8 @@ export class LoadingScene extends SceneBase {
// videos do not need to be preloaded
intro.loadURL("images/intro_dark.mp4", true);
if (mobile) {
intro.video.setAttribute("webkit-playsinline", "webkit-playsinline");
intro.video.setAttribute("playsinline", "playsinline");
intro.video?.setAttribute("webkit-playsinline", "webkit-playsinline");
intro.video?.setAttribute("playsinline", "playsinline");
}
intro.play();
});

View File

@ -0,0 +1,63 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const abilityTriggers: SimpleTranslationEntries = {
"blockRecoilDamage": "{{pokemonName}}'s {{abilityName}}\nprotected it from recoil!",
"badDreams": "{{pokemonName}} is tormented!",
"costar": "{{pokemonName}} copied {{allyName}}'s stat changes!",
"iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!",
"perishBody": "{{pokemonName}}'s {{abilityName}}\nwill faint both pokemon in 3 turns!",
"poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!",
"trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!",
"windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!",
"quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
"postDefendDisguise": "{{pokemonNameWithAffix}}'s disguise was busted!",
"moveImmunity": "It doesn't affect {{pokemonNameWithAffix}}!",
"reverseDrain": "{{pokemonNameWithAffix}} sucked up the liquid ooze!",
"postDefendTypeChange": "{{pokemonNameWithAffix}}'s {{abilityName}}\nmade it the {{typeName}} type!",
"postDefendContactDamage": "{{pokemonNameWithAffix}}'s {{abilityName}}\nhurt its attacker!",
"postDefendAbilitySwap": "{{pokemonNameWithAffix}} swapped\nabilities with its target!",
"postDefendAbilityGive": "{{pokemonNameWithAffix}} gave its target\n{{abilityName}}!",
"postDefendMoveDisable": "{{pokemonNameWithAffix}}'s {{moveName}}\nwas disabled!",
"pokemonTypeChange": "{{pokemonNameWithAffix}} transformed into the {{moveType}} type!",
"postAttackStealHeldItem": "{{pokemonNameWithAffix}} stole\n{{defenderName}}'s {{stolenItemType}}!",
"postDefendStealHeldItem": "{{pokemonNameWithAffix}} stole\n{{attackerName}}'s {{stolenItemType}}!",
"copyFaintedAllyAbility": "{{pokemonNameWithAffix}}'s {{abilityName}} was taken over!",
"intimidateImmunity": "{{pokemonNameWithAffix}}'s {{abilityName}} prevented it from being Intimidated!",
"postSummonAllyHeal": "{{pokemonNameWithAffix}} drank down all the\nmatcha that {{pokemonName}} made!",
"postSummonClearAllyStats": "{{pokemonNameWithAffix}}'s stat changes\nwere removed!",
"postSummonTransform": "{{pokemonNameWithAffix}} transformed\ninto {{targetName}}!",
"protectStat": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents lowering its {{statName}}!",
"statusEffectImmunityWithName": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents {{statusEffectName}}!",
"statusEffectImmunity": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents status problems!",
"battlerTagImmunity": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents {{battlerTagName}}!",
"forewarn": "{{pokemonNameWithAffix}} was forewarned about {{moveName}}!",
"frisk": "{{pokemonNameWithAffix}} frisked {{opponentName}}'s {{opponentAbilityName}}!",
"postWeatherLapseHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"postWeatherLapseDamage": "{{pokemonNameWithAffix}} is hurt\nby its {{abilityName}}!",
"postTurnLootCreateEatenBerry": "{{pokemonNameWithAffix}} harvested one {{berryName}}!",
"postTurnHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"fetchBall": "{{pokemonNameWithAffix}} found a\n{{pokeballName}}!",
"healFromBerryUse": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP!",
"arenaTrap": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents switching!",
"postBattleLoot": "{{pokemonNameWithAffix}} picked up\n{{itemName}}!",
"postFaintContactDamage": "{{pokemonNameWithAffix}}'s {{abilityName}}\nhurt its attacker!",
"postFaintHpDamage": "{{pokemonNameWithAffix}}'s {{abilityName}}\nhurt its attacker!",
"postSummonPressure": "{{pokemonNameWithAffix}} is exerting its Pressure!",
"postSummonMoldBreaker": "{{pokemonNameWithAffix}} breaks the mold!",
"postSummonAnticipation": "{{pokemonNameWithAffix}} shuddered!",
"postSummonTurboblaze": "{{pokemonNameWithAffix}} is radiating a blazing aura!",
"postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!",
"postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!",
"postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!",
"postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!",
"postSummonVesselOfRuin": "{{pokemonNameWithAffix}}'s Vessel of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
"postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
"preventBerryUse": "{{pokemonNameWithAffix}} is too\nnervous to eat berries!",
} as const;

1244
src/locales/ca_ES/ability.ts Normal file

File diff suppressed because it is too large Load Diff

278
src/locales/ca_ES/achv.ts Normal file
View File

@ -0,0 +1,278 @@
import { AchievementTranslationEntries } from "#app/interfaces/locales.js";
// Achievement translations for the when the player character is male
export const PGMachv: AchievementTranslationEntries = {
"Achievements": {
name: "Achievements",
},
"Locked": {
name: "Locked",
},
"MoneyAchv": {
description: "Accumulate a total of ₽{{moneyAmount}}",
},
"10K_MONEY": {
name: "Money Haver",
},
"100K_MONEY": {
name: "Rich",
},
"1M_MONEY": {
name: "Millionaire",
},
"10M_MONEY": {
name: "One Percenter",
},
"DamageAchv": {
description: "Inflict {{damageAmount}} damage in one hit",
},
"250_DMG": {
name: "Hard Hitter",
},
"1000_DMG": {
name: "Harder Hitter",
},
"2500_DMG": {
name: "That's a Lotta Damage!",
},
"10000_DMG": {
name: "One Punch Man",
},
"HealAchv": {
description: "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item",
},
"250_HEAL": {
name: "Novice Healer",
},
"1000_HEAL": {
name: "Big Healer",
},
"2500_HEAL": {
name: "Cleric",
},
"10000_HEAL": {
name: "Recovery Master",
},
"LevelAchv": {
description: "Level up a Pokémon to Lv{{level}}",
},
"LV_100": {
name: "But Wait, There's More!",
},
"LV_250": {
name: "Elite",
},
"LV_1000": {
name: "To Go Even Further Beyond",
},
"RibbonAchv": {
description: "Accumulate a total of {{ribbonAmount}} Ribbons",
},
"10_RIBBONS": {
name: "Pokémon League Champion",
},
"25_RIBBONS": {
name: "Great League Champion",
},
"50_RIBBONS": {
name: "Ultra League Champion",
},
"75_RIBBONS": {
name: "Rogue League Champion",
},
"100_RIBBONS": {
name: "Master League Champion",
},
"TRANSFER_MAX_BATTLE_STAT": {
name: "Teamwork",
description: "Baton pass to another party member with at least one stat maxed out",
},
"MAX_FRIENDSHIP": {
name: "Friendmaxxing",
description: "Reach max friendship on a Pokémon",
},
"MEGA_EVOLVE": {
name: "Megamorph",
description: "Mega evolve a Pokémon",
},
"GIGANTAMAX": {
name: "Absolute Unit",
description: "Gigantamax a Pokémon",
},
"TERASTALLIZE": {
name: "STAB Enthusiast",
description: "Terastallize a Pokémon",
},
"STELLAR_TERASTALLIZE": {
name: "The Hidden Type",
description: "Stellar Terastallize a Pokémon",
},
"SPLICE": {
name: "Infinite Fusion",
description: "Splice two Pokémon together with DNA Splicers",
},
"MINI_BLACK_HOLE": {
name: "A Hole Lot of Items",
description: "Acquire a Mini Black Hole",
},
"CATCH_MYTHICAL": {
name: "Mythical",
description: "Catch a mythical Pokémon",
},
"CATCH_SUB_LEGENDARY": {
name: "(Sub-)Legendary",
description: "Catch a sub-legendary Pokémon",
},
"CATCH_LEGENDARY": {
name: "Legendary",
description: "Catch a legendary Pokémon",
},
"SEE_SHINY": {
name: "Shiny",
description: "Find a shiny Pokémon in the wild",
},
"SHINY_PARTY": {
name: "That's Dedication",
description: "Have a full party of shiny Pokémon",
},
"HATCH_MYTHICAL": {
name: "Mythical Egg",
description: "Hatch a mythical Pokémon from an egg",
},
"HATCH_SUB_LEGENDARY": {
name: "Sub-Legendary Egg",
description: "Hatch a sub-legendary Pokémon from an egg",
},
"HATCH_LEGENDARY": {
name: "Legendary Egg",
description: "Hatch a legendary Pokémon from an egg",
},
"HATCH_SHINY": {
name: "Shiny Egg",
description: "Hatch a shiny Pokémon from an egg",
},
"HIDDEN_ABILITY": {
name: "Hidden Potential",
description: "Catch a Pokémon with a hidden ability",
},
"PERFECT_IVS": {
name: "Certificate of Authenticity",
description: "Get perfect IVs on a Pokémon",
},
"CLASSIC_VICTORY": {
name: "Undefeated",
description: "Beat the game in classic mode",
},
"UNEVOLVED_CLASSIC_VICTORY": {
name: "Bring Your Child To Work Day",
description: "Beat the game in Classic Mode with at least one unevolved party member."
},
"MONO_GEN_ONE": {
name: "The Original Rival",
description: "Complete the generation one only challenge.",
},
"MONO_GEN_TWO": {
name: "Generation 1.5",
description: "Complete the generation two only challenge.",
},
"MONO_GEN_THREE": {
name: "Too much water?",
description: "Complete the generation three only challenge.",
},
"MONO_GEN_FOUR": {
name: "Is she really the hardest?",
description: "Complete the generation four only challenge.",
},
"MONO_GEN_FIVE": {
name: "All Original",
description: "Complete the generation five only challenge.",
},
"MONO_GEN_SIX": {
name: "Almost Royalty",
description: "Complete the generation six only challenge.",
},
"MONO_GEN_SEVEN": {
name: "Only Technically",
description: "Complete the generation seven only challenge.",
},
"MONO_GEN_EIGHT": {
name: "A Champion Time!",
description: "Complete the generation eight only challenge.",
},
"MONO_GEN_NINE": {
name: "She was going easy on you",
description: "Complete the generation nine only challenge.",
},
"MonoType": {
description: "Complete the {{type}} monotype challenge.",
},
"MONO_NORMAL": {
name: "Extra Ordinary",
},
"MONO_FIGHTING": {
name: "I Know Kung Fu",
},
"MONO_FLYING": {
name: "Angry Birds",
},
"MONO_POISON": {
name: "Kanto's Favourite",
},
"MONO_GROUND": {
name: "Forecast: Earthquakes",
},
"MONO_ROCK": {
name: "Brock Hard",
},
"MONO_BUG": {
name: "You Like Jazz?",
},
"MONO_GHOST": {
name: "Who You Gonna Call?",
},
"MONO_STEEL": {
name: "Iron Giant",
},
"MONO_FIRE": {
name: "I Cast Fireball!",
},
"MONO_WATER": {
name: "When It Rains, It Pours",
},
"MONO_GRASS": {
name: "Can't Touch This",
},
"MONO_ELECTRIC": {
name: "Aim For The Horn!",
},
"MONO_PSYCHIC": {
name: "Big Brain Energy",
},
"MONO_ICE": {
name: "Walking On Thin Ice",
},
"MONO_DRAGON": {
name: "Pseudo-Legend Club",
},
"MONO_DARK": {
name: "It's Just A Phase",
},
"MONO_FAIRY": {
name: "Hey! Listen!",
},
"FRESH_START": {
name: "First Try!",
description: "Complete the Fresh Start challenge."
}
} as const;
// Achievement translations for the when the player character is female (it for now uses the same translations as the male version)
export const PGFachv: AchievementTranslationEntries = PGMachv;

View File

@ -0,0 +1,49 @@
import { SimpleTranslationEntries } from "#app/interfaces/locales";
export const arenaFlyout: SimpleTranslationEntries = {
// Title
"activeBattleEffects": "Active Battle Effects",
"player": "Player",
"neutral": "Neutral",
"enemy": "Enemy",
// WeatherType
"sunny": "Sunny",
"rain": "Rain",
"sandstorm": "Sandstorm",
"hail": "Hail",
"snow": "Snow",
"fog": "Fog",
"heavyRain": "Heavy Rain",
"harshSun": "Harsh Sun",
"strongWinds": "Strong Winds",
// TerrainType
"misty": "Misty Terrain",
"electric": "Electric Terrain",
"grassy": "Grassy Terrain",
"psychic": "Psychic Terrain",
// ArenaTagType
"mudSport": "Mud Sport",
"waterSport": "Water Sport",
"spikes": "Spikes",
"toxicSpikes": "Toxic Spikes",
"mist": "Mist",
"futureSight": "Future Sight",
"doomDesire": "Doom Desire",
"wish": "Wish",
"stealthRock": "Stealth Rock",
"stickyWeb": "Sticky Web",
"trickRoom": "Trick Room",
"gravity": "Gravity",
"reflect": "Reflect",
"lightScreen": "Light Screen",
"auroraVeil": "Aurora Veil",
"quickGuard": "Quick Guard",
"wideGuard": "Wide Guard",
"matBlock": "Mat Block",
"craftyShield": "Crafty Shield",
"tailwind": "Tailwind",
"happyHour": "Happy Hour",
};

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