[Feature] Tera Rework (#5233)

* Commit old stashed changes

* Complete basic implementation of Tera

* Fix effectiveness test

* Make tera retain until forced recall or faint, regain on biome change

* Experimental sparkle fix

* Fix champion teras

* Attempted fix for double battles tera UI bug

* Fix the fix

* Fix linting and test issues

* Fix more tests

* Change int type

* Implement tera for ME trainers

* Cleanup species inclusivity check

* Make tera instant recharge if terapagos in party

* Make useless tera shards not generate

* Implement stellar tera damage boost

* Improve tera selection UI

* Tidy up animation and localisation

* Improve tera button sprite

* Fix Lance tera

* Make tera instant recharge during E4 in classic modes.

* Fix formatting in the tera common animation

The animation was also not playing due to `frameTimedEvents` being missing as well.

* Make tera effect start after animation

* Implement save migration

* Update version number for migration code

---------

Co-authored-by: Madmadness65 <blaze.the.fireman@gmail.com>
Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com>
This commit is contained in:
Xavion3 2025-02-17 08:20:50 +11:00 committed by GitHub
parent 4bc617bd5f
commit 90d32b886c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1784 additions and 433 deletions

View File

@ -0,0 +1,774 @@
{
"graphic": "terastallize",
"frames": [
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 150,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 225,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 70,
"zoomY": 70,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 70,
"zoomY": 70,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 90,
"zoomY": 90,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 90,
"zoomY": 90,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 200,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
100,
100,
100,
0
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 100,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
100,
100,
100,
0
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 100,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
100,
100,
100,
0
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 60,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
100,
100,
100,
0
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 60,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
100,
100,
100,
0
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 60,
"priority": 1,
"focus": 2
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
100,
100,
100,
0
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
255,
255,
255,
255
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
255,
255,
255,
0
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [
255,
255,
255,
255
],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
}
]
],
"frameTimedEvents": {},
"position": 4,
"hue": 0
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,158 @@
{ "frames": {
"unknown": {
"frame": { "x": 0, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"bug": {
"frame": { "x": 18, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"dark": {
"frame": { "x": 36, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"dragon": {
"frame": { "x": 54, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"electric": {
"frame": { "x": 72, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"fairy": {
"frame": { "x": 0, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"fighting": {
"frame": { "x": 18, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"fire": {
"frame": { "x": 36, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"flying": {
"frame": { "x": 54, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"ghost": {
"frame": { "x": 72, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"grass": {
"frame": { "x": 0, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"ground": {
"frame": { "x": 18, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"ice": {
"frame": { "x": 36, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"normal": {
"frame": { "x": 54, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"poison": {
"frame": { "x": 72, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"psychic": {
"frame": { "x": 0, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"rock": {
"frame": { "x": 18, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"steel": {
"frame": { "x": 36, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"water": {
"frame": { "x": 54, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"stellar": {
"frame": { "x": 72, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
}
},
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-dev",
"image": "button_tera.png",
"format": "RGBA8888",
"size": { "w": 90, "h": 84 },
"scale": "1",
"frameTags": [
],
"layers": [
{ "name": "Sprite Sheet", "opacity": 255, "blendMode": "normal" }
],
"slices": [
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -0,0 +1,158 @@
{ "frames": {
"unknown": {
"frame": { "x": 0, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"bug": {
"frame": { "x": 18, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"dark": {
"frame": { "x": 36, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"dragon": {
"frame": { "x": 54, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"electric": {
"frame": { "x": 72, "y": 0, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"fairy": {
"frame": { "x": 0, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"fighting": {
"frame": { "x": 18, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"fire": {
"frame": { "x": 36, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"flying": {
"frame": { "x": 54, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"ghost": {
"frame": { "x": 72, "y": 21, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"grass": {
"frame": { "x": 0, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"ground": {
"frame": { "x": 18, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"ice": {
"frame": { "x": 36, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"normal": {
"frame": { "x": 54, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"poison": {
"frame": { "x": 72, "y": 42, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"psychic": {
"frame": { "x": 0, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"rock": {
"frame": { "x": 18, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"steel": {
"frame": { "x": 36, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"water": {
"frame": { "x": 54, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
},
"stellar": {
"frame": { "x": 72, "y": 63, "w": 18, "h": 21 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 18, "h": 21 },
"sourceSize": { "w": 20, "h": 23 }
}
},
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-dev",
"image": "button_tera.png",
"format": "RGBA8888",
"size": { "w": 90, "h": 84 },
"scale": "1",
"frameTags": [
],
"layers": [
{ "name": "Sprite Sheet", "opacity": 255, "blendMode": "normal" }
],
"slices": [
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -9,7 +9,7 @@ import type { Constructor } from "#app/utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier";
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier } from "./modifier/modifier"; import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier } from "./modifier/modifier";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims"; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
import type { Phase } from "#app/phase"; import type { Phase } from "#app/phase";
@ -1373,7 +1373,11 @@ export default class BattleScene extends SceneBase {
for (const pokemon of this.getPlayerParty()) { for (const pokemon of this.getPlayerParty()) {
pokemon.resetBattleData(); pokemon.resetBattleData();
pokemon.resetTera();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
if (pokemon.hasSpecies(Species.TERAPAGOS) || (this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)) {
this.arena.playerTerasUsed = 0;
}
} }
if (!this.trainer.visible) { if (!this.trainer.visible) {
@ -1658,7 +1662,7 @@ export default class BattleScene extends SceneBase {
} }
initPokemonSprite(sprite: Phaser.GameObjects.Sprite, pokemon?: Pokemon, hasShadow: boolean = false, ignoreOverride: boolean = false): Phaser.GameObjects.Sprite { initPokemonSprite(sprite: Phaser.GameObjects.Sprite, pokemon?: Pokemon, hasShadow: boolean = false, ignoreOverride: boolean = false): Phaser.GameObjects.Sprite {
sprite.setPipeline(this.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: hasShadow, ignoreOverride: ignoreOverride, teraColor: pokemon ? getTypeRgb(pokemon.getTeraType()) : undefined }); sprite.setPipeline(this.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: hasShadow, ignoreOverride: ignoreOverride, teraColor: pokemon ? getTypeRgb(pokemon.getTeraType()) : undefined, isTerastallized: pokemon ? pokemon.isTerastallized : false });
this.spriteSparkleHandler.add(sprite); this.spriteSparkleHandler.add(sprite);
return sprite; return sprite;
} }
@ -2589,11 +2593,8 @@ export default class BattleScene extends SceneBase {
const modifiersToRemove: PersistentModifier[] = []; const modifiersToRemove: PersistentModifier[] = [];
const modifierPromises: Promise<boolean>[] = []; const modifierPromises: Promise<boolean>[] = [];
if (modifier instanceof PersistentModifier) { if (modifier instanceof PersistentModifier) {
if (modifier instanceof TerastallizeModifier) {
modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId)));
}
if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) {
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier) {
const pokemon = this.getPokemonById(modifier.pokemonId); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) { if (pokemon) {
success = modifier.apply(pokemon, true); success = modifier.apply(pokemon, true);
@ -2670,11 +2671,8 @@ export default class BattleScene extends SceneBase {
addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise<void> { addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const modifiersToRemove: PersistentModifier[] = []; const modifiersToRemove: PersistentModifier[] = [];
if (modifier instanceof TerastallizeModifier) {
modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId, false)));
}
if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) { if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) {
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier) {
const pokemon = this.getPokemonById(modifier.pokemonId); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) { if (pokemon) {
modifier.apply(pokemon, true); modifier.apply(pokemon, true);
@ -2798,6 +2796,8 @@ export default class BattleScene extends SceneBase {
for (const modifier of modifiers) { for (const modifier of modifiers) {
this.addEnemyModifier(modifier, true, true); this.addEnemyModifier(modifier, true, true);
} }
this.currentBattle.trainer.genAI(party);
} }
party.forEach((enemyPokemon: EnemyPokemon, i: number) => { party.forEach((enemyPokemon: EnemyPokemon, i: number) => {
@ -2929,7 +2929,7 @@ export default class BattleScene extends SceneBase {
const modifierIndex = modifiers.indexOf(modifier); const modifierIndex = modifiers.indexOf(modifier);
if (modifierIndex > -1) { if (modifierIndex > -1) {
modifiers.splice(modifierIndex, 1); modifiers.splice(modifierIndex, 1);
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier) {
const pokemon = this.getPokemonById(modifier.pokemonId); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) { if (pokemon) {
modifier.apply(pokemon, false); modifier.apply(pokemon, false);
@ -3130,7 +3130,8 @@ export default class BattleScene extends SceneBase {
name: p.name, name: p.name,
form: p.getFormKey(), form: p.getFormKey(),
types: p.getTypes().map((type) => Type[type]), types: p.getTypes().map((type) => Type[type]),
teraType: p.getTeraType() !== Type.UNKNOWN ? Type[p.getTeraType()] : "", teraType: Type[p.getTeraType()],
isTerastallized: p.isTerastallized,
level: p.level, level: p.level,
currentHP: p.hp, currentHP: p.hp,
maxHP: p.getMaxHp(), maxHP: p.getMaxHp(),

View File

@ -92,6 +92,7 @@ export default class Battle {
public started: boolean = false; public started: boolean = false;
public enemySwitchCounter: number = 0; public enemySwitchCounter: number = 0;
public turn: number = 0; public turn: number = 0;
public preTurnCommands: TurnCommands;
public turnCommands: TurnCommands; public turnCommands: TurnCommands;
public playerParticipantIds: Set<number> = new Set<number>(); public playerParticipantIds: Set<number> = new Set<number>();
public battleScore: number = 0; public battleScore: number = 0;
@ -180,6 +181,7 @@ export default class Battle {
incrementTurn(): void { incrementTurn(): void {
this.turn++; this.turn++;
this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ])); this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
this.preTurnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
this.battleSeedState = null; this.battleSeedState = null;
} }

View File

@ -239,39 +239,27 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr {
} }
} }
export class PostBattleInitStatStageChangeAbAttr extends PostBattleInitAbAttr { export class PostTeraFormChangeStatChangeAbAttr extends AbAttr {
private stats: BattleStat[]; private stats: BattleStat[];
private stages: number; private stages: number;
private selfTarget: boolean;
constructor(stats: BattleStat[], stages: number, selfTarget?: boolean) { constructor(stats: BattleStat[], stages: number) {
super(); super();
this.stats = stats; this.stats = stats;
this.stages = stages; this.stages = stages;
this.selfTarget = !!selfTarget;
} }
applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
const statStageChangePhases: StatStageChangePhase[] = []; const statStageChangePhases: StatStageChangePhase[] = [];
if (!simulated) { if (!simulated) {
if (this.selfTarget) {
statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages));
} else {
for (const opponent of pokemon.getOpponents()) {
statStageChangePhases.push(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages));
}
}
for (const statStageChangePhase of statStageChangePhases) { for (const statStageChangePhase of statStageChangePhases) {
if (!this.selfTarget && !statStageChangePhase.getPokemon()?.summonData) {
globalScene.pushPhase(statStageChangePhase);
} else { // TODO: This causes the ability bar to be shown at the wrong time
globalScene.unshiftPhase(statStageChangePhase); globalScene.unshiftPhase(statStageChangePhase);
} }
} }
}
return true; return true;
} }
@ -1307,7 +1295,7 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if ( if (
!pokemon.isTerastallized() && !pokemon.isTerastallized &&
move.id !== Moves.STRUGGLE && move.id !== Moves.STRUGGLE &&
/** /**
* Skip moves that call other moves because these moves generate a following move that will trigger this ability attribute * Skip moves that call other moves because these moves generate a following move that will trigger this ability attribute
@ -4788,7 +4776,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr {
} }
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): boolean { override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): boolean {
if (pokemon.isTerastallized()) { if (pokemon.isTerastallized) {
return false; return false;
} }
const currentTerrain = globalScene.arena.getTerrainType(); const currentTerrain = globalScene.arena.getTerrainType();
@ -6201,7 +6189,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.condition((pokemon) => !pokemon.isTerastallized()), .condition((pokemon) => !pokemon.isTerastallized),
new Ability(Abilities.QUICK_DRAW, 8) new Ability(Abilities.QUICK_DRAW, 8)
.attr(BypassSpeedChanceAbAttr, 30), .attr(BypassSpeedChanceAbAttr, 30),
new Ability(Abilities.UNSEEN_FIST, 8) new Ability(Abilities.UNSEEN_FIST, 8)
@ -6353,29 +6341,25 @@ export function initAbilities() {
new Ability(Abilities.TOXIC_CHAIN, 9) new Ability(Abilities.TOXIC_CHAIN, 9)
.attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC), .attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC),
new Ability(Abilities.EMBODY_ASPECT_TEAL, 9) new Ability(Abilities.EMBODY_ASPECT_TEAL, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPD ], 1, true) .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.SPD ], 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9) new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPDEF ], 1, true) .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.SPDEF ], 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9) new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.ATK ], 1, true) .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.ATK ], 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9) new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.DEF ], 1, true) .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.DEF ], 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.TERA_SHIFT, 9) new Ability(Abilities.TERA_SHIFT, 9)
.attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1) .attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)

View File

@ -56,6 +56,7 @@ export enum ChargeAnim {
export enum CommonAnim { export enum CommonAnim {
USE_ITEM = 2000, USE_ITEM = 2000,
HEALTH_UP, HEALTH_UP,
TERASTALLIZE,
POISON = 2010, POISON = 2010,
TOXIC, TOXIC,
PARALYSIS, PARALYSIS,

View File

@ -2485,7 +2485,7 @@ export class TarShotTag extends BattlerTag {
* @returns whether the tag is applied * @returns whether the tag is applied
*/ */
override canAdd(pokemon: Pokemon): boolean { override canAdd(pokemon: Pokemon): boolean {
return !pokemon.isTerastallized(); return !pokemon.isTerastallized;
} }
override onAdd(pokemon: Pokemon): void { override onAdd(pokemon: Pokemon): void {

View File

@ -791,7 +791,7 @@ export default class Move implements Localizable {
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType(); const sourceTeraType = source.getTeraType();
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60; power.value = 60;
} }
@ -4634,7 +4634,7 @@ export class TeraMoveCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.NumberHolder); const category = (args[0] as Utils.NumberHolder);
if (user.isTerastallized() && user.getEffectiveStat(Stat.ATK, target, move, true, true, false, false, true) > if (user.isTerastallized && user.getEffectiveStat(Stat.ATK, target, move, true, true, false, false, true) >
user.getEffectiveStat(Stat.SPATK, target, move, true, true, false, false, true)) { user.getEffectiveStat(Stat.SPATK, target, move, true, true, false, false, true)) {
category.value = MoveCategory.PHYSICAL; category.value = MoveCategory.PHYSICAL;
return true; return true;
@ -4662,7 +4662,7 @@ export class TeraBlastPowerAttr extends VariablePowerAttr {
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const power = args[0] as Utils.NumberHolder; const power = args[0] as Utils.NumberHolder;
if (user.isTerastallized() && user.getTeraType() === Type.STELLAR) { if (user.isTerastallized && user.getTeraType() === Type.STELLAR) {
power.value = 100; power.value = 100;
return true; return true;
} }
@ -4720,30 +4720,6 @@ export class VariableMoveTypeAttr extends MoveAttr {
} }
} }
/**
* Attribute used for Tera Starstorm that changes the move type to Stellar
* @extends VariableMoveTypeAttr
*/
export class TeraStarstormTypeAttr extends VariableMoveTypeAttr {
/**
*
* @param user the {@linkcode Pokemon} using the move
* @param target n/a
* @param move n/a
* @param args[0] {@linkcode Utils.NumberHolder} the move type
* @returns `true` if the move type is changed to {@linkcode Type.STELLAR}, `false` otherwise
*/
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (user.isTerastallized() && (user.hasFusionSpecies(Species.TERAPAGOS) || user.species.speciesId === Species.TERAPAGOS)) {
const moveType = args[0] as Utils.NumberHolder;
moveType.value = Type.STELLAR;
return true;
}
return false;
}
}
export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { export class FormChangeItemTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const moveType = args[0]; const moveType = args[0];
@ -5009,7 +4985,7 @@ export class TeraBlastTypeAttr extends VariableMoveTypeAttr {
return false; return false;
} }
if (user.isTerastallized()) { if (user.isTerastallized) {
moveType.value = user.getTeraType(); // changes move type to tera type moveType.value = user.getTeraType(); // changes move type to tera type
return true; return true;
} }
@ -5018,6 +4994,30 @@ export class TeraBlastTypeAttr extends VariableMoveTypeAttr {
} }
} }
/**
* Attribute used for Tera Starstorm that changes the move type to Stellar
* @extends VariableMoveTypeAttr
*/
export class TeraStarstormTypeAttr extends VariableMoveTypeAttr {
/**
*
* @param user the {@linkcode Pokemon} using the move
* @param target n/a
* @param move n/a
* @param args[0] {@linkcode Utils.NumberHolder} the move type
* @returns `true` if the move type is changed to {@linkcode Type.STELLAR}, `false` otherwise
*/
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (user.isTerastallized && user.hasSpecies(Species.TERAPAGOS)) {
const moveType = args[0] as Utils.NumberHolder;
moveType.value = Type.STELLAR;
return true;
}
return false;
}
}
export class MatchUserTypeAttr extends VariableMoveTypeAttr { export class MatchUserTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const moveType = args[0]; const moveType = args[0];
@ -6345,7 +6345,7 @@ export class RemoveTypeAttr extends MoveEffectAttr {
return false; return false;
} }
if (user.isTerastallized() && user.getTeraType() === this.removedType) { // active tera types cannot be removed if (user.isTerastallized && user.getTeraType() === this.removedType) { // active tera types cannot be removed
return false; return false;
} }
@ -6525,7 +6525,7 @@ export class ChangeTypeAttr extends MoveEffectAttr {
} }
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => !target.isTerastallized() && !target.hasAbility(Abilities.MULTITYPE) && !target.hasAbility(Abilities.RKS_SYSTEM) && !(target.getTypes().length === 1 && target.getTypes()[0] === this.type); return (user, target, move) => !target.isTerastallized && !target.hasAbility(Abilities.MULTITYPE) && !target.hasAbility(Abilities.RKS_SYSTEM) && !(target.getTypes().length === 1 && target.getTypes()[0] === this.type);
} }
} }
@ -6548,7 +6548,7 @@ export class AddTypeAttr extends MoveEffectAttr {
} }
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => !target.isTerastallized() && !target.getTypes().includes(this.type); return (user, target, move) => !target.isTerastallized && !target.getTypes().includes(this.type);
} }
} }
@ -11076,7 +11076,7 @@ export function initMoves() {
.attr(TeraMoveCategoryAttr) .attr(TeraMoveCategoryAttr)
.attr(TeraBlastTypeAttr) .attr(TeraBlastTypeAttr)
.attr(TeraBlastPowerAttr) .attr(TeraBlastPowerAttr)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, { condition: (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR) }), .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, { condition: (user, target, move) => user.isTerastallized && user.isOfType(Type.STELLAR) }),
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9) new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.SILK_TRAP) .attr(ProtectAttr, BattlerTagType.SILK_TRAP)
.condition(failIfLastCondition), .condition(failIfLastCondition),
@ -11271,7 +11271,7 @@ export function initMoves() {
new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
.attr(TeraMoveCategoryAttr) .attr(TeraMoveCategoryAttr)
.attr(TeraStarstormTypeAttr) .attr(TeraStarstormTypeAttr)
.attr(VariableTargetAttr, (user, target, move) => (user.hasFusionSpecies(Species.TERAPAGOS) || user.species.speciesId === Species.TERAPAGOS) && user.isTerastallized() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER) .attr(VariableTargetAttr, (user, target, move) => user.hasSpecies(Species.TERAPAGOS) && user.isTerastallized ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER)
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */ .partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
new AttackMove(Moves.FICKLE_BEAM, Type.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9) new AttackMove(Moves.FICKLE_BEAM, Type.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9)
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc) .attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)

View File

@ -594,7 +594,7 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()), isTerastallized: tradedPokemon.isTerastallized });
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", tradedPokemon.shiny); sprite.setPipelineData("shiny", tradedPokemon.shiny);
@ -615,7 +615,7 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()), isTerastallized: tradedPokemon.isTerastallized });
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", receivedPokemon.shiny); sprite.setPipelineData("shiny", receivedPokemon.shiny);

View File

@ -1,5 +1,5 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -23,7 +23,6 @@ import { EggSourceType } from "#enums/egg-source-types";
import { EggTier } from "#enums/egg-type"; import { EggTier } from "#enums/egg-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Type } from "#enums/type"; import { Type } from "#enums/type";
import { getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballTintColor } from "#app/data/pokeball";
@ -387,11 +386,7 @@ function getPartyConfig(): EnemyPartyConfig {
nature: Nature.ADAMANT, nature: Nature.ADAMANT,
moveSet: [ Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH ], moveSet: [ Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH ],
ivs: [ 31, 31, 31, 31, 31, 31 ], ivs: [ 31, 31, 31, 31, 31, 31 ],
modifierConfigs: [ tera: Type.STEEL,
{
modifier: generateModifierType(modifierTypes.TERA_SHARD, [ Type.STEEL ]) as PokemonHeldItemModifierType,
}
]
} }
] ]
}; };

View File

@ -46,6 +46,7 @@ import type { Variant } from "#app/data/variant";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Type } from "#app/enums/type";
/** /**
* Animates exclamation sprite over trainer's head at start of encounter * Animates exclamation sprite over trainer's head at start of encounter
@ -98,6 +99,7 @@ export interface EnemyPokemonConfig {
modifierConfigs?: HeldModifierConfig[]; modifierConfigs?: HeldModifierConfig[];
tags?: BattlerTagType[]; tags?: BattlerTagType[];
dataSource?: PokemonData; dataSource?: PokemonData;
tera?: Type;
aiType?: AiType; aiType?: AiType;
} }
@ -329,6 +331,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
tags.forEach(tag => enemyPokemon.addTag(tag)); tags.forEach(tag => enemyPokemon.addTag(tag));
} }
// Set tera
if (config.tera && config.tera !== Type.UNKNOWN) {
enemyPokemon.teraType = config.tera;
if (battle.trainer) {
battle.trainer.config.setInstantTera(e);
}
}
// mysteryEncounterBattleEffects will only be used IFF MYSTERY_ENCOUNTER_POST_SUMMON tag is applied // mysteryEncounterBattleEffects will only be used IFF MYSTERY_ENCOUNTER_POST_SUMMON tag is applied
if (config.mysteryEncounterBattleEffects) { if (config.mysteryEncounterBattleEffects) {
enemyPokemon.mysteryEncounterBattleEffects = config.mysteryEncounterBattleEffects; enemyPokemon.mysteryEncounterBattleEffects = config.mysteryEncounterBattleEffects;

View File

@ -61,7 +61,7 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()), isTerastallized: previousPokemon.isTerastallized });
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
sprite.setPipelineData("shiny", previousPokemon.shiny); sprite.setPipelineData("shiny", previousPokemon.shiny);

View File

@ -1,8 +1,7 @@
import { PokemonFormChangeItemModifier, TerastallizeModifier } from "../modifier/modifier"; import { PokemonFormChangeItemModifier } from "../modifier/modifier";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { MoveCategory, allMoves } from "./move"; import { MoveCategory, allMoves } from "./move";
import { Type } from "#enums/type";
import type { Constructor, nil } from "#app/utils"; import type { Constructor, nil } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -399,23 +398,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
* @extends SpeciesFormChangeTrigger * @extends SpeciesFormChangeTrigger
*/ */
export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {
/** The Tera type that triggers the form change */ description = i18next.t("pokemonEvolutions:Forms.tera" );
private teraType: Type;
constructor(teraType: Type) {
super();
this.teraType = teraType;
this.description = i18next.t("pokemonEvolutions:Forms.tera", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
/**
* Checks if the associated Pokémon has the required Tera Shard that matches with the associated Tera type.
* @param {Pokemon} pokemon the Pokémon that is trying to do the form change
* @returns `true` if the Pokémon can change forms, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id && m.teraType === this.teraType);
}
} }
/** /**
@ -425,10 +408,6 @@ export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {
*/ */
export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger { export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {
description = i18next.t("pokemonEvolutions:Forms.teraLapse"); description = i18next.t("pokemonEvolutions:Forms.teraLapse");
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id);
}
} }
/** /**
@ -992,19 +971,19 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.OGERPON, "teal-mask", "wellspring-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.WELLSPRING_MASK)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "wellspring-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.WELLSPRING_MASK)),
new SpeciesFormChange(Species.OGERPON, "teal-mask", "hearthflame-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.HEARTHFLAME_MASK)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "hearthflame-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.HEARTHFLAME_MASK)),
new SpeciesFormChange(Species.OGERPON, "teal-mask", "cornerstone-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.CORNERSTONE_MASK)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "cornerstone-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.CORNERSTONE_MASK)),
new SpeciesFormChange(Species.OGERPON, "teal-mask", "teal-mask-tera", new SpeciesFormChangeTeraTrigger(Type.GRASS)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "teal-mask-tera", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "teal-mask-tera", "teal-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.GRASS)), new SpeciesFormChange(Species.OGERPON, "teal-mask-tera", "teal-mask", new SpeciesFormChangeLapseTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "wellspring-mask", "wellspring-mask-tera", new SpeciesFormChangeTeraTrigger(Type.WATER)), new SpeciesFormChange(Species.OGERPON, "wellspring-mask", "wellspring-mask-tera", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "wellspring-mask-tera", "wellspring-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.WATER)), new SpeciesFormChange(Species.OGERPON, "wellspring-mask-tera", "wellspring-mask", new SpeciesFormChangeLapseTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "hearthflame-mask", "hearthflame-mask-tera", new SpeciesFormChangeTeraTrigger(Type.FIRE)), new SpeciesFormChange(Species.OGERPON, "hearthflame-mask", "hearthflame-mask-tera", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "hearthflame-mask-tera", "hearthflame-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.FIRE)), new SpeciesFormChange(Species.OGERPON, "hearthflame-mask-tera", "hearthflame-mask", new SpeciesFormChangeLapseTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "cornerstone-mask", "cornerstone-mask-tera", new SpeciesFormChangeTeraTrigger(Type.ROCK)), new SpeciesFormChange(Species.OGERPON, "cornerstone-mask", "cornerstone-mask-tera", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "cornerstone-mask-tera", "cornerstone-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.ROCK)) new SpeciesFormChange(Species.OGERPON, "cornerstone-mask-tera", "cornerstone-mask", new SpeciesFormChangeLapseTeraTrigger(), true)
], ],
[Species.TERAPAGOS]: [ [Species.TERAPAGOS]: [
new SpeciesFormChange(Species.TERAPAGOS, "", "terastal", new SpeciesFormChangeAbilityTrigger(), true), new SpeciesFormChange(Species.TERAPAGOS, "", "terastal", new SpeciesFormChangeAbilityTrigger(), true),
new SpeciesFormChange(Species.TERAPAGOS, "terastal", "stellar", new SpeciesFormChangeTeraTrigger(Type.STELLAR)), new SpeciesFormChange(Species.TERAPAGOS, "terastal", "stellar", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.TERAPAGOS, "stellar", "terastal", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.STELLAR)) new SpeciesFormChange(Species.TERAPAGOS, "stellar", "terastal", new SpeciesFormChangeLapseTeraTrigger(), true)
], ],
[Species.GALAR_DARMANITAN]: [ [Species.GALAR_DARMANITAN]: [
new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeAbilityTrigger(), true), new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeAbilityTrigger(), true),

View File

@ -175,11 +175,51 @@ export const trainerPartyTemplates = {
type PartyTemplateFunc = () => TrainerPartyTemplate; type PartyTemplateFunc = () => TrainerPartyTemplate;
type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon; type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;
type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[]; type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[];
type GenAIFunc = (party: EnemyPokemon[]) => void;
export interface PartyMemberFuncs { export interface PartyMemberFuncs {
[key: number]: PartyMemberFunc [key: number]: PartyMemberFunc
} }
export enum TeraAIMode {
NO_TERA,
INSTANT_TERA,
SMART_TERA
}
/**
* Stores data and helper functions about a trainers AI options.
*/
export class TrainerAI {
public teraMode: TeraAIMode = TeraAIMode.NO_TERA;
public instantTeras: number[];
/**
* @param canTerastallize Whether this trainer is allowed to tera
*/
constructor(teraMode: TeraAIMode = TeraAIMode.NO_TERA) {
this.teraMode = teraMode;
this.instantTeras = [];
}
/**
* Checks if a trainer can tera
* @returns Whether this trainer can currently tera
*/
public canTerastallize() {
return this.teraMode !== TeraAIMode.NO_TERA;
}
/**
* Sets a pokemon on this AI to just instantly tera on first move used
* @param index The index of the pokemon to instantly tera
*/
public setInstantTera(index: number) {
this.teraMode = TeraAIMode.INSTANT_TERA;
this.instantTeras.push(index);
}
}
export class TrainerConfig { export class TrainerConfig {
public trainerType: TrainerType; public trainerType: TrainerType;
public trainerTypeDouble: TrainerType; public trainerTypeDouble: TrainerType;
@ -203,6 +243,7 @@ export class TrainerConfig {
public doubleEncounterBgm: string; public doubleEncounterBgm: string;
public victoryBgm: string; public victoryBgm: string;
public genModifiersFunc: GenModifiersFunc; public genModifiersFunc: GenModifiersFunc;
public genAIFuncs: GenAIFunc[] = [];
public modifierRewardFuncs: ModifierTypeFunc[] = []; public modifierRewardFuncs: ModifierTypeFunc[] = [];
public partyTemplates: TrainerPartyTemplate[]; public partyTemplates: TrainerPartyTemplate[];
public partyTemplateFunc: PartyTemplateFunc; public partyTemplateFunc: PartyTemplateFunc;
@ -212,6 +253,7 @@ export class TrainerConfig {
public speciesFilter: PokemonSpeciesFilter; public speciesFilter: PokemonSpeciesFilter;
public specialtyTypes: Type[] = []; public specialtyTypes: Type[] = [];
public hasVoucher: boolean = false; public hasVoucher: boolean = false;
public trainerAI: TrainerAI;
public encounterMessages: string[] = []; public encounterMessages: string[] = [];
public victoryMessages: string[] = []; public victoryMessages: string[] = [];
@ -227,6 +269,7 @@ export class TrainerConfig {
constructor(trainerType: TrainerType, allowLegendaries?: boolean) { constructor(trainerType: TrainerType, allowLegendaries?: boolean) {
this.trainerType = trainerType; this.trainerType = trainerType;
this.trainerAI = new TrainerAI();
this.name = Utils.toReadableString(TrainerType[this.getDerivedType()]); this.name = Utils.toReadableString(TrainerType[this.getDerivedType()]);
this.battleBgm = "battle_trainer"; this.battleBgm = "battle_trainer";
this.mixedBattleBgm = "battle_trainer"; this.mixedBattleBgm = "battle_trainer";
@ -550,6 +593,47 @@ export class TrainerConfig {
return this; return this;
} }
/**
* Sets random pokemon from the trainers team to instant tera. Uses their specialty types is they have one.
* @param count The amount of pokemon to have instant tera
* @returns this
*/
setRandomTeraModifiers(count: () => integer): TrainerConfig {
this.genAIFuncs.push((party: EnemyPokemon[]) => {
const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
for (let t = 0; t < Math.min(count(), party.length); t++) {
const randomIndex = Utils.randSeedItem(partyMemberIndexes);
partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
if (this.specialtyTypes?.length) {
party[randomIndex].teraType = Utils.randSeedItem(this.specialtyTypes);
}
this.trainerAI.setInstantTera(randomIndex);
}
});
return this;
}
/**
* Sets a specific pokemon to instant tera
* @param index The index within the team to have instant tera
* @returns this
*/
setInstantTera(index: number): TrainerConfig {
this.trainerAI.setInstantTera(index);
return this;
}
// function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: Type[]): PersistentModifier[] {
// const ret: PersistentModifier[] = [];
// const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
// for (let t = 0; t < Math.min(count, party.length); t++) {
// const randomIndex = Utils.randSeedItem(partyMemberIndexes);
// partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
// ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct?
// }
// return ret;
// }
setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
this.eventRewardFuncs = modifierTypeFuncs.map(func => () => { this.eventRewardFuncs = modifierTypeFuncs.map(func => () => {
const modifierTypeFunc = func(); const modifierTypeFunc = func();
@ -851,10 +935,7 @@ export class TrainerConfig {
this.setHasVoucher(true); this.setHasVoucher(true);
this.setBattleBgm("battle_unova_gym"); this.setBattleBgm("battle_unova_gym");
this.setVictoryBgm("victory_gym"); this.setVictoryBgm("victory_gym");
this.setGenModifiersFunc(party => { this.setRandomTeraModifiers(() => globalScene.currentBattle.waveIndex >= 100 ? 1 : 0);
const waveIndex = globalScene.currentBattle.waveIndex;
return getRandomTeraModifiers(party, waveIndex >= 100 ? 1 : 0, specialtyTypes.length ? specialtyTypes : undefined);
});
return this; return this;
} }
@ -910,7 +991,7 @@ export class TrainerConfig {
this.setHasVoucher(true); this.setHasVoucher(true);
this.setBattleBgm("battle_unova_elite"); this.setBattleBgm("battle_unova_elite");
this.setVictoryBgm("victory_gym"); this.setVictoryBgm("victory_gym");
this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 1, specialtyTypes.length ? specialtyTypes : undefined)); this.setRandomTeraModifiers(() => 1);
return this; return this;
} }
@ -1197,16 +1278,6 @@ function getSpeciesFilterRandomPartyMemberFunc(
}; };
} }
function getRandomTeraModifiers(party: EnemyPokemon[], count: number, types?: Type[]): PersistentModifier[] {
const ret: PersistentModifier[] = [];
const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
for (let t = 0; t < Math.min(count, party.length); t++) {
const randomIndex = Utils.randSeedItem(partyMemberIndexes);
partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct?
}
return ret;
}
type SignatureSpecies = { type SignatureSpecies = {
[key in string]: (Species | Species[])[]; [key in string]: (Species | Species[])[];
@ -1747,10 +1818,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName(); p.generateName();
p.gender = Gender.MALE; p.gender = Gender.MALE;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(3),
const teraPokemon = party[3];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.RED]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_johto_champion").setMixedBattleBgm("battle_johto_champion").setHasDouble("red_blue_double").setDoubleTrainerType(TrainerType.BLUE).setDoubleTitle("champion_double") [TrainerType.RED]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_johto_champion").setMixedBattleBgm("battle_johto_champion").setHasDouble("red_blue_double").setDoubleTrainerType(TrainerType.BLUE).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PIKACHU ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PIKACHU ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 8; // G-Max Pikachu p.formIndex = 8; // G-Max Pikachu
@ -1774,10 +1842,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName(); p.generateName();
p.gender = Gender.MALE; p.gender = Gender.MALE;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(3),
const teraPokemon = party[3];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.LANCE_CHAMPION]: new TrainerConfig(++t).setName("Lance").initForChampion(true).setBattleBgm("battle_johto_champion").setMixedBattleBgm("battle_johto_champion") [TrainerType.LANCE_CHAMPION]: new TrainerConfig(++t).setName("Lance").initForChampion(true).setBattleBgm("battle_johto_champion").setMixedBattleBgm("battle_johto_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GYARADOS, Species.KINGDRA ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GYARADOS, Species.KINGDRA ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AERODACTYL ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AERODACTYL ]))
@ -1787,16 +1852,15 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName(); p.generateName();
})) }))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.CHARIZARD ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.CHARIZARD ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.TYRANITAR, Species.GARCHOMP, Species.KOMMO_O ])) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.TYRANITAR, Species.GARCHOMP, Species.KOMMO_O ], TrainerSlot.TRAINER, true, p => {
p.teraType = p.species.type1;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DRAGONITE ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DRAGONITE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.gender = Gender.MALE; p.gender = Gender.MALE;
p.setBoss(true, 2); p.setBoss(true, 2);
})) }))
.setGenModifiersFunc(party => { .setInstantTera(4),
const teraPokemon = party[4];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.STEVEN]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_hoenn_champion_g5").setMixedBattleBgm("battle_hoenn_champion_g6").setHasDouble("steven_wallace_double").setDoubleTrainerType(TrainerType.WALLACE).setDoubleTitle("champion_double") [TrainerType.STEVEN]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_hoenn_champion_g5").setMixedBattleBgm("battle_hoenn_champion_g6").setHasDouble("steven_wallace_double").setDoubleTrainerType(TrainerType.WALLACE).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SKARMORY ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SKARMORY ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.CRADILY, Species.ARMALDO ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.CRADILY, Species.ARMALDO ]))
@ -1814,10 +1878,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
})) }))
.setGenModifiersFunc(party => { .setInstantTera(4),
const teraPokemon = party[4];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.WALLACE]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_hoenn_champion_g5").setMixedBattleBgm("battle_hoenn_champion_g6").setHasDouble("wallace_steven_double").setDoubleTrainerType(TrainerType.STEVEN).setDoubleTitle("champion_double") [TrainerType.WALLACE]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_hoenn_champion_g5").setMixedBattleBgm("battle_hoenn_champion_g6").setHasDouble("wallace_steven_double").setDoubleTrainerType(TrainerType.STEVEN).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PELIPPER ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PELIPPER ], TrainerSlot.TRAINER, true, p => {
p.abilityIndex = 1; // Drizzle p.abilityIndex = 1; // Drizzle
@ -1840,10 +1901,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.FEMALE; p.gender = Gender.FEMALE;
p.setBoss(true, 2); p.setBoss(true, 2);
})) }))
.setGenModifiersFunc(party => { .setInstantTera(4),
const teraPokemon = party[4];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.CYNTHIA]: new TrainerConfig(++t).initForChampion(false).setBattleBgm("battle_sinnoh_champion").setMixedBattleBgm("battle_sinnoh_champion") [TrainerType.CYNTHIA]: new TrainerConfig(++t).initForChampion(false).setBattleBgm("battle_sinnoh_champion").setMixedBattleBgm("battle_sinnoh_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SPIRITOMB ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SPIRITOMB ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -1853,7 +1911,9 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
})) }))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.MILOTIC, Species.ROSERADE, Species.HISUI_ARCANINE ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.MILOTIC, Species.ROSERADE, Species.HISUI_ARCANINE ], TrainerSlot.TRAINER, true, p => {
p.teraType = p.species.type1;
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.TOGEKISS ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.TOGEKISS ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.setBoss(true, 2); p.setBoss(true, 2);
@ -1864,10 +1924,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName(); p.generateName();
p.gender = Gender.FEMALE; p.gender = Gender.FEMALE;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(3),
const teraPokemon = party[3];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.ALDER]: new TrainerConfig(++t).initForChampion(true).setHasDouble("alder_iris_double").setDoubleTrainerType(TrainerType.IRIS).setDoubleTitle("champion_double").setBattleBgm("battle_champion_alder").setMixedBattleBgm("battle_champion_alder") [TrainerType.ALDER]: new TrainerConfig(++t).initForChampion(true).setHasDouble("alder_iris_double").setDoubleTrainerType(TrainerType.IRIS).setDoubleTitle("champion_double").setBattleBgm("battle_champion_alder").setMixedBattleBgm("battle_champion_alder")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BOUFFALANT, Species.BRAVIARY ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BOUFFALANT, Species.BRAVIARY ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HISUI_LILLIGANT, Species.HISUI_ZOROARK, Species.BASCULEGION ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HISUI_LILLIGANT, Species.HISUI_ZOROARK, Species.BASCULEGION ], TrainerSlot.TRAINER, true, p => {
@ -1882,16 +1939,15 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})) }))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.CHANDELURE, Species.KROOKODILE, Species.REUNICLUS, Species.CONKELDURR ])) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.CHANDELURE, Species.KROOKODILE, Species.REUNICLUS, Species.CONKELDURR ], TrainerSlot.TRAINER, true, p => {
p.teraType = p.species.speciesId === Species.KROOKODILE ? Type.DARK : p.species.type1;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.VOLCARONA ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.VOLCARONA ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.gender = Gender.MALE; p.gender = Gender.MALE;
p.setBoss(true, 2); p.setBoss(true, 2);
})) }))
.setGenModifiersFunc(party => { .setInstantTera(4),
const pokemon = party[4];
return [ modifierTypes.TERA_SHARD().generateType([], [ pokemon.species.speciesId === Species.KROOKODILE ? pokemon.species.type2 : pokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(pokemon) as PersistentModifier ];
}),
[TrainerType.IRIS]: new TrainerConfig(++t).initForChampion(false).setBattleBgm("battle_champion_iris").setMixedBattleBgm("battle_champion_iris").setHasDouble("iris_alder_double").setDoubleTrainerType(TrainerType.ALDER).setDoubleTitle("champion_double") [TrainerType.IRIS]: new TrainerConfig(++t).initForChampion(false).setBattleBgm("battle_champion_iris").setMixedBattleBgm("battle_champion_iris").setHasDouble("iris_alder_double").setDoubleTrainerType(TrainerType.ALDER).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.DRUDDIGON ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.DRUDDIGON ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ARCHEOPS ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ARCHEOPS ]))
@ -1899,7 +1955,9 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
})) }))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SALAMENCE, Species.HYDREIGON, Species.ARCHALUDON ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SALAMENCE, Species.HYDREIGON, Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => {
p.teraType = Type.DRAGON;
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.LAPRAS ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.LAPRAS ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1; // G-Max Lapras p.formIndex = 1; // G-Max Lapras
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -1911,10 +1969,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.FEMALE; p.gender = Gender.FEMALE;
p.setBoss(true, 2); p.setBoss(true, 2);
})) }))
.setGenModifiersFunc(party => { .setInstantTera(3),
const teraPokemon = party[3];
return [ modifierTypes.TERA_SHARD().generateType([], [ Type.DRAGON ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.DIANTHA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_kalos_champion") [TrainerType.DIANTHA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_kalos_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.HAWLUCHA ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.HAWLUCHA ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -1927,6 +1982,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.TYRANTRUM, Species.AURORUS ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.TYRANTRUM, Species.AURORUS ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Rock Head Tyrantrum, Snow Warning Aurorus p.abilityIndex = 2; // Rock Head Tyrantrum, Snow Warning Aurorus
p.teraType = p.species.speciesId === Species.TYRANTRUM ? Type.DRAGON : Type.ICE;
})) }))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GOODRA ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GOODRA ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -1938,10 +1994,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName(); p.generateName();
p.gender = Gender.FEMALE; p.gender = Gender.FEMALE;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(3),
const teraPokemon = party[3];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type2 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.KUKUI]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_champion_kukui") [TrainerType.KUKUI]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_champion_kukui")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LYCANROC ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LYCANROC ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -1965,11 +2018,9 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.INCINEROAR, Species.HISUI_DECIDUEYE ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.INCINEROAR, Species.HISUI_DECIDUEYE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.gender = Gender.MALE; p.gender = Gender.MALE;
p.teraType = p.species.speciesId === Species.INCINEROAR ? Type.DARK : Type.FIGHTING;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(5),
const teraPokemon = party[5];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type2 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.HAU]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_alola_champion") [TrainerType.HAU]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_alola_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ALOLA_RAICHU ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ALOLA_RAICHU ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -1982,6 +2033,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.TAPU_LELE, Species.TAPU_BULU ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.TAPU_LELE, Species.TAPU_BULU ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.teraType = p.species.type1;
})) }))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.ZYGARDE ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.ZYGARDE ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1; // Zygarde 10% forme, Aura Break p.formIndex = 1; // Zygarde 10% forme, Aura Break
@ -1993,10 +2045,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2); p.setBoss(true, 2);
p.gender = p.species.speciesId === Species.PRIMARINA ? Gender.FEMALE : Gender.MALE; p.gender = p.species.speciesId === Species.PRIMARINA ? Gender.FEMALE : Gender.MALE;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(3),
const teraPokemon = party[3];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.LEON]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_galar_champion") [TrainerType.LEON]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_galar_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AEGISLASH ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AEGISLASH ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -2017,10 +2066,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName(); p.generateName();
p.gender = Gender.MALE; p.gender = Gender.MALE;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(3),
const teraPokemon = party[3];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.MUSTARD]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_mustard") [TrainerType.MUSTARD]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_mustard")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CORVIKNIGHT ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CORVIKNIGHT ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -2033,6 +2079,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.GALAR_SLOWBRO, Species.GALAR_SLOWKING ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.GALAR_SLOWBRO, Species.GALAR_SLOWKING ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.teraType = Type.PSYCHIC;
})) }))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALAR_DARMANITAN ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALAR_DARMANITAN ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -2050,10 +2097,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.MALE; p.gender = Gender.MALE;
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(2),
const teraPokemon = party[2];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type2 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.GEETA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_champion_geeta") [TrainerType.GEETA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_champion_geeta")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GLIMMORA ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GLIMMORA ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -2070,11 +2114,9 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KINGAMBIT ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KINGAMBIT ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.abilityIndex = 1; // Supreme Overlord p.abilityIndex = 1; // Supreme Overlord
p.teraType = Type.FLYING;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(5),
const teraPokemon = party[5];
return [ modifierTypes.TERA_SHARD().generateType([], [ Type.FLYING ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_champion_nemona") [TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_champion_nemona")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LYCANROC ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LYCANROC ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 0; // Midday form p.formIndex = 0; // Midday form
@ -2086,16 +2128,15 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
})) }))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GHOLDENGO ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GHOLDENGO ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.ARMAROUGE, Species.CERULEDGE ])) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.ARMAROUGE, Species.CERULEDGE ], TrainerSlot.TRAINER, true, p => {
p.teraType = p.species.speciesId === Species.ARMAROUGE ? Type.PSYCHIC : Type.GHOST;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.gender = Gender.MALE; p.gender = Gender.MALE;
p.setBoss(true, 2); p.setBoss(true, 2);
})) }))
.setGenModifiersFunc(party => { .setInstantTera(4),
const teraPokemon = party[4];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type2 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ];
}),
[TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_champion_kieran") [TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_champion_kieran")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.POLIWRATH, Species.POLITOED ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.POLIWRATH, Species.POLITOED ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -2117,7 +2158,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})) }))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.OGERPON ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.OGERPON ], TrainerSlot.TRAINER, true, p => {
p.formIndex = Utils.randSeedInt(4, 4); // Random Ogerpon Tera Mask p.formIndex = Utils.randSeedInt(4); // Random Ogerpon Tera Mask
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
if (!p.moveset.some(move => !Utils.isNullOrUndefined(move) && move.moveId === Moves.IVY_CUDGEL)) { // Check if Ivy Cudgel is in the moveset, if not, replace the first move with Ivy Cudgel. if (!p.moveset.some(move => !Utils.isNullOrUndefined(move) && move.moveId === Moves.IVY_CUDGEL)) { // Check if Ivy Cudgel is in the moveset, if not, replace the first move with Ivy Cudgel.
@ -2129,17 +2170,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.MALE; p.gender = Gender.MALE;
p.setBoss(true, 2); p.setBoss(true, 2);
})) }))
.setGenModifiersFunc(party => { .setInstantTera(4),
const starter = party[4];
let teraShardType: Type;
const pokemonType2 = starter.species.forms[starter.formIndex].type2;
if (starter.formIndex === 4 || Utils.isNullOrUndefined(pokemonType2)) {
teraShardType = starter.species.type1;
} else {
teraShardType = pokemonType2;
}
return [ modifierTypes.TERA_SHARD().generateType([], [ teraShardType ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ];
}),
[TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL) [TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL)
.setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE) .setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE)
@ -2162,19 +2193,20 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesFilter(species => species.baseTotal >= 540), .setSpeciesFilter(species => species.baseTotal >= 540),
[TrainerType.RIVAL_4]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_2").setMixedBattleBgm("battle_rival_2").setPartyTemplates(trainerPartyTemplates.RIVAL_4) [TrainerType.RIVAL_4]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_2").setMixedBattleBgm("battle_rival_2").setPartyTemplates(trainerPartyTemplates.RIVAL_4)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true,
(p => p.abilityIndex = 0))) (p => {
p.abilityIndex = 0;
p.teraType = p.species.type1;
})))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
.setSpeciesFilter(species => species.baseTotal >= 540) .setSpeciesFilter(species => species.baseTotal >= 540)
.setGenModifiersFunc(party => { .setInstantTera(0),
const starter = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; // TODO: is the bang correct?
}),
[TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5) [TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true,
p => { p => {
p.setBoss(true, 2); p.setBoss(true, 2);
p.abilityIndex = 0; p.abilityIndex = 0;
p.teraType = p.species.type1;
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
@ -2185,15 +2217,13 @@ export const trainerConfigs: TrainerConfigs = {
p.shiny = true; p.shiny = true;
p.variant = 1; p.variant = 1;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(0),
const starter = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6) [TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true,
p => { p => {
p.setBoss(true, 3); p.setBoss(true, 3);
p.abilityIndex = 0; p.abilityIndex = 0;
p.teraType = p.species.type1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true, .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true,
@ -2212,10 +2242,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // Mega Rayquaza p.formIndex = 1; // Mega Rayquaza
p.generateName(); p.generateName();
})) }))
.setGenModifiersFunc(party => { .setInstantTera(0),
const starter = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; // TODO: is the bang correct?
}),
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma") [TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN ], TrainerSlot.TRAINER, true, p => {
@ -2713,10 +2740,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.generateName(); p.generateName();
})) }))
.setGenModifiersFunc(party => { .setInstantTera(4),
const teraPokemon = party[4];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") [TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -2749,10 +2773,12 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName(); p.generateName();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})) }))
.setGenModifiersFunc(party => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.ZAMAZENTA ], TrainerSlot.TRAINER, true, p => {
const teraPokemon = party[0]; p.setBoss(true, 2);
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct? p.generateAndPopulateMoveset();
}), p.pokeball = PokeballType.MASTER_BALL;
}))
.setInstantTera(0),
[TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true) [TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLAYDOL ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLAYDOL ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 3); p.setBoss(true, 3);

View File

@ -44,6 +44,7 @@ export class Arena {
public bgm: string; public bgm: string;
public ignoreAbilities: boolean; public ignoreAbilities: boolean;
public ignoringEffectSource: BattlerIndex | null; public ignoringEffectSource: BattlerIndex | null;
public playerTerasUsed: number;
/** /**
* Saves the number of times a party pokemon faints during a arena encounter. * Saves the number of times a party pokemon faints during a arena encounter.
* {@linkcode globalScene.currentBattle.enemyFaints} is the corresponding faint counter for the enemy (this resets every wave). * {@linkcode globalScene.currentBattle.enemyFaints} is the corresponding faint counter for the enemy (this resets every wave).
@ -63,6 +64,7 @@ export class Arena {
this.bgm = bgm; this.bgm = bgm;
this.trainerPool = biomeTrainerPools[biome]; this.trainerPool = biomeTrainerPools[biome];
this.updatePoolsForTimeOfDay(); this.updatePoolsForTimeOfDay();
this.playerTerasUsed = 0;
this.playerFaints = playerFaints; this.playerFaints = playerFaints;
} }

View File

@ -27,6 +27,9 @@ export default class PokemonSpriteSparkleHandler {
if (!s.visible || (s.parentContainer instanceof Pokemon && !s.parentContainer.parentContainer)) { if (!s.visible || (s.parentContainer instanceof Pokemon && !s.parentContainer.parentContainer)) {
continue; continue;
} }
if (!(s.parentContainer instanceof Pokemon) || !(s.parentContainer as Pokemon).isTerastallized) {
continue;
}
const pokemon = s.parentContainer instanceof Pokemon ? s.parentContainer as Pokemon : null; const pokemon = s.parentContainer instanceof Pokemon ? s.parentContainer as Pokemon : null;
const parent = (pokemon || s).parentContainer; const parent = (pokemon || s).parentContainer;
const texture = s.texture; const texture = s.texture;

View File

@ -52,7 +52,7 @@ import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type";
import { Type } from "#enums/type"; import { Type } from "#enums/type";
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat"; import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, PokemonMultiHitModifier } from "#app/modifier/modifier"; import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, PokemonMultiHitModifier } from "#app/modifier/modifier";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
@ -79,7 +79,7 @@ import { DexAttr } from "#app/system/game-data";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import { getNatureStatMultiplier } from "#app/data/nature"; import { getNatureStatMultiplier } from "#app/data/nature";
import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import type { TrainerSlot } from "#app/data/trainer-config"; import type { TrainerSlot } from "#app/data/trainer-config";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
@ -163,6 +163,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public pokerus: boolean; public pokerus: boolean;
public switchOutStatus: boolean; public switchOutStatus: boolean;
public evoCounter: number; public evoCounter: number;
public teraType: Type;
public isTerastallized: boolean;
public stellarTypesBoosted: Type[];
public fusionSpecies: PokemonSpecies | null; public fusionSpecies: PokemonSpecies | null;
public fusionFormIndex: number; public fusionFormIndex: number;
@ -172,6 +175,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fusionGender: Gender; public fusionGender: Gender;
public fusionLuck: number; public fusionLuck: number;
public fusionCustomPokemonData: CustomPokemonData | null; public fusionCustomPokemonData: CustomPokemonData | null;
public fusionTeraType: Type;
private summonDataPrimer: PokemonSummonData | null; private summonDataPrimer: PokemonSummonData | null;
@ -269,8 +273,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.fusionGender = dataSource.fusionGender; this.fusionGender = dataSource.fusionGender;
this.fusionLuck = dataSource.fusionLuck; this.fusionLuck = dataSource.fusionLuck;
this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData;
this.fusionTeraType = dataSource.fusionTeraType;
this.usedTMs = dataSource.usedTMs ?? []; this.usedTMs = dataSource.usedTMs ?? [];
this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData);
this.teraType = dataSource.teraType;
this.isTerastallized = dataSource.isTerastallized;
this.stellarTypesBoosted = dataSource.stellarTypesBoosted ?? [];
} else { } else {
this.id = Utils.randSeedInt(4294967296); this.id = Utils.randSeedInt(4294967296);
this.ivs = ivs || Utils.getIvsFromId(this.id); this.ivs = ivs || Utils.getIvsFromId(this.id);
@ -319,6 +327,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0);
this.fusionLuck = this.luck; this.fusionLuck = this.luck;
this.teraType = Utils.randSeedItem(this.getTypes(false, false, true));
this.isTerastallized = false;
this.stellarTypesBoosted = [];
} }
this.generateName(); this.generateName();
@ -355,7 +367,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const getSprite = (hasShadow?: boolean) => { const getSprite = (hasShadow?: boolean) => {
const ret = globalScene.addPokemonSprite(this, 0, 0, `pkmn__${this.isPlayer() ? "back__" : ""}sub`, undefined, true); const ret = globalScene.addPokemonSprite(this, 0, 0, `pkmn__${this.isPlayer() ? "back__" : ""}sub`, undefined, true);
ret.setOrigin(0.5, 1); ret.setOrigin(0.5, 1);
ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, teraColor: getTypeRgb(this.getTeraType()) }); ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, teraColor: getTypeRgb(this.getTeraType()), isTerastallized: this.isTerastallized });
return ret; return ret;
}; };
@ -723,7 +735,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
updateSpritePipelineData(): void { updateSpritePipelineData(): void {
[ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType())); [ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => {
s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType());
s.pipelineData["isTerastallized"] = this.isTerastallized;
});
this.updateInfo(true); this.updateInfo(true);
} }
@ -1208,6 +1223,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.fusionSpecies?.speciesId === species; return this.fusionSpecies?.speciesId === species;
} }
/**
* Checks if the {@linkcode Pokemon} has is the specified {@linkcode Species} or is fused with it.
* @param species the pokemon {@linkcode Species} to check
* @returns `true` if the pokemon is the species or is fused with it, `false` otherwise
*/
hasSpecies(species: Species): boolean {
return this.species.speciesId === species || this.fusionSpecies?.speciesId === species;
}
abstract isBoss(): boolean; abstract isBoss(): boolean;
getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] { getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] {
@ -1284,9 +1308,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride: boolean = false): Type[] { public getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride: boolean = false): Type[] {
const types: Type[] = []; const types: Type[] = [];
if (includeTeraType) { if (includeTeraType && this.isTerastallized) {
const teraType = this.getTeraType(); const teraType = this.getTeraType();
if (teraType !== Type.UNKNOWN) { if (this.isTerastallized && !(forDefend && teraType === Type.STELLAR)) { // Stellar tera uses its original types defensively
types.push(teraType); types.push(teraType);
if (forDefend) { if (forDefend) {
return types; return types;
@ -1590,23 +1614,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** /**
* @returns the pokemon's current tera {@linkcode Type}, or `Type.UNKNOWN` if the pokemon is not terastallized * @returns the pokemon's current tera {@linkcode Type}
*/ */
public getTeraType(): Type { getTeraType(): Type {
// I don't think this should be possible anymore, please report if you encounter this. --NightKev if (this.hasSpecies(Species.TERAPAGOS)) {
if (globalScene === undefined) { return Type.STELLAR;
console.warn("Pokemon.getTeraType(): Global scene is not defined!"); } else if (this.hasSpecies(Species.OGERPON)) {
return Type.UNKNOWN; const ogerponForm = this.species.speciesId === Species.OGERPON ? this.formIndex : this.fusionFormIndex;
switch (ogerponForm) {
case 0:
case 4:
return Type.GRASS;
case 1:
case 5:
return Type.WATER;
case 2:
case 6:
return Type.FIRE;
case 3:
case 7:
return Type.ROCK;
} }
const teraModifier = globalScene.findModifier(m => } else if (this.hasSpecies(Species.SHEDINJA)) {
m instanceof TerastallizeModifier return Type.BUG;
&& m.pokemonId === this.id
&& m.getBattlesLeft() > 0, this.isPlayer()) as TerastallizeModifier;
return teraModifier?.teraType ?? Type.UNKNOWN;
} }
return this.teraType;
public isTerastallized(): boolean {
return this.getTeraType() !== Type.UNKNOWN;
} }
public isGrounded(): boolean { public isGrounded(): boolean {
@ -1748,7 +1780,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true, move?: Move): TypeDamageMultiplier { getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true, move?: Move): TypeDamageMultiplier {
if (moveType === Type.STELLAR) { if (moveType === Type.STELLAR) {
return this.isTerastallized() ? 2 : 1; return this.isTerastallized ? 2 : 1;
} }
const types = this.getTypes(true, true); const types = this.getTypes(true, true);
const arena = globalScene.arena; const arena = globalScene.arena;
@ -2785,11 +2817,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const matchesSourceType = sourceTypes.includes(moveType); const matchesSourceType = sourceTypes.includes(moveType);
/** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */ /** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */
const stabMultiplier = new Utils.NumberHolder(1); const stabMultiplier = new Utils.NumberHolder(1);
if (matchesSourceType) { if (matchesSourceType && moveType !== Type.STELLAR) {
stabMultiplier.value += 0.5;
}
applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier);
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === moveType) {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} }
@ -2797,6 +2825,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier); applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
} }
applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier);
if (source.isTerastallized && sourceTeraType === moveType && moveType !== Type.STELLAR) {
stabMultiplier.value += 0.5;
}
if (source.isTerastallized && source.teraType === Type.STELLAR && (!source.stellarTypesBoosted.includes(moveType) || source.hasSpecies(Species.TERAPAGOS))) {
if (matchesSourceType) {
stabMultiplier.value += 0.5;
} else {
stabMultiplier.value += 0.2;
}
}
stabMultiplier.value = Math.min(stabMultiplier.value, 2.25); stabMultiplier.value = Math.min(stabMultiplier.value, 2.25);
/** Halves damage if the attacker is using a physical attack while burned */ /** Halves damage if the attacker is using a physical attack while burned */
@ -3837,6 +3879,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
resetTera(): void {
const wasTerastallized = this.isTerastallized;
this.isTerastallized = false;
this.stellarTypesBoosted = [];
if (wasTerastallized) {
this.updateSpritePipelineData();
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeLapseTeraTrigger);
}
}
resetTurnData(): void { resetTurnData(): void {
this.turnData = new PokemonTurnData(); this.turnData = new PokemonTurnData();
} }
@ -4600,6 +4652,7 @@ export class PlayerPokemon extends Pokemon {
newPokemon.fusionVariant = this.fusionVariant; newPokemon.fusionVariant = this.fusionVariant;
newPokemon.fusionGender = this.fusionGender; newPokemon.fusionGender = this.fusionGender;
newPokemon.fusionLuck = this.fusionLuck; newPokemon.fusionLuck = this.fusionLuck;
newPokemon.fusionTeraType = this.teraType;
newPokemon.usedTMs = this.usedTMs; newPokemon.usedTMs = this.usedTMs;
globalScene.getPlayerParty().push(newPokemon); globalScene.getPlayerParty().push(newPokemon);
@ -4751,6 +4804,7 @@ export class EnemyPokemon extends Pokemon {
public aiType: AiType; public aiType: AiType;
public bossSegments: number; public bossSegments: number;
public bossSegmentIndex: number; public bossSegmentIndex: number;
public initialTeamIndex: number;
/** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */ /** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */
public readonly isPopulatedFromDataSource: boolean; public readonly isPopulatedFromDataSource: boolean;
@ -4760,6 +4814,7 @@ export class EnemyPokemon extends Pokemon {
undefined, dataSource ? dataSource.nature : undefined, dataSource); undefined, dataSource ? dataSource.nature : undefined, dataSource);
this.trainerSlot = trainerSlot; this.trainerSlot = trainerSlot;
this.initialTeamIndex = globalScene.currentBattle?.enemyParty.length ?? 0;
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
if (boss) { if (boss) {
this.setBoss(boss, dataSource?.bossSegments); this.setBoss(boss, dataSource?.bossSegments);

View File

@ -11,7 +11,8 @@ import {
TrainerSlot, TrainerSlot,
trainerConfigs, trainerConfigs,
trainerPartyTemplates, trainerPartyTemplates,
signatureSpecies signatureSpecies,
TeraAIMode
} from "#app/data/trainer-config"; } from "#app/data/trainer-config";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
@ -36,6 +37,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
public partyTemplateIndex: number; public partyTemplateIndex: number;
public name: string; public name: string;
public partnerName: string; public partnerName: string;
public originalIndexes: { [key: number]: number } = {};
constructor(trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: number, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) { constructor(trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: number, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) {
super(globalScene, -72, 80); super(globalScene, -72, 80);
@ -546,6 +548,13 @@ export default class Trainer extends Phaser.GameObjects.Container {
return []; return [];
} }
genAI(party: EnemyPokemon[]) {
if (this.config.genAIFuncs) {
this.config.genAIFuncs.forEach(f => f(party));
}
console.log("Generated AI funcs");
}
loadAssets(): Promise<void> { loadAssets(): Promise<void> {
return this.config.loadAssets(this.variant); return this.config.loadAssets(this.variant);
} }
@ -667,4 +676,13 @@ export default class Trainer extends Phaser.GameObjects.Container {
} }
}); });
} }
shouldTera(pokemon: EnemyPokemon): boolean {
if (this.config.trainerAI.teraMode === TeraAIMode.INSTANT_TERA) {
if (!pokemon.isTerastallized && this.config.trainerAI.instantTeras.includes(pokemon.initialTeamIndex)) {
return true;
}
}
return false;
}
} }

View File

@ -103,6 +103,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("icon_tera", "ui"); this.loadImage("icon_tera", "ui");
this.loadImage("type_tera", "ui"); this.loadImage("type_tera", "ui");
this.loadAtlas("type_bgs", "ui"); this.loadAtlas("type_bgs", "ui");
this.loadAtlas("button_tera", "ui");
this.loadImage("mystery_egg", "ui"); this.loadImage("mystery_egg", "ui");
this.loadImage("normal_memory", "ui"); this.loadImage("normal_memory", "ui");

View File

@ -11,7 +11,7 @@ import { Type } from "#enums/type";
import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerrastalizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#app/system/unlockables";
@ -19,7 +19,7 @@ import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system
import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import PartyUiHandler from "#app/ui/party-ui-handler"; import PartyUiHandler from "#app/ui/party-ui-handler";
import { getModifierTierTextTint } from "#app/ui/text"; import { getModifierTierTextTint } from "#app/ui/text";
import { formatMoney, getEnumKeys, getEnumValues, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; import { formatMoney, getEnumKeys, getEnumValues, isNullOrUndefined, NumberHolder, padInt, randSeedInt } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
@ -275,6 +275,36 @@ export class PokemonHeldItemModifierType extends PokemonModifierType {
} }
} }
export class TerastallizeModifierType extends PokemonModifierType {
private teraType: Type;
constructor(teraType: Type) {
super("", `${Type[teraType].toLowerCase()}_tera_shard`, (type, args) => new TerrastalizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType),
(pokemon: PlayerPokemon) => {
if ([ pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId ].filter(s => s === Species.TERAPAGOS || s === Species.OGERPON || s === Species.SHEDINJA).length > 0) {
return PartyUiHandler.NoEffectMessage;
}
return null;
},
"tera_shard");
this.teraType = teraType;
}
get name(): string {
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
getDescription(): string {
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
getPregenArgs(): any[] {
return [ this.teraType ];
}
}
export class PokemonHpRestoreModifierType extends PokemonModifierType { export class PokemonHpRestoreModifierType extends PokemonModifierType {
protected restorePoints: number; protected restorePoints: number;
protected restorePercent: number; protected restorePercent: number;
@ -1192,28 +1222,6 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
} }
} }
export class TerastallizeModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
private teraType: Type;
constructor(teraType: Type) {
super("", `${Type[teraType].toLowerCase()}_tera_shard`, (type, args) => new TerastallizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), "tera_shard");
this.teraType = teraType;
}
get name(): string {
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
getDescription(): string {
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
getPregenArgs(): any[] {
return [ this.teraType ];
}
}
export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType { export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType {
private chancePercent: number; private chancePercent: number;
@ -1469,14 +1477,21 @@ export const modifierTypes = {
if (!globalScene.getModifiers(TerastallizeAccessModifier).length) { if (!globalScene.getModifiers(TerastallizeAccessModifier).length) {
return null; return null;
} }
let type: Type; const teraTypes: Type[] = [];
if (!randSeedInt(3)) { party.forEach(p => {
const partyMemberTypes = party.map(p => p.getTypes(false, false, true)).flat(); if (!(p.hasSpecies(Species.TERAPAGOS) || p.hasSpecies(Species.OGERPON) || p.hasSpecies(Species.SHEDINJA))) {
type = randSeedItem(partyMemberTypes); teraTypes.push(p.teraType);
} else {
type = randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR;
} }
return new TerastallizeModifierType(type); });
let excludedType = Type.UNKNOWN;
if (teraTypes.length > 0 && teraTypes.filter(t => t === teraTypes[0]).length === teraTypes.length) {
excludedType = teraTypes[0];
}
let shardType = randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR;
while (shardType === excludedType) {
shardType = randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR;
}
return new TerastallizeModifierType(shardType);
}), }),
BERRY: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { BERRY: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
@ -1719,7 +1734,7 @@ const modifierPool: ModifierPool = {
return Math.min(Math.ceil(highestPartyLevel / 20), 4); return Math.min(Math.ceil(highestPartyLevel / 20), 4);
}, 4), }, 4),
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3),
new WeightedModifierType(modifierTypes.TERA_SHARD, 1), new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) => party.filter(p => !(p.hasSpecies(Species.TERAPAGOS) || p.hasSpecies(Species.OGERPON) || p.hasSpecies(Species.SHEDINJA))).length > 0 ? 1 : 0),
new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => {
if (party.filter(p => !p.fusionSpecies).length > 1) { if (party.filter(p => !p.fusionSpecies).length > 1) {
if (globalScene.gameMode.isSplicedOnly) { if (globalScene.gameMode.isSplicedOnly) {

View File

@ -3,16 +3,16 @@ import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry";
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { allMoves } from "#app/data/move"; import { allMoves } from "#app/data/move";
import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
import { type FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms";
import { getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectHealText } from "#app/data/status-effect";
import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { EvolutionPhase } from "#app/phases/evolution-phase"; import { EvolutionPhase } from "#app/phases/evolution-phase";
import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase"; import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
import { LevelUpPhase } from "#app/phases/level-up-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { achvs } from "#app/system/achv";
import type { VoucherType } from "#app/system/voucher"; import type { VoucherType } from "#app/system/voucher";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#app/ui/command-ui-handler";
import { addTextObject, TextStyle } from "#app/ui/text"; import { addTextObject, TextStyle } from "#app/ui/text";
@ -25,7 +25,7 @@ import type { PokeballType } from "#enums/pokeball";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { Type } from "#enums/type"; import type { Type } from "#enums/type";
import i18next from "i18next"; import i18next from "i18next";
import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type"; import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type";
import { Color, ShadowColor } from "#enums/color"; import { Color, ShadowColor } from "#enums/color";
@ -786,72 +786,6 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi
} }
} }
export class TerastallizeModifier extends LapsingPokemonHeldItemModifier {
public override type: TerastallizeModifierType;
public teraType: Type;
public isTransferable: boolean = false;
constructor(type: TerastallizeModifierType, pokemonId: number, teraType: Type, battlesLeft?: number, stackCount?: number) {
super(type, pokemonId, battlesLeft || 10, stackCount);
this.teraType = teraType;
}
matchType(modifier: Modifier): boolean {
if (modifier instanceof TerastallizeModifier && modifier.teraType === this.teraType) {
return true;
}
return false;
}
clone(): TerastallizeModifier {
return new TerastallizeModifier(this.type, this.pokemonId, this.teraType, this.battlesLeft, this.stackCount);
}
getArgs(): any[] {
return [ this.pokemonId, this.teraType, this.battlesLeft ];
}
/**
* Applies the {@linkcode TerastallizeModifier} to the specified {@linkcode Pokemon}.
* @param pokemon the {@linkcode Pokemon} to be terastallized
* @returns always `true`
*/
override apply(pokemon: Pokemon): boolean {
if (pokemon.isPlayer()) {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeTeraTrigger);
globalScene.validateAchv(achvs.TERASTALLIZE);
if (this.teraType === Type.STELLAR) {
globalScene.validateAchv(achvs.STELLAR_TERASTALLIZE);
}
}
pokemon.updateSpritePipelineData();
return true;
}
/**
* Triggers {@linkcode LapsingPokemonHeldItemModifier.lapse} and if it returns `0` a form change is triggered.
* @param pokemon THe {@linkcode Pokemon} to be terastallized
* @returns the result of {@linkcode LapsingPokemonHeldItemModifier.lapse}
*/
public override lapse(pokemon: Pokemon): boolean {
const ret = super.lapse(pokemon);
if (!ret) {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeLapseTeraTrigger);
pokemon.updateSpritePipelineData();
}
return ret;
}
getScoreMultiplier(): number {
return 1.25;
}
getMaxHeldItemCount(pokemon: Pokemon): number {
return 1;
}
}
/** /**
* Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that * Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that
* increase the value of a given {@linkcode PermanentStat}. * increase the value of a given {@linkcode PermanentStat}.
@ -2022,6 +1956,36 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier {
} }
} }
export class TerrastalizeModifier extends ConsumablePokemonModifier {
public override type: TerastallizeModifierType;
public teraType: Type;
constructor(type: TerastallizeModifierType, pokemonId: number, teraType: Type) {
super(type, pokemonId);
this.teraType = teraType;
}
/**
* Checks if {@linkcode TerrastalizeModifier} should be applied
* @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item
* @returns `true` if the {@linkcode TerrastalizeModifier} should be applied
*/
override shouldApply(playerPokemon?: PlayerPokemon): boolean {
return super.shouldApply(playerPokemon) && [ playerPokemon?.species.speciesId, playerPokemon?.fusionSpecies?.speciesId ].filter(s => s === Species.TERAPAGOS || s === Species.OGERPON || s === Species.SHEDINJA).length === 0;
}
/**
* Applies {@linkcode TerrastalizeModifier}
* @param pokemon The {@linkcode PlayerPokemon} that consumes the item
* @returns `true` if hp was restored
*/
override apply(pokemon: Pokemon): boolean {
pokemon.teraType = this.teraType;
return true;
}
}
export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { export class PokemonHpRestoreModifier extends ConsumablePokemonModifier {
private restorePoints: number; private restorePoints: number;
private restorePercent: number; private restorePercent: number;

View File

@ -118,6 +118,7 @@ export class CommandPhase extends FieldPhase {
let success: boolean = false; let success: boolean = false;
switch (command) { switch (command) {
case Command.TERA:
case Command.FIGHT: case Command.FIGHT:
let useStruggle = false; let useStruggle = false;
const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined); const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined);
@ -137,6 +138,7 @@ export class CommandPhase extends FieldPhase {
} }
const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args };
const preTurnCommand: TurnCommand = { command: command, targets: [ this.fieldIndex ], skip: command === Command.FIGHT };
const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 }; const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 };
if (!moveId) { if (!moveId) {
turnCommand.targets = [ this.fieldIndex ]; turnCommand.targets = [ this.fieldIndex ];
@ -152,6 +154,7 @@ export class CommandPhase extends FieldPhase {
} else { } else {
globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex));
} }
globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand;
globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
success = true; success = true;
} else if (cursor < playerPokemon.getMoveset().length) { } else if (cursor < playerPokemon.getMoveset().length) {

View File

@ -81,6 +81,10 @@ export class EnemyCommandPhase extends FieldPhase {
/** Select a move to use (and a target to use it against, if applicable) */ /** Select a move to use (and a target to use it against, if applicable) */
const nextMove = enemyPokemon.getNextMove(); const nextMove = enemyPokemon.getNextMove();
if (trainer && trainer.shouldTera(enemyPokemon)) {
globalScene.currentBattle.preTurnCommands[this.fieldIndex + BattlerIndex.ENEMY] = { command: Command.TERA };
}
globalScene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = globalScene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] =
{ command: Command.FIGHT, move: nextMove, skip: this.skipTurn }; { command: Command.FIGHT, move: nextMove, skip: this.skipTurn };

View File

@ -116,7 +116,7 @@ export class EvolutionPhase extends Phase {
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()), isTerastallized: this.pokemon.isTerastallized });
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
sprite.setPipelineData("shiny", this.pokemon.shiny); sprite.setPipelineData("shiny", this.pokemon.shiny);

View File

@ -108,6 +108,8 @@ export class FaintPhase extends PokemonPhase {
globalScene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); globalScene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true);
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
pokemon.resetTera();
if (pokemon.turnData?.attacksReceived?.length) { if (pokemon.turnData?.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0]; const lastAttack = pokemon.turnData.attacksReceived[0];
applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, globalScene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct? applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, globalScene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct?

View File

@ -450,6 +450,12 @@ export class MoveEffectPhase extends PokemonPhase {
target.lapseTag(BattlerTagType.SUBSTITUTE); target.lapseTag(BattlerTagType.SUBSTITUTE);
} }
}); });
const moveType = user.getMoveType(move, true);
if (move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) {
user.stellarTypesBoosted.push(moveType);
}
this.end(); this.end();
}); });
}); });

View File

@ -36,5 +36,6 @@ export class PartyHealPhase extends BattlePhase {
globalScene.ui.fadeIn(500).then(() => this.end()); globalScene.ui.fadeIn(500).then(() => this.end());
}); });
}); });
globalScene.arena.playerTerasUsed = 0;
} }
} }

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { SemiInvulnerableTag } from "#app/data/battler-tags"; import { SemiInvulnerableTag } from "#app/data/battler-tags";
import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms"; import { getSpeciesFormChangeMessage, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { BattleSpec } from "#app/enums/battle-spec"; import { BattleSpec } from "#app/enums/battle-spec";
import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type";
@ -11,6 +11,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
import { MovePhase } from "./move-phase"; import { MovePhase } from "./move-phase";
import { PokemonHealPhase } from "./pokemon-heal-phase"; import { PokemonHealPhase } from "./pokemon-heal-phase";
import { applyAbAttrs, PostTeraFormChangeStatChangeAbAttr } from "#app/data/ability";
export class QuietFormChangePhase extends BattlePhase { export class QuietFormChangePhase extends BattlePhase {
protected pokemon: Pokemon; protected pokemon: Pokemon;
@ -51,7 +52,7 @@ export class QuietFormChangePhase extends BattlePhase {
} catch (err: unknown) { } catch (err: unknown) {
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()), isTerastallized: this.pokemon.isTerastallized });
[ "spriteColors", "fusionSpriteColors" ].map(k => { [ "spriteColors", "fusionSpriteColors" ].map(k => {
if (this.pokemon.summonData?.speciesForm) { if (this.pokemon.summonData?.speciesForm) {
k += "Base"; k += "Base";
@ -145,6 +146,9 @@ export class QuietFormChangePhase extends BattlePhase {
movePhase.cancel(); movePhase.cancel();
} }
} }
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
applyAbAttrs(PostTeraFormChangeStatChangeAbAttr, this.pokemon, null);
}
super.end(); super.end();
} }

51
src/phases/tera-phase.ts Normal file
View File

@ -0,0 +1,51 @@
import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { BattlePhase } from "./battle-phase";
import i18next from "i18next";
import { globalScene } from "#app/global-scene";
import { Type } from "#app/enums/type";
import { achvs } from "#app/system/achv";
import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
export class TeraPhase extends BattlePhase {
public pokemon: Pokemon;
constructor(pokemon: Pokemon) {
super();
this.pokemon = pokemon;
}
start() {
super.start();
console.log(this.pokemon.name, "terastallized to", Type[this.pokemon.teraType].toString());
globalScene.queueMessage(i18next.t("battle:pokemonTerastallized", { pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), type: i18next.t(`pokemonInfo:Type.${Type[this.pokemon.teraType]}`) }));
new CommonBattleAnim(CommonAnim.TERASTALLIZE, this.pokemon).play(false, () => {
this.end();
});
}
end() {
this.pokemon.isTerastallized = true;
this.pokemon.updateSpritePipelineData();
if (this.pokemon.isPlayer()) {
globalScene.arena.playerTerasUsed += 1;
}
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangeTeraTrigger);
if (this.pokemon.isPlayer()) {
globalScene.validateAchv(achvs.TERASTALLIZE);
if (this.pokemon.teraType === Type.STELLAR) {
globalScene.validateAchv(achvs.STELLAR_TERASTALLIZE);
}
}
super.end();
}
}

View File

@ -21,6 +21,7 @@ import { BattlerIndex } from "#app/battle";
import { TrickRoomTag } from "#app/data/arena-tag"; import { TrickRoomTag } from "#app/data/arena-tag";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { TeraPhase } from "./tera-phase";
export class TurnStartPhase extends FieldPhase { export class TurnStartPhase extends FieldPhase {
constructor() { constructor() {
@ -139,6 +140,20 @@ export class TurnStartPhase extends FieldPhase {
let orderIndex = 0; let orderIndex = 0;
for (const o of this.getSpeedOrder()) {
const pokemon = field[o];
const preTurnCommand = globalScene.currentBattle.preTurnCommands[o];
if (preTurnCommand?.skip) {
continue;
}
switch (preTurnCommand?.command) {
case Command.TERA:
globalScene.pushPhase(new TeraPhase(pokemon));
}
}
for (const o of moveOrder) { for (const o of moveOrder) {
const pokemon = field[o]; const pokemon = field[o];

View File

@ -351,7 +351,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
const data = sprite.pipelineData; const data = sprite.pipelineData;
const tone = data["tone"] as number[]; const tone = data["tone"] as number[];
const teraColor = data["teraColor"] as number[] ?? [ 0, 0, 0 ]; const teraColor = (data["isTerastallized"] as boolean) ? (data["teraColor"] as number[] ?? [ 0, 0, 0 ]) : [ 0, 0, 0 ];
const hasShadow = data["hasShadow"] as boolean; const hasShadow = data["hasShadow"] as boolean;
const yShadowOffset = data["yShadowOffset"] as number; const yShadowOffset = data["yShadowOffset"] as number;
const ignoreFieldPos = data["ignoreFieldPos"] as boolean; const ignoreFieldPos = data["ignoreFieldPos"] as boolean;

View File

@ -10,12 +10,14 @@ export default class ArenaData {
public weather: Weather | null; public weather: Weather | null;
public terrain: Terrain | null; public terrain: Terrain | null;
public tags: ArenaTag[]; public tags: ArenaTag[];
public playerTerasUsed: number;
constructor(source: Arena | any) { constructor(source: Arena | any) {
const sourceArena = source instanceof Arena ? source as Arena : null; const sourceArena = source instanceof Arena ? source as Arena : null;
this.biome = sourceArena ? sourceArena.biomeType : source.biome; this.biome = sourceArena ? sourceArena.biomeType : source.biome;
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null; this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null; this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
this.playerTerasUsed = (sourceArena ? sourceArena.playerTerasUsed : source.playerTerasUsed) ?? 0;
this.tags = []; this.tags = [];
if (source.tags) { if (source.tags) {

View File

@ -1087,6 +1087,8 @@ export class GameData {
globalScene.arena.terrain = sessionData.arena.terrain; globalScene.arena.terrain = sessionData.arena.terrain;
globalScene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, globalScene.arena.terrain?.terrainType!, globalScene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct? globalScene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, globalScene.arena.terrain?.terrainType!, globalScene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct?
globalScene.arena.playerTerasUsed = sessionData.arena.playerTerasUsed;
globalScene.arena.tags = sessionData.arena.tags; globalScene.arena.tags = sessionData.arena.tags;
if (globalScene.arena.tags) { if (globalScene.arena.tags) {
for (const tag of globalScene.arena.tags) { for (const tag of globalScene.arena.tags) {

View File

@ -13,6 +13,7 @@ import type { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import type { Species } from "#enums/species"; import type { Species } from "#enums/species";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import type { Type } from "#app/enums/type";
export default class PokemonData { export default class PokemonData {
public id: number; public id: number;
@ -45,6 +46,9 @@ export default class PokemonData {
public pokerus: boolean; public pokerus: boolean;
public usedTMs: Moves[]; public usedTMs: Moves[];
public evoCounter: number; public evoCounter: number;
public teraType: Type;
public isTerastallized: boolean;
public stellarTypesBoosted: Type[];
public fusionSpecies: Species; public fusionSpecies: Species;
public fusionFormIndex: number; public fusionFormIndex: number;
@ -53,6 +57,7 @@ export default class PokemonData {
public fusionVariant: Variant; public fusionVariant: Variant;
public fusionGender: Gender; public fusionGender: Gender;
public fusionLuck: number; public fusionLuck: number;
public fusionTeraType: Type;
public boss: boolean; public boss: boolean;
public bossSegments?: number; public bossSegments?: number;
@ -103,6 +108,9 @@ export default class PokemonData {
this.evoCounter = source.evoCounter ?? 0; this.evoCounter = source.evoCounter ?? 0;
} }
this.pokerus = !!source.pokerus; this.pokerus = !!source.pokerus;
this.teraType = source.teraType as Type;
this.isTerastallized = source.isTerastallized || false;
this.stellarTypesBoosted = source.stellarTypesBoosted || [];
this.fusionSpecies = sourcePokemon ? sourcePokemon.fusionSpecies?.speciesId : source.fusionSpecies; this.fusionSpecies = sourcePokemon ? sourcePokemon.fusionSpecies?.speciesId : source.fusionSpecies;
this.fusionFormIndex = source.fusionFormIndex; this.fusionFormIndex = source.fusionFormIndex;
@ -112,6 +120,7 @@ export default class PokemonData {
this.fusionGender = source.fusionGender; this.fusionGender = source.fusionGender;
this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0); this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0);
this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData); this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData);
this.fusionTeraType = (source.fusionTeraType ?? 0) as Type;
this.usedTMs = source.usedTMs ?? []; this.usedTMs = source.usedTMs ?? [];
this.customPokemonData = new CustomPokemonData(source.customPokemonData); this.customPokemonData = new CustomPokemonData(source.customPokemonData);

View File

@ -7,6 +7,9 @@ import * as v1_0_4 from "./versions/v1_0_4";
// --- v1.1.0 PATCHES --- // // --- v1.1.0 PATCHES --- //
import * as v1_1_0 from "./versions/v1_1_0"; import * as v1_1_0 from "./versions/v1_1_0";
// --- v1.7.0 PATCHES --- //
import * as v1_7_0 from "./versions/v1_7_0";
const LATEST_VERSION = version.split(".").map(value => parseInt(value)); const LATEST_VERSION = version.split(".").map(value => parseInt(value));
/** /**
@ -138,6 +141,10 @@ class SessionVersionConverter extends VersionConverter {
console.log("Applying v1.1.0 session data migration!"); console.log("Applying v1.1.0 session data migration!");
this.callMigrators(data, v1_1_0.sessionMigrators); this.callMigrators(data, v1_1_0.sessionMigrators);
} }
if (curMinor < 7) {
console.log("Applying v1.7.0 session data migration!");
this.callMigrators(data, v1_7_0.sessionMigrators);
}
} }
console.log(`Session data successfully migrated to v${version}!`); console.log(`Session data successfully migrated to v${version}!`);
@ -164,6 +171,10 @@ class SystemVersionConverter extends VersionConverter {
console.log("Applying v1.1.0 system data migraton!"); console.log("Applying v1.1.0 system data migraton!");
this.callMigrators(data, v1_1_0.systemMigrators); this.callMigrators(data, v1_1_0.systemMigrators);
} }
if (curMinor < 7) {
console.log("Applying v1.7.0 session data migration!");
this.callMigrators(data, v1_7_0.systemMigrators);
}
} }
console.log(`System data successfully migrated to v${version}!`); console.log(`System data successfully migrated to v${version}!`);
@ -190,6 +201,10 @@ class SettingsVersionConverter extends VersionConverter {
console.log("Applying v1.1.0 settings data migraton!"); console.log("Applying v1.1.0 settings data migraton!");
this.callMigrators(data, v1_1_0.settingsMigrators); this.callMigrators(data, v1_1_0.settingsMigrators);
} }
if (curMinor < 7) {
console.log("Applying v1.7.0 session data migration!");
this.callMigrators(data, v1_7_0.settingsMigrators);
}
} }
console.log(`System data successfully migrated to v${version}!`); console.log(`System data successfully migrated to v${version}!`);

View File

@ -0,0 +1,49 @@
import { getPokemonSpeciesForm } from "#app/data/pokemon-species";
import type { SessionSaveData } from "#app/system/game-data";
import * as Utils from "#app/utils";
export const systemMigrators = [] as const;
export const settingsMigrators = [] as const;
export const sessionMigrators = [
function migrateTera(data: SessionSaveData) {
for (let i = 0; i < data.modifiers.length;) {
if (data.modifiers[i].className === "TerastallizeModifier") {
data.party.forEach((p) => {
if (p.id === data.modifiers[i].args[0]) {
p.teraType = data.modifiers[i].args[1];
}
});
data.modifiers.splice(i, 1);
} else {
i++;
}
}
for (let i = 0; i < data.enemyModifiers.length;) {
if (data.enemyModifiers[i].className === "TerastallizeModifier") {
data.enemyParty.forEach((p) => {
if (p.id === data.enemyModifiers[i].args[0]) {
p.teraType = data.enemyModifiers[i].args[1];
}
});
data.enemyModifiers.splice(i, 1);
} else {
i++;
}
}
data.party.forEach(p => {
if (Utils.isNullOrUndefined(p.teraType)) {
p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1;
}
});
data.enemyParty.forEach(p => {
if (Utils.isNullOrUndefined(p.teraType)) {
p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1;
}
});
}
] as const;

View File

@ -11,7 +11,7 @@ import { Species } from "#enums/species";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
describe("Abilities - Libero", () => { describe("Abilities - Libero", () => {
@ -258,7 +258,7 @@ describe("Abilities - Libero", () => {
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true); leadPokemon.isTerastallized = true;
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);

View File

@ -11,7 +11,7 @@ import { Species } from "#enums/species";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
describe("Abilities - Protean", () => { describe("Abilities - Protean", () => {
@ -258,7 +258,7 @@ describe("Abilities - Protean", () => {
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true); leadPokemon.isTerastallized = true;
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);

View File

@ -6,7 +6,6 @@ import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves"; import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species"; import { Species } from "#app/enums/species";
import * as Messages from "#app/messages"; import * as Messages from "#app/messages";
import { TerastallizeModifier, overrideHeldItems } from "#app/modifier/modifier";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
@ -15,15 +14,14 @@ function testMoveEffectiveness(game: GameManager, move: Moves, targetSpecies: Sp
expected: number, targetAbility: Abilities = Abilities.BALL_FETCH, teraType?: Type): void { expected: number, targetAbility: Abilities = Abilities.BALL_FETCH, teraType?: Type): void {
// Suppress getPokemonNameWithAffix because it calls on a null battle spec // Suppress getPokemonNameWithAffix because it calls on a null battle spec
vi.spyOn(Messages, "getPokemonNameWithAffix").mockReturnValue(""); vi.spyOn(Messages, "getPokemonNameWithAffix").mockReturnValue("");
game.override game.override.enemyAbility(targetAbility);
.enemyAbility(targetAbility)
.enemyHeldItems([{ name:"TERA_SHARD", type: teraType }]);
const user = game.scene.addPlayerPokemon(getPokemonSpecies(Species.SNORLAX), 5); const user = game.scene.addPlayerPokemon(getPokemonSpecies(Species.SNORLAX), 5);
const target = game.scene.addEnemyPokemon(getPokemonSpecies(targetSpecies), 5, TrainerSlot.NONE); const target = game.scene.addEnemyPokemon(getPokemonSpecies(targetSpecies), 5, TrainerSlot.NONE);
if (teraType !== undefined) { if (teraType !== undefined) {
overrideHeldItems(target, false); target.teraType = teraType;
target.isTerastallized = true;
} }
expect(target.getMoveEffectiveness(user, allMoves[move])).toBe(expected); expect(target.getMoveEffectiveness(user, allMoves[move])).toBe(expected);
@ -40,7 +38,6 @@ describe("Moves - Type Effectiveness", () => {
type: Phaser.HEADLESS, type: Phaser.HEADLESS,
}); });
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
TerastallizeModifier.prototype.apply = (args) => true;
game.override.ability(Abilities.BALL_FETCH); game.override.ability(Abilities.BALL_FETCH);
}); });

View File

@ -117,11 +117,12 @@ describe("Moves - Freeze-Dry", () => {
}); });
it("should deal 2x damage to steel type terastallized into water", async () => { it("should deal 2x damage to steel type terastallized into water", async () => {
game.override.enemySpecies(Species.SKARMORY) game.override.enemySpecies(Species.SKARMORY);
.enemyHeldItems([{ name: "TERA_SHARD", type: Type.WATER }]);
await game.classicMode.startBattle(); await game.classicMode.startBattle();
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.teraType = Type.WATER;
enemy.isTerastallized = true;
vi.spyOn(enemy, "getMoveEffectiveness"); vi.spyOn(enemy, "getMoveEffectiveness");
game.move.select(Moves.FREEZE_DRY); game.move.select(Moves.FREEZE_DRY);
@ -132,11 +133,12 @@ describe("Moves - Freeze-Dry", () => {
}); });
it("should deal 0.5x damage to water type terastallized into fire", async () => { it("should deal 0.5x damage to water type terastallized into fire", async () => {
game.override.enemySpecies(Species.PELIPPER) game.override.enemySpecies(Species.PELIPPER);
.enemyHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }]);
await game.classicMode.startBattle(); await game.classicMode.startBattle();
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.teraType = Type.FIRE;
enemy.isTerastallized = true;
vi.spyOn(enemy, "getMoveEffectiveness"); vi.spyOn(enemy, "getMoveEffectiveness");
game.move.select(Moves.FREEZE_DRY); game.move.select(Moves.FREEZE_DRY);

View File

@ -83,10 +83,12 @@ describe("Moves - Tar Shot", () => {
}); });
it("does not double the effectiveness of Fire-type moves against a Pokémon that is Terastallized", async () => { it("does not double the effectiveness of Fire-type moves against a Pokémon that is Terastallized", async () => {
game.override.enemyHeldItems([{ name: "TERA_SHARD", type: Type.GRASS }]).enemySpecies(Species.SPRIGATITO); game.override.enemySpecies(Species.SPRIGATITO);
await game.classicMode.startBattle([ Species.PIKACHU ]); await game.classicMode.startBattle([ Species.PIKACHU ]);
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
enemy.teraType = Type.GRASS;
enemy.isTerastallized = true;
vi.spyOn(enemy, "getMoveEffectiveness"); vi.spyOn(enemy, "getMoveEffectiveness");
@ -119,7 +121,8 @@ describe("Moves - Tar Shot", () => {
await game.toNextTurn(); await game.toNextTurn();
game.override.enemyHeldItems([{ name: "TERA_SHARD", type: Type.GRASS }]); enemy.teraType = Type.GRASS;
enemy.isTerastallized = true;
game.move.select(Moves.FIRE_PUNCH); game.move.select(Moves.FIRE_PUNCH);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);

View File

@ -35,7 +35,6 @@ describe("Moves - Tera Blast", () => {
.starterSpecies(Species.FEEBAS) .starterSpecies(Species.FEEBAS)
.moveset([ Moves.TERA_BLAST ]) .moveset([ Moves.TERA_BLAST ])
.ability(Abilities.BALL_FETCH) .ability(Abilities.BALL_FETCH)
.startingHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }])
.enemySpecies(Species.MAGIKARP) .enemySpecies(Species.MAGIKARP)
.enemyMoveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH)
.enemyAbility(Abilities.STURDY) .enemyAbility(Abilities.STURDY)
@ -45,13 +44,15 @@ describe("Moves - Tera Blast", () => {
}); });
it("changes type to match user's tera type", async () => { it("changes type to match user's tera type", async () => {
game.override game.override.enemySpecies(Species.FURRET);
.enemySpecies(Species.FURRET)
.startingHeldItems([{ name: "TERA_SHARD", type: Type.FIGHTING }]);
await game.startBattle(); await game.startBattle();
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply"); vi.spyOn(enemyPokemon, "apply");
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = Type.FIGHTING;
playerPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST); game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to("MoveEffectPhase"); await game.phaseInterceptor.to("MoveEffectPhase");
@ -60,10 +61,12 @@ describe("Moves - Tera Blast", () => {
}, 20000); }, 20000);
it("increases power if user is Stellar tera type", async () => { it("increases power if user is Stellar tera type", async () => {
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
await game.startBattle(); await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = Type.STELLAR;
playerPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST); game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to("MoveEffectPhase"); await game.phaseInterceptor.to("MoveEffectPhase");
@ -72,13 +75,15 @@ describe("Moves - Tera Blast", () => {
}, 20000); }, 20000);
it("is super effective against terastallized targets if user is Stellar tera type", async () => { it("is super effective against terastallized targets if user is Stellar tera type", async () => {
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
await game.startBattle(); await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = Type.STELLAR;
playerPokemon.isTerastallized = true;
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply"); vi.spyOn(enemyPokemon, "apply");
vi.spyOn(enemyPokemon, "isTerastallized").mockReturnValue(true); enemyPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST); game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
@ -93,6 +98,7 @@ describe("Moves - Tera Blast", () => {
const playerPokemon = game.scene.getPlayerPokemon()!; const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 100; playerPokemon.stats[Stat.ATK] = 100;
playerPokemon.stats[Stat.SPATK] = 1; playerPokemon.stats[Stat.SPATK] = 1;
playerPokemon.isTerastallized = true;
vi.spyOn(teraBlastAttr, "apply"); vi.spyOn(teraBlastAttr, "apply");
@ -169,10 +175,11 @@ describe("Moves - Tera Blast", () => {
it("causes stat drops if user is Stellar tera type", async () => { it("causes stat drops if user is Stellar tera type", async () => {
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
await game.startBattle(); await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!; const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = Type.STELLAR;
playerPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST); game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);

View File

@ -29,8 +29,7 @@ describe("Moves - Tera Starstorm", () => {
.enemyAbility(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH)
.enemyLevel(30) .enemyLevel(30)
.enemySpecies(Species.MAGIKARP) .enemySpecies(Species.MAGIKARP);
.startingHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }]);
}); });
it("changes type to Stellar when used by Terapagos in its Stellar Form", async () => { it("changes type to Stellar when used by Terapagos in its Stellar Form", async () => {
@ -38,19 +37,22 @@ describe("Moves - Tera Starstorm", () => {
await game.classicMode.startBattle([ Species.TERAPAGOS ]); await game.classicMode.startBattle([ Species.TERAPAGOS ]);
const terapagos = game.scene.getPlayerPokemon()!; const terapagos = game.scene.getPlayerPokemon()!;
terapagos.isTerastallized = true;
vi.spyOn(terapagos, "getMoveType"); vi.spyOn(terapagos, "getMoveType");
game.move.select(Moves.TERA_STARSTORM); game.move.select(Moves.TERA_STARSTORM);
await game.phaseInterceptor.to("TurnEndPhase"); await game.phaseInterceptor.to("TurnEndPhase");
expect(terapagos.isTerastallized()).toBe(true);
expect(terapagos.getMoveType).toHaveReturnedWith(Type.STELLAR); expect(terapagos.getMoveType).toHaveReturnedWith(Type.STELLAR);
}); });
it("targets both opponents in a double battle when used by Terapagos in its Stellar Form", async () => { it("targets both opponents in a double battle when used by Terapagos in its Stellar Form", async () => {
await game.classicMode.startBattle([ Species.MAGIKARP, Species.TERAPAGOS ]); await game.classicMode.startBattle([ Species.MAGIKARP, Species.TERAPAGOS ]);
const terapagos = game.scene.getPlayerParty()[1];
terapagos.isTerastallized = true;
game.move.select(Moves.TERA_STARSTORM, 0, BattlerIndex.ENEMY); game.move.select(Moves.TERA_STARSTORM, 0, BattlerIndex.ENEMY);
game.move.select(Moves.TERA_STARSTORM, 1); game.move.select(Moves.TERA_STARSTORM, 1);
@ -82,6 +84,8 @@ describe("Moves - Tera Starstorm", () => {
fusionedMon.fusionGender = magikarp.gender; fusionedMon.fusionGender = magikarp.gender;
fusionedMon.fusionLuck = magikarp.luck; fusionedMon.fusionLuck = magikarp.luck;
fusionedMon.isTerastallized = true;
vi.spyOn(fusionedMon, "getMoveType"); vi.spyOn(fusionedMon, "getMoveType");
game.move.select(Moves.TERA_STARSTORM, 0); game.move.select(Moves.TERA_STARSTORM, 0);
@ -90,7 +94,6 @@ describe("Moves - Tera Starstorm", () => {
// Fusion and terastallized // Fusion and terastallized
expect(fusionedMon.isFusion()).toBe(true); expect(fusionedMon.isFusion()).toBe(true);
expect(fusionedMon.isTerastallized()).toBe(true);
// Move effects should be applied // Move effects should be applied
expect(fusionedMon.getMoveType).toHaveReturnedWith(Type.STELLAR); expect(fusionedMon.getMoveType).toHaveReturnedWith(Type.STELLAR);
expect(game.scene.getEnemyField().every(pokemon => pokemon.isFullHp())).toBe(false); expect(game.scene.getEnemyField().every(pokemon => pokemon.isFullHp())).toBe(false);

View File

@ -324,9 +324,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.lastTeraType = pokemon.getTeraType(); this.lastTeraType = pokemon.getTeraType();
this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2);
this.teraIcon.setVisible(this.lastTeraType !== Type.UNKNOWN); this.teraIcon.setVisible(pokemon.isTerastallized);
this.teraIcon.on("pointerover", () => { this.teraIcon.on("pointerover", () => {
if (this.lastTeraType !== Type.UNKNOWN) { if (pokemon.isTerastallized) {
globalScene.ui.showTooltip("", i18next.t("fightUiHandler:teraHover", { type: i18next.t(`pokemonInfo:Type.${Type[this.lastTeraType]}`) })); globalScene.ui.showTooltip("", i18next.t("fightUiHandler:teraHover", { type: i18next.t(`pokemonInfo:Type.${Type[this.lastTeraType]}`) }));
} }
}); });
@ -542,7 +542,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0); this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0);
} }
const teraType = pokemon.getTeraType(); const teraType = pokemon.isTerastallized ? pokemon.getTeraType() : Type.UNKNOWN;
const teraTypeUpdated = this.lastTeraType !== teraType; const teraTypeUpdated = this.lastTeraType !== teraType;
if (teraTypeUpdated) { if (teraTypeUpdated) {

View File

@ -7,18 +7,24 @@ import { Button } from "#enums/buttons";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { TerastallizeAccessModifier } from "#app/modifier/modifier";
import { Type } from "#app/enums/type";
import { getTypeRgb } from "#app/data/type";
export enum Command { export enum Command {
FIGHT = 0, FIGHT = 0,
BALL, BALL,
POKEMON, POKEMON,
RUN RUN,
TERA
} }
export default class CommandUiHandler extends UiHandler { export default class CommandUiHandler extends UiHandler {
private commandsContainer: Phaser.GameObjects.Container; private commandsContainer: Phaser.GameObjects.Container;
private cursorObj: Phaser.GameObjects.Image | null; private cursorObj: Phaser.GameObjects.Image | null;
private teraButton: Phaser.GameObjects.Sprite;
protected fieldIndex: number = 0; protected fieldIndex: number = 0;
protected cursor2: number = 0; protected cursor2: number = 0;
@ -40,6 +46,13 @@ export default class CommandUiHandler extends UiHandler {
this.commandsContainer.setVisible(false); this.commandsContainer.setVisible(false);
ui.add(this.commandsContainer); ui.add(this.commandsContainer);
this.teraButton = globalScene.add.sprite(-32, 15, "button_tera");
this.teraButton.setName("terrastallize-button");
this.teraButton.setScale(1.3);
this.teraButton.setFrame("fire");
this.teraButton.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true, teraColor: getTypeRgb(Type.FIRE), isTerastallized: false });
this.commandsContainer.add(this.teraButton);
for (let c = 0; c < commands.length; c++) { for (let c = 0; c < commands.length; c++) {
const commandText = addTextObject(c % 2 === 0 ? 0 : 55.8, c < 2 ? 0 : 16, commands[c], TextStyle.WINDOW); const commandText = addTextObject(c % 2 === 0 ? 0 : 55.8, c < 2 ? 0 : 16, commands[c], TextStyle.WINDOW);
commandText.setName(commands[c]); commandText.setName(commands[c]);
@ -62,11 +75,22 @@ export default class CommandUiHandler extends UiHandler {
commandPhase = globalScene.getStandbyPhase() as CommandPhase; commandPhase = globalScene.getStandbyPhase() as CommandPhase;
} }
if (this.canTera()) {
this.teraButton.setVisible(true);
this.teraButton.setFrame(Type[globalScene.getField()[this.fieldIndex].getTeraType()].toLowerCase());
} else {
this.teraButton.setVisible(false);
if (this.cursor === Command.TERA) {
this.setCursor(Command.FIGHT);
}
}
this.toggleTeraButton();
const messageHandler = this.getUi().getMessageHandler(); const messageHandler = this.getUi().getMessageHandler();
messageHandler.bg.setVisible(true); messageHandler.bg.setVisible(true);
messageHandler.commandWindow.setVisible(true); messageHandler.commandWindow.setVisible(true);
messageHandler.movesWindowContainer.setVisible(false); messageHandler.movesWindowContainer.setVisible(false);
messageHandler.message.setWordWrapWidth(1110); messageHandler.message.setWordWrapWidth(this.canTera() ? 910 : 1110);
messageHandler.showText(i18next.t("commandUiHandler:actionMessage", { pokemonName: getPokemonNameWithAffix(commandPhase.getPokemon()) }), 0); messageHandler.showText(i18next.t("commandUiHandler:actionMessage", { pokemonName: getPokemonNameWithAffix(commandPhase.getPokemon()) }), 0);
if (this.getCursor() === Command.POKEMON) { if (this.getCursor() === Command.POKEMON) {
this.setCursor(Command.FIGHT); this.setCursor(Command.FIGHT);
@ -108,6 +132,10 @@ export default class CommandUiHandler extends UiHandler {
(globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); (globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0);
success = true; success = true;
break; break;
case Command.TERA:
ui.setMode(Mode.FIGHT, (globalScene.getCurrentPhase() as CommandPhase).getFieldIndex(), Command.TERA);
success = true;
break;
} }
} else { } else {
(globalScene.getCurrentPhase() as CommandPhase).cancel(); (globalScene.getCurrentPhase() as CommandPhase).cancel();
@ -115,23 +143,29 @@ export default class CommandUiHandler extends UiHandler {
} else { } else {
switch (button) { switch (button) {
case Button.UP: case Button.UP:
if (cursor >= 2) { if (cursor === Command.POKEMON || cursor === Command.RUN) {
success = this.setCursor(cursor - 2); success = this.setCursor(cursor - 2);
} }
break; break;
case Button.DOWN: case Button.DOWN:
if (cursor < 2) { if (cursor === Command.FIGHT || cursor === Command.BALL) {
success = this.setCursor(cursor + 2); success = this.setCursor(cursor + 2);
} }
break; break;
case Button.LEFT: case Button.LEFT:
if (cursor % 2 === 1) { if (cursor === Command.BALL || cursor === Command.RUN) {
success = this.setCursor(cursor - 1); success = this.setCursor(cursor - 1);
} else if ((cursor === Command.FIGHT || cursor === Command.POKEMON) && this.canTera()) {
success = this.setCursor(Command.TERA);
this.toggleTeraButton();
} }
break; break;
case Button.RIGHT: case Button.RIGHT:
if (cursor % 2 === 0) { if (cursor === Command.FIGHT || cursor === Command.POKEMON) {
success = this.setCursor(cursor + 1); success = this.setCursor(cursor + 1);
} else if (cursor === Command.TERA) {
success = this.setCursor(Command.FIGHT);
this.toggleTeraButton();
} }
break; break;
} }
@ -144,6 +178,17 @@ export default class CommandUiHandler extends UiHandler {
return success; return success;
} }
canTera(): boolean {
const hasTeraMod = !!globalScene.getModifiers(TerastallizeAccessModifier).length;
const currentTeras = globalScene.arena.playerTerasUsed;
const plannedTera = globalScene.currentBattle.preTurnCommands[0]?.command === Command.TERA && this.fieldIndex > 0 ? 1 : 0;
return hasTeraMod && (currentTeras + plannedTera) < 1;
}
toggleTeraButton() {
this.teraButton.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true, teraColor: getTypeRgb(globalScene.getField()[this.fieldIndex].getTeraType()), isTerastallized: this.getCursor() === Command.TERA });
}
getCursor(): number { getCursor(): number {
return !this.fieldIndex ? this.cursor : this.cursor2; return !this.fieldIndex ? this.cursor : this.cursor2;
} }
@ -163,7 +208,12 @@ export default class CommandUiHandler extends UiHandler {
this.commandsContainer.add(this.cursorObj); this.commandsContainer.add(this.cursorObj);
} }
if (cursor === Command.TERA) {
this.cursorObj.setVisible(false);
} else {
this.cursorObj.setPosition(-5 + (cursor % 2 === 1 ? 56 : 0), 8 + (cursor >= 2 ? 16 : 0)); this.cursorObj.setPosition(-5 + (cursor % 2 === 1 ? 56 : 0), 8 + (cursor >= 2 ? 16 : 0));
this.cursorObj.setVisible(true);
}
return changed; return changed;
} }

View File

@ -33,6 +33,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
private moveInfoOverlay : MoveInfoOverlay; private moveInfoOverlay : MoveInfoOverlay;
protected fieldIndex: number = 0; protected fieldIndex: number = 0;
protected fromCommand: Command = Command.FIGHT;
protected cursor2: number = 0; protected cursor2: number = 0;
constructor() { constructor() {
@ -114,6 +115,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
super.show(args); super.show(args);
this.fieldIndex = args.length ? args[0] as number : 0; this.fieldIndex = args.length ? args[0] as number : 0;
this.fromCommand = args.length > 1 ? args[1] as Command : Command.FIGHT;
const messageHandler = this.getUi().getMessageHandler(); const messageHandler = this.getUi().getMessageHandler();
messageHandler.bg.setVisible(false); messageHandler.bg.setVisible(false);
@ -140,7 +142,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
if (button === Button.CANCEL || button === Button.ACTION) { if (button === Button.CANCEL || button === Button.ACTION) {
if (button === Button.ACTION) { if (button === Button.ACTION) {
if ((globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, cursor, false)) { if ((globalScene.getCurrentPhase() as CommandPhase).handleCommand(this.fromCommand, cursor, false)) {
success = true; success = true;
} else { } else {
ui.playError(); ui.playError();

View File

@ -420,17 +420,6 @@ export default class RunInfoUiHandler extends UiHandler {
private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) {
// Loads and adds trainer sprites to the UI // Loads and adds trainer sprites to the UI
this.showTrainerSprites(enemyContainer); this.showTrainerSprites(enemyContainer);
// Determining which Terastallize Modifier belongs to which Pokemon
// Creates a dictionary {PokemonId: TeraShardType}
const teraPokemon = {};
this.runInfo.enemyModifiers.forEach((m) => {
const modifier = m.toModifier(this.modifiersModule[m.className]);
if (modifier instanceof Modifier.TerastallizeModifier) {
const teraDetails = modifier?.getArgs();
const pkmnId = teraDetails[0];
teraPokemon[pkmnId] = teraDetails[1];
}
});
// Creates the Pokemon icons + level information and adds it to enemyContainer // Creates the Pokemon icons + level information and adds it to enemyContainer
// 2 Rows x 3 Columns // 2 Rows x 3 Columns
@ -444,18 +433,6 @@ export default class RunInfoUiHandler extends UiHandler {
enemyData["player"] = true; enemyData["player"] = true;
const enemy = enemyData.toPokemon(); const enemy = enemyData.toPokemon();
const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0); const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0);
// Applying Terastallizing Type tint to Pokemon icon
// If the Pokemon is a fusion, it has two sprites and so, the tint has to be applied to each icon separately
const enemySprite1 = enemyIcon.list[0] as Phaser.GameObjects.Sprite;
const enemySprite2 = (enemyIcon.list.length > 1) ? enemyIcon.list[1] as Phaser.GameObjects.Sprite : undefined;
if (teraPokemon[enemyData.id]) {
const teraTint = getTypeRgb(teraPokemon[enemyData.id]);
const teraColor = new Phaser.Display.Color(teraTint[0], teraTint[1], teraTint[2]);
enemySprite1.setTint(teraColor.color);
if (enemySprite2) {
enemySprite2.setTint(teraColor.color);
}
}
enemyIcon.setPosition(39 * (e % 3) + 5, (35 * pokemonRowHeight)); enemyIcon.setPosition(39 * (e % 3) + 5, (35 * pokemonRowHeight));
const enemyLevel = addTextObject(43 * (e % 3), (27 * (pokemonRowHeight + 1)), `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "54px" }); const enemyLevel = addTextObject(43 * (e % 3), (27 * (pokemonRowHeight + 1)), `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "54px" });
enemyLevel.setShadow(0, 0, undefined); enemyLevel.setShadow(0, 0, undefined);

View File

@ -334,6 +334,7 @@ export default class SummaryUiHandler extends UiHandler {
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType())); this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType()));
this.pokemonSprite.setPipelineData("isTerastallized", this.pokemon.isTerastallized);
this.pokemonSprite.setPipelineData("ignoreTimeTint", true); this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny); this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);
@ -782,7 +783,7 @@ export default class SummaryUiHandler extends UiHandler {
if (types.length > 1) { if (types.length > 1) {
profileContainer.add(getTypeIcon(1, types[1])); profileContainer.add(getTypeIcon(1, types[1]));
} }
if (this.pokemon?.isTerastallized()) { if (this.pokemon?.isTerastallized) {
profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true)); profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true));
} }