Merge pull request #3787 from AsdarDevelops/mystery-encounters-beta

Moving MEs branch to main repo
This commit is contained in:
ImperialSympathizer 2024-08-26 14:54:47 -04:00 committed by GitHub
commit a38a66dd78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
226 changed files with 34761 additions and 4249 deletions

Binary file not shown.

View File

@ -0,0 +1,951 @@
{
"id": 686,
"graphic": "PRAS- Dragon Dance",
"frames": [
[
{
"x": 4,
"y": -8,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 12,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 70,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -12,
"y": -0.5,
"zoomX": 100,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 70,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": 12,
"y": -12,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 16,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -16,
"y": -0.5,
"zoomX": 100,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": 24,
"y": -12,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 20,
"y": 0,
"zoomX": 108,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -20,
"y": -0.5,
"zoomX": 108,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": 32,
"y": -8,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 24,
"y": 0,
"zoomX": 108,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -24,
"y": -0.5,
"zoomX": 108,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": 36,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 28,
"y": 0,
"zoomX": 108,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 70,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -28,
"y": -0.5,
"zoomX": 108,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 70,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": 36,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 36,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 32,
"y": -8,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 24,
"y": -12,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 12,
"y": -12,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": 4,
"y": -8,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"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": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"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": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
}
],
[
{
"x": -4,
"y": -8,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 12,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 70,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -12,
"y": -0.5,
"zoomX": 100,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 70,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": -12,
"y": -12,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 16,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -16,
"y": -0.5,
"zoomX": 100,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": -24,
"y": -12,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 20,
"y": 0,
"zoomX": 108,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -20,
"y": -0.5,
"zoomX": 108,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": -32,
"y": -8,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 24,
"y": 0,
"zoomX": 108,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -24,
"y": -0.5,
"zoomX": 108,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 155,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": -36,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
},
{
"x": 28,
"y": 0,
"zoomX": 108,
"zoomY": 100,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 70,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
},
{
"x": -28,
"y": -0.5,
"zoomX": 108,
"zoomY": 100,
"mirror": true,
"visible": true,
"blendType": 1,
"target": 2,
"graphicFrame": 0,
"opacity": 70,
"tone": [
0,
0,
0,
255
],
"priority": 1,
"focus": 3
}
],
[
{
"x": -36,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": -36,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": -32,
"y": -8,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": -24,
"y": -12,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": -12,
"y": -12,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"priority": 1,
"focus": 2
}
],
[
{
"x": -4,
"y": -8,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"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": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"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": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"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": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"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": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"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": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"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
}
]
],
"frameTimedEvents": {
"0": [
{
"frameIndex": 0,
"resourceName": "PRSFX- Attract.wav",
"volume": 100,
"pitch": 100,
"eventType": "AnimTimedSoundEvent"
}
],
"1": [
{
"frameIndex": 0,
"resourceName": "PRSFX- Ally Switch.wav",
"volume": 80,
"pitch": 100,
"eventType": "AnimTimedSoundEvent"
}
]
},
"position": 4,
"hue": 0
}

View File

@ -0,0 +1,66 @@
{
"frames": [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
],
"frameTimedEvents": {
"0": [
{
"frameIndex": 0,
"resourceName": "PRAS- Fire BG",
"bgX": 0,
"bgY": 0,
"opacity": 0,
"duration": 35,
"eventType": "AnimTimedAddBgEvent"
},
{
"frameIndex": 0,
"resourceName": "",
"bgX": 0,
"bgY": 0,
"opacity": 255,
"duration": 12,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"25": [
{
"frameIndex": 25,
"resourceName": "",
"bgX": 0,
"bgY": 0,
"opacity": 0,
"duration": 8,
"eventType": "AnimTimedUpdateBgEvent"
}
]
},
"position": 1,
"hue": 0
}

View File

@ -0,0 +1,902 @@
{
"graphic": "PRAS- Magma Storm",
"frames": [
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 2,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 2,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 2,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 3,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 3,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 3,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 4,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 4,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 4,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 5,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 5,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 5,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 6,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 6,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 6,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 7,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 7,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 7,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 120,
"y": -56,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 8,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 144,
"y": -84,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 8,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 100,
"y": -86.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 8,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 140,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 9,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 136,
"y": -92,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 9,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 108,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 9,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 152,
"y": -76,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 10,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 116,
"y": -88,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 10,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 128,
"y": -62.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 10,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 136,
"y": -96,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 7,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 100,
"y": -76,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 7,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 148,
"y": -66.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 7,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 108,
"y": -92,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 8,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 120,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 8,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 144,
"y": -86.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 8,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 100,
"y": -76,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 9,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 136,
"y": -68,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 9,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 128,
"y": -94.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 9,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 100.5,
"y": -70,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 10,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 144,
"y": -66,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 10,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 126,
"y": -86.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 10,
"opacity": 255,
"priority": 4,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 6,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 6,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 6,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 5,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 5,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 5,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 4,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 4,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 4,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 3,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 3,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 3,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 2,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 2,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 2,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 255,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 255,
"priority": 4,
"focus": 1
}
],
[
{
"x": 101,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 130,
"priority": 4,
"focus": 1
},
{
"x": 152,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 130,
"priority": 4,
"focus": 1
},
{
"x": 124.5,
"y": -78.5,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 140,
"priority": 4,
"focus": 1
}
]
],
"frameTimedEvents": {
"0": [
{
"frameIndex": 0,
"resourceName": "PRSFX- Magma Storm1.wav",
"volume": 100,
"pitch": 100,
"eventType": "AnimTimedSoundEvent"
}
],
"8": [
{
"frameIndex": 8,
"resourceName": "PRSFX- Magma Storm2.wav",
"volume": 100,
"pitch": 100,
"eventType": "AnimTimedSoundEvent"
}
]
},
"position": 1,
"hue": 0
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

View File

@ -0,0 +1,734 @@
{
"textures": [
{
"image": "b2w2_lady.png",
"format": "RGBA8888",
"size": {
"w": 399,
"h": 360
},
"scale": 1,
"frames": [
{
"filename": "0000.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 0,
"y": 0,
"w": 56,
"h": 72
}
},
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 57,
"y": 0,
"w": 56,
"h": 72
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 114,
"y": 0,
"w": 56,
"h": 72
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 9,
"y": 8,
"w": 55,
"h": 72
},
"frame": {
"x": 171,
"y": 0,
"w": 55,
"h": 72
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 11,
"y": 8,
"w": 54,
"h": 72
},
"frame": {
"x": 228,
"y": 0,
"w": 54,
"h": 72
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 11,
"y": 8,
"w": 54,
"h": 72
},
"frame": {
"x": 285,
"y": 0,
"w": 54,
"h": 72
}
},
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 14,
"y": 8,
"w": 52,
"h": 72
},
"frame": {
"x": 342,
"y": 0,
"w": 52,
"h": 72
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 20,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 0,
"y": 72,
"w": 48,
"h": 72
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 47,
"h": 72
},
"frame": {
"x": 57,
"y": 72,
"w": 47,
"h": 72
}
},
{
"filename": "0009.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 47,
"h": 72
},
"frame": {
"x": 114,
"y": 72,
"w": 47,
"h": 72
}
},
{
"filename": "0010.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 171,
"y": 72,
"w": 48,
"h": 72
}
},
{
"filename": "0011.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 228,
"y": 72,
"w": 48,
"h": 72
}
},
{
"filename": "0012.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 285,
"y": 72,
"w": 48,
"h": 72
}
},
{
"filename": "0013.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 342,
"y": 72,
"w": 48,
"h": 72
}
},
{
"filename": "0014.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 49,
"h": 72
},
"frame": {
"x": 0,
"y": 144,
"w": 49,
"h": 72
}
},
{
"filename": "0015.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 49,
"h": 72
},
"frame": {
"x": 57,
"y": 144,
"w": 49,
"h": 72
}
},
{
"filename": "0016.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 49,
"h": 72
},
"frame": {
"x": 114,
"y": 144,
"w": 49,
"h": 72
}
},
{
"filename": "0017.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 49,
"h": 72
},
"frame": {
"x": 171,
"y": 144,
"w": 49,
"h": 72
}
},
{
"filename": "0018.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 228,
"y": 144,
"w": 48,
"h": 72
}
},
{
"filename": "0019.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 285,
"y": 144,
"w": 48,
"h": 72
}
},
{
"filename": "0020.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 342,
"y": 144,
"w": 48,
"h": 72
}
},
{
"filename": "0021.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 22,
"y": 8,
"w": 48,
"h": 72
},
"frame": {
"x": 0,
"y": 216,
"w": 48,
"h": 72
}
},
{
"filename": "0022.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 20,
"y": 8,
"w": 50,
"h": 72
},
"frame": {
"x": 57,
"y": 216,
"w": 50,
"h": 72
}
},
{
"filename": "0023.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 18,
"y": 8,
"w": 51,
"h": 72
},
"frame": {
"x": 114,
"y": 216,
"w": 51,
"h": 72
}
},
{
"filename": "0024.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 18,
"y": 8,
"w": 51,
"h": 72
},
"frame": {
"x": 171,
"y": 216,
"w": 51,
"h": 72
}
},
{
"filename": "0025.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 15,
"y": 8,
"w": 53,
"h": 72
},
"frame": {
"x": 228,
"y": 216,
"w": 53,
"h": 72
}
},
{
"filename": "0026.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 10,
"y": 8,
"w": 57,
"h": 72
},
"frame": {
"x": 285,
"y": 216,
"w": 57,
"h": 72
}
},
{
"filename": "0027.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 10,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 342,
"y": 216,
"w": 56,
"h": 72
}
},
{
"filename": "0028.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 10,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 0,
"y": 288,
"w": 56,
"h": 72
}
},
{
"filename": "0029.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 9,
"y": 8,
"w": 55,
"h": 72
},
"frame": {
"x": 57,
"y": 288,
"w": 55,
"h": 72
}
},
{
"filename": "0030.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 114,
"y": 288,
"w": 56,
"h": 72
}
},
{
"filename": "0031.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 171,
"y": 288,
"w": 56,
"h": 72
}
},
{
"filename": "0032.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 228,
"y": 288,
"w": 56,
"h": 72
}
},
{
"filename": "0033.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 56,
"h": 72
},
"frame": {
"x": 285,
"y": 288,
"w": 56,
"h": 72
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:e7f062304401dbd7b3ec79512f0ff4cb:0136dac01331f88892a3df26aeab78f5:1ed1e22abb9b55d76337a5a599835c06$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,797 @@
{
"textures": [
{
"image": "b2w2_veteran_m.png",
"format": "RGBA8888",
"size": {
"w": 424,
"h": 390
},
"scale": 1,
"frames": [
{
"filename": "0000.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 43,
"h": 78
},
"frame": {
"x": 0,
"y": 0,
"w": 43,
"h": 78
}
},
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 43,
"h": 78
},
"frame": {
"x": 53,
"y": 0,
"w": 43,
"h": 78
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 43,
"h": 78
},
"frame": {
"x": 106,
"y": 0,
"w": 43,
"h": 78
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 43,
"h": 78
},
"frame": {
"x": 159,
"y": 0,
"w": 43,
"h": 78
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 212,
"y": 0,
"w": 44,
"h": 78
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 265,
"y": 0,
"w": 44,
"h": 78
}
},
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 318,
"y": 0,
"w": 44,
"h": 78
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 371,
"y": 0,
"w": 44,
"h": 78
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 0,
"y": 78,
"w": 44,
"h": 78
}
},
{
"filename": "0009.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 53,
"y": 78,
"w": 44,
"h": 78
}
},
{
"filename": "0010.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 48,
"h": 78
},
"frame": {
"x": 106,
"y": 78,
"w": 48,
"h": 78
}
},
{
"filename": "0011.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 50,
"h": 78
},
"frame": {
"x": 159,
"y": 78,
"w": 50,
"h": 78
}
},
{
"filename": "0012.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 53,
"h": 78
},
"frame": {
"x": 212,
"y": 78,
"w": 53,
"h": 78
}
},
{
"filename": "0013.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 53,
"h": 78
},
"frame": {
"x": 265,
"y": 78,
"w": 53,
"h": 78
}
},
{
"filename": "0014.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 52,
"h": 78
},
"frame": {
"x": 318,
"y": 78,
"w": 52,
"h": 78
}
},
{
"filename": "0015.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 51,
"h": 78
},
"frame": {
"x": 371,
"y": 78,
"w": 51,
"h": 78
}
},
{
"filename": "0016.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 52,
"h": 78
},
"frame": {
"x": 0,
"y": 156,
"w": 52,
"h": 78
}
},
{
"filename": "0017.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 52,
"h": 78
},
"frame": {
"x": 53,
"y": 156,
"w": 52,
"h": 78
}
},
{
"filename": "0018.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 53,
"h": 78
},
"frame": {
"x": 106,
"y": 156,
"w": 53,
"h": 78
}
},
{
"filename": "0019.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 53,
"h": 78
},
"frame": {
"x": 159,
"y": 156,
"w": 53,
"h": 78
}
},
{
"filename": "0020.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 53,
"h": 78
},
"frame": {
"x": 212,
"y": 156,
"w": 53,
"h": 78
}
},
{
"filename": "0021.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 52,
"h": 78
},
"frame": {
"x": 265,
"y": 156,
"w": 52,
"h": 78
}
},
{
"filename": "0022.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 51,
"h": 78
},
"frame": {
"x": 318,
"y": 156,
"w": 51,
"h": 78
}
},
{
"filename": "0023.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 51,
"h": 78
},
"frame": {
"x": 371,
"y": 156,
"w": 51,
"h": 78
}
},
{
"filename": "0024.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 51,
"h": 78
},
"frame": {
"x": 0,
"y": 234,
"w": 51,
"h": 78
}
},
{
"filename": "0025.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 50,
"h": 78
},
"frame": {
"x": 53,
"y": 234,
"w": 50,
"h": 78
}
},
{
"filename": "0026.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 48,
"h": 78
},
"frame": {
"x": 106,
"y": 234,
"w": 48,
"h": 78
}
},
{
"filename": "0027.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 46,
"h": 78
},
"frame": {
"x": 159,
"y": 234,
"w": 46,
"h": 78
}
},
{
"filename": "0028.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 46,
"h": 78
},
"frame": {
"x": 212,
"y": 234,
"w": 46,
"h": 78
}
},
{
"filename": "0029.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 265,
"y": 234,
"w": 44,
"h": 78
}
},
{
"filename": "0030.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 318,
"y": 234,
"w": 44,
"h": 78
}
},
{
"filename": "0031.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 371,
"y": 234,
"w": 44,
"h": 78
}
},
{
"filename": "0032.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 44,
"h": 78
},
"frame": {
"x": 0,
"y": 312,
"w": 44,
"h": 78
}
},
{
"filename": "0033.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 43,
"h": 78
},
"frame": {
"x": 53,
"y": 312,
"w": 43,
"h": 78
}
},
{
"filename": "0034.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 43,
"h": 78
},
"frame": {
"x": 106,
"y": 312,
"w": 43,
"h": 78
}
},
{
"filename": "0035.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 43,
"h": 78
},
"frame": {
"x": 159,
"y": 312,
"w": 43,
"h": 78
}
},
{
"filename": "0036.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 43,
"h": 78
},
"frame": {
"x": 212,
"y": 312,
"w": 43,
"h": 78
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:4deb068879a8ac195cb4f00c8b17b7f5:b32f0f90436649264b6f3c49b09ac06a:05e903aa75b8e50c28334d9b5e14c85a$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,83 @@
{
"textures": [
{
"image": "bait.png",
"format": "RGBA8888",
"size": {
"w": 14,
"h": 43
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 12,
"h": 16
},
"spriteSourceSize": {
"x": 0,
"y": 3,
"w": 12,
"h": 13
},
"frame": {
"x": 1,
"y": 1,
"w": 12,
"h": 13
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 12,
"h": 16
},
"spriteSourceSize": {
"x": 0,
"y": 3,
"w": 12,
"h": 13
},
"frame": {
"x": 1,
"y": 16,
"w": 12,
"h": 13
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 12,
"h": 16
},
"spriteSourceSize": {
"x": 0,
"y": 5,
"w": 11,
"h": 11
},
"frame": {
"x": 1,
"y": 31,
"w": 11,
"h": 11
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:f0ec04fcd67ac346dce973693711d032:b697e09191c4312b8faaa0a080a309b7:1af241a52e61fa01ca849aa03c112f85$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

View File

@ -0,0 +1,19 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 46, "h": 60 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 46, "h": 60 },
"sourceSize": { "w": 46, "h": 60 }
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"image": "buoy-sheet.png",
"format": "RGBA8888",
"size": { "w": 46, "h": 60 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,209 @@
{
"textures": [
{
"image": "chest_blue.png",
"format": "RGBA8888",
"size": {
"w": 58,
"h": 528
},
"scale": 1,
"frames": [
{
"filename": "0000.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 14,
"y": 30,
"w": 48,
"h": 41
},
"frame": {
"x": 1,
"y": 1,
"w": 48,
"h": 41
}
},
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 14,
"y": 34,
"w": 49,
"h": 37
},
"frame": {
"x": 1,
"y": 44,
"w": 49,
"h": 37
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 14,
"y": 30,
"w": 48,
"h": 41
},
"frame": {
"x": 1,
"y": 83,
"w": 48,
"h": 41
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 14,
"y": 23,
"w": 48,
"h": 48
},
"frame": {
"x": 1,
"y": 126,
"w": 48,
"h": 48
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 13,
"y": 4,
"w": 55,
"h": 67
},
"frame": {
"x": 1,
"y": 176,
"w": 55,
"h": 67
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 15,
"y": 2,
"w": 56,
"h": 69
},
"frame": {
"x": 1,
"y": 245,
"w": 56,
"h": 69
}
},
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 15,
"y": 2,
"w": 56,
"h": 69
},
"frame": {
"x": 1,
"y": 316,
"w": 56,
"h": 69
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 56,
"h": 69
},
"frame": {
"x": 1,
"y": 387,
"w": 56,
"h": 69
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 75,
"h": 75
},
"spriteSourceSize": {
"x": 13,
"y": 2,
"w": 56,
"h": 69
},
"frame": {
"x": 1,
"y": 458,
"w": 56,
"h": 69
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:5f36000f6160ee6f397afe5a6fd60b73:cf6f4b08e23400447813583c322eb6c7:f4f3c064e6c93b8d1290f93bee927f60$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "chest_red.png",
"format": "RGBA8888",
"size": {
"w": 76,
"h": 57
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 76,
"h": 57
},
"spriteSourceSize": {
"x": 10,
"y": 3,
"w": 56,
"h": 54
},
"frame": {
"x": 8,
"y": 0,
"w": 56,
"h": 54
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "dark_deal_porygon.png",
"format": "RGBA8888",
"size": {
"w": 36,
"h": 45
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 36,
"h": 45
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 44,
"h": 44
},
"frame": {
"x": 0,
"y": 0,
"w": 36,
"h": 45
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "girawitch.png",
"format": "RGBA8888",
"size": {
"w": 46,
"h": 76
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 46,
"h": 76
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 46,
"h": 76
},
"frame": {
"x": 0,
"y": 0,
"w": 46,
"h": 76
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:e68bbc186f511d505c53b2beec3c3741:7108795fc29d953a1d3729ad93d70936:1661aeeeb2f0e4561c644aff254770b3$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "mad_scientist_m.png",
"format": "RGBA8888",
"size": {
"w": 46,
"h": 76
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 44,
"h": 74
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 44,
"h": 74
},
"frame": {
"x": 1,
"y": 1,
"w": 44,
"h": 74
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:a7f8ff2bbb362868f51125c254eb6681:cf76e61ddd31a8f46af67ced168c44a2:4fc09abe16c0608828269e5da81d0744$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

View File

@ -0,0 +1,104 @@
{
"textures": [
{
"image": "mud.png",
"format": "RGBA8888",
"size": {
"w": 14,
"h": 68
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 12,
"h": 20
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 12,
"h": 13
},
"frame": {
"x": 1,
"y": 1,
"w": 12,
"h": 13
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 12,
"h": 20
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 12,
"h": 14
},
"frame": {
"x": 1,
"y": 16,
"w": 12,
"h": 14
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 12,
"h": 20
},
"spriteSourceSize": {
"x": 0,
"y": 1,
"w": 12,
"h": 16
},
"frame": {
"x": 1,
"y": 32,
"w": 12,
"h": 16
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 12,
"h": 20
},
"spriteSourceSize": {
"x": 0,
"y": 3,
"w": 12,
"h": 17
},
"frame": {
"x": 1,
"y": 50,
"w": 12,
"h": 17
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:4f18a8effb8f01eb70f9f25b8294c1bf:ad663a73c51f780bbf45d00a52519553:c64f6b8befc3d5e9f836246d2b9536be$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "pokemon_salesman.png",
"format": "RGBA8888",
"size": {
"w": 40,
"h": 80
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 21,
"y": 2,
"w": 38,
"h": 78
},
"frame": {
"x": 1,
"y": 1,
"w": 38,
"h": 78
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:dd57e3db21f3933c15be65bec261f4c1:05c7ef32252a5c2d3ad007b7e26fabd7:ae82f52e471ed81e2558206f05476cd7$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "safari_zone.png",
"format": "RGBA8888",
"size": {
"w": 120,
"h": 84
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 118,
"h": 82
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 118,
"h": 82
},
"frame": {
"x": 1,
"y": 1,
"w": 118,
"h": 82
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:6fad7a61e47043b974153148b4fd3997:5ec4d0890f2f03446daf22c8ae8ba77b:87aa745cd95eef6cbf38935230f4e10f$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "teacher.png",
"format": "RGBA8888",
"size": {
"w": 43,
"h": 74
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 19,
"y": 8,
"w": 41,
"h": 72
},
"frame": {
"x": 1,
"y": 1,
"w": 41,
"h": 72
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:506e5a4ce79c134a7b4af90a90aef244:1b81d3d84bf12cedc419805eaff82548:59bc5dd000b5e72588320b473e31c312$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "teleporter.png",
"format": "RGBA8888",
"size": {
"w": 64,
"h": 78
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 64,
"h": 78
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 64,
"h": 78
},
"frame": {
"x": 0,
"y": 0,
"w": 64,
"h": 78
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:a8e006630c2838130468b0d5c9aeb8a6:684c1813cb6c86e395c18027a593ed28:ce1615396ce7b0a146766d50b319bb81$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "training_gear.png",
"format": "RGBA8888",
"size": {
"w": 76,
"h": 57
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 76,
"h": 57
},
"spriteSourceSize": {
"x": 10,
"y": 3,
"w": 56,
"h": 54
},
"frame": {
"x": 8,
"y": 0,
"w": 56,
"h": 54
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,524 @@
{
"textures": [
{
"image": "buck.png",
"format": "RGBA8888",
"size": {
"w": 120,
"h": 78
},
"scale": 1,
"frames": [
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 33,
"y": 4,
"w": 35,
"h": 76
},
"frame": {
"x": 1,
"y": 1,
"w": 35,
"h": 76
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 33,
"y": 4,
"w": 35,
"h": 76
},
"frame": {
"x": 1,
"y": 1,
"w": 35,
"h": 76
}
},
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 33,
"y": 4,
"w": 35,
"h": 76
},
"frame": {
"x": 1,
"y": 1,
"w": 35,
"h": 76
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 33,
"y": 4,
"w": 35,
"h": 76
},
"frame": {
"x": 1,
"y": 1,
"w": 35,
"h": 76
}
},
{
"filename": "0010.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 33,
"y": 4,
"w": 35,
"h": 76
},
"frame": {
"x": 1,
"y": 1,
"w": 35,
"h": 76
}
},
{
"filename": "0011.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 33,
"y": 4,
"w": 35,
"h": 76
},
"frame": {
"x": 1,
"y": 1,
"w": 35,
"h": 76
}
},
{
"filename": "0014.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 33,
"y": 4,
"w": 35,
"h": 76
},
"frame": {
"x": 1,
"y": 1,
"w": 35,
"h": 76
}
},
{
"filename": "0015.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 33,
"y": 4,
"w": 35,
"h": 76
},
"frame": {
"x": 1,
"y": 1,
"w": 35,
"h": 76
}
},
{
"filename": "0018.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 18,
"y": 8,
"w": 44,
"h": 72
},
"frame": {
"x": 38,
"y": 1,
"w": 44,
"h": 72
}
},
{
"filename": "0019.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 18,
"y": 8,
"w": 44,
"h": 72
},
"frame": {
"x": 38,
"y": 1,
"w": 44,
"h": 72
}
},
{
"filename": "0020.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 15,
"y": 8,
"w": 44,
"h": 72
},
"frame": {
"x": 38,
"y": 1,
"w": 44,
"h": 72
}
},
{
"filename": "0021.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 15,
"y": 8,
"w": 44,
"h": 72
},
"frame": {
"x": 38,
"y": 1,
"w": 44,
"h": 72
}
},
{
"filename": "0022.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 8,
"w": 44,
"h": 72
},
"frame": {
"x": 38,
"y": 1,
"w": 44,
"h": 72
}
},
{
"filename": "0023.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 8,
"w": 44,
"h": 72
},
"frame": {
"x": 38,
"y": 1,
"w": 44,
"h": 72
}
},
{
"filename": "0000.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0009.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0012.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0013.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0016.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
},
{
"filename": "0017.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 34,
"y": 5,
"w": 35,
"h": 75
},
"frame": {
"x": 84,
"y": 1,
"w": 35,
"h": 75
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:033f3d363b4192f64c92e02c19622c15:0d06141bef5af87ef82da967253207cb:3347efe478119141b0e3e6eccdecd0f5$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,398 @@
{
"textures": [
{
"image": "cheryl.png",
"format": "RGBA8888",
"size": {
"w": 154,
"h": 83
},
"scale": 1,
"frames": [
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 25,
"y": 0,
"w": 41,
"h": 81
},
"frame": {
"x": 1,
"y": 1,
"w": 41,
"h": 81
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 25,
"y": 0,
"w": 41,
"h": 81
},
"frame": {
"x": 1,
"y": 1,
"w": 41,
"h": 81
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 26,
"y": 0,
"w": 41,
"h": 81
},
"frame": {
"x": 1,
"y": 1,
"w": 41,
"h": 81
}
},
{
"filename": "0009.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 26,
"y": 0,
"w": 41,
"h": 81
},
"frame": {
"x": 1,
"y": 1,
"w": 41,
"h": 81
}
},
{
"filename": "0010.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 27,
"y": 0,
"w": 41,
"h": 81
},
"frame": {
"x": 44,
"y": 1,
"w": 41,
"h": 81
}
},
{
"filename": "0011.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 27,
"y": 0,
"w": 41,
"h": 81
},
"frame": {
"x": 44,
"y": 1,
"w": 41,
"h": 81
}
},
{
"filename": "0012.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 24,
"y": 0,
"w": 41,
"h": 81
},
"frame": {
"x": 44,
"y": 1,
"w": 41,
"h": 81
}
},
{
"filename": "0013.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 24,
"y": 0,
"w": 41,
"h": 81
},
"frame": {
"x": 44,
"y": 1,
"w": 41,
"h": 81
}
},
{
"filename": "0014.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 27,
"y": 0,
"w": 33,
"h": 81
},
"frame": {
"x": 87,
"y": 1,
"w": 33,
"h": 81
}
},
{
"filename": "0015.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 27,
"y": 0,
"w": 33,
"h": 81
},
"frame": {
"x": 87,
"y": 1,
"w": 33,
"h": 81
}
},
{
"filename": "0016.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 26,
"y": 0,
"w": 33,
"h": 81
},
"frame": {
"x": 87,
"y": 1,
"w": 33,
"h": 81
}
},
{
"filename": "0017.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 26,
"y": 0,
"w": 33,
"h": 81
},
"frame": {
"x": 87,
"y": 1,
"w": 33,
"h": 81
}
},
{
"filename": "0000.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 20,
"y": 0,
"w": 31,
"h": 81
},
"frame": {
"x": 122,
"y": 1,
"w": 31,
"h": 81
}
},
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 20,
"y": 0,
"w": 31,
"h": 81
},
"frame": {
"x": 122,
"y": 1,
"w": 31,
"h": 81
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 20,
"y": 0,
"w": 31,
"h": 81
},
"frame": {
"x": 122,
"y": 1,
"w": 31,
"h": 81
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 20,
"y": 0,
"w": 31,
"h": 81
},
"frame": {
"x": 122,
"y": 1,
"w": 31,
"h": 81
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 21,
"y": 0,
"w": 31,
"h": 81
},
"frame": {
"x": 122,
"y": 1,
"w": 31,
"h": 81
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 81
},
"spriteSourceSize": {
"x": 21,
"y": 0,
"w": 31,
"h": 81
},
"frame": {
"x": 122,
"y": 1,
"w": 31,
"h": 81
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:dfcf7aedbd588c4e42427a2e17c171bf:206549943a0e3325d20a017ef01eefee:a233cd27590422717866c66e366b68fb$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,83 @@
{ "frames": [
{
"filename": "0000.png",
"frame": { "x": 0, "y": 0, "w": 31, "h": 77 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 26, "y": 2, "w": 31, "h": 77 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 31, "h": 77 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 26, "y": 2, "w": 31, "h": 77 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 0, "y": 0, "w": 31, "h": 77 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 26, "y": 2, "w": 31, "h": 77 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 0, "y": 0, "w": 31, "h": 77 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 26, "y": 2, "w": 31, "h": 77 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 32, "y": 0, "w": 28, "h": 78 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 28, "y": 1, "w": 28, "h": 78 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 32, "y": 0, "w": 28, "h": 78 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 28, "y": 1, "w": 28, "h": 78 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 0, "y": 78, "w": 31, "h": 77 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 28, "y": 2, "w": 31, "h": 77 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 0, "y": 78, "w": 31, "h": 77 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 28, "y": 2, "w": 31, "h": 77 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
}
],
"meta": {
"app": "https://www.pngprite.org/",
"version": "1.3.7-x64",
"image": "marley.png",
"format": "I8",
"size": { "w": 60, "h": 155 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,209 @@
{ "frames": [
{
"filename": "0000.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 14, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0001.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 14, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 13, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 11, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 11, "w": 53, "h": 63 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 12, "w": 53, "h": 63 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 11, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 13, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 14, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 13, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 11, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 11, "w": 53, "h": 63 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 12, "w": 53, "h": 63 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 11, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 13, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 14, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 13, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 11, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 11, "w": 53, "h": 63 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 12, "w": 53, "h": 63 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 11, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 13, "w": 44, "h": 65 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"image": "mira.png",
"format": "I8",
"size": { "w": 97, "h": 128 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,209 @@
{ "frames": [
{
"filename": "0000.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 31, "y": 0, "w": 37, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 31, "y": 0, "w": 37, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 30, "y": 0, "w": 37, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 30, "y": 0, "w": 37, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 28, "y": 0, "w": 37, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 28, "y": 0, "w": 37, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 55, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 55, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 0, "y": 80, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 0, "y": 80, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 55, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 55, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
"sourceSize": { "w": 80, "h": 80 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"image": "riley.png",
"format": "I8",
"size": { "w": 110, "h": 160 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "vicky.png",
"format": "RGBA8888",
"size": {
"w": 52,
"h": 53
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 27,
"w": 52,
"h": 53
},
"frame": {
"x": 0,
"y": 0,
"w": 52,
"h": 53
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:bf9d2d417a1982282dd711456ac71206:101e07828e3d6e2a2a7a80aebfa802ad:cabe44a4410c334298b1984a219f8160$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "victor.png",
"format": "RGBA8888",
"size": {
"w": 55,
"h": 53
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 12,
"y": 27,
"w": 55,
"h": 53
},
"frame": {
"x": 0,
"y": 0,
"w": 55,
"h": 53
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:64eff0f697754cdf9552b46342c9292a:611e0e2cacbd90c1229ce5443b2414f0:0cc0f5a2c1b2eedb46dd8318e8feb1d8$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "victoria.png",
"format": "RGBA8888",
"size": {
"w": 52,
"h": 54
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 14,
"y": 26,
"w": 52,
"h": 54
},
"frame": {
"x": 0,
"y": 0,
"w": 52,
"h": 54
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:4dafeae3674d63b12cc4d8044f67b5a3:7834687d784c31169256927f419c7958:cf0eb39e0a3f2e42f23ca29747d73c40$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "vito.png",
"format": "RGBA8888",
"size": {
"w": 41,
"h": 78
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 20,
"y": 2,
"w": 41,
"h": 78
},
"frame": {
"x": 0,
"y": 0,
"w": 41,
"h": 78
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:cb988be58fcd5381174e9d120b051e38:4d4723dbbcd9713ee0ed3c2d84ef4bfb:1c7723b536b218346e3138016d865ce9$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "vivi.png",
"format": "RGBA8888",
"size": {
"w": 48,
"h": 69
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 11,
"w": 48,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 48,
"h": 69
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:0a51b4df0b2ed0fed7e3bdb5dffd9e28:af1f3b1480023b3e3761c49e49faf5f1:4fc6bf2bec74c4bb8809df38231deb01$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

View File

@ -2,7 +2,7 @@ import Phaser from "phaser";
import UI from "./ui/ui"; import UI from "./ui/ui";
import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon"; import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species"; import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
import { Constructor } from "#app/utils"; import { Constructor, isNullOrUndefined } from "#app/utils";
import * as Utils from "./utils"; import * as Utils from "./utils";
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, TurnHeldItemTransferModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from "./modifier/modifier"; import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, TurnHeldItemTransferModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball"; import { PokeballType } from "./data/pokeball";
@ -11,7 +11,7 @@ import { Phase } from "./phase";
import { initGameSpeed } from "./system/game-speed"; import { initGameSpeed } from "./system/game-speed";
import { Arena, ArenaBase } from "./field/arena"; import { Arena, ArenaBase } from "./field/arena";
import { GameData } from "./system/game-data"; import { GameData } from "./system/game-data";
import { TextStyle, addTextObject, getTextColor } from "./ui/text"; import { addTextObject, getTextColor, TextStyle } from "./ui/text";
import { allMoves } from "./data/move"; import { allMoves } from "./data/move";
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, modifierTypes } from "./modifier/modifier-type"; import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, modifierTypes } from "./modifier/modifier-type";
import AbilityBar from "./ui/ability-bar"; import AbilityBar from "./ui/ability-bar";
@ -22,14 +22,14 @@ import { GameMode, GameModes, getGameMode } from "./game-mode";
import FieldSpritePipeline from "./pipelines/field-sprite"; import FieldSpritePipeline from "./pipelines/field-sprite";
import SpritePipeline from "./pipelines/sprite"; import SpritePipeline from "./pipelines/sprite";
import PartyExpBar from "./ui/party-exp-bar"; import PartyExpBar from "./ui/party-exp-bar";
import { TrainerSlot, trainerConfigs } from "./data/trainer-config"; import { trainerConfigs, TrainerSlot } from "./data/trainer-config";
import Trainer, { TrainerVariant } from "./field/trainer"; import Trainer, { TrainerVariant } from "./field/trainer";
import TrainerData from "./system/trainer-data"; import TrainerData from "./system/trainer-data";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { pokemonPrevolutions } from "./data/pokemon-evolutions"; import { pokemonPrevolutions } from "./data/pokemon-evolutions";
import PokeballTray from "./ui/pokeball-tray"; import PokeballTray from "./ui/pokeball-tray";
import InvertPostFX from "./pipelines/invert"; import InvertPostFX from "./pipelines/invert";
import { Achv, ModifierAchv, MoneyAchv, achvs } from "./system/achv"; import { Achv, achvs, ModifierAchv, MoneyAchv } from "./system/achv";
import { Voucher, vouchers } from "./system/voucher"; import { Voucher, vouchers } from "./system/voucher";
import { Gender } from "./data/gender"; import { Gender } from "./data/gender";
import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
@ -84,6 +84,13 @@ import { TitlePhase } from "./phases/title-phase";
import { ToggleDoublePositionPhase } from "./phases/toggle-double-position-phase"; import { ToggleDoublePositionPhase } from "./phases/toggle-double-position-phase";
import { TurnInitPhase } from "./phases/turn-init-phase"; import { TurnInitPhase } from "./phases/turn-init-phase";
import { ShopCursorTarget } from "./enums/shop-cursor-target"; import { ShopCursorTarget } from "./enums/shop-cursor-target";
import MysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import { allMysteryEncounters, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
import { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -236,6 +243,8 @@ export default class BattleScene extends SceneBase {
public money: integer; public money: integer;
public pokemonInfoContainer: PokemonInfoContainer; public pokemonInfoContainer: PokemonInfoContainer;
private party: PlayerPokemon[]; private party: PlayerPokemon[];
public mysteryEncounterData: MysteryEncounterData = new MysteryEncounterData(null);
public lastMysteryEncounter: MysteryEncounter;
/** Combined Biome and Wave count text */ /** Combined Biome and Wave count text */
private biomeWaveText: Phaser.GameObjects.Text; private biomeWaveText: Phaser.GameObjects.Text;
private moneyText: Phaser.GameObjects.Text; private moneyText: Phaser.GameObjects.Text;
@ -389,6 +398,7 @@ export default class BattleScene extends SceneBase {
this.fieldUI = fieldUI; this.fieldUI = fieldUI;
// @ts-ignore
const transition = this.make.rexTransitionImagePack({ const transition = this.make.rexTransitionImagePack({
x: 0, x: 0,
y: 0, y: 0,
@ -874,6 +884,20 @@ export default class BattleScene extends SceneBase {
return pokemon; return pokemon;
} }
removePokemonFromPlayerParty(pokemon: PlayerPokemon, destroy: boolean = true) {
if (!pokemon) {
return;
}
const partyIndex = this.party.indexOf(pokemon);
this.party.splice(partyIndex, 1);
if (destroy) {
this.field.remove(pokemon, true);
pokemon.destroy();
}
this.updateModifiers(true);
}
addPokemonIcon(pokemon: Pokemon, x: number, y: number, originX: number = 0.5, originY: number = 0.5, ignoreOverride: boolean = false): Phaser.GameObjects.Container { addPokemonIcon(pokemon: Pokemon, x: number, y: number, originX: number = 0.5, originY: number = 0.5, ignoreOverride: boolean = false): Phaser.GameObjects.Container {
const container = this.add.container(x, y); const container = this.add.container(x, y);
container.setName(`${pokemon.name}-icon`); container.setName(`${pokemon.name}-icon`);
@ -1065,7 +1089,7 @@ export default class BattleScene extends SceneBase {
} }
} }
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle | null { newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounter?: MysteryEncounter): Battle | null {
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave; const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1); const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
let newDouble: boolean | undefined; let newDouble: boolean | undefined;
@ -1113,6 +1137,40 @@ export default class BattleScene extends SceneBase {
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT); newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
this.field.add(newTrainer); this.field.add(newTrainer);
} }
// TODO: remove these once ME spawn rates are finalized
// let testStartingWeight = 0;
// while (testStartingWeight < 3) {
// calculateMEAggregateStats(this, testStartingWeight);
// testStartingWeight += 2;
// }
// calculateRareSpawnAggregateStats(this, 14);
// Check for mystery encounter
// Can only occur in place of a standard wild battle, waves 10-180
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < 180 && newWaveIndex > 10) {
const roll = Utils.randSeedInt(256);
// Base spawn weight is 1/256, and increases by 5/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = !isNullOrUndefined(this.mysteryEncounterData?.encounterSpawnChance) ? this.mysteryEncounterData.encounterSpawnChance : BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn
// Do the reverse as well
// Reduces occurrence of runs with very few (<6) and a ton (>10) of encounters
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (180 - 10) * newWaveIndex;
const currentRunDiffFromAvg = expectedEncountersByFloor - (this.mysteryEncounterData?.encounteredEvents?.length || 0);
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * 5;
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;
if (roll < successRate) {
newBattleType = BattleType.MYSTERY_ENCOUNTER;
// Reset base spawn weight
this.mysteryEncounterData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
} else {
this.mysteryEncounterData.encounterSpawnChance = sessionEncounterRate + WEIGHT_INCREMENT_ON_SPAWN_MISS;
}
}
} }
if (double === undefined && newWaveIndex > 1) { if (double === undefined && newWaveIndex > 1) {
@ -1145,12 +1203,21 @@ export default class BattleScene extends SceneBase {
const maxExpLevel = this.getMaxExpLevel(); const maxExpLevel = this.getMaxExpLevel();
this.lastEnemyTrainer = lastBattle?.trainer ?? null; this.lastEnemyTrainer = lastBattle?.trainer ?? null;
this.lastMysteryEncounter = lastBattle?.mysteryEncounter ?? null;
this.executeWithSeedOffset(() => { this.executeWithSeedOffset(() => {
this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble); this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble);
}, newWaveIndex << 3, this.waveSeed); }, newWaveIndex << 3, this.waveSeed);
this.currentBattle.incrementTurn(this); this.currentBattle.incrementTurn(this);
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
this.currentBattle.double = false;
this.executeWithSeedOffset(() => {
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounter);
}, this.currentBattle.waveIndex << 4);
}
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6)); //this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
if (!waveIndex && lastBattle) { if (!waveIndex && lastBattle) {
@ -1167,14 +1234,16 @@ export default class BattleScene extends SceneBase {
} }
if (resetArenaState) { if (resetArenaState) {
this.arena.resetArenaEffects(); this.arena.resetArenaEffects();
playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p))); if (lastBattle?.mysteryEncounter?.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p)));
for (const pokemon of this.getParty()) { for (const pokemon of this.getParty()) {
pokemon.resetBattleData(); pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}
this.pushPhase(new ShowTrainerPhase(this));
} }
this.pushPhase(new ShowTrainerPhase(this));
} }
for (const pokemon of this.getParty()) { for (const pokemon of this.getParty()) {
@ -2418,7 +2487,7 @@ export default class BattleScene extends SceneBase {
}); });
} }
generateEnemyModifiers(): Promise<void> { generateEnemyModifiers(heldModifiersConfigs?: HeldModifierConfig[][]): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
return resolve(); return resolve();
@ -2440,29 +2509,42 @@ export default class BattleScene extends SceneBase {
} }
party.forEach((enemyPokemon: EnemyPokemon, i: integer) => { party.forEach((enemyPokemon: EnemyPokemon, i: integer) => {
const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss); if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i] && heldModifiersConfigs[i].length > 0) {
let upgradeChance = 32; heldModifiersConfigs[i].forEach(mt => {
if (isBoss) { const stackCount = mt.stackCount ?? 1;
upgradeChance /= 2; // const isTransferable = mt.isTransferable ?? true;
} const modifier = mt.modifierType.newModifier(enemyPokemon);
if (isFinalBoss) { modifier.stackCount = stackCount;
upgradeChance /= 8; // TODO: set isTransferable
} // modifier.setIsTransferable(isTransferable);
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss); this.addEnemyModifier(modifier, true);
let pokemonModifierChance = modifierChance; });
if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer) } else {
pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss);
let count = 0; let upgradeChance = 32;
for (let c = 0; c < chances; c++) { if (isBoss) {
if (!Utils.randSeedInt(modifierChance)) { upgradeChance /= 2;
count++;
} }
if (isFinalBoss) {
upgradeChance /= 8;
}
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss);
let pokemonModifierChance = modifierChance;
if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer)
pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line
let count = 0;
for (let c = 0; c < chances; c++) {
if (!Utils.randSeedInt(modifierChance)) {
count++;
}
}
if (isBoss) {
count = Math.max(count, Math.floor(chances / 2));
}
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
} }
if (isBoss) { return true;
count = Math.max(count, Math.floor(chances / 2));
}
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
}); });
this.updateModifiers(false).then(() => resolve()); this.updateModifiers(false).then(() => resolve());
}); });
@ -2731,4 +2813,114 @@ export default class BattleScene extends SceneBase {
this.shiftPhase(); this.shiftPhase();
} }
/**
* Loads or generates a mystery encounter
* @param override - used to load session encounter when restarting game, etc.
* @returns
*/
getMysteryEncounter(override: MysteryEncounter | undefined): MysteryEncounter {
// Loading override or session encounter
let encounter: MysteryEncounter | null;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE!)) {
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE!];
} else {
encounter = override?.encounterType && override.encounterType >= 0 ? allMysteryEncounters[override.encounterType] : null;
}
// Check for queued encounters first
if (!encounter && this.mysteryEncounterData?.nextEncounterQueue && this.mysteryEncounterData.nextEncounterQueue.length > 0) {
let i = 0;
while (i < this.mysteryEncounterData.nextEncounterQueue.length && !!encounter) {
const candidate = this.mysteryEncounterData.nextEncounterQueue[i];
const forcedChance = candidate[1];
if (Utils.randSeedInt(100) < forcedChance) {
encounter = allMysteryEncounters[candidate[0]];
}
i++;
}
}
if (encounter) {
encounter = new MysteryEncounter(encounter);
encounter.populateDialogueTokensFromRequirements(this);
return encounter;
}
// See Enum values for base tier weights
const tierWeights = [MysteryEncounterTier.COMMON, MysteryEncounterTier.GREAT, MysteryEncounterTier.ULTRA, MysteryEncounterTier.ROGUE];
// Adjust tier weights by previously encountered events to lower odds of only common/uncommons in run
this.mysteryEncounterData.encounteredEvents.forEach(val => {
const tier = val[1];
if (tier === MysteryEncounterTier.COMMON) {
tierWeights[0] = tierWeights[0] - 6;
} else if (tier === MysteryEncounterTier.GREAT) {
tierWeights[1] = tierWeights[1] - 4;
}
});
const totalWeight = tierWeights.reduce((a, b) => a + b);
const tierValue = Utils.randSeedInt(totalWeight);
const commonThreshold = totalWeight - tierWeights[0];
const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1];
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2];
let tier: MysteryEncounterTier | null = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > uncommonThreshold ? MysteryEncounterTier.GREAT : tierValue > rareThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) {
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE!;
}
let availableEncounters: MysteryEncounter[] = [];
// New encounter will never be the same as the most recent encounter
const previousEncounter = this.mysteryEncounterData.encounteredEvents?.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.encounteredEvents.length - 1][0] : null;
const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? [];
// If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available
while (availableEncounters.length === 0 && tier !== null) {
availableEncounters = biomeMysteryEncounters
.filter((encounterType) => {
const encounterCandidate = allMysteryEncounters[encounterType];
if (!encounterCandidate) {
return false;
}
if (encounterCandidate.encounterTier !== tier) { // Encounter is in tier
return false;
}
if (!encounterCandidate.meetsRequirements!(this)) { // Meets encounter requirements
return false;
}
if (!isNullOrUndefined(previousEncounter) && encounterType === previousEncounter) { // Previous encounter was not this one
return false;
}
if (this.mysteryEncounterData.encounteredEvents?.length > 0 && // Encounter has not exceeded max allowed encounters
(encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters > 0)
&& this.mysteryEncounterData.encounteredEvents.filter(e => e[0] === encounterType).length >= encounterCandidate.maxAllowedEncounters) {
return false;
}
return true;
})
.map((m) => (allMysteryEncounters[m]));
// Decrement tier
if (tier === MysteryEncounterTier.ROGUE) {
tier = MysteryEncounterTier.ULTRA;
} else if (tier === MysteryEncounterTier.ULTRA) {
tier = MysteryEncounterTier.GREAT;
} else if (tier === MysteryEncounterTier.GREAT) {
tier = MysteryEncounterTier.COMMON;
} else {
tier = null; // Ends loop
}
}
// If absolutely no encounters are available, spawn 0th encounter
if (availableEncounters.length === 0) {
return allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHALLENGERS];
}
encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)];
// New encounter object to not dirty flags
encounter = new MysteryEncounter(encounter);
encounter.populateDialogueTokensFromRequirements!(this);
return encounter;
}
} }

View File

@ -14,32 +14,35 @@ import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import MysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
export enum BattleType { export enum BattleType {
WILD, WILD,
TRAINER, TRAINER,
CLEAR CLEAR,
MYSTERY_ENCOUNTER
} }
export enum BattlerIndex { export enum BattlerIndex {
ATTACKER = -1, ATTACKER = -1,
PLAYER, PLAYER,
PLAYER_2, PLAYER_2,
ENEMY, ENEMY,
ENEMY_2 ENEMY_2
} }
export interface TurnCommand { export interface TurnCommand {
command: Command; command: Command;
cursor?: integer; cursor?: integer;
move?: QueuedMove; move?: QueuedMove;
targets?: BattlerIndex[]; targets?: BattlerIndex[];
skip?: boolean; skip?: boolean;
args?: any[]; args?: any[];
} }
interface TurnCommands { interface TurnCommands {
[key: integer]: TurnCommand | null [key: integer]: TurnCommand | null
} }
export default class Battle { export default class Battle {
@ -67,6 +70,7 @@ export default class Battle {
public lastUsedPokeball: PokeballType | null; public lastUsedPokeball: PokeballType | null;
public playerFaints: number; // The amount of times pokemon on the players side have fainted public playerFaints: number; // The amount of times pokemon on the players side have fainted
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
public mysteryEncounter: MysteryEncounter;
private rngCounter: integer = 0; private rngCounter: integer = 0;
@ -105,7 +109,7 @@ export default class Battle {
this.battleSpec = spec; this.battleSpec = spec;
} }
private getLevelForWave(): integer { public getLevelForWave(): integer {
const levelWaveIndex = this.gameMode.getWaveForDifficulty(this.waveIndex); const levelWaveIndex = this.gameMode.getWaveForDifficulty(this.waveIndex);
const baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2); const baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2);
const bossMultiplier = 1.2; const bossMultiplier = 1.2;
@ -203,7 +207,7 @@ export default class Battle {
getBgmOverride(scene: BattleScene): string | null { getBgmOverride(scene: BattleScene): string | null {
const battlers = this.enemyParty.slice(0, this.getBattlerCount()); const battlers = this.enemyParty.slice(0, this.getBattlerCount());
if (this.battleType === BattleType.TRAINER) { if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) { if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer?.getEncounterBgm()}`; return `encounter_${this.trainer?.getEncounterBgm()}`;
} }

View File

@ -6,6 +6,8 @@ import * as Utils from "../utils";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { Element } from "json-stable-stringify"; import { Element } from "json-stable-stringify";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { isNullOrUndefined } from "../utils";
import Phaser from "phaser";
//import fs from 'vite-plugin-fs/browser'; //import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget { export enum AnimFrameTarget {
@ -102,6 +104,18 @@ export enum CommonAnim {
LOCK_ON = 2120 LOCK_ON = 2120
} }
/**
* Animations used for Mystery Encounters
* These are custom animations that may or may not work in any other circumstance
* Use at your own risk
*/
export enum EncounterAnim {
MAGMA_BG,
MAGMA_SPOUT,
SMOKESCREEN,
DANCE
}
export class AnimConfig { export class AnimConfig {
public id: integer; public id: integer;
public graphic: string; public graphic: string;
@ -303,7 +317,7 @@ abstract class AnimTimedEvent {
this.resourceName = resourceName; this.resourceName = resourceName;
} }
abstract execute(scene: BattleScene, battleAnim: BattleAnim): integer; abstract execute(scene: BattleScene, battleAnim: BattleAnim, priority?: number): integer;
abstract getEventType(): string; abstract getEventType(): string;
} }
@ -321,7 +335,7 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
} }
} }
execute(scene: BattleScene, battleAnim: BattleAnim): integer { execute(scene: BattleScene, battleAnim: BattleAnim, priority?: number): integer {
const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) }; const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) };
if (this.resourceName) { if (this.resourceName) {
try { try {
@ -383,7 +397,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
super(frameIndex, resourceName, source); super(frameIndex, resourceName, source);
} }
execute(scene: BattleScene, moveAnim: MoveAnim): integer { execute(scene: BattleScene, moveAnim: MoveAnim, priority?: number): integer {
const tweenProps = {}; const tweenProps = {};
if (this.bgX !== undefined) { if (this.bgX !== undefined) {
tweenProps["x"] = (this.bgX * 0.5) - 320; tweenProps["x"] = (this.bgX * 0.5) - 320;
@ -413,7 +427,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
super(frameIndex, resourceName, source); super(frameIndex, resourceName, source);
} }
execute(scene: BattleScene, moveAnim: MoveAnim): integer { execute(scene: BattleScene, moveAnim: MoveAnim, priority?: number): integer {
if (moveAnim.bgSprite) { if (moveAnim.bgSprite) {
moveAnim.bgSprite.destroy(); moveAnim.bgSprite.destroy();
} }
@ -425,7 +439,9 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
moveAnim.bgSprite.setAlpha(this.opacity / 255); moveAnim.bgSprite.setAlpha(this.opacity / 255);
scene.field.add(moveAnim.bgSprite); scene.field.add(moveAnim.bgSprite);
const fieldPokemon = scene.getEnemyPokemon() || scene.getPlayerPokemon(); const fieldPokemon = scene.getEnemyPokemon() || scene.getPlayerPokemon();
if (fieldPokemon?.isOnField()) { if (!isNullOrUndefined(priority)) {
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority!);
} else if (fieldPokemon?.isOnField()) {
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon); scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
} }
@ -445,6 +461,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
export const moveAnims = new Map<Moves, AnimConfig | [AnimConfig, AnimConfig] | null>(); export const moveAnims = new Map<Moves, AnimConfig | [AnimConfig, AnimConfig] | null>();
export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimConfig] | null>(); export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimConfig] | null>();
export const commonAnims = new Map<CommonAnim, AnimConfig>(); export const commonAnims = new Map<CommonAnim, AnimConfig>();
export const encounterAnims = new Map<EncounterAnim, AnimConfig>();
export function initCommonAnims(scene: BattleScene): Promise<void> { export function initCommonAnims(scene: BattleScene): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
@ -515,6 +532,26 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
}); });
} }
/**
* Fetches animation configs to be used in a Mystery Encounter
* @param scene
* @param encounterAnim - one or more animations to fetch
*/
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
for (const anim of anims) {
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
continue;
}
encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json())
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))));
}
await Promise.allSettled(encounterAnimFetches);
}
export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim): Promise<void> { export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
if (chargeAnims.has(chargeAnim)) { if (chargeAnims.has(chargeAnim)) {
@ -569,6 +606,16 @@ export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): P
}); });
} }
/**
* Loads encounter animation assets to scene
* MUST be called after [initEncounterAnims()](./battle-anims.ts) to load all required animations properly
* @param scene
* @param startLoad
*/
export async function loadEncounterAnimAssets(scene: BattleScene, startLoad?: boolean): Promise<void> {
await loadAnimAssets(scene, Array.from(encounterAnims.values()), startLoad);
}
export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise<void> { export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat(); const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
@ -678,14 +725,16 @@ export abstract class BattleAnim {
public target: Pokemon | null; public target: Pokemon | null;
public sprites: Phaser.GameObjects.Sprite[]; public sprites: Phaser.GameObjects.Sprite[];
public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle; public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle;
public playOnEmptyField: boolean;
private srcLine: number[]; private srcLine: number[];
private dstLine: number[]; private dstLine: number[];
constructor(user?: Pokemon, target?: Pokemon) { constructor(user?: Pokemon, target?: Pokemon, playOnEmptyField: boolean = false) {
this.user = user ?? null; this.user = user ?? null;
this.target = target ?? null; this.target = target ?? null;
this.sprites = []; this.sprites = [];
this.playOnEmptyField = playOnEmptyField;
} }
abstract getAnim(): AnimConfig | null; abstract getAnim(): AnimConfig | null;
@ -757,9 +806,9 @@ export abstract class BattleAnim {
play(scene: BattleScene, callback?: Function) { play(scene: BattleScene, callback?: Function) {
const isOppAnim = this.isOppAnim(); const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct? const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
const target = !isOppAnim ? this.target : this.user; const target = !isOppAnim ? this.target! : this.user!;
if (!target?.isOnField()) { if (!target?.isOnField() && !this.playOnEmptyField) {
if (callback) { if (callback) {
callback(); callback();
} }
@ -983,13 +1032,181 @@ export abstract class BattleAnim {
} }
}); });
} }
private getGraphicFrameDataWithoutTarget(frames: AnimFrame[], targetInitialX: number, targetInitialY: number): Map<integer, Map<AnimFrameTarget, GraphicFrameData>> {
const ret: Map<integer, Map<AnimFrameTarget, GraphicFrameData>> = new Map([
[AnimFrameTarget.GRAPHIC, new Map<AnimFrameTarget, GraphicFrameData>() ],
[AnimFrameTarget.USER, new Map<AnimFrameTarget, GraphicFrameData>() ],
[AnimFrameTarget.TARGET, new Map<AnimFrameTarget, GraphicFrameData>() ]
]);
let g = 0;
let u = 0;
let t = 0;
for (const frame of frames) {
let { x, y } = frame;
const scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1);
const scaleY = (frame.zoomY / 100);
x += targetInitialX;
y += targetInitialY;
const angle = -frame.angle;
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
ret.get(frame.target)?.set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle });
}
return ret;
}
/**
*
* @param scene
* @param targetInitialX
* @param targetInitialY
* @param frameTimeMult
* @param frameTimedEventPriority
* - 0 is behind all other sprites (except BG)
* - 1 on top of player field
* - 3 is on top of both fields
* - 5 is on top of player sprite
* @param callback
*/
playWithoutTargets(scene: BattleScene, targetInitialX: number, targetInitialY: number, frameTimeMult: number, frameTimedEventPriority?: 0 | 1 | 3 | 5, callback?: Function) {
const spriteCache: SpriteCache = {
[AnimFrameTarget.GRAPHIC]: [],
[AnimFrameTarget.USER]: [],
[AnimFrameTarget.TARGET]: []
};
const spritePriorities: integer[] = [];
const cleanUpAndComplete = () => {
for (const ms of Object.values(spriteCache).flat()) {
if (ms) {
ms.destroy();
}
}
if (this.bgSprite) {
this.bgSprite.destroy();
}
if (callback) {
callback();
}
};
if (!scene.moveAnimations) {
return cleanUpAndComplete();
}
const anim = this.getAnim();
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
this.dstLine = [ 150, 75, targetInitialX, targetInitialY ];
let r = anim!.frames.length;
let f = 0;
const existingFieldSprites = [...scene.field.getAll()];
scene.tweens.addCounter({
duration: Utils.getFrameMs(3) * frameTimeMult,
repeat: anim!.frames.length,
onRepeat: () => {
const spriteFrames = anim!.frames[f];
const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[f], targetInitialX, targetInitialY);
const u = 0;
const t = 0;
let g = 0;
for (const frame of spriteFrames) {
if (frame.target !== AnimFrameTarget.GRAPHIC) {
console.log("Encounter animations do not support targets");
continue;
}
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
if (g === sprites.length) {
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1);
sprites.push(newSprite);
scene.field.add(newSprite);
spritePriorities.push(1);
}
const graphicIndex = g++;
const moveSprite = sprites[graphicIndex];
spritePriorities[graphicIndex] = frame.priority;
if (!isNullOrUndefined(frame.priority)) {
const setSpritePriority = (priority: integer) => {
if (existingFieldSprites.length > priority) {
// Move to specified priority index
scene.field.moveTo(moveSprite, scene.field.getIndex(existingFieldSprites[priority]));
} else {
// Move to top of scene
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
}
};
setSpritePriority(frame.priority);
}
moveSprite.setFrame(frame.graphicFrame);
const graphicFrameData = frameData.get(frame.target)?.get(graphicIndex);
if (graphicFrameData) {
moveSprite.setPosition(graphicFrameData.x, graphicFrameData.y);
moveSprite.setAngle(graphicFrameData.angle);
moveSprite.setScale(graphicFrameData.scaleX, graphicFrameData.scaleY);
moveSprite.setAlpha(frame.opacity / 255);
moveSprite.setVisible(frame.visible);
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
}
}
if (anim?.frameTimedEvents.get(f)) {
for (const event of anim.frameTimedEvents.get(f)!) {
r = Math.max((anim.frames.length - f) + event.execute(scene, this, frameTimedEventPriority), r);
}
}
const targets = Utils.getEnumValues(AnimFrameTarget);
for (const i of targets) {
const count = i === AnimFrameTarget.GRAPHIC ? g : i === AnimFrameTarget.USER ? u : t;
if (count < spriteCache[i].length) {
const spritesToRemove = spriteCache[i].slice(count, spriteCache[i].length);
for (const rs of spritesToRemove) {
if (!rs.getData("locked") as boolean) {
const spriteCacheIndex = spriteCache[i].indexOf(rs);
spriteCache[i].splice(spriteCacheIndex, 1);
if (i === AnimFrameTarget.GRAPHIC) {
spritePriorities.splice(spriteCacheIndex, 1);
}
rs.destroy();
}
}
}
}
f++;
r--;
},
onComplete: () => {
for (const ms of Object.values(spriteCache).flat()) {
if (ms && !ms.getData("locked")) {
ms.destroy();
}
}
if (r) {
scene.tweens.addCounter({
duration: Utils.getFrameMs(r),
onComplete: () => cleanUpAndComplete()
});
} else {
cleanUpAndComplete();
}
}
});
}
} }
export class CommonBattleAnim extends BattleAnim { export class CommonBattleAnim extends BattleAnim {
public commonAnim: CommonAnim | null; public commonAnim: CommonAnim | null;
constructor(commonAnim: CommonAnim | null, user: Pokemon, target?: Pokemon) { constructor(commonAnim: CommonAnim | null, user: Pokemon, target?: Pokemon, playOnEmptyField: boolean = false) {
super(user, target || user); super(user, target || user, playOnEmptyField);
this.commonAnim = commonAnim; this.commonAnim = commonAnim;
} }
@ -1051,6 +1268,26 @@ export class MoveChargeAnim extends MoveAnim {
} }
} }
export class EncounterBattleAnim extends BattleAnim {
public encounterAnim: EncounterAnim;
public oppAnim: boolean;
constructor(encounterAnim: EncounterAnim, user: Pokemon, target?: Pokemon, oppAnim?: boolean) {
super(user, target || user, true);
this.encounterAnim = encounterAnim;
this.oppAnim = oppAnim ?? false;
}
getAnim(): AnimConfig | null {
return encounterAnims.get(this.encounterAnim) ?? null;
}
isOppAnim(): boolean {
return this.oppAnim;
}
}
export async function populateAnims() { export async function populateAnims() {
const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase()); const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase());
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/\_/g, "")); const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/\_/g, ""));

View File

@ -6,7 +6,7 @@ import { StatusEffect } from "./status-effect";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { ChargeAttr, MoveFlags, allMoves } from "./move"; import { ChargeAttr, MoveFlags, allMoves } from "./move";
import { Type } from "./type"; import { Type } from "./type";
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability"; import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
import { TerrainType } from "./terrain"; import { TerrainType } from "./terrain";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { BattleStat } from "./battle-stat"; import { BattleStat } from "./battle-stat";
@ -1827,6 +1827,37 @@ export class ExposedTag extends BattlerTag {
} }
} }
export class MysteryEncounterPostSummonTag extends BattlerTag {
constructor(sourceMove: Moves) {
super(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON, BattlerTagLapseType.CUSTOM, 1, sourceMove);
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
// Give pokemon +1 stats for battle
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
if (!cancelled.value) {
const mysteryEncounterBattleEffects = pokemon.mysteryEncounterBattleEffects;
if (mysteryEncounterBattleEffects) {
mysteryEncounterBattleEffects(pokemon);
}
}
}
return ret;
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
}
}
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) { switch (tagType) {
@ -1962,6 +1993,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.GULP_MISSILE_ARROKUDA: case BattlerTagType.GULP_MISSILE_ARROKUDA:
case BattlerTagType.GULP_MISSILE_PIKACHU: case BattlerTagType.GULP_MISSILE_PIKACHU:
return new GulpMissileTag(tagType, sourceMove); return new GulpMissileTag(tagType, sourceMove);
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
return new MysteryEncounterPostSummonTag(sourceMove);
case BattlerTagType.NONE: case BattlerTagType.NONE:
default: default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -909,6 +909,126 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
] ]
} }
], ],
[TrainerType.BUCK]: [
{
encounter: [
"dialogue:stat_trainer_buck.encounter.1",
"dialogue:stat_trainer_buck.encounter.2"
],
victory: [
"dialogue:stat_trainer_buck.victory.1"
],
defeat: [
"dialogue:stat_trainer_buck.defeat.1"
]
}
],
[TrainerType.CHERYL]: [
{
encounter: [
"dialogue:stat_trainer_cheryl.encounter.1",
"dialogue:stat_trainer_cheryl.encounter.2"
],
victory: [
"dialogue:stat_trainer_cheryl.victory.1"
],
defeat: [
"dialogue:stat_trainer_cheryl.defeat.1"
]
}
],
[TrainerType.MARLEY]: [
{
encounter: [
"dialogue:stat_trainer_marley.encounter.1",
"dialogue:stat_trainer_marley.encounter.2"
],
victory: [
"dialogue:stat_trainer_marley.victory.1"
],
defeat: [
"dialogue:stat_trainer_marley.defeat.1"
]
}
],
[TrainerType.MIRA]: [
{
encounter: [
"dialogue:stat_trainer_mira.encounter.1",
"dialogue:stat_trainer_mira.encounter.2"
],
victory: [
"dialogue:stat_trainer_mira.victory.1"
],
defeat: [
"dialogue:stat_trainer_mira.defeat.1"
]
}
],
[TrainerType.RILEY]: [
{
encounter: [
"dialogue:stat_trainer_riley.encounter.1",
"dialogue:stat_trainer_riley.encounter.2"
],
victory: [
"dialogue:stat_trainer_riley.victory.1"
],
defeat: [
"dialogue:stat_trainer_riley.defeat.1"
]
}
],
[TrainerType.VICTOR]: [
{
encounter: [
"dialogue:winstrates_victor.encounter.1",
],
victory: [
"dialogue:winstrates_victor.victory.1"
],
}
],
[TrainerType.VICTORIA]: [
{
encounter: [
"dialogue:winstrates_victoria.encounter.1",
],
victory: [
"dialogue:winstrates_victoria.victory.1"
],
}
],
[TrainerType.VIVI]: [
{
encounter: [
"dialogue:winstrates_vivi.encounter.1",
],
victory: [
"dialogue:winstrates_vivi.victory.1"
],
}
],
[TrainerType.VICKY]: [
{
encounter: [
"dialogue:winstrates_vicky.encounter.1",
],
victory: [
"dialogue:winstrates_vicky.victory.1"
],
}
],
[TrainerType.VITO]: [
{
encounter: [
"dialogue:winstrates_vito.encounter.1",
],
victory: [
"dialogue:winstrates_vito.victory.1"
],
}
],
[TrainerType.BROCK]: { [TrainerType.BROCK]: {
encounter: [ encounter: [
"dialogue:brock.encounter.1", "dialogue:brock.encounter.1",

View File

@ -61,7 +61,10 @@ export interface IEggOptions {
/** Defines if the egg will hatch with the hidden ability of this species. /** Defines if the egg will hatch with the hidden ability of this species.
* If no hidden ability exist, a random one will get choosen. * If no hidden ability exist, a random one will get choosen.
*/ */
overrideHiddenAbility?: boolean overrideHiddenAbility?: boolean,
/** If Egg is of {@link EggSourceType.EVENT}, can customize the message displayed for where the egg was obtained */
eventEggTypeDescriptor?: string;
} }
export class Egg { export class Egg {
@ -83,6 +86,8 @@ export class Egg {
private _overrideHiddenAbility: boolean; private _overrideHiddenAbility: boolean;
private _eventEggTypeDescriptor?: string;
//// ////
// #endregion // #endregion
//// ////
@ -180,6 +185,8 @@ export class Egg {
this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct? this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct? this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
} }
this._eventEggTypeDescriptor = eggOptions?.eventEggTypeDescriptor;
} }
//// ////
@ -279,6 +286,8 @@ export class Egg {
return i18next.t("egg:gachaTypeShiny"); return i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE: case EggSourceType.GACHA_MOVE:
return i18next.t("egg:gachaTypeMove"); return i18next.t("egg:gachaTypeMove");
case EggSourceType.EVENT:
return this._eventEggTypeDescriptor ?? i18next.t("egg:eventType");
default: default:
console.warn("getEggTypeDescriptor case not defined. Returning default empty string"); console.warn("getEggTypeDescriptor case not defined. Returning default empty string");
return ""; return "";

View File

@ -0,0 +1,184 @@
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { randSeedInt } from "#app/utils";
import i18next from "i18next";
import { IEggOptions } from "#app/data/egg";
import { EggSourceType } from "#enums/egg-source-types";
import { EggTier } from "#enums/egg-type";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:aTrainersTest";
/**
* A Trainer's Test encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/115 | GitHub Issue #115}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ATrainersTestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(100, 180)
.withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withAutoHideIntroVisuals(false)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Randomly pick from 1 of the 5 stat trainers to spawn
let trainerType: TrainerType;
let spriteKeys;
let trainerNameKey: string;
switch (randSeedInt(5)) {
default:
case 0:
trainerType = TrainerType.BUCK;
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
trainerNameKey = "buck";
break;
case 1:
trainerType = TrainerType.CHERYL;
spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY);
trainerNameKey = "cheryl";
break;
case 2:
trainerType = TrainerType.MARLEY;
spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE);
trainerNameKey = "marley";
break;
case 3:
trainerType = TrainerType.MIRA;
spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1);
trainerNameKey = "mira";
break;
case 4:
trainerType = TrainerType.RILEY;
spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1);
trainerNameKey = "riley";
break;
}
// Dialogue and tokens for trainer
encounter.dialogue.intro = [
{
speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}.${trainerNameKey}.intro_dialogue`
}
];
encounter.options[0].dialogue!.selected = [
{
speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}.${trainerNameKey}.accept`
}
];
encounter.options[1].dialogue!.selected = [
{
speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}.${trainerNameKey}.decline`
}
];
encounter.setDialogueToken("statTrainerName", i18next.t(`trainerNames:${trainerNameKey}`));
const eggDescription = i18next.t(`${namespace}.title`) + ":\n" + i18next.t(`trainerNames:${trainerNameKey}`);
encounter.misc = { trainerType, trainerNameKey, trainerEggDescription: eggDescription };
// Trainer config
const trainerConfig = trainerConfigs[trainerType].copy();
const trainerSpriteKey = trainerConfig.getSpriteKey();
encounter.enemyPartyConfigs.push({
levelAdditiveMultiplier: 1,
trainerConfig: trainerConfig
});
encounter.spriteConfigs = [
{
spriteKey: spriteKeys.spriteKey,
fileRoot: spriteKeys.fileRoot,
hasShadow: true,
repeat: true,
isPokemon: true,
x: 22,
y: -2,
yShadow: -2
},
{
spriteKey: trainerSpriteKey,
fileRoot: "trainer",
hasShadow: true,
disableAnimation: true,
x: -24,
y: 4,
yShadow: 4
}
];
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withIntroDialogue()
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
await transitionMysteryEncounterIntroVisuals(scene);
const eggOptions: IEggOptions = {
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eventEggTypeDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.ULTRA
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`));
setEncounterRewards(scene, { fillRemaining: true }, [eggOptions]);
return initBattleWithEnemyConfig(scene, config);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Full heal party
scene.unshiftPhase(new PartyHealPhase(scene, true));
const eggOptions: IEggOptions = {
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eventEggTypeDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.GREAT
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.rare`));
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: 0 }, [eggOptions]);
leaveEncounterWithoutBattle(scene);
}
)
.withOutroDialogue([
{
text: `${namespace}.outro`,
},
])
.build();

View File

@ -0,0 +1,514 @@
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { PersistentModifierRequirement } from "../mystery-encounter-requirements";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryModifier } from "#app/modifier/modifier";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BattleStat } from "#app/data/battle-stat";
import { randInt } from "#app/utils";
import { BattlerIndex } from "#app/battle";
import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#app/data/pokeball";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { BerryType } from "#enums/berry-type";
import { StatChangePhase } from "#app/phases/stat-change-phase";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:absoluteAvarice";
/**
* Absolute Avarice encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/58 | GitHub Issue #58}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const AbsoluteAvariceEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
.withIntroSpriteConfigs([
{
// This sprite has the shadow
spriteKey: "",
fileRoot: "",
species: Species.GREEDENT,
hasShadow: true,
alpha: 0.001,
repeat: true,
x: -5
},
{
spriteKey: "",
fileRoot: "",
species: Species.GREEDENT,
hasShadow: false,
repeat: true,
x: -5
},
{
spriteKey: "lum_berry",
fileRoot: "items",
isItem: true,
x: 7,
y: -14,
hidden: true,
disableAnimation: true
},
{
spriteKey: "salac_berry",
fileRoot: "items",
isItem: true,
x: 2,
y: 4,
hidden: true,
disableAnimation: true
},
{
spriteKey: "lansat_berry",
fileRoot: "items",
isItem: true,
x: 32,
y: 5,
hidden: true,
disableAnimation: true
},
{
spriteKey: "liechi_berry",
fileRoot: "items",
isItem: true,
x: 6,
y: -5,
hidden: true,
disableAnimation: true
},
{
spriteKey: "sitrus_berry",
fileRoot: "items",
isItem: true,
x: 7,
y: 8,
hidden: true,
disableAnimation: true
},
{
spriteKey: "enigma_berry",
fileRoot: "items",
isItem: true,
x: 26,
y: -4,
hidden: true,
disableAnimation: true
},
{
spriteKey: "leppa_berry",
fileRoot: "items",
isItem: true,
x: 16,
y: -27,
hidden: true,
disableAnimation: true
},
{
spriteKey: "petaya_berry",
fileRoot: "items",
isItem: true,
x: 30,
y: -17,
hidden: true,
disableAnimation: true
},
{
spriteKey: "ganlon_berry",
fileRoot: "items",
isItem: true,
x: 16,
y: -11,
hidden: true,
disableAnimation: true
},
{
spriteKey: "apicot_berry",
fileRoot: "items",
isItem: true,
x: 14,
y: -2,
hidden: true,
disableAnimation: true
},
{
spriteKey: "starf_berry",
fileRoot: "items",
isItem: true,
x: 18,
y: 9,
hidden: true,
disableAnimation: true
},
])
.withHideWildIntroMessage(true)
.withAutoHideIntroVisuals(false)
.withOnVisualsStart((scene: BattleScene) => {
doGreedentSpriteSteal(scene);
doBerrySpritePile(scene);
return true;
})
.withIntroDialogue([
{
text: `${namespace}.intro`,
}
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
scene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
// Get all player berry items, remove from party, and store reference
const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
// Sort berries by party member ID to more easily re-add later if necessary
const berryItemsMap = new Map<number, BerryModifier[]>();
scene.getParty().forEach(pokemon => {
const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
if (pokemonBerries?.length > 0) {
berryItemsMap.set(pokemon.id, pokemonBerries);
}
});
encounter.misc = { berryItemsMap };
// Generates copies of the stolen berries to put on the Greedent
const bossModifierConfigs: HeldModifierConfig[] = [];
berryItems.forEach(berryMod => {
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
for (let i = 0; i < berryMod.stackCount; i++) {
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
bossModifierConfigs.push({ modifierType });
}
scene.removeModifier(berryMod);
});
// Calculate boss mon
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.GREEDENT),
isBoss: true,
bossSegments: 3,
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF],
modifierConfigs: bossModifierConfigs,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
}
}
],
};
encounter.enemyPartyConfigs = [config];
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
// Provides 1x Reviver Seed to each party member at end of battle
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
const givePartyPokemonReviverSeeds = () => {
const party = scene.getParty();
party.forEach(p => {
const seedModifier = revSeed.newModifier(p);
scene.addModifier(seedModifier, false, false, false, true);
});
queueEncounterMessage(scene, `${namespace}.option.1.food_stash`);
};
setEncounterRewards(scene, { fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY],
move: new PokemonMove(Moves.STUFF_CHEEKS),
ignorePp: true
});
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const berryMap = encounter.misc.berryItemsMap;
// Returns 2/5 of the berries stolen from each Pokemon
const party = scene.getParty();
party.forEach(pokemon => {
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
const berryTypesAsArray: BerryType[] = [];
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
const returnedBerryCount = Math.floor((berryTypesAsArray.length ?? 0) * 2 / 5);
if (returnedBerryCount > 0) {
for (let i = 0; i < returnedBerryCount; i++) {
// Shuffle remaining berry types and pop
Phaser.Math.RND.shuffle(berryTypesAsArray);
const randBerryType = berryTypesAsArray.pop();
const berryModType = generateModifierType(scene, modifierTypes.BERRY, [randBerryType]) as BerryModifierType;
applyModifierTypeToPlayerPokemon(scene, pokemon, berryModType);
}
}
});
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Animate berries being eaten
doGreedentEatBerries(scene);
doBerrySpritePile(scene, true);
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
// Let it have the food
// Greedent joins the team, level equal to 2 below highest party member
const level = getHighestLevelPlayerPokemon(scene).level - 2;
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false);
greedent.moveset = [new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF)];
greedent.passive = true;
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await catchPokemon(scene, greedent, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.build();
function doGreedentSpriteSteal(scene: BattleScene) {
const shakeDelay = 50;
const slideDelay = 500;
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals?.getSpriteAtIndex(1);
scene.playSound("Follow Me");
scene.tweens.chain({
targets: greedentSprites,
tweens: [
{ // Slide Greedent diagonally
duration: slideDelay,
ease: "Cubic.easeOut",
y: "+=75",
x: "-=65",
scale: 1.1
},
{ // Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Slide Greedent diagonally
duration: slideDelay,
ease: "Cubic.easeOut",
y: "-=75",
x: "+=65",
scale: 1
},
{ // Bounce at the end
duration: 300,
ease: "Cubic.easeOut",
yoyo: true,
y: "-=20",
loop: 1,
}
]
});
}
function doGreedentEatBerries(scene: BattleScene) {
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals?.getSpriteAtIndex(1);
let index = 1;
scene.tweens.add({
targets: greedentSprites,
duration: 150,
ease: "Cubic.easeOut",
yoyo: true,
y: "-=8",
loop: 5,
onStart: () => {
scene.playSound("PRSFX- Bug Bite");
},
onLoop: () => {
if (index % 2 === 0) {
scene.playSound("PRSFX- Bug Bite");
}
index++;
}
});
}
/**
*
* @param scene
* @param isEat - default false. Will "create" pile when false, and remove pile when true.
*/
function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
const berryAddDelay = 150;
let animationOrder = ["starf", "sitrus", "lansat", "salac", "apicot", "enigma", "liechi", "ganlon", "lum", "petaya", "leppa"];
if (isEat) {
animationOrder = animationOrder.reverse();
}
const encounter = scene.currentBattle.mysteryEncounter;
animationOrder.forEach((berry, i) => {
const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry));
let sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite;
const sprites = encounter.introVisuals?.getSpriteAtIndex(introVisualsIndex);
if (sprites) {
sprite = sprites[0];
tintSprite = sprites[1];
}
scene.time.delayedCall(berryAddDelay * i + 400, () => {
if (sprite) {
sprite.setVisible(!isEat);
}
if (tintSprite) {
tintSprite.setVisible(!isEat);
}
// Animate Petaya berry falling off the pile
if (berry === "petaya" && sprite && tintSprite && !isEat) {
scene.time.delayedCall(200, () => {
doBerryBounce(scene, [sprite, tintSprite], 30, 500);
});
}
});
});
}
function doBerryBounce(scene: BattleScene, berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: integer) {
let bouncePower = 1;
let bounceYOffset = yd;
const doBounce = () => {
scene.tweens.add({
targets: berrySprites,
y: "+=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeIn",
onComplete: () => {
bouncePower = bouncePower > 0.01 ? bouncePower * 0.5 : 0;
if (bouncePower) {
bounceYOffset = bounceYOffset * bouncePower;
scene.tweens.add({
targets: berrySprites,
y: "-=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeOut",
onComplete: () => doBounce()
});
}
}
});
};
doBounce();
}

View File

@ -0,0 +1,162 @@
import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:offerYouCantRefuse";
/**
* An Offer You Can't Refuse encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/72 | GitHub Issue #72}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
.withIntroSpriteConfigs([
{
spriteKey: Species.LIEPARD.toString(),
fileRoot: "pokemon",
hasShadow: true,
repeat: true,
x: 0,
y: -4,
yShadow: -4
},
{
spriteKey: "rich_kid_m",
fileRoot: "trainer",
hasShadow: true,
x: 2,
y: 5,
yShadow: 5
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
text: `${namespace}.intro_dialogue`,
speaker: `${namespace}.speaker`,
},
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
const price = scene.getWaveMoneyAmount(10);
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString());
// Store pokemon and price
encounter.misc = {
pokemon: pokemon,
price: price
};
// If player meets the combo OR requirements for option 2, populate the token
const opt2Req = encounter.options[1].primaryPokemonRequirements[0];
if (opt2Req.meetsRequirement(scene)) {
const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"];
const moveToken = encounter.dialogueTokens["option2PrimaryMove"];
if (abilityToken) {
encounter.setDialogueToken("moveOrAbility", abilityToken);
} else if (moveToken) {
encounter.setDialogueToken("moveOrAbility", moveToken);
}
}
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
speaker: `${namespace}.speaker`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);
scene.removePokemonFromPlayerParty(encounter.misc.pokemon);
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
// Give the player a Shiny charm
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM));
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
new MoveRequirement(EXTORTION_MOVES),
new AbilityRequirement(EXTORTION_ABILITIES))
)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.tooltip_disabled`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.2.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Extort the rich kid for money
const encounter = scene.currentBattle.mysteryEncounter;
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);
setEncounterExp(scene, encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.3.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();

View File

@ -0,0 +1,267 @@
import { BattleStat } from "#app/data/battle-stat";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import {
EnemyPartyConfig, generateModifierType, generateModifierTypeOption,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, setEncounterExp,
setEncounterRewards
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import {
BerryModifierType,
getPartyLuckValue,
ModifierPoolType,
ModifierTypeOption, modifierTypes,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { TrainerSlot } from "#app/data/trainer-config";
import { applyModifierTypeToPlayerPokemon, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data";
import { BerryModifier } from "#app/modifier/modifier";
import i18next from "#app/plugins/i18n";
import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat";
import { StatChangePhase } from "#app/phases/stat-change-phase";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:berriesAbound";
/**
* Berries Abound encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const BerriesAboundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withCatchAllowed(true)
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([]) // Set in onInit()
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculate boss mon
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [{
level: level,
species: bossSpecies,
dataSource: new PokemonData(bossPokemon),
isBoss: true
}],
};
encounter.enemyPartyConfigs = [config];
// Calculate the number of extra berries that player receives
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
const numBerries =
scene.currentBattle.waveIndex > 160 ? 7
: scene.currentBattle.waveIndex > 120 ? 5
: scene.currentBattle.waveIndex > 40 ? 4 : 2;
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
encounter.misc = { numBerries };
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
encounter.spriteConfigs = [
{
spriteKey: "berry_bush",
fileRoot: "mystery-encounters",
x: 25,
y: -6,
yShadow: -7,
disableAnimation: true,
hasShadow: true
},
{
spriteKey: spriteKey,
fileRoot: fileRoot,
hasShadow: true,
tint: 0.25,
x: -5,
repeat: true,
isPokemon: true
}
];
// Get fastest party pokemon for option 2
const fastestPokemon = getHighestStatPlayerPokemon(scene, Stat.SPD, true);
encounter.misc.fastestPokemon = fastestPokemon;
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
},
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
const numBerries = encounter.misc.numBerries;
const doBerryRewards = async () => {
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGain", { modifierName: berryText }));
// Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) {
await tryGiveBerry(scene);
}
};
const shopOptions: ModifierTypeOption[] = [];
for (let i = 0; i < 5; i++) {
// Generate shop berries
shopOptions.push(generateModifierTypeOption(scene, modifierTypes.BERRY));
}
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
}
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick race for berries
const encounter = scene.currentBattle.mysteryEncounter;
const fastestPokemon = encounter.misc.fastestPokemon;
const enemySpeed = encounter.misc.enemySpeed;
const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
const numBerries = encounter.misc.numBerries;
const shopOptions: ModifierTypeOption[] = [];
for (let i = 0; i < 5; i++) {
// Generate shop berries
shopOptions.push(generateModifierTypeOption(scene, modifierTypes.BERRY));
}
if (speedDiff < 1) {
// Caught and attacked by boss, gets +1 to all stats at start of fight
const doBerryRewards = async () => {
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGain", { modifierName: berryText }));
// Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) {
await tryGiveBerry(scene);
}
};
const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
};
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await showEncounterText(scene, `${namespace}.option.2.selected_bad`);
await initBattleWithEnemyConfig(scene, config);
return;
} else {
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1)/0.08), numBerries), 2);
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
const doFasterBerryRewards = async () => {
const berryText = numBerriesGrabbed + " " + i18next.t(`${namespace}.berries`);
scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGain", { modifierName: berryText }));
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
for (let i = 0; i < numBerriesGrabbed; i++) {
await tryGiveBerry(scene, fastestPokemon);
}
};
setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards);
await showEncounterText(scene, `${namespace}.option.2.selected`);
leaveEncounterWithoutBattle(scene);
}
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();
async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType;
const party = scene.getParty();
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
if (prioritizedPokemon) {
const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
return;
}
}
// Iterate over the party until berry was successfully given
for (const pokemon of party) {
const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, berry);
break;
}
}
}

View File

@ -0,0 +1,496 @@
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Abilities } from "#enums/abilities";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { Type } from "#app/data/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { randSeedInt, randSeedShuffle } from "#app/utils";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Mode } from "#app/ui/ui";
import i18next from "i18next";
import { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { Ability } from "#app/data/ability";
import { BerryModifier } from "#app/modifier/modifier";
import { BerryType } from "#enums/berry-type";
import { BattlerIndex } from "#app/battle";
import { Moves } from "#enums/moves";
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#app/data/move";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:clowningAround";
const RANDOM_ABILITY_POOL = [
Abilities.STURDY,
Abilities.PICKUP,
Abilities.INTIMIDATE,
Abilities.GUTS,
Abilities.DROUGHT,
Abilities.DRIZZLE,
Abilities.SNOW_WARNING,
Abilities.SAND_STREAM,
Abilities.ELECTRIC_SURGE,
Abilities.PSYCHIC_SURGE,
Abilities.GRASSY_SURGE,
Abilities.MISTY_SURGE,
Abilities.MAGICIAN,
Abilities.SHEER_FORCE,
Abilities.PRANKSTER
];
/**
* Clowning Around encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ClowningAroundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(80, 180)
.withAnimations(EncounterAnim.SMOKESCREEN)
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
{
spriteKey: Species.MR_MIME.toString(),
fileRoot: "pokemon",
hasShadow: true,
repeat: true,
x: -25,
tint: 0.3,
y: -3,
yShadow: -3
},
{
spriteKey: Species.BLACEPHALON.toString(),
fileRoot: "pokemon/exp",
hasShadow: true,
repeat: true,
x: 25,
tint: 0.3,
y: -3,
yShadow: -3
},
{
spriteKey: "harlequin",
fileRoot: "trainer",
hasShadow: true,
x: 0,
y: 2,
yShadow: 2
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
text: `${namespace}.intro_dialogue`,
speaker: `${namespace}.speaker`
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const clownTrainerType = TrainerType.HARLEQUIN;
const clownConfig = trainerConfigs[clownTrainerType].copy();
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
clownConfig.setPartyTemplates(clownPartyTemplate);
clownConfig.setDoubleOnly();
// @ts-ignore
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
// Generate random ability for Blacephalon from pool
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
encounter.setDialogueToken("ability", new Ability(ability, 3).name);
encounter.misc = { ability };
encounter.enemyPartyConfigs.push({
trainerConfig: clownConfig,
pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon
{
species: getPokemonSpecies(Species.MR_MIME),
isBoss: true,
moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC]
},
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
species: getPokemonSpecies(Species.BLACEPHALON),
mysteryEncounterData: new MysteryEncounterPokemonData(undefined, ability, undefined, [randSeedInt(18), randSeedInt(18)]),
isBoss: true,
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
},
],
doubleBattle: true
});
// Load animations/sfx for start of fight moves
loadCustomMovesForEncounter(scene, [Moves.ROLE_PLAY, Moves.TAUNT]);
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
speaker: `${namespace}.speaker`
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Spawn battle
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards(scene, { fillRemaining: true });
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
encounter.startOfBattleEffects.push(
{ // Mr. Mime copies the Blacephalon's random ability
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY_2],
move: new PokemonMove(Moves.ROLE_PLAY),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.TAUNT),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(Moves.TAUNT),
ignorePp: true
});
await transitionMysteryEncounterIntroVisuals(scene);
await initBattleWithEnemyConfig(scene, config);
})
.withPostOptionPhase(async (scene: BattleScene): Promise<boolean> => {
// After the battle, offer the player the opportunity to permanently swap ability
const abilityWasSwapped = await handleSwapAbility(scene);
if (abilityWasSwapped) {
await showEncounterText(scene, `${namespace}.option.1.ability_gained`);
}
// Play animations once ability swap is complete
// Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals
scene.tweens.add({
targets: scene.currentBattle.trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 250
});
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
return true;
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
speaker: `${namespace}.speaker`
},
{
text: `${namespace}.option.2.selected_2`,
},
{
text: `${namespace}.option.2.selected_3`,
speaker: `${namespace}.speaker`
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Swap player's items on pokemon with the most items
// Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items
// So Vitamins, form change items, etc. are not included
const encounter = scene.currentBattle.mysteryEncounter;
const party = scene.getParty();
let mostHeldItemsPokemon = party[0];
let count = mostHeldItemsPokemon.getHeldItems()
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0);
party.forEach(pokemon => {
const nextCount = pokemon.getHeldItems()
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0);
if (nextCount > count) {
mostHeldItemsPokemon = pokemon;
count = nextCount;
}
});
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
const items = mostHeldItemsPokemon.getHeldItems();
// Shuffles Berries (if they have any)
let numBerries = 0;
items.filter(m => m instanceof BerryModifier)
.forEach(m => {
numBerries += m.stackCount;
scene.removeModifier(m);
});
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
let numUltra = 0;
let numRogue = 0;
items.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
.forEach(m => {
const type = m.type.withTierFromPool();
const tier = type.tier ?? ModifierTier.ULTRA;
if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) {
numUltra += m.stackCount;
scene.removeModifier(m);
} else if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
numRogue += m.stackCount;
scene.removeModifier(m);
}
});
generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
})
.withOptionPhase(async (scene: BattleScene) => {
leaveEncounterWithoutBattle(scene, true);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
await transitionMysteryEncounterIntroVisuals(scene);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
speaker: `${namespace}.speaker`
},
{
text: `${namespace}.option.3.selected_2`,
},
{
text: `${namespace}.option.3.selected_3`,
speaker: `${namespace}.speaker`
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Swap player's types on all party pokemon
// If a Pokemon had a single type prior, they will still have a single type after
for (const pokemon of scene.getParty()) {
const originalTypes = pokemon.getTypes(false, false, true);
// If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
// Makes the "randomness" of the shuffle slightly less punishing
let priorityTypes = pokemon.moveset
.filter(move => move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS)
.map(move => move!.getMove().type);
if (priorityTypes?.length > 0) {
priorityTypes = [...new Set(priorityTypes)];
randSeedShuffle(priorityTypes);
}
let newTypes;
if (!originalTypes || originalTypes.length < 1) {
newTypes = priorityTypes?.length > 0 ? [priorityTypes.pop()] : [(randSeedInt(18) as Type)];
} else {
newTypes = originalTypes.map(m => {
if (priorityTypes?.length > 0) {
const ret = priorityTypes.pop();
randSeedShuffle(priorityTypes);
return ret;
}
return randSeedInt(18) as Type;
});
}
if (!pokemon.mysteryEncounterData) {
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes);
} else {
pokemon.mysteryEncounterData.types = newTypes;
}
}
})
.withOptionPhase(async (scene: BattleScene) => {
leaveEncounterWithoutBattle(scene, true);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
await transitionMysteryEncounterIntroVisuals(scene);
})
.build()
)
.withOutroDialogue([
{
text: `${namespace}.outro`,
},
])
.build();
async function handleSwapAbility(scene: BattleScene) {
return new Promise<boolean>(async resolve => {
await showEncounterDialogue(scene, `${namespace}.option.1.apply_ability_dialogue`, `${namespace}.speaker`);
await showEncounterText(scene, `${namespace}.option.1.apply_ability_message`);
scene.ui.setMode(Mode.MESSAGE).then(() => {
displayYesNoOptions(scene, resolve);
});
});
}
function displayYesNoOptions(scene: BattleScene, resolve) {
showEncounterText(scene, `${namespace}.option.1.ability_prompt`, 500, false);
const fullOptions = [
{
label: i18next.t("menu:yes"),
handler: () => {
onYesAbilitySwap(scene, resolve);
return true;
}
},
{
label: i18next.t("menu:no"),
handler: () => {
resolve(false);
return true;
}
}
];
const config: OptionSelectConfig = {
options: fullOptions,
maxOptions: 7,
yOffset: 0
};
scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
}
function onYesAbilitySwap(scene: BattleScene, resolve) {
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Do ability swap
if (!pokemon.mysteryEncounterData) {
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, Abilities.AERILATE);
}
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability;
scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
};
const onPokemonNotSelected = () => {
scene.ui.setMode(Mode.MESSAGE).then(() => {
displayYesNoOptions(scene, resolve);
});
};
selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected);
}
function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: integer, tier: ModifierTier | "Berries") {
// These pools have to be defined at runtime so that modifierTypes exist
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
// This is to prevent "over-generating" a random item of a certain type during item swaps
const ultraPool = [
[modifierTypes.REVIVER_SEED, 1],
[modifierTypes.GOLDEN_PUNCH, 5],
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
[modifierTypes.QUICK_CLAW, 3],
[modifierTypes.WIDE_LENS, 3]
];
const roguePool = [
[modifierTypes.LEFTOVERS, 4],
[modifierTypes.SHELL_BELL, 4],
[modifierTypes.SOUL_DEW, 10],
[modifierTypes.SOOTHE_BELL, 3],
[modifierTypes.SCOPE_LENS, 1],
[modifierTypes.BATON, 1],
[modifierTypes.FOCUS_BAND, 5],
[modifierTypes.KINGS_ROCK, 3],
[modifierTypes.GRIP_CLAW, 5]
];
const berryPool = [
[BerryType.APICOT, 3],
[BerryType.ENIGMA, 2],
[BerryType.GANLON, 3],
[BerryType.LANSAT, 3],
[BerryType.LEPPA, 2],
[BerryType.LIECHI, 3],
[BerryType.LUM, 2],
[BerryType.PETAYA, 3],
[BerryType.SALAC, 2],
[BerryType.SITRUS, 2],
[BerryType.STARF, 3]
];
let pool: any[];
if (tier === "Berries") {
pool = berryPool;
} else {
pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool;
}
for (let i = 0; i < numItems; i++) {
const randIndex = randSeedInt(pool.length);
const newItemType = pool[randIndex];
let newMod;
if (tier === "Berries") {
newMod = generateModifierType(scene, modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
} else {
newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType;
}
applyModifierTypeToPlayerPokemon(scene, pokemon, newMod);
// Decrement max stacks and remove from pool if at max
newItemType[1]--;
if (newItemType[1] <= 0) {
pool.splice(randIndex, 1);
}
}
}

View File

@ -0,0 +1,295 @@
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { TrainerSlot } from "#app/data/trainer-config";
import PokemonData from "#app/system/pokemon-data";
import { Biome } from "#enums/biome";
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
import { BattlerTagType } from "#enums/battler-tag-type";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { BattleStat } from "#app/data/battle-stat";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { BattlerIndex } from "#app/battle";
import { catchPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { PokeballType } from "#enums/pokeball";
import { modifierTypes } from "#app/modifier/modifier-type";
import { StatChangePhase } from "#app/phases/stat-change-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:dancingLessons";
// Fire form
const BAILE_STYLE_BIOMES = [
Biome.VOLCANO,
Biome.BEACH,
Biome.ISLAND,
Biome.WASTELAND,
Biome.MOUNTAIN,
Biome.BADLANDS,
Biome.DESERT
];
// Electric form
const POM_POM_STYLE_BIOMES = [
Biome.CONSTRUCTION_SITE,
Biome.POWER_PLANT,
Biome.FACTORY,
Biome.LABORATORY,
Biome.SLUM,
Biome.METROPOLIS,
Biome.DOJO
];
// Psychic form
const PAU_STYLE_BIOMES = [
Biome.JUNGLE,
Biome.FAIRY_CAVE,
Biome.MEADOW,
Biome.PLAINS,
Biome.GRASS,
Biome.TALL_GRASS,
Biome.FOREST
];
// Ghost form
const SENSU_STYLE_BIOMES = [
Biome.RUINS,
Biome.SWAMP,
Biome.CAVE,
Biome.ABYSS,
Biome.GRAVEYARD,
Biome.LAKE,
Biome.TEMPLE
];
/**
* Dancing Lessons encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/130 | GitHub Issue #130}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DancingLessonsEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals
.withAnimations(EncounterAnim.DANCE)
.withHideWildIntroMessage(true)
.withAutoHideIntroVisuals(false)
.withCatchAllowed(true)
.withOnVisualsStart((scene: BattleScene) => {
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon());
danceAnim.play(scene);
return true;
})
.withIntroDialogue([
{
text: `${namespace}.intro`,
}
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const species = getPokemonSpecies(Species.ORICORIO);
const enemyPokemon = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels![0], TrainerSlot.NONE, false);
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
if (enemyPokemon.moveset.length < 4) {
enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE));
} else {
enemyPokemon.moveset[0] = new PokemonMove(Moves.REVELATION_DANCE);
}
}
// Set the form index based on the biome
// Defaults to Baile style if somehow nothing matches
const currentBiome = scene.arena.biomeType;
if (BAILE_STYLE_BIOMES.includes(currentBiome)) {
enemyPokemon.formIndex = 0;
} else if (POM_POM_STYLE_BIOMES.includes(currentBiome)) {
enemyPokemon.formIndex = 1;
} else if (PAU_STYLE_BIOMES.includes(currentBiome)) {
enemyPokemon.formIndex = 2;
} else if (SENSU_STYLE_BIOMES.includes(currentBiome)) {
enemyPokemon.formIndex = 3;
} else {
enemyPokemon.formIndex = 0;
}
const oricorioData = new PokemonData(enemyPokemon);
// Adds a real Pokemon sprite to the field (required for the animation)
scene.currentBattle.enemyParty[0] = enemyPokemon;
scene.field.add(enemyPokemon);
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [{
species: species,
dataSource: oricorioData,
isBoss: true,
// Gets +1 to all stats on battle start
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
}
}],
};
encounter.enemyPartyConfigs = [config];
encounter.misc = {
oricorioData
};
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.REVELATION_DANCE),
ignorePp: true
});
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.BATON], fillRemaining: true });
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Learn its Dance
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
scene.unshiftPhase(new LearnMovePhase(scene, scene.getParty().indexOf(pokemon), Moves.REVELATION_DANCE));
// Play animation again to "learn" the dance
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon());
danceAnim.play(scene);
};
return selectPokemonForOption(scene, onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
// Learn its Dance
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
secondOptionPrompt: `${namespace}.option.3.select_prompt`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Open menu for selecting pokemon with a Dancing move
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for nature selection
return pokemon.moveset
.filter(move => move && DANCING_MOVES.includes(move.getMove().id))
.map((move: PokemonMove) => {
const option: OptionSelectItem = {
label: move.getName(),
handler: () => {
// Pokemon and second option selected
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
encounter.setDialogueToken("selectedMove", move.getName());
encounter.misc.selectedMove = move;
return true;
},
};
return option;
});
};
// Only Pokemon that have a Dancing move can be selected
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Show the Oricorio a dance, and recruit it
const encounter = scene.currentBattle.mysteryEncounter;
const oricorio = encounter.misc.oricorioData.toPokemon(scene);
oricorio.passive = true;
// Ensure the Oricorio's moveset gains the Dance move the player used
const move = encounter.misc.selectedMove?.getMove().id;
if (!oricorio.moveset.some(m => m.getMove().id === move)) {
if (oricorio.moveset.length < 4) {
oricorio.moveset.push(new PokemonMove(move));
} else {
oricorio.moveset[3] = new PokemonMove(move);
}
}
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await catchPokemon(scene, oricorio, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.build();

View File

@ -0,0 +1,187 @@
import { Type } from "#app/data/type";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { modifierTypes } from "#app/modifier/modifier-type";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:darkDeal";
/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and egg-locked mythicals */
const excludedBosses = [
Species.NECROZMA,
Species.COSMOG,
Species.COSMOEM,
Species.SOLGALEO,
Species.LUNALA,
Species.ETERNATUS,
Species.NIHILEGO,
Species.BUZZWOLE,
Species.PHEROMOSA,
Species.XURKITREE,
Species.CELESTEELA,
Species.KARTANA,
Species.GUZZLORD,
Species.POIPOLE,
Species.NAGANADEL,
Species.STAKATAKA,
Species.BLACEPHALON,
Species.GREAT_TUSK,
Species.SCREAM_TAIL,
Species.BRUTE_BONNET,
Species.FLUTTER_MANE,
Species.SLITHER_WING,
Species.SANDY_SHOCKS,
Species.ROARING_MOON,
Species.KORAIDON,
Species.WALKING_WAKE,
Species.GOUGING_FIRE,
Species.RAGING_BOLT,
Species.IRON_TREADS,
Species.IRON_BUNDLE,
Species.IRON_HANDS,
Species.IRON_JUGULIS,
Species.IRON_MOTH,
Species.IRON_THORNS,
Species.IRON_VALIANT,
Species.MIRAIDON,
Species.IRON_LEAVES,
Species.IRON_BOULDER,
Species.IRON_CROWN,
Species.MEW,
Species.CELEBI,
Species.DEOXYS,
Species.JIRACHI,
Species.PHIONE,
Species.MANAPHY,
Species.ARCEUS,
Species.VICTINI,
Species.MELTAN,
Species.PECHARUNT,
];
/**
* Dark Deal encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/61 | GitHub Issue #61}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DarkDealEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([
{
spriteKey: "mad_scientist_m",
fileRoot: "mystery-encounters",
hasShadow: true,
},
{
spriteKey: "dark_deal_porygon",
fileRoot: "mystery-encounters",
hasShadow: true,
repeat: true,
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
speaker: `${namespace}.speaker`,
text: `${namespace}.intro_dialogue`,
},
])
.withSceneWaveRangeRequirement(30, 180) // waves 30 to 180
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
.withCatchAllowed(true)
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.1.selected_dialogue`,
},
{
text: `${namespace}.option.1.selected_message`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
// Will never return last battle able mon and instead pick fainted/unable to battle
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
scene.removePokemonFromPlayerParty(removedPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
// Store removed pokemon types
scene.currentBattle.mysteryEncounter.misc = [
removedPokemon.species.type1,
];
if (removedPokemon.species.type2) {
scene.currentBattle.mysteryEncounter.misc.push(removedPokemon.species.type2);
}
})
.withOptionPhase(async (scene: BattleScene) => {
// Give the player 5 Rogue Balls
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL));
// Start encounter with random legendary (7-10 starter strength) that has level additive
const bossTypes = scene.currentBattle.mysteryEncounter.misc as Type[];
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
const roll = randSeedInt(100);
const starterTier: number | [number, number] =
roll > 65 ? 6 : roll > 15 ? 7 : roll > 5 ? 8 : [9, 10];
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterTier(starterTier, excludedBosses, bossTypes));
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,
isBoss: true,
};
if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) {
pokemonConfig.formIndex = 0;
}
const config: EnemyPartyConfig = {
pokemonConfigs: [pokemonConfig],
};
return initBattleWithEnemyConfig(scene, config);
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.withOutroDialogue([
{
text: `${namespace}.outro`
}
])
.build();

View File

@ -0,0 +1,302 @@
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { HealingBoosterModifier, HiddenAbilityRateBoosterModifier, LevelIncrementBoosterModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import i18next from "#app/plugins/i18n";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:delibirdy";
/** Berries only */
const OPTION_2_ALLOWED_MODIFIERS = ["BerryModifier", "PokemonInstantReviveModifier"];
/** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */
const OPTION_3_DISALLOWED_MODIFIERS = [
"BerryModifier",
"PokemonInstantReviveModifier",
"TerastallizeModifier",
"PokemonBaseStatModifier",
"PokemonBaseStatTotalModifier"
];
/**
* Delibird-y encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/57 | GitHub Issue #57}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DelibirdyEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Must have enough money for it to spawn at the very least
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // Must also have either option 2 or 3 available to spawn
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)
))
.withIntroSpriteConfigs([
{
spriteKey: "",
fileRoot: "",
species: Species.DELIBIRD,
hasShadow: true,
repeat: true,
startFrame: 38,
scale: 0.94
},
{
spriteKey: "",
fileRoot: "",
species: Species.DELIBIRD,
hasShadow: true,
repeat: true,
scale: 1.06
},
{
spriteKey: "",
fileRoot: "",
species: Species.DELIBIRD,
hasShadow: true,
repeat: true,
startFrame: 65,
x: 1,
y: 5,
yShadow: 5
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
}
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOutroDialogue([
{
text: `${namespace}.outro`,
}
])
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, 2.75) // Must have money to spawn
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
// Give the player an Ability Charm
// Check if the player has max stacks of that item already
const existing = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
}
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
secondOptionPrompt: `${namespace}.option.2.select_prompt`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
});
return validItems.map((modifier: PokemonHeldItemModifier) => {
const option: OptionSelectItem = {
label: modifier.type.name,
handler: () => {
// Pokemon and item selected
encounter.setDialogueToken("chosenItem", modifier.type.name);
encounter.misc = {
chosenPokemon: pokemon,
chosenModifier: modifier,
};
return true;
},
};
return option;
});
};
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const modifier = encounter.misc.chosenModifier;
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed
if (modifier.type.name.includes("Berry")) {
// Check if the player has max stacks of that Candy Jar already
const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
}
} else {
// Check if the player has max stacks of that Healing Charm already
const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
}
}
// Remove the modifier if its stacks go to 0
modifier.stackCount -= 1;
if (modifier.stackCount === 0) {
scene.removeModifier(modifier);
}
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
secondOptionPrompt: `${namespace}.option.3.select_prompt`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
});
return validItems.map((modifier: PokemonHeldItemModifier) => {
const option: OptionSelectItem = {
label: modifier.type.name,
handler: () => {
// Pokemon and item selected
encounter.setDialogueToken("chosenItem", modifier.type.name);
encounter.misc = {
chosenPokemon: pokemon,
chosenModifier: modifier,
};
return true;
},
};
return option;
});
};
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const modifier = encounter.misc.chosenModifier;
// Check if the player has max stacks of Berry Pouch already
const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
}
// Remove the modifier if its stacks go to 0
modifier.stackCount -= 1;
if (modifier.stackCount === 0) {
scene.removeModifier(modifier);
}
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.build();

View File

@ -0,0 +1,163 @@
import {
leaveEncounterWithoutBattle,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, {
MysteryEncounterBuilder,
} from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:departmentStoreSale";
/**
* Department Store Sale encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/33 | GitHub Issue #33}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DepartmentStoreSaleEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 100)
.withIntroSpriteConfigs([
{
spriteKey: "b2w2_lady",
fileRoot: "mystery-encounters",
hasShadow: true,
x: -20,
},
{
spriteKey: "",
fileRoot: "",
species: Species.FURFROU,
hasShadow: true,
repeat: true,
x: 30,
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
text: `${namespace}.intro_dialogue`,
speaker: `${namespace}.speaker`,
},
])
.withAutoHideIntroVisuals(false)
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
},
async (scene: BattleScene) => {
// Choose TMs
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
while (i < 4) {
// 2/2/1 weight on TM rarity
const roll = randSeedInt(5);
if (roll < 2) {
modifiers.push(modifierTypes.TM_COMMON);
} else if (roll < 4) {
modifiers.push(modifierTypes.TM_GREAT);
} else {
modifiers.push(modifierTypes.TM_ULTRA);
}
i++;
}
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle(scene);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
},
async (scene: BattleScene) => {
// Choose Vitamins
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
while (i < 3) {
// 2/1 weight on base stat booster vs PP Up
const roll = randSeedInt(3);
if (roll === 0) {
modifiers.push(modifierTypes.PP_UP);
} else {
modifiers.push(modifierTypes.BASE_STAT_BOOSTER);
}
i++;
}
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle(scene);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
},
async (scene: BattleScene) => {
// Choose X Items
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
while (i < 5) {
// 4/1 weight on base stat booster vs Dire Hit
const roll = randSeedInt(5);
if (roll === 0) {
modifiers.push(modifierTypes.DIRE_HIT);
} else {
modifiers.push(modifierTypes.TEMP_STAT_BOOSTER);
}
i++;
}
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle(scene);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.4.label`,
buttonTooltip: `${namespace}.option.4.tooltip`,
},
async (scene: BattleScene) => {
// Choose Pokeballs
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
while (i < 4) {
// 10/30/20/5 weight on pokeballs
const roll = randSeedInt(65);
if (roll < 10) {
modifiers.push(modifierTypes.POKEBALL);
} else if (roll < 40) {
modifiers.push(modifierTypes.GREAT_BALL);
} else if (roll < 60) {
modifiers.push(modifierTypes.ULTRA_BALL);
} else {
modifiers.push(modifierTypes.ROGUE_BALL);
}
i++;
}
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle(scene);
}
)
.withOutroDialogue([
{
text: `${namespace}.outro`,
}
])
.build();

View File

@ -0,0 +1,320 @@
import { MoveCategory } from "#app/data/move";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { TempBattleStat } from "#app/data/temp-battle-stat";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieldTrip";
/**
* Field Trip encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/17 | GitHub Issue #17}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FieldTripEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withIntroSpriteConfigs([
{
spriteKey: "preschooler_m",
fileRoot: "trainer",
hasShadow: true,
},
{
spriteKey: "teacher",
fileRoot: "mystery-encounters",
hasShadow: true,
},
{
spriteKey: "preschooler_f",
fileRoot: "trainer",
hasShadow: true,
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
text: `${namespace}.intro_dialogue`,
speaker: `${namespace}.speaker`,
},
])
.withAutoHideIntroVisuals(false)
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
secondOptionPrompt: `${namespace}.second_option_prompt`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
const option: OptionSelectItem = {
label: move.getName(),
handler: () => {
// Pokemon and move selected
const correctMove = move.getMove().category === MoveCategory.PHYSICAL;
encounter.setDialogueToken("moveCategory", "Physical");
if (!correctMove) {
encounter.options[0].dialogue!.selected = [
{
text: `${namespace}.option.incorrect`,
speaker: `${namespace}.speaker`,
},
{
text: `${namespace}.option.lesson_learned`,
},
];
encounter.dialogue.outro = [
{
text: `${namespace}.outro_bad`,
speaker: `${namespace}.speaker`,
},
];
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
} else {
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName());
encounter.options[0].dialogue!.selected = [
{
text: `${namespace}.option.selected`,
},
];
encounter.dialogue.outro = [
{
text: `${namespace}.outro_good`,
speaker: `${namespace}.speaker`,
},
];
setEncounterExp(scene, [pokemon.id], 100);
}
encounter.misc = {
correctMove: correctMove,
};
return true;
},
};
return option;
});
};
return selectPokemonForOption(scene, onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ATK]),
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.DEF]),
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]),
generateModifierTypeOption(scene, modifierTypes.DIRE_HIT),
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
}
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
secondOptionPrompt: `${namespace}.second_option_prompt`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
const option: OptionSelectItem = {
label: move.getName(),
handler: () => {
// Pokemon and move selected
const correctMove = move.getMove().category === MoveCategory.SPECIAL;
encounter.setDialogueToken("moveCategory", "Special");
if (!correctMove) {
encounter.options[1].dialogue!.selected = [
{
text: `${namespace}.option.incorrect`,
speaker: `${namespace}.speaker`,
},
{
text: `${namespace}.option.lesson_learned`,
},
];
encounter.dialogue.outro = [
{
text: `${namespace}.outro_bad`,
speaker: `${namespace}.speaker`,
},
];
encounter.dialogue.outro = [
{
text: `${namespace}.outro_bad`,
speaker: `${namespace}.speaker`,
},
];
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
} else {
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName());
encounter.options[1].dialogue!.selected = [
{
text: `${namespace}.option.selected`,
},
];
encounter.dialogue.outro = [
{
text: `${namespace}.outro_good`,
speaker: `${namespace}.speaker`,
},
];
setEncounterExp(scene, [pokemon.id], 100);
}
encounter.misc = {
correctMove: correctMove,
};
return true;
},
};
return option;
});
};
return selectPokemonForOption(scene, onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPATK]),
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPDEF]),
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]),
generateModifierTypeOption(scene, modifierTypes.DIRE_HIT),
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
}
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
secondOptionPrompt: `${namespace}.second_option_prompt`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
const option: OptionSelectItem = {
label: move.getName(),
handler: () => {
// Pokemon and move selected
const correctMove = move.getMove().category === MoveCategory.STATUS;
encounter.setDialogueToken("moveCategory", "Status");
if (!correctMove) {
encounter.options[2].dialogue!.selected = [
{
text: `${namespace}.option.incorrect`,
speaker: `${namespace}.speaker`,
},
{
text: `${namespace}.option.lesson_learned`,
},
];
encounter.dialogue.outro = [
{
text: `${namespace}.outro_bad`,
speaker: `${namespace}.speaker`,
},
];
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
} else {
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName());
encounter.options[2].dialogue!.selected = [
{
text: `${namespace}.option.selected`,
},
];
encounter.dialogue.outro = [
{
text: `${namespace}.outro_good`,
speaker: `${namespace}.speaker`,
},
];
setEncounterExp(scene, [pokemon.id], 100);
}
encounter.misc = {
correctMove: correctMove,
};
return true;
},
};
return option;
});
};
return selectPokemonForOption(scene, onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ACC]),
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]),
generateModifierTypeOption(scene, modifierTypes.GREAT_BALL),
generateModifierTypeOption(scene, modifierTypes.IV_SCANNER),
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
}
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
})
.build()
)
.build();

View File

@ -0,0 +1,251 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { TypeRequirement } from "../mystery-encounter-requirements";
import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender";
import { Type } from "#app/data/type";
import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
import { Moves } from "#enums/moves";
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#app/data/weather";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { StatusEffect } from "#app/data/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieryFallout";
/**
* Damage percentage taken when suffering the heat.
* Can be a number between `0` - `100`.
* The higher the more damage taken (100% = instant KO).
*/
const DAMAGE_PERCENTAGE: number = 20;
/**
* Fiery Fallout encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/88 | GitHub Issue #88}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FieryFalloutEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(40, 180)
.withCatchAllowed(true)
.withIntroSpriteConfigs([]) // Set in onInit()
.withAnimations(EncounterAnim.MAGMA_BG, EncounterAnim.MAGMA_SPOUT)
.withAutoHideIntroVisuals(false)
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculate boss mons
const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA);
const config: EnemyPartyConfig = {
pokemonConfigs: [
{
species: volcaronaSpecies,
isBoss: false,
gender: Gender.MALE
},
{
species: volcaronaSpecies,
isBoss: false,
gender: Gender.FEMALE
}
],
doubleBattle: true,
disableSwitch: true
};
encounter.enemyPartyConfigs = [config];
// Load hidden Volcarona sprites
encounter.spriteConfigs = [
{
spriteKey: "",
fileRoot: "",
species: Species.VOLCARONA,
repeat: true,
hidden: true,
hasShadow: true,
x: -20,
startFrame: 20
},
{
spriteKey: "",
fileRoot: "",
species: Species.VOLCARONA,
repeat: true,
hidden: true,
hasShadow: true,
x: 20
},
];
// Load animations/sfx for Volcarona moves
loadCustomMovesForEncounter(scene, [Moves.FIRE_SPIN, Moves.QUIVER_DANCE]);
scene.arena.trySetWeather(WeatherType.SUNNY, true);
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 200, 70, 2, 3);
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
animation.playWithoutTargets(scene, 80, 100, 2);
scene.time.delayedCall(600, () => {
animation.playWithoutTargets(scene, -20, 100, 2);
});
scene.time.delayedCall(1200, () => {
animation.playWithoutTargets(scene, 140, 150, 2);
});
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
},
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonCharcoal(scene));
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY],
move: new PokemonMove(Moves.QUIVER_DANCE),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.ENEMY_2],
move: new PokemonMove(Moves.QUIVER_DANCE),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Damage non-fire types and burn 1 random non-fire type member
const encounter = scene.currentBattle.mysteryEncounter;
const nonFireTypes = scene.getParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE));
for (const pkm of nonFireTypes) {
const percentage = DAMAGE_PERCENTAGE / 100;
const damage = Math.floor(pkm.getMaxHp() * percentage);
applyDamageToPokemon(scene, pkm, damage);
}
// Burn random member
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.BURN);
if (burnable?.length > 0) {
const roll = randSeedInt(burnable.length);
const chosenPokemon = burnable[roll];
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
// Burn applied
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}.option.2.target_burned`);
}
}
// No rewards
leaveEncounterWithoutBattle(scene, true);
}
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3PrimaryName dialogue token automatically
.withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
transitionMysteryEncounterIntroVisuals(scene, false, false, 2000);
})
.withOptionPhase(async (scene: BattleScene) => {
// Fire types help calm the Volcarona
const encounter = scene.currentBattle.mysteryEncounter;
transitionMysteryEncounterIntroVisuals(scene);
setEncounterRewards(scene,
{ fillRemaining: true },
undefined,
() => {
giveLeadPokemonCharcoal(scene);
});
const primary = encounter.options[2].primaryPokemon!;
const secondary = encounter.options[2].secondaryPokemon![0];
setEncounterExp(scene, [primary.id, secondary.id], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
leaveEncounterWithoutBattle(scene);
})
.build()
)
.build();
function giveLeadPokemonCharcoal(scene: BattleScene) {
// Give first party pokemon Charcoal for free at end of battle
const leadPokemon = scene.getParty()?.[0];
if (leadPokemon) {
const charcoal = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]) as AttackTypeBoosterModifierType;
applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal);
scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}.found_charcoal`);
}
}

View File

@ -0,0 +1,174 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import {
EnemyPartyConfig,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, setEncounterExp,
setEncounterRewards
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { EnemyPokemon } from "#app/field/pokemon";
import { ModifierTier } from "#app/modifier/modifier-tier";
import {
getPartyLuckValue,
getPlayerModifierTypeOptions,
ModifierPoolType,
ModifierTypeOption,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { TrainerSlot } from "#app/data/trainer-config";
import { getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fightOrFlight";
/**
* Fight or Flight encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FightOrFlightEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withCatchAllowed(true)
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([]) // Set in onInit()
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculate boss mon
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [{
level: level,
species: bossSpecies,
dataSource: new PokemonData(bossPokemon),
isBoss: true
}],
};
encounter.enemyPartyConfigs = [config];
// Calculate item
// 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
const tier =
scene.currentBattle.waveIndex > 160
? ModifierTier.MASTER
: scene.currentBattle.waveIndex > 120
? ModifierTier.ROGUE
: scene.currentBattle.waveIndex > 40
? ModifierTier.ULTRA
: ModifierTier.GREAT;
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
let item: ModifierTypeOption | null = null;
// TMs excluded from possible rewards as they're too swingy in value for a singular item reward
while (!item || item.type.id.includes("TM_")) {
item = getPlayerModifierTypeOptions(1, scene.getParty(), [], { guaranteedModifierTiers: [tier], allowLuckUpgrades: false })[0];
}
encounter.setDialogueToken("itemName", item.type.name);
encounter.misc = item;
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
encounter.spriteConfigs = [
{
spriteKey: item.type.iconImage,
fileRoot: "items",
hasShadow: false,
x: 35,
y: -5,
scale: 0.75,
isItem: true,
disableAnimation: true
},
{
spriteKey: spriteKey,
fileRoot: fileRoot,
hasShadow: true,
tint: 0.25,
x: -5,
repeat: true,
isPokemon: true
},
];
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
},
async (scene: BattleScene) => {
// Pick battle
const item = scene.currentBattle.mysteryEncounter
.misc as ModifierTypeOption;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
}
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`
}
]
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick steal
const encounter = scene.currentBattle.mysteryEncounter;
const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
// Use primaryPokemon to execute the thievery
const primaryPokemon = encounter.options[1].primaryPokemon!;
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle(scene);
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();

View File

@ -0,0 +1,142 @@
import { getPokemonSpecies } from "#app/data/pokemon-species.js";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
const OPTION_1_REQUIRED_MOVE = Moves.SURF;
const OPTION_2_REQUIRED_MOVE = Moves.FLY;
/**
* Damage percentage taken when wandering aimlessly.
* Can be a number between `0` - `100`.
* The higher the more damage taken (100% = instant KO).
*/
const DAMAGE_PERCENTAGE: number = 25;
/** The i18n namespace for the encounter */
const namespace = "mysteryEncounter:lostAtSea";
/**
* Lost at sea encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/9 | GitHub Issue #9}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(11, 179)
.withIntroSpriteConfigs([
{
spriteKey: "buoy",
fileRoot: "mystery-encounters",
hasShadow: false,
x: 20,
y: 3,
},
])
.withIntroDialogue([{ text: `${namespace}.intro` }])
.withOnInit((scene: BattleScene) => {
const { mysteryEncounter } = scene.currentBattle;
mysteryEncounter.setDialogueToken("damagePercentage", String(DAMAGE_PERCENTAGE));
mysteryEncounter.setDialogueToken("option1RequiredMove", Moves[OPTION_1_REQUIRED_MOVE]);
mysteryEncounter.setDialogueToken("option2RequiredMove", Moves[OPTION_2_REQUIRED_MOVE]);
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
disabledButtonLabel: `${namespace}.option.1.label_disabled`,
buttonTooltip: `${namespace}.option.1.tooltip`,
disabledButtonTooltip: `${namespace}.option.1.tooltip_disabled`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene))
.build()
)
.withOption(
//Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
disabledButtonLabel: `${namespace}.option.2.label_disabled`,
buttonTooltip: `${namespace}.option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.tooltip_disabled`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene))
.build()
)
.withSimpleOption(
// Option 3: Wander aimlessly
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
},
async (scene: BattleScene) => {
const allowedPokemon = scene.getParty().filter((p) => p.isAllowedInBattle());
for (const pkm of allowedPokemon) {
const percentage = DAMAGE_PERCENTAGE / 100;
const damage = Math.floor(pkm.getMaxHp() * percentage);
applyDamageToPokemon(scene, pkm, damage);
}
leaveEncounterWithoutBattle(scene);
return true;
}
)
.withOutroDialogue([
{
text: `${namespace}.outro`,
},
])
.build();
/**
* Generic handler for using a guiding pokemon to guide you back.
*
* @param scene Battle scene
* @param guidePokemon pokemon choosen as a guide
*/
async function handlePokemonGuidingYouPhase(scene: BattleScene) {
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
const { mysteryEncounter } = scene.currentBattle;
if (mysteryEncounter.selectedOption?.primaryPokemon?.id) {
setEncounterExp(scene, mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true);
} else {
console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?");
}
leaveEncounterWithoutBattle(scene);
return true;
}

View File

@ -0,0 +1,212 @@
import {
EnemyPartyConfig,
initBattleWithEnemyConfig,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
trainerConfigs,
TrainerPartyCompoundTemplate,
TrainerPartyTemplate,
trainerPartyTemplates,
} from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:mysteriousChallengers";
/**
* Mysterious Challengers encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/41 | GitHub Issue #41}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const MysteriousChallengersEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculates what trainers are available for battle in the encounter
// Normal difficulty trainer is randomly pulled from biome
const normalTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
const normalConfig = trainerConfigs[normalTrainerType].copy();
let female = false;
if (normalConfig.hasGenders) {
female = !!Utils.randSeedInt(2);
}
const normalSpriteKey = normalConfig.getSpriteKey(female, normalConfig.doubleOnly);
encounter.enemyPartyConfigs.push({
trainerConfig: normalConfig,
female: female,
});
// Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config
// Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100
const hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
const hardTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true),
new TrainerPartyTemplate(
Math.min(Math.ceil(scene.currentBattle.waveIndex / 20), 5),
PartyMemberStrength.AVERAGE,
false,
true
)
);
const hardConfig = trainerConfigs[hardTrainerType].copy();
hardConfig.setPartyTemplates(hardTemplate);
female = false;
if (hardConfig.hasGenders) {
female = !!Utils.randSeedInt(2);
}
const hardSpriteKey = hardConfig.getSpriteKey(female, hardConfig.doubleOnly);
encounter.enemyPartyConfigs.push({
trainerConfig: hardConfig,
levelAdditiveMultiplier: 0.5,
female: female,
});
// Brutal trainer is pulled from pool of boss trainers (gym leaders) for the biome
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
const brutalTrainerType = scene.arena.randomTrainerType(
scene.currentBattle.waveIndex,
true
);
const e4Template = trainerPartyTemplates.ELITE_FOUR;
const brutalConfig = trainerConfigs[brutalTrainerType].copy();
brutalConfig.setPartyTemplates(e4Template);
// @ts-ignore
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func
female = false;
if (brutalConfig.hasGenders) {
female = !!Utils.randSeedInt(2);
}
const brutalSpriteKey = brutalConfig.getSpriteKey(female, brutalConfig.doubleOnly);
encounter.enemyPartyConfigs.push({
trainerConfig: brutalConfig,
levelAdditiveMultiplier: 1,
female: female,
});
encounter.spriteConfigs = [
{
spriteKey: normalSpriteKey,
fileRoot: "trainer",
hasShadow: true,
tint: 1,
},
{
spriteKey: hardSpriteKey,
fileRoot: "trainer",
hasShadow: true,
tint: 1,
},
{
spriteKey: brutalSpriteKey,
fileRoot: "trainer",
hasShadow: true,
tint: 1,
},
];
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
scene.executeWithSeedOffset(() => {
ret = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 10);
return ret;
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Spawn hard fight with ULTRA/GREAT reward (can improve with luck)
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
scene.executeWithSeedOffset(() => {
ret = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 100);
return ret;
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Spawn brutal fight with ROGUE/ULTRA/GREAT reward (can improve with luck)
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2];
// To avoid player level snowballing from picking this option
encounter.expMultiplier = 0.9;
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
scene.executeWithSeedOffset(() => {
ret = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 1000);
return ret;
}
)
.withOutroDialogue([
{
text: `${namespace}.outro`,
},
])
.build();

View File

@ -0,0 +1,141 @@
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { leaveEncounterWithoutBattle, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { randSeedInt } from "#app/utils.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:mysteriousChest";
/**
* Mysterious Chest encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/32 | GitHub Issue #32}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const MysteriousChestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 2 to 180
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
{
spriteKey: "chest_blue",
fileRoot: "mystery-encounters",
hasShadow: true,
x: 4,
y: 10,
yShadow: 3,
disableAnimation: true, // Re-enabled after option select
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
}
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Play animation
const introVisuals =
scene.currentBattle.mysteryEncounter.introVisuals!;
introVisuals.spriteConfigs[0].disableAnimation = false;
introVisuals.playAnim();
})
.withOptionPhase(async (scene: BattleScene) => {
// Open the chest
const roll = randSeedInt(100);
if (roll > 60) {
// Choose between 2 COMMON / 2 GREAT tier items (40%)
setEncounterRewards(scene, {
guaranteedModifierTiers: [
ModifierTier.COMMON,
ModifierTier.COMMON,
ModifierTier.GREAT,
ModifierTier.GREAT,
],
});
// Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.normal`);
leaveEncounterWithoutBattle(scene);
} else if (roll > 40) {
// Choose between 3 ULTRA tier items (20%)
setEncounterRewards(scene, {
guaranteedModifierTiers: [
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
});
// Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.good`);
leaveEncounterWithoutBattle(scene);
} else if (roll > 36) {
// Choose between 2 ROGUE tier items (4%)
setEncounterRewards(scene, {
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
});
// Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.great`);
leaveEncounterWithoutBattle(scene);
} else if (roll > 35) {
// Choose 1 MASTER tier item (1%)
setEncounterRewards(scene, {
guaranteedModifierTiers: [ModifierTier.MASTER],
});
// Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.amazing`);
leaveEncounterWithoutBattle(scene);
} else {
// Your highest level unfainted Pok<6F>mon gets OHKO. Progress with no rewards (35%)
const highestLevelPokemon = getHighestLevelPlayerPokemon(
scene,
true
);
koPlayerPokemon(scene, highestLevelPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
// Show which Pokemon was KOed, then leave encounter with no rewards
// Does this synchronously so that game over doesn't happen over result message
await showEncounterText(scene, `${namespace}.option.1.bad`);
leaveEncounterWithoutBattle(scene);
}
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();

View File

@ -0,0 +1,339 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Stat } from "#enums/stat";
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getEncounterText, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "i18next";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:partTimer";
/**
* Part Timer encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/82 | GitHub Issue #82}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const PartTimerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withIntroSpriteConfigs([
{
spriteKey: "worker_f",
fileRoot: "trainer",
hasShadow: true,
x: -20
},
{
spriteKey: "training_gear",
fileRoot: "mystery-encounters",
hasShadow: true,
y: 6,
x: 20,
yShadow: -2
}
])
.withAutoHideIntroVisuals(false)
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
speaker: `${namespace}.speaker`,
text: `${namespace}.intro_dialogue`,
},
])
.withOnInit((scene: BattleScene) => {
// Load sfx
scene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav");
scene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav");
scene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav");
scene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.wav");
scene.loadSe("PRSFX- Agility", "battle_anims", "PRSFX- Agility.wav");
scene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav");
scene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav");
scene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav");
scene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav");
scene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav");
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`
}
]
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
// Calculate the "baseline" stat value (90 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
// Calculation from Pokemon.calculateStats
const baselineValue = Math.floor(((2 * 90 + 16) * pokemon.level) * 0.01) + 5;
const percentDiff = (pokemon.getStat(Stat.SPD) - baselineValue) / baselineValue;
const moneyMultiplier = Math.min(Math.max(2.5 * (1+ percentDiff), 1), 4);
encounter.misc = {
moneyMultiplier
};
// Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => {
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
setEncounterExp(scene, pokemon.id, 100);
// Hide intro visuals
transitionMysteryEncounterIntroVisuals(scene, true, false);
// Play sfx for "working"
doDeliverySfx(scene);
};
// Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => {
if (!pokemon.isAllowedInBattle()) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick Deliveries
// Bring visuals back in
await transitionMysteryEncounterIntroVisuals(scene, false, false);
const moneyMultiplier = scene.currentBattle.mysteryEncounter.misc.moneyMultiplier;
// Give money and do dialogue
if (moneyMultiplier > 2.5) {
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`);
} else {
await showEncounterDialogue(scene, `${namespace}.job_complete_bad`, `${namespace}.speaker`);
}
const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounter:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}.pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene);
})
.build()
)
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`
}
]
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
// Calculate the "baseline" stat value (75 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
// Calculation from Pokemon.calculateStats
const baselineHp = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + pokemon.level + 10;
const baselineAtkDef = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + 5;
const baselineValue = baselineHp + 1.5 * (baselineAtkDef * 2);
const strongestValue = pokemon.getStat(Stat.HP) + 1.5 * (pokemon.getStat(Stat.ATK) + pokemon.getStat(Stat.DEF));
const percentDiff = (strongestValue - baselineValue) / baselineValue;
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
encounter.misc = {
moneyMultiplier
};
// Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => {
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
setEncounterExp(scene, pokemon.id, 100);
// Hide intro visuals
transitionMysteryEncounterIntroVisuals(scene, true, false);
// Play sfx for "working"
doStrongWorkSfx(scene);
};
// Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => {
if (!pokemon.isAllowedInBattle()) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick Move Warehouse items
// Bring visuals back in
await transitionMysteryEncounterIntroVisuals(scene, false, false);
const moneyMultiplier = scene.currentBattle.mysteryEncounter.misc.moneyMultiplier;
// Give money and do dialogue
if (moneyMultiplier > 2.5) {
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`);
} else {
await showEncounterDialogue(scene, `${namespace}.job_complete_bad`, `${namespace}.speaker`);
}
const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounter:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}.pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const selectedPokemon = encounter.selectedOption?.primaryPokemon!;
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
// Reduce all PP to 2 (if they started at greater than 2)
selectedPokemon.moveset.forEach(move => {
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
setEncounterExp(scene, selectedPokemon.id, 100);
// Hide intro visuals
transitionMysteryEncounterIntroVisuals(scene, true, false);
// Play sfx for "working"
doSalesSfx(scene);
return true;
})
.withOptionPhase(async (scene: BattleScene) => {
// Assist with Sales
// Bring visuals back in
await transitionMysteryEncounterIntroVisuals(scene, false, false);
// Give money and do dialogue
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`);
const moneyChange = scene.getWaveMoneyAmount(2.5);
updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounter:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}.pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene);
})
.build()
)
.withOutroDialogue([
{
speaker: `${namespace}.speaker`,
text: `${namespace}.outro`,
}
])
.build();
function doStrongWorkSfx(scene: BattleScene) {
scene.playSound("PRSFX- Horn Drill1");
scene.playSound("PRSFX- Horn Drill1");
scene.time.delayedCall(1000, () => {
scene.playSound("PRSFX- Guillotine2");
});
scene.time.delayedCall(2000, () => {
scene.playSound("PRSFX- Heavy Slam2");
});
scene.time.delayedCall(2500, () => {
scene.playSound("PRSFX- Guillotine2");
});
}
function doDeliverySfx(scene: BattleScene) {
scene.playSound("PRSFX- Accelerock1");
scene.time.delayedCall(1500, () => {
scene.playSound("PRSFX- Extremespeed1");
});
scene.time.delayedCall(2000, () => {
scene.playSound("PRSFX- Extremespeed1");
});
scene.time.delayedCall(2250, () => {
scene.playSound("PRSFX- Agility");
});
}
function doSalesSfx(scene: BattleScene) {
scene.playSound("PRSFX- Captivate");
scene.time.delayedCall(1500, () => {
scene.playSound("PRSFX- Attract2");
});
scene.time.delayedCall(2000, () => {
scene.playSound("PRSFX- Aurora Veil2");
});
scene.time.delayedCall(3000, () => {
scene.playSound("PRSFX- Attract2");
});
}

View File

@ -0,0 +1,501 @@
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier";
import { EnemyPokemon } from "#app/field/pokemon";
import { PokeballType } from "#app/data/pokeball";
import { PlayerGender } from "#enums/player-gender";
import { IntegerHolder, randSeedInt } from "#app/utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SummonPhase } from "#app/phases/summon-phase";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:safariZone";
const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768];
/**
* Safari Zone encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/39 | GitHub Issue #39}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const SafariZoneEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
.withIntroSpriteConfigs([
{
spriteKey: "safari_zone",
fileRoot: "mystery-encounters",
hasShadow: false,
x: 4,
y: 6
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter;
encounter.continuousEncounter = true;
encounter.misc = {
safariPokemonRemaining: 3
};
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Load bait/mud assets
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims");
scene.loadSe("PRSFX- Taunt2", "battle_anims");
scene.loadAtlas("bait", "mystery-encounters");
scene.loadAtlas("mud", "mystery-encounters");
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, hideDescription: true });
return true;
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();
/**
* SAFARI ZONE MINIGAME OPTIONS
*
* Catch and flee rate stages are calculated in the same way stat changes are (they range from -6/+6)
* https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Great_Marsh_and_Johto_Safari_Zone
*
* Catch Rate calculation:
* catchRate = speciesCatchRate [1 to 255] * catchStageMultiplier [2/8 to 8/2] * ballCatchRate [1.5]
*
* Flee calculation:
* The harder a species is to catch, the higher its flee rate is
* (Caps at 50% base chance to flee for the hardest to catch Pokemon, before factoring in flee stage)
* fleeRate = ((255^2 - speciesCatchRate^2) / 255 / 2) [0 to 127.5] * fleeStageMultiplier [2/8 to 8/2]
* Flee chance = fleeRate / 255
*/
const safariZoneGameOptions: MysteryEncounterOption[] = [
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.safari.1.label`,
buttonTooltip: `${namespace}.safari.1.tooltip`,
selected: [
{
text: `${namespace}.safari.1.selected`,
}
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw a ball option
const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = encounter.misc.pokemon;
const catchResult = await throwPokeball(scene, pokemon);
if (catchResult) {
// You caught pokemon
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
}
} else {
// Pokemon catch failed, end turn
await doEndTurn(scene, 0);
}
return true;
})
.build(),
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.safari.2.label`,
buttonTooltip: `${namespace}.safari.2.tooltip`,
selected: [
{
text: `${namespace}.safari.2.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw bait option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
await throwBait(scene, pokemon);
// 100% chance to increase catch stage +2
tryChangeCatchStage(scene, 2);
// 80% chance to increase flee stage +1
const fleeChangeResult = tryChangeFleeStage(scene, 1, 8);
if (!fleeChangeResult) {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.busy_eating`) ?? "", 1000, false );
} else {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.eating`) ?? "", 1000, false);
}
await doEndTurn(scene, 1);
return true;
})
.build(),
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.safari.3.label`,
buttonTooltip: `${namespace}.safari.3.tooltip`,
selected: [
{
text: `${namespace}.safari.3.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw mud option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
await throwMud(scene, pokemon);
// 100% chance to decrease flee stage -2
tryChangeFleeStage(scene, -2);
// 80% chance to decrease catch stage -1
const catchChangeResult = tryChangeCatchStage(scene, -1, 8);
if (!catchChangeResult) {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.beside_itself_angry`) ?? "", 1000, false );
} else {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.angry`) ?? "", 1000, false );
}
await doEndTurn(scene, 2);
return true;
})
.build(),
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.safari.4.label`,
buttonTooltip: `${namespace}.safari.4.tooltip`,
})
.withOptionPhase(async (scene: BattleScene) => {
// Flee option
const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = encounter.misc.pokemon;
await doPlayerFlee(scene, pokemon);
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
}
return true;
})
.build()
];
async function summonSafariPokemon(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
// Message pokemon remaining
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.remaining_count`) ?? "", null, true);
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
let enemySpecies;
let pokemon;
scene.executeWithSeedOffset(() => {
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(scene.currentBattle.waveIndex, true, false, scene.gameMode));
scene.currentBattle.enemyParty = [];
pokemon = scene.addEnemyPokemon(enemySpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, false);
// Roll shiny twice
if (!pokemon.shiny) {
pokemon.trySetShiny();
}
// Roll HA twice
if (pokemon.species.abilityHidden) {
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
if (pokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new IntegerHolder(256);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
if (hasHiddenAbility) {
pokemon.abilityIndex = hiddenIndex;
}
}
}
pokemon.calculateStats();
scene.currentBattle.enemyParty[0] = pokemon;
}, scene.currentBattle.waveIndex * 1000 + encounter.misc.safariPokemonRemaining);
scene.gameData.setPokemonSeen(pokemon, true);
await pokemon.loadAssets();
// Reset safari catch and flee rates
encounter.misc.catchStage = 0;
encounter.misc.fleeStage = 0;
encounter.misc.pokemon = pokemon;
encounter.misc.safariPokemonRemaining -= 1;
scene.unshiftPhase(new SummonPhase(scene, 0, false));
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", 1500, false)
.then(() => {
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
}
});
}
function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
const baseCatchRate = pokemon.species.catchRate;
// Catch stage ranges from -6 to +6 (like stat boost stages)
const safariCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage;
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6)
const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
// Catch rate same as safari ball
const pokeballMultiplier = 1.5;
const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier);
const ballTwitchRate = Math.round(1048560 / Math.sqrt(Math.sqrt(16711680 / catchRate)));
return trainerThrowPokeball(scene, pokemon, PokeballType.POKEBALL, ballTwitchRate);
}
async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
const originalY: number = pokemon.y;
const fpOffset = pokemon.getFieldPositionOffset();
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "bait", "0001.png");
bait.setOrigin(0.5, 0.625);
scene.field.add(bait);
return new Promise(resolve => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
scene.playSound("pb_throw");
// Trainer throw frames
scene.trainer.setFrame("2");
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
scene.trainer.setFrame("3");
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
});
});
// Pokeball move and catch logic
scene.tweens.add({
targets: bait,
x: { value: 210 + fpOffset[0], ease: "Linear" },
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
duration: 500,
onComplete: () => {
let index = 1;
scene.time.delayedCall(768, () => {
scene.tweens.add({
targets: pokemon,
duration: 150,
ease: "Cubic.easeOut",
yoyo: true,
y: originalY - 5,
loop: 6,
onStart: () => {
scene.playSound("PRSFX- Bug Bite");
bait.setFrame("0002.png");
},
onLoop: () => {
if (index % 2 === 0) {
scene.playSound("PRSFX- Bug Bite");
}
if (index === 4) {
bait.setFrame("0003.png");
}
index++;
},
onComplete: () => {
scene.time.delayedCall(256, () => {
bait.destroy();
resolve(true);
});
}
});
});
}
});
});
});
}
async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
const originalY: number = pokemon.y;
const fpOffset = pokemon.getFieldPositionOffset();
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 35, "mud", "0001.png");
mud.setOrigin(0.5, 0.625);
scene.field.add(mud);
return new Promise(resolve => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
scene.playSound("pb_throw");
// Trainer throw frames
scene.trainer.setFrame("2");
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
scene.trainer.setFrame("3");
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
});
});
// Mud throw and splat
scene.tweens.add({
targets: mud,
x: { value: 230 + fpOffset[0], ease: "Linear" },
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
duration: 500,
onComplete: () => {
// Mud frame 2
scene.playSound("PRSFX- Sludge Bomb2");
mud.setFrame("0002.png");
// Mud splat
scene.time.delayedCall(200, () => {
mud.setFrame("0003.png");
scene.time.delayedCall(400, () => {
mud.setFrame("0004.png");
});
});
// Fade mud then angry animation
scene.tweens.add({
targets: mud,
alpha: 0,
ease: "Cubic.easeIn",
duration: 1000,
onComplete: () => {
mud.destroy();
scene.tweens.add({
targets: pokemon,
duration: 300,
ease: "Cubic.easeOut",
yoyo: true,
y: originalY - 20,
loop: 1,
onStart: () => {
scene.playSound("PRSFX- Taunt2");
},
onLoop: () => {
scene.playSound("PRSFX- Taunt2");
},
onComplete: () => {
resolve(true);
}
});
}
});
}
});
});
});
}
function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
const speciesCatchRate = pokemon.species.catchRate;
const fleeModifier = (2 + Math.min(Math.max(fleeStage, 0), 6)) / (2 - Math.max(Math.min(fleeStage, 0), -6));
const fleeRate = (255 * 255 - speciesCatchRate * speciesCatchRate) / 255 / 2 * fleeModifier;
console.log("Flee rate: " + fleeRate);
const roll = randSeedInt(256);
console.log("Roll: " + roll);
return roll < fleeRate;
}
function tryChangeFleeStage(scene: BattleScene, change: number, chance?: number): boolean {
if (chance && randSeedInt(10) >= chance) {
return false;
}
const currentFleeStage = scene.currentBattle.mysteryEncounter.misc.fleeStage ?? 0;
scene.currentBattle.mysteryEncounter.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6);
return true;
}
function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number): boolean {
if (chance && randSeedInt(10) >= chance) {
return false;
}
const currentCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage ?? 0;
scene.currentBattle.mysteryEncounter.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6);
return true;
}
async function doEndTurn(scene: BattleScene, cursorIndex: number) {
const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = encounter.misc.pokemon;
const isFlee = isPokemonFlee(pokemon, encounter.misc.fleeStage);
if (isFlee) {
// Pokemon flees!
await doPokemonFlee(scene, pokemon);
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
}
} else {
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.watching`) ?? "", 0, null, 1000);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
}
}

View File

@ -0,0 +1,242 @@
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { StatusEffect } from "#app/data/status-effect";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:shadyVitaminDealer";
/**
* Shady Vitamin Dealer encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/34 | GitHub Issue #34}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ShadyVitaminDealerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withPrimaryPokemonStatusEffectRequirement([StatusEffect.NONE]) // Pokemon must not have status
.withPrimaryPokemonHealthRatioRequirement([0.34, 1]) // Pokemon must have above 1/3rd HP
.withIntroSpriteConfigs([
{
spriteKey: Species.KROOKODILE.toString(),
fileRoot: "pokemon",
hasShadow: true,
repeat: true,
x: 12,
y: -5,
yShadow: -5
},
{
spriteKey: "b2w2_veteran_m",
fileRoot: "mystery-encounters",
hasShadow: true,
x: -12,
y: 3,
yShadow: 3
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
text: `${namespace}.intro_dialogue`,
speaker: `${namespace}.speaker`,
},
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, 2) // Wave scaling money multiplier of 2
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Update money
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Calculate modifiers and dialogue tokens
const modifiers = [
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
];
encounter.setDialogueToken("boost1", modifiers[0].name);
encounter.setDialogueToken("boost2", modifiers[1].name);
encounter.misc = {
chosenPokemon: pokemon,
modifiers: modifiers,
};
};
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Choose Cheap Option
const encounter = scene.currentBattle.mysteryEncounter;
const chosenPokemon = encounter.misc.chosenPokemon;
const modifiers = encounter.misc.modifiers;
for (const modType of modifiers) {
await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType);
}
leaveEncounterWithoutBattle(scene);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Damage and status applied after dealer leaves (to make thematic sense)
const encounter = scene.currentBattle.mysteryEncounter;
const chosenPokemon = encounter.misc.chosenPokemon;
// Pokemon takes 1/3 max HP damage
applyDamageToPokemon(scene, chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 3));
// Roll for poison (80%)
if (randSeedInt(10) < 8) {
if (chosenPokemon.trySetStatus(StatusEffect.TOXIC)) {
// Toxic applied
queueEncounterMessage(scene, `${namespace}.bad_poison`);
} else {
// Pokemon immune or something else prevents status
queueEncounterMessage(scene, `${namespace}.damage_only`);
}
} else {
queueEncounterMessage(scene, `${namespace}.damage_only`);
}
setEncounterExp(scene, [chosenPokemon.id], 100);
chosenPokemon.updateInfo();
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, 5) // Wave scaling money multiplier of 5
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Update money
updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
// Calculate modifiers and dialogue tokens
const modifiers = [
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
];
encounter.setDialogueToken("boost1", modifiers[0].name);
encounter.setDialogueToken("boost2", modifiers[1].name);
encounter.misc = {
chosenPokemon: pokemon,
modifiers: modifiers,
};
};
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Choose Expensive Option
const encounter = scene.currentBattle.mysteryEncounter;
const chosenPokemon = encounter.misc.chosenPokemon;
const modifiers = encounter.misc.modifiers;
for (const modType of modifiers) {
await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType);
}
leaveEncounterWithoutBattle(scene);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Status applied after dealer leaves (to make thematic sense)
const encounter = scene.currentBattle.mysteryEncounter;
const chosenPokemon = encounter.misc.chosenPokemon;
// Roll for poison (20%)
if (randSeedInt(10) < 2) {
if (chosenPokemon.trySetStatus(StatusEffect.POISON)) {
// Poison applied
queueEncounterMessage(scene, `${namespace}.poison`);
} else {
// Pokemon immune or something else prevents status
queueEncounterMessage(scene, `${namespace}.no_bad_effects`);
}
} else {
queueEncounterMessage(scene, `${namespace}.no_bad_effects`);
}
setEncounterExp(scene, [chosenPokemon.id], 100);
chosenPokemon.updateInfo();
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
speaker: `${namespace}.speaker`
}
]
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();

View File

@ -0,0 +1,148 @@
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { StatusEffect } from "#app/data/status-effect";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounter:slumberingSnorlax";
/**
* Sleeping Snorlax encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/103 | GitHub Issue #103}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const SlumberingSnorlaxEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withCatchAllowed(true)
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([
{
spriteKey: Species.SNORLAX.toString(),
fileRoot: "pokemon",
hasShadow: true,
tint: 0.25,
scale: 1.5,
repeat: true,
y: 5,
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
console.log(encounter);
// Calculate boss mon
const bossSpecies = getPokemonSpecies(Species.SNORLAX);
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,
isBoss: true,
status: [StatusEffect.SLEEP, 5], // Extra turns on timer for Snorlax's start of fight moves
moveSet: [Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT]
};
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 0.5,
pokemonConfigs: [pokemonConfig],
};
encounter.enemyPartyConfigs = [config];
// Load animations/sfx for Snorlax fight start moves
loadCustomMovesForEncounter(scene, [Moves.SNORE]);
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
},
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: true});
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.SNORE),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.SNORE),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Fall asleep waiting for Snorlax
// Full heal party
scene.unshiftPhase(new PartyHealPhase(scene, true));
queueEncounterMessage(scene, `${namespace}.option.2.rest_result`);
leaveEncounterWithoutBattle(scene);
}
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES))
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`
}
]
})
.withOptionPhase(async (scene: BattleScene) => {
// Steal the Snorlax's Leftovers
const instance = scene.currentBattle.mysteryEncounter;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: false });
// Snorlax exp to Pokemon that did the stealing
setEncounterExp(scene, instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
leaveEncounterWithoutBattle(scene);
})
.build()
)
.build();

View File

@ -0,0 +1,235 @@
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoneyRequirement, WaveModulusRequirement } from "../mystery-encounter-requirements";
import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Biome } from "#enums/biome";
import { getBiomeKey } from "#app/field/arena";
import { Type } from "#app/data/type";
import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type";
import { TrainerSlot } from "#app/data/trainer-config";
import { BattlerTagType } from "#enums/battler-tag-type";
import { StatChangePhase } from "#app/phases/stat-change-phase";
import { BattleStat } from "#app/data/battle-stat";
import { getPokemonNameWithAffix } from "#app/messages";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:teleportingHijinks";
const MONEY_COST_MULTIPLIER = 2.5;
const BIOME_CANDIDATES = [Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND];
const MACHINE_INTERFACING_TYPES = [Type.ELECTRIC, Type.STEEL];
/**
* Teleporting Hijinks encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/119 | GitHub Issue #119}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TeleportingHijinksEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TELEPORTING_HIJINKS)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
.withSceneRequirement(new MoneyRequirement(undefined, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
.withAutoHideIntroVisuals(false)
.withCatchAllowed(true)
.withIntroSpriteConfigs([
{
spriteKey: "teleporter",
fileRoot: "mystery-encounters",
hasShadow: true,
y: 4
}
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
}
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const price = scene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
price
};
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(undefined, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
}
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Update money
updatePlayerMoney(scene, -scene.currentBattle.mysteryEncounter.misc.price, true, false);
})
.withOptionPhase(async (scene: BattleScene) => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(scene);
setEncounterRewards(scene, { fillRemaining: true });
await initBattleWithEnemyConfig(scene, config);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPokemonTypeRequirement(MACHINE_INTERFACING_TYPES, true, 1) // Must have Steel or Electric type
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
}
],
})
.withOptionPhase(async (scene: BattleScene) => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(scene);
setEncounterRewards(scene, { fillRemaining: true });
setEncounterExp(scene, scene.currentBattle.mysteryEncounter.selectedOption!.primaryPokemon!.id, 100);
await initBattleWithEnemyConfig(scene, config);
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.3.selected`,
},
],
},
async (scene: BattleScene) => {
// Inspect the Machine
const encounter = scene.currentBattle.mysteryEncounter;
// Init enemy
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
pokemonConfigs: [{
level: level,
species: bossSpecies,
dataSource: new PokemonData(bossPokemon),
isBoss: true,
}],
};
const magnet = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.STEEL]);
const metalCoat = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.ELECTRIC]);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [magnet, metalCoat], fillRemaining: true });
setEncounterExp(scene, encounter.selectedOption!.primaryPokemon!.id, 100);
transitionMysteryEncounterIntroVisuals(scene, true, true);
await initBattleWithEnemyConfig(scene, config);
}
)
.build();
async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculate new biome (cannot be current biome)
const filteredBiomes = BIOME_CANDIDATES.filter(b => scene.arena.biomeType !== b);
const newBiome = filteredBiomes[randSeedInt(filteredBiomes.length)];
// Show dialogue and transition biome
await showEncounterText(scene, `${namespace}.transport`);
await Promise.all([animateBiomeChange(scene, newBiome), transitionMysteryEncounterIntroVisuals(scene)]);
scene.playBgm();
await showEncounterText(scene, `${namespace}.attacked`);
// Init enemy
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
pokemonConfigs: [{
level: level,
species: bossSpecies,
dataSource: new PokemonData(bossPokemon),
isBoss: true,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.boss_enraged`);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
}
}],
};
return config;
}
async function animateBiomeChange(scene: BattleScene, nextBiome: Biome) {
return new Promise<void>(resolve => {
scene.tweens.add({
targets: [scene.arenaEnemy, scene.lastEnemyTrainer],
x: "+=300",
duration: 2000,
onComplete: () => {
scene.newArena(nextBiome);
const biomeKey = getBiomeKey(nextBiome);
const bgTexture = `${biomeKey}_bg`;
scene.arenaBgTransition.setTexture(bgTexture);
scene.arenaBgTransition.setAlpha(0);
scene.arenaBgTransition.setVisible(true);
scene.arenaPlayerTransition.setBiome(nextBiome);
scene.arenaPlayerTransition.setAlpha(0);
scene.arenaPlayerTransition.setVisible(true);
scene.tweens.add({
targets: [scene.arenaPlayer, scene.arenaBgTransition, scene.arenaPlayerTransition],
duration: 1000,
ease: "Sine.easeInOut",
alpha: (target: any) => target === scene.arenaPlayer ? 0 : 1,
onComplete: () => {
scene.arenaBg.setTexture(bgTexture);
scene.arenaPlayer.setBiome(nextBiome);
scene.arenaPlayer.setAlpha(1);
scene.arenaEnemy.setBiome(nextBiome);
scene.arenaEnemy.setAlpha(1);
scene.arenaNextEnemy.setBiome(nextBiome);
scene.arenaBgTransition.setVisible(false);
scene.arenaPlayerTransition.setVisible(false);
if (scene.lastEnemyTrainer) {
scene.lastEnemyTrainer.destroy();
}
resolve();
scene.tweens.add({
targets: scene.arenaEnemy,
x: "-=300",
});
}
});
}
});
});
}

View File

@ -0,0 +1,157 @@
import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoneyRequirement } from "../mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
import { PokeballType } from "#app/data/pokeball";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:pokemonSalesman";
const MAX_POKEMON_PRICE_MULTIPLIER = 6;
/**
* Pokemon Salesman encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/36 | GitHub Issue #36}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180)
.withSceneRequirement(new MoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
{
spriteKey: "pokemon_salesman",
fileRoot: "mystery-encounters",
hasShadow: true
}
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
text: `${namespace}.intro_dialogue`,
speaker: `${namespace}.speaker`,
},
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
const tries = 0;
// Reroll any species that don't have HAs
while (isNullOrUndefined(species.abilityHidden) && tries < 5) {
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
}
let pokemon: PlayerPokemon;
if (isNullOrUndefined(species.abilityHidden) || randSeedInt(100) === 0) {
// If no HA mon found or you roll 1%, give shiny Magikarp
species = getPokemonSpecies(Species.MAGIKARP);
const hiddenIndex = species.ability2 ? 2 : 1;
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true);
} else {
const hiddenIndex = species.ability2 ? 2 : 1;
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
}
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
encounter.spriteConfigs.push({
spriteKey: spriteKey,
fileRoot: fileRoot,
hasShadow: true,
repeat: true,
isPokemon: true
});
const starterTier = speciesStarters[species.speciesId];
// Prices decrease by starter tier less than 5, but only reduces cost by half at max
let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5);
if (pokemon.shiny) {
// Always max price for shiny (flip HA back to normal), and add special messaging
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
pokemon.abilityIndex = 0;
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}.description_shiny`;
encounter.options[0].dialogue!.buttonTooltip = `${namespace}.option.1.tooltip_shiny`;
}
const price = scene.getWaveMoneyAmount(priceMultiplier);
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
price: price,
pokemon: pokemon
};
pokemon.calculateStats();
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withHasDexProgress(true)
.withSceneMoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected_message`,
}
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const price = encounter.misc.price;
const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon;
// Update money
updatePlayerMoney(scene, -price, true, false);
// Show dialogue
await showEncounterDialogue(scene, `${namespace}.option.1.selected_dialogue`, `${namespace}.speaker`);
await transitionMysteryEncounterIntroVisuals(scene);
// "Catch" purchased pokemon
const data = new PokemonData(purchasedPokemon);
data.player = false;
await catchPokemon(scene, data.toPokemon(scene) as EnemyPokemon, null, PokeballType.POKEBALL, true, true);
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();

View File

@ -0,0 +1,199 @@
import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
import { Nature } from "#app/data/nature";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { BattleStat } from "#app/data/battle-stat";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { StatChangePhase } from "#app/phases/stat-change-phase";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theStrongStuff";
/**
* The Strong Stuff encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/54 | GitHub Issue #54}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TheStrongStuffEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withHideWildIntroMessage(true)
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
{
spriteKey: "berry_juice",
fileRoot: "items",
hasShadow: true,
isItem: true,
scale: 1.5,
x: -15,
y: 3,
disableAnimation: true
},
{
spriteKey: Species.SHUCKLE.toString(),
fileRoot: "pokemon",
hasShadow: true,
repeat: true,
scale: 1.5,
x: 20,
y: 10,
yShadow: 7
},
]) // Set in onInit()
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculate boss mon
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
disableSwitch: true,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.SHUCKLE),
isBoss: true,
bossSegments: 5,
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
nature: Nature.BOLD,
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2
}
],
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.stat_boost`);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF, BattleStat.SPDEF], 2));
}
}
],
};
encounter.enemyPartyConfigs = [config];
loadCustomMovesForEncounter(scene, [Moves.GASTRO_ACID, Moves.STEALTH_ROCK]);
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`
}
]
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Do blackout and hide intro visuals during blackout
scene.time.delayedCall(750, () => {
transitionMysteryEncounterIntroVisuals(scene, true, true, 50);
});
// -20 to all base stats of highest BST, +10 to all base stats of rest of party
// Get highest BST mon
const party = scene.getParty();
let highestBst: PlayerPokemon | null = null;
let statTotal = 0;
for (const pokemon of party) {
if (!highestBst) {
highestBst = pokemon;
statTotal = pokemon.getSpeciesForm().getBaseStatTotal();
continue;
}
const total = pokemon.getSpeciesForm().getBaseStatTotal();
if (total > statTotal) {
highestBst = pokemon;
statTotal = total;
}
}
if (!highestBst) {
highestBst = party[0];
}
modifyPlayerPokemonBST(highestBst, -20);
for (const pokemon of party) {
if (highestBst.id === pokemon.id) {
continue;
}
modifyPlayerPokemonBST(pokemon, 10);
}
encounter.setDialogueToken("highBstPokemon", highestBst.getNameToRender());
await showEncounterText(scene, `${namespace}.option.1.selected_2`, undefined, true);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW], fillRemaining: true });
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.GASTRO_ACID),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.STEALTH_ROCK),
ignorePp: true
});
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
}
)
.build();

View File

@ -0,0 +1,497 @@
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { Nature } from "#enums/nature";
import { Type } from "#app/data/type";
import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat";
import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/ability";
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { ReturnPhase } from "#app/phases/return-phase";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theWinstrateChallenge";
/**
* The Winstrate Challenge encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/136 | GitHub Issue #136}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TheWinstrateChallengeEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(80, 180)
.withIntroSpriteConfigs([
{
spriteKey: "vito",
fileRoot: "trainer",
hasShadow: false,
x: 16,
y: -4
},
{
spriteKey: "vivi",
fileRoot: "trainer",
hasShadow: false,
x: -14,
y: -4
},
{
spriteKey: "victor",
fileRoot: "trainer",
hasShadow: true,
x: -32
},
{
spriteKey: "victoria",
fileRoot: "trainer",
hasShadow: true,
x: 40,
},
{
spriteKey: "vicky",
fileRoot: "trainer",
hasShadow: true,
x: 3,
y: 5,
yShadow: 5
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
speaker: `${namespace}.speaker`,
text: `${namespace}.intro_dialogue`,
},
])
.withAutoHideIntroVisuals(false)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Loaded back to front for pop() operations
encounter.enemyPartyConfigs.push(getVitoTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVickyTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getViviTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVictorTrainerConfig(scene));
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
speaker: "trainerNames:victor",
text: `${namespace}.option.1.selected`,
},
],
},
async (scene: BattleScene) => {
// Spawn 5 trainer battles back to back with Macho Brace in rewards
// scene.currentBattle.mysteryEncounter.continuousEncounter = true;
scene.currentBattle.mysteryEncounter.doContinueEncounter = (scene: BattleScene) => {
return endTrainerBattleAndShowDialogue(scene);
};
await transitionMysteryEncounterIntroVisuals(scene, true, false);
await spawnNextTrainerOrEndEncounter(scene);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
scene.unshiftPhase(new PartyHealPhase(scene, true));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], fillRemaining: false });
leaveEncounterWithoutBattle(scene);
}
)
.build();
async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
const nextConfig = encounter.enemyPartyConfigs.pop();
if (!nextConfig) {
await transitionMysteryEncounterIntroVisuals(scene, false, false);
await showEncounterDialogue(scene, `${namespace}.victory`, `${namespace}.speaker`);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE], fillRemaining: false });
encounter.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, false, MysteryEncounterMode.TRAINER_BATTLE);
} else {
await initBattleWithEnemyConfig(scene, nextConfig);
}
}
function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
return new Promise(async resolve => {
if (scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length === 0) {
// Battle is over
const trainer = scene.currentBattle.trainer;
if (trainer) {
scene.tweens.add({
targets: trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(trainer, true);
}
});
}
await spawnNextTrainerOrEndEncounter(scene);
resolve(); // Wait for all dialogue/post battle stuff to complete before resolving
} else {
scene.arena.resetArenaEffects();
const playerField = scene.getPlayerField();
playerField.forEach((_, p) => scene.unshiftPhase(new ReturnPhase(scene, p)));
for (const pokemon of scene.getParty()) {
// Only trigger form change when Eiscue is in Noice form
// Hardcoded Eiscue for now in case it is fused with another pokemon
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}
scene.unshiftPhase(new ShowTrainerPhase(scene));
// Hide the trainer and init next battle
const trainer = scene.currentBattle.trainer;
// Unassign previous trainer from battle so it isn't destroyed before animation completes
scene.currentBattle.trainer = null;
await spawnNextTrainerOrEndEncounter(scene);
if (trainer) {
scene.tweens.add({
targets: trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(trainer, true);
resolve();
}
});
}
}
});
}
function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VICTOR,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.SWELLOW),
isBoss: false,
abilityIndex: 0, // Guts
nature: Nature.ADAMANT,
moveSet: [Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
]
},
{
species: getPokemonSpecies(Species.OBSTAGOON),
isBoss: false,
abilityIndex: 1, // Guts
nature: Nature.ADAMANT,
moveSet: [Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
}
]
};
}
function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VICTORIA,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.ROSERADE),
isBoss: false,
abilityIndex: 0, // Natural Cure
nature: Nature.CALM,
moveSet: [Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.GARDEVOIR),
isBoss: false,
formIndex: 1,
nature: Nature.TIMID,
moveSet: [Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.PSYCHIC]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FAIRY]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
}
]
}
]
};
}
function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VIVI,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.SEAKING),
isBoss: false,
abilityIndex: 3, // Lightning Rod
nature: Nature.ADAMANT,
moveSet: [Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.BRELOOM),
isBoss: false,
abilityIndex: 1, // Poison Heal
nature: Nature.JOLLY,
moveSet: [Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.CAMERUPT),
isBoss: false,
formIndex: 1,
nature: Nature.CALM,
moveSet: [Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 3,
isTransferable: false
},
]
}
]
};
}
function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VICKY,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.MEDICHAM),
isBoss: false,
formIndex: 1,
nature: Nature.IMPISH,
moveSet: [Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
isTransferable: false
}
]
}
]
};
}
function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VITO,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.HISUI_ELECTRODE),
isBoss: false,
abilityIndex: 0, // Soundproof
nature: Nature.MODEST,
moveSet: [Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.SWALOT),
isBoss: false,
abilityIndex: 2, // Gluttony
nature: Nature.QUIET,
moveSet: [Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
stackCount: 2,
}
]
},
{
species: getPokemonSpecies(Species.DODRIO),
isBoss: false,
abilityIndex: 2, // Tangled Feet
nature: Nature.JOLLY,
moveSet: [Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.ALAKAZAM),
isBoss: false,
formIndex: 1,
nature: Nature.BOLD,
moveSet: [Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
]
},
{
species: getPokemonSpecies(Species.DARMANITAN),
isBoss: false,
abilityIndex: 0, // Sheer Force
nature: Nature.IMPISH,
moveSet: [Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
]
}
]
};
}

View File

@ -0,0 +1,415 @@
import { Ability, allAbilities } from "#app/data/ability";
import { EnemyPartyConfig, initBattleWithEnemyConfig, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getNatureName, Nature } from "#app/data/nature";
import { speciesStarters } from "#app/data/pokemon-species";
import { getStatName } from "#app/data/pokemon-stat";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { AbilityAttr } from "#app/system/game-data";
import PokemonData from "#app/system/pokemon-data";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { isNullOrUndefined, randSeedShuffle } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
/** The i18n namespace for the encounter */
const namespace = "mysteryEncounter:trainingSession";
/**
* Training Session encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/43 | GitHub Issue #43}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TrainingSessionEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([
{
spriteKey: "training_gear",
fileRoot: "mystery-encounters",
hasShadow: true,
y: 6,
x: 5,
yShadow: -2
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
}
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.misc = {
playerPokemon: pokemon,
};
};
// Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => {
const meetsReqs = pokemon.isAllowedInBattle();
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn light training session with chosen pokemon
// Every 50 waves, add +1 boss segment, capping at 5
const segments = Math.min(
2 + Math.floor(scene.currentBattle.waveIndex / 50),
5
);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(
scene,
playerPokemon,
segments,
modifiers
);
scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
encounter.setDialogueToken("stat1", "-");
encounter.setDialogueToken("stat2", "-");
// Add the pokemon back to party with IV boost
const ivIndexes: any[] = [];
playerPokemon.ivs.forEach((iv, index) => {
if (iv < 31) {
ivIndexes.push({ iv: iv, index: index });
}
});
// Improves 2 random non-maxed IVs
// +10 if IV is < 10, +5 if between 10-20, and +3 if > 20
// A 0-4 starting IV will cap in 6 encounters (assuming you always rolled that IV)
// 5-14 starting IV caps in 5 encounters
// 15-19 starting IV caps in 4 encounters
// 20-24 starting IV caps in 3 encounters
// 25-27 starting IV caps in 2 encounters
let improvedCount = 0;
while (ivIndexes.length > 0 && improvedCount < 2) {
randSeedShuffle(ivIndexes);
const ivToChange = ivIndexes.pop();
let newVal = ivToChange.iv;
if (improvedCount === 0) {
encounter.setDialogueToken(
"stat1",
getStatName(ivToChange.index) ?? ""
);
} else {
encounter.setDialogueToken(
"stat2",
getStatName(ivToChange.index) ?? ""
);
}
// Corrects required encounter breakpoints to be continuous for all IV values
if (ivToChange.iv <= 21 && ivToChange.iv - (1 % 5) === 0) {
newVal += 1;
}
newVal += ivToChange.iv <= 10 ? 10 : ivToChange.iv <= 20 ? 5 : 3;
newVal = Math.min(newVal, 31);
playerPokemon.ivs[ivToChange.index] = newVal;
improvedCount++;
}
if (improvedCount > 0) {
playerPokemon.calculateStats();
scene.gameData.updateSpeciesDexIvs(
playerPokemon.species.getRootSpeciesId(true),
playerPokemon.ivs
);
scene.gameData.setPokemonCaught(playerPokemon, false);
}
// Add pokemon and mods back
scene.getParty().push(playerPokemon);
for (const mod of modifiers.value) {
scene.addModifier(mod, true, false, false, true);
}
scene.updateModifiers(true);
queueEncounterMessage(scene, `${namespace}.option.1.finished`);
};
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
secondOptionPrompt: `${namespace}.option.2.select_prompt`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
// Open menu for selecting pokemon and Nature
const encounter = scene.currentBattle.mysteryEncounter;
const natures = new Array(25).fill(null).map((val, i) => i as Nature);
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for nature selection
return natures.map((nature: Nature) => {
const option: OptionSelectItem = {
label: getNatureName(nature, true, true, true, scene.uiTheme),
handler: () => {
// Pokemon and second option selected
encounter.setDialogueToken("nature", getNatureName(nature));
encounter.misc = {
playerPokemon: pokemon,
chosenNature: nature,
};
return true;
},
};
return option;
});
};
// Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => {
const meetsReqs = pokemon.isAllowedInBattle();
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn medium training session with chosen pokemon
// Every 40 waves, add +1 boss segment, capping at 6
const segments = Math.min(
2 + Math.floor(scene.currentBattle.waveIndex / 40),
6
);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(
scene,
playerPokemon,
segments,
modifiers
);
scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
queueEncounterMessage(scene, `${namespace}.option.2.finished`);
// Add the pokemon back to party with Nature change
playerPokemon.setNature(encounter.misc.chosenNature);
scene.gameData.setPokemonCaught(playerPokemon, false);
// Add pokemon and mods back
scene.getParty().push(playerPokemon);
for (const mod of modifiers.value) {
scene.addModifier(mod, true, false, false, true);
}
scene.updateModifiers(true);
};
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
secondOptionPrompt: `${namespace}.option.3.select_prompt`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
// Open menu for selecting pokemon and ability to learn
const encounter = scene.currentBattle.mysteryEncounter;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for ability selection
const speciesForm = !!pokemon.getFusionSpeciesForm()
? pokemon.getFusionSpeciesForm()
: pokemon.getSpeciesForm();
const abilityCount = speciesForm.getAbilityCount();
const abilities = new Array(abilityCount)
.fill(null)
.map((val, i) => allAbilities[speciesForm.getAbility(i)]);
return abilities.map((ability: Ability, index) => {
const option: OptionSelectItem = {
label: ability.name,
handler: () => {
// Pokemon and ability selected
encounter.setDialogueToken("ability", ability.name);
encounter.misc = {
playerPokemon: pokemon,
abilityIndex: index,
};
return true;
},
onHover: () => {
scene.ui.showText(ability.description);
},
};
return option;
});
};
// Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => {
const meetsReqs = pokemon.isAllowedInBattle();
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn hard training session with chosen pokemon
// Every 30 waves, add +1 boss segment, capping at 6
// Also starts with +1 to all stats
const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 30), 6);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(scene, playerPokemon, segments, modifiers);
config.pokemonConfigs![0].tags = [
BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON,
];
scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
queueEncounterMessage(scene, `${namespace}.option.3.finished`);
// Add the pokemon back to party with ability change
const abilityIndex = encounter.misc.abilityIndex;
if (!!playerPokemon.getFusionSpeciesForm()) {
playerPokemon.fusionAbilityIndex = abilityIndex;
if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies!.speciesId)) {
scene.gameData.starterData[playerPokemon.fusionSpecies!.speciesId]
.abilityAttr |=
abilityIndex !== 1 || playerPokemon.fusionSpecies!.ability2
? Math.pow(2, playerPokemon.fusionAbilityIndex)
: AbilityAttr.ABILITY_HIDDEN;
}
} else {
playerPokemon.abilityIndex = abilityIndex;
if (
speciesStarters.hasOwnProperty(playerPokemon.species.speciesId)
) {
scene.gameData.starterData[
playerPokemon.species.speciesId
].abilityAttr |=
abilityIndex !== 1 || playerPokemon.species.ability2
? Math.pow(2, playerPokemon.abilityIndex)
: AbilityAttr.ABILITY_HIDDEN;
}
}
playerPokemon.getAbility();
playerPokemon.calculateStats();
scene.gameData.setPokemonCaught(playerPokemon, false);
// Add pokemon and mods back
scene.getParty().push(playerPokemon);
for (const mod of modifiers.value) {
scene.addModifier(mod, true, false, false, true);
}
scene.updateModifiers(true);
};
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})
.build()
)
.build();
function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig {
playerPokemon.resetSummonData();
// Passes modifiers by reference
modifiers.value = scene.findModifiers((m) => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === playerPokemon.id) as PokemonHeldItemModifier[];
const modifierConfigs = modifiers.value.map((mod) => {
return {
modifierType: mod.type
};
}) as HeldModifierConfig[];
const data = new PokemonData(playerPokemon);
return {
pokemonConfigs: [
{
species: playerPokemon.species,
isBoss: true,
bossSegments: segments,
formIndex: playerPokemon.formIndex,
level: playerPokemon.level,
dataSource: data,
modifierConfigs: modifierConfigs,
},
],
};
}
class ModifiersHolder {
public value: PokemonHeldItemModifier[] = [];
constructor() {}
}

View File

@ -0,0 +1,220 @@
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Species } from "#enums/species";
import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app/modifier/modifier";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "#app/plugins/i18n";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:trashToTreasure";
const SOUND_EFFECT_WAIT_TIME = 700;
/**
* Trash to Treasure encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/74 | GitHub Issue #74}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TrashToTreasureEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180)
.withMaxAllowedEncounters(1)
.withIntroSpriteConfigs([
{
spriteKey: Species.GARBODOR.toString() + "-gigantamax",
fileRoot: "pokemon",
hasShadow: false,
disableAnimation: true,
scale: 1.5,
y: 8,
tint: 0.4
}
])
.withAutoHideIntroVisuals(false)
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculate boss mon
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,
isBoss: true,
formIndex: 1, // Gmax
bossSegmentModifier: 1, // +1 Segment from normal
moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH]
};
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [pokemonConfig],
disableSwitch: true
};
encounter.enemyPartyConfigs = [config];
// Load animations/sfx for Garbodor fight start moves
loadCustomMovesForEncounter(scene, [Moves.TOXIC, Moves.AMNESIA]);
scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Play Dig2 and then Venom Drench sfx
doGarbageDig(scene);
})
.withOptionPhase(async (scene: BattleScene) => {
// Gain 2 Leftovers and 2 Shell Bell
transitionMysteryEncounterIntroVisuals(scene);
await tryApplyDigRewardItems(scene);
// Give the player the Black Sludge curse
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE));
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
// Investigate garbage, battle Gmax Garbodor
scene.setFieldScale(0.75);
await showEncounterText(scene, `${namespace}.option.2.selected_2`);
transitionMysteryEncounterIntroVisuals(scene);
const encounter = scene.currentBattle.mysteryEncounter;
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.TOXIC),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY],
move: new PokemonMove(Moves.AMNESIA),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
})
.build()
)
.build();
async function tryApplyDigRewardItems(scene: BattleScene) {
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
const leftovers = generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
const party = scene.getParty();
// Iterate over the party until an item was successfully given
// First leftovers
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers);
break;
}
}
// Second leftovers
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers);
break;
}
}
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), undefined, true);
// First Shell bell
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell);
break;
}
}
// Second Shell bell
for (const pokemon of party) {
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell);
break;
}
}
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), undefined, true);
}
async function doGarbageDig(scene: BattleScene) {
scene.playSound("PRSFX- Dig2");
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
scene.playSound("PRSFX- Dig2");
scene.playSound("PRSFX- Venom Drench", { volume: 2 });
});
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => {
scene.playSound("PRSFX- Dig2");
});
}

View File

@ -0,0 +1,541 @@
import { Type } from "#app/data/type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { HiddenAbilityRateBoosterModifier, PokemonBaseStatTotalModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { achvs } from "#app/system/achv";
import { speciesEggMoves } from "#app/data/egg-moves";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifierTypes } from "#app/modifier/modifier-type";
import { Stat } from "#app/data/pokemon-stat";
import i18next from "#app/plugins/i18n";
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
import { getLevelTotalExp } from "#app/data/exp";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:weirdDream";
/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, Urshifu, the Poison Chain trio, Ogerpon */
const excludedPokemon = [
Species.ETERNATUS,
/** UBs */
Species.NIHILEGO,
Species.BUZZWOLE,
Species.PHEROMOSA,
Species.XURKITREE,
Species.CELESTEELA,
Species.KARTANA,
Species.GUZZLORD,
Species.POIPOLE,
Species.NAGANADEL,
Species.STAKATAKA,
Species.BLACEPHALON,
/** Paradox */
Species.GREAT_TUSK,
Species.SCREAM_TAIL,
Species.BRUTE_BONNET,
Species.FLUTTER_MANE,
Species.SLITHER_WING,
Species.SANDY_SHOCKS,
Species.ROARING_MOON,
Species.WALKING_WAKE,
Species.GOUGING_FIRE,
Species.RAGING_BOLT,
Species.IRON_TREADS,
Species.IRON_BUNDLE,
Species.IRON_HANDS,
Species.IRON_JUGULIS,
Species.IRON_MOTH,
Species.IRON_THORNS,
Species.IRON_VALIANT,
Species.IRON_LEAVES,
Species.IRON_BOULDER,
Species.IRON_CROWN,
/** These are banned so they don't appear in the < 570 BST pool */
Species.COSMOG,
Species.MELTAN,
Species.KUBFU,
Species.COSMOEM,
Species.POIPOLE,
Species.TERAPAGOS,
Species.TYPE_NULL,
Species.CALYREX,
Species.NAGANADEL,
Species.URSHIFU,
Species.OGERPON,
Species.OKIDOGI,
Species.MUNKIDORI,
Species.FEZANDIPITI,
];
/**
* Weird Dream encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/137 | GitHub Issue #137}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const WeirdDreamEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([
{
spriteKey: "girawitch",
fileRoot: "mystery-encounters",
hasShadow: false,
y: 4
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
speaker: `${namespace}.speaker`,
text: `${namespace}.intro_dialogue`,
},
])
.withSceneWaveRangeRequirement(10, 180)
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
// Change the bgm
scene.fadeOutBgm(3000, false);
scene.time.delayedCall(3000, () => {
scene.playBgm("mystery_encounter_weird_dream");
});
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
}
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Play the animation as the player goes through the dialogue
scene.time.delayedCall(1000, () => {
doShowDreamBackground(scene);
});
// Calculate all the newly transformed Pokemon and begin asset load
const teamTransformations = getTeamTransformations(scene);
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
scene.currentBattle.mysteryEncounter.misc = {
teamTransformations,
loadAssets
};
})
.withOptionPhase(async (scene: BattleScene) => {
// Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue
const cutsceneDialoguePromise = showEncounterText(scene, `${namespace}.option.1.cutscene`);
// Change the entire player's party
// Wait for all new Pokemon assets to be loaded before showing transformation animations
await Promise.all(scene.currentBattle.mysteryEncounter.misc.loadAssets);
const transformations = scene.currentBattle.mysteryEncounter.misc.teamTransformations;
// If there are 1-3 transformations, do them centered back to back
// Otherwise, the first 3 transformations are executed side-by-side, then any remaining 1-3 transformations occur in those same respective positions
if (transformations.length <= 3) {
for (const transformation of transformations) {
const pokemon1 = transformation.previousPokemon;
const pokemon2 = transformation.newPokemon;
await doPokemonTransformationSequence(scene, pokemon1, pokemon2, TransformationScreenPosition.CENTER);
}
} else {
await doSideBySideTransformations(scene, transformations);
}
// Make sure player has finished cutscene dialogue
await cutsceneDialoguePromise;
doHideDreamBackground(scene);
await showEncounterText(scene, `${namespace}.option.1.dream_complete`);
await doNewTeamPostProcess(scene, transformations);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT]});
leaveEncounterWithoutBattle(scene, true);
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Reduce party levels by 20%
for (const pokemon of scene.getParty()) {
pokemon.level = Math.max(Math.ceil(0.8 * pokemon.level), 1);
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
pokemon.levelExp = 0;
pokemon.calculateStats();
pokemon.updateInfo();
}
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();
interface PokemonTransformation {
previousPokemon: PlayerPokemon;
newSpecies: PokemonSpecies;
newPokemon: PlayerPokemon;
heldItems: PokemonHeldItemModifier[];
}
function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
const party = scene.getParty();
// Removes all pokemon from the party
const alreadyUsedSpecies: PokemonSpecies[] = [];
const pokemonTransformations: PokemonTransformation[] = party.map(p => {
return {
previousPokemon: p
} as PokemonTransformation;
});
// Only 1 Pokemon can be transformed into BST higher than 600
let hasPokemonBstHigherThan600 = false;
// Only 1 other Pokemon can be transformed into BST between 570-600
let hasPokemonBstBetween570And600 = false;
// First, roll 2 of the party members to new Pokemon at a +90 to +110 BST difference
// Then, roll the remainder of the party members at a +40 to +50 BST difference
const numPokemon = party.length;
for (let i = 0; i < numPokemon; i++) {
const removed = party[randSeedInt(party.length)];
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
scene.removePokemonFromPlayerParty(removed, false);
const bst = getOriginalBst(scene, removed);
let newBstRange;
if (i < 2) {
newBstRange = [90, 110];
} else {
newBstRange = [40, 50];
}
const newSpecies = getTransformedSpecies(bst, newBstRange, hasPokemonBstHigherThan600, hasPokemonBstBetween570And600, alreadyUsedSpecies);
const newSpeciesBst = newSpecies.getBaseStatTotal();
if (newSpeciesBst > 600) {
hasPokemonBstHigherThan600 = true;
}
if (newSpeciesBst <= 600 && newSpeciesBst >= 570) {
hasPokemonBstBetween570And600 = true;
}
pokemonTransformations[index].newSpecies = newSpecies;
alreadyUsedSpecies.push(newSpecies);
}
for (const transformation of pokemonTransformations) {
const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount());
const newPlayerPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined);
transformation.newPokemon = newPlayerPokemon;
scene.getParty().push(newPlayerPokemon);
}
return pokemonTransformations;
}
async function doNewTeamPostProcess(scene: BattleScene, transformations: PokemonTransformation[]) {
let atLeastOneNewStarter = false;
for (const transformation of transformations) {
const previousPokemon = transformation.previousPokemon;
const newPokemon = transformation.newPokemon;
const speciesRootForm = newPokemon.species.getRootSpeciesId();
// Roll HA a second time
if (newPokemon.species.abilityHidden) {
const hiddenIndex = newPokemon.species.ability2 ? 2 : 1;
if (newPokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new IntegerHolder(256);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
if (hasHiddenAbility) {
newPokemon.abilityIndex = hiddenIndex;
}
}
}
// Roll IVs a second time
newPokemon.ivs = newPokemon.ivs.map(iv => {
const newValue = randSeedInt(31);
return newValue > iv ? newValue : iv;
});
// For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it
if (newPokemon.getSpeciesForm().getBaseStatTotal() <= 570 || newPokemon.isShiny()) {
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) {
scene.validateAchv(achvs.HIDDEN_ABILITY);
}
if (newPokemon.species.subLegendary) {
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
}
if (newPokemon.species.legendary) {
scene.validateAchv(achvs.CATCH_LEGENDARY);
}
if (newPokemon.species.mythical) {
scene.validateAchv(achvs.CATCH_MYTHICAL);
}
scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs);
const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false);
if (newStarterUnlocked) {
atLeastOneNewStarter = true;
queueEncounterMessage(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
}
}
// If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock)
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
});
// For pokemon that the player owns (including ones just caught), gain a candy
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
}
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move of the new species
newPokemon.moveset = previousPokemon.moveset;
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
const eggMoves = speciesEggMoves[speciesRootForm];
const eggMoveIndex = randSeedInt(4);
const randomEggMove = eggMoves[eggMoveIndex];
if (newPokemon.moveset.length < 4) {
newPokemon.moveset.push(new PokemonMove(randomEggMove));
} else {
newPokemon.moveset[randSeedInt(4)] = new PokemonMove(randomEggMove);
}
// For pokemon that the player owns (including ones just caught), unlock the egg move
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), eggMoveIndex, true);
}
}
// Randomize the second type of the pokemon
// If the pokemon does not normally have a second type, it will gain 1
const newTypes = [newPokemon.getTypes()[0]];
let newType = randSeedInt(18) as Type;
while (newType === newTypes[0]) {
newType = randSeedInt(18) as Type;
}
newTypes.push(newType);
if (!newPokemon.mysteryEncounterData) {
newPokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes);
} else {
newPokemon.mysteryEncounterData.types = newTypes;
}
for (const item of transformation.heldItems) {
item.pokemonId = newPokemon.id;
scene.addModifier(item, false, false, false, true);
}
// Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP, lowest of Atk/SpAtk, and lowest of Def/SpDef
if (newPokemon.getSpeciesForm().getBaseStatTotal() <= 450) {
const stats: Stat[] = [Stat.HP];
const baseStats = newPokemon.getSpeciesForm().baseStats.slice(0);
// Attack or SpAtk
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
// Def or SpDef
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
// const mod = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().newModifier(newPokemon, 20, stats);
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().generateType(scene.getParty(), [20, stats]);
const modifier = modType?.newModifier(newPokemon);
if (modifier) {
scene.addModifier(modifier);
}
}
// Enable passive if previous had it
newPokemon.passive = previousPokemon.passive;
newPokemon.calculateStats();
newPokemon.initBattleInfo();
}
// One random pokemon will get its passive unlocked
const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive);
if (passiveDisabledPokemon?.length > 0) {
passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)].passive = true;
}
// If at least one new starter was unlocked, play 1 fanfare
if (atLeastOneNewStarter) {
scene.playSound("level_up_fanfare");
}
}
function getOriginalBst(scene: BattleScene, pokemon: Pokemon) {
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
scene.applyModifiers(PokemonBaseStatTotalModifier, true, pokemon, baseStats);
if (pokemon.fusionSpecies) {
const fusionBaseStats = pokemon.getFusionSpeciesForm().baseStats;
for (let s = 0; s < pokemon.stats.length; s++) {
baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2);
}
} else if (scene.gameMode.isSplicedOnly) {
for (let s = 0; s < pokemon.stats.length; s++) {
baseStats[s] = Math.ceil(baseStats[s] / 2);
}
}
return baseStats.reduce((a, b) => a + b, 0);
}
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies {
let newSpecies: PokemonSpecies | undefined;
while (isNullOrUndefined(newSpecies)) {
const bstCap = originalBst + bstSearchRange[1];
const bstMin = Math.max(originalBst + bstSearchRange[0], 0);
// Get any/all species that fall within the Bst range requirements
let validSpecies = allSpecies
.filter(s => {
const speciesBst = s.getBaseStatTotal();
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
// Checks that a Pokemon has not already been added in the +600 or 570-600 slots;
const validBst = (!hasPokemonBstBetween570And600 || (speciesBst < 570 || speciesBst > 600)) &&
(!hasPokemonBstHigherThan600 || speciesBst <= 600);
return bstInRange && validBst && !excludedPokemon.includes(s.speciesId);
});
// There must be at least 20 species available before it will choose one
if (validSpecies?.length > 20) {
validSpecies = randSeedShuffle(validSpecies);
newSpecies = validSpecies.pop();
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) {
newSpecies = validSpecies.pop();
}
} else {
// Expands search rand until a Pokemon is found
bstSearchRange[0] -= 10;
bstSearchRange[1] += 10;
}
}
return newSpecies!;
}
function doShowDreamBackground(scene: BattleScene) {
const transformationContainer = scene.add.container(0, -scene.game.canvas.height / 6);
transformationContainer.name = "Dream Background";
// In case it takes a bit for video to load
const transformationStaticBg = scene.add.rectangle(0, 0, scene.game.canvas.width / 6, scene.game.canvas.height / 6, 0);
transformationStaticBg.setName("Black Background");
transformationStaticBg.setOrigin(0, 0);
transformationContainer.add(transformationStaticBg);
transformationStaticBg.setVisible(true);
const transformationVideoBg: Phaser.GameObjects.Video = scene.add.video(0, 0, "evo_bg").stop();
transformationVideoBg.setLoop(true);
transformationVideoBg.setOrigin(0, 0);
transformationVideoBg.setScale(0.4359673025);
transformationContainer.add(transformationVideoBg);
scene.fieldUI.add(transformationContainer);
scene.fieldUI.bringToTop(transformationContainer);
transformationVideoBg.play();
transformationContainer.setVisible(true);
transformationContainer.alpha = 0;
scene.tweens.add({
targets: transformationContainer,
alpha: 1,
duration: 3000,
ease: "Sine.easeInOut"
});
}
function doHideDreamBackground(scene: BattleScene) {
const transformationContainer = scene.fieldUI.getByName("Dream Background");
scene.tweens.add({
targets: transformationContainer,
alpha: 0,
duration: 3000,
ease: "Sine.easeInOut",
onComplete: () => {
scene.fieldUI.remove(transformationContainer, true);
}
});
}
function doSideBySideTransformations(scene: BattleScene, transformations: PokemonTransformation[]) {
return new Promise<void>(resolve => {
const allTransformationPromises: Promise<void>[] = [];
for (let i = 0; i < 3; i++) {
const delay = i * 4000;
scene.time.delayedCall(delay, () => {
const transformation = transformations[i];
const pokemon1 = transformation.previousPokemon;
const pokemon2 = transformation.newPokemon;
const screenPosition = i as TransformationScreenPosition;
const transformationPromise = doPokemonTransformationSequence(scene, pokemon1, pokemon2, screenPosition)
.then(() => {
if (transformations.length > i + 3) {
const nextTransformationAtPosition = transformations[i + 3];
const nextPokemon1 = nextTransformationAtPosition.previousPokemon;
const nextPokemon2 = nextTransformationAtPosition.newPokemon;
allTransformationPromises.push(doPokemonTransformationSequence(scene, nextPokemon1, nextPokemon2, screenPosition));
}
});
allTransformationPromises.push(transformationPromise);
});
}
// Wait for all transformations to be loaded into promise array
const id = setInterval(checkAllPromisesExist, 500);
async function checkAllPromisesExist() {
if (allTransformationPromises.length === transformations.length) {
clearInterval(id);
await Promise.all(allTransformationPromises);
resolve();
}
}
});
}

View File

@ -0,0 +1,16 @@
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters";
import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export class MysteryEncounterData {
encounteredEvents: [MysteryEncounterType, MysteryEncounterTier][] = [];
encounterSpawnChance: number = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
nextEncounterQueue: [MysteryEncounterType, integer][] = [];
constructor(flags: MysteryEncounterData | null) {
if (!isNullOrUndefined(flags)) {
Object.assign(this, flags);
}
}
}

View File

@ -0,0 +1,74 @@
import { TextStyle } from "#app/ui/text";
export class TextDisplay {
speaker?: string;
text: string;
style?: TextStyle;
}
export class OptionTextDisplay {
buttonLabel: string;
buttonTooltip?: string;
disabledButtonLabel?: string;
disabledButtonTooltip?: string;
secondOptionPrompt?: string;
selected?: TextDisplay[];
style?: TextStyle;
}
export class EncounterOptionsDialogue {
title?: string;
description?: string;
query?: string;
options?: [...OptionTextDisplay[]]; // Options array with minimum 2 options
}
/**
* Example MysteryEncounterDialogue object:
*
{
intro: [
{
text: "this is a rendered as a message window (no title display)"
},
{
speaker: "John"
text: "this is a rendered as a dialogue window (title "John" is displayed above text)"
}
],
encounterOptionsDialogue: {
title: "This is the title displayed at top of encounter description box",
description: "This is the description in the middle of encounter description box",
query: "This is an optional question displayed at the bottom of the description box (keep it short)",
options: [
{
buttonLabel: "Option #1 button label (keep these short)",
selected: [ // Optional dialogue windows displayed when specific option is selected and before functional logic for the option is executed
{
text: "You chose option #1 message"
},
{
speaker: "John"
text: "So, you've chosen option #1! It's time to d-d-d-duel!"
}
]
},
{
buttonLabel: "Option #2"
}
],
},
outro: [
{
text: "This message will be displayed at the very end of the encounter (i.e. post battle, post reward, etc.)"
}
],
}
*
*/
export default class MysteryEncounterDialogue {
intro?: TextDisplay[];
encounterOptionsDialogue?: EncounterOptionsDialogue;
outro?: TextDisplay[];
}

View File

@ -0,0 +1,260 @@
import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue";
import { Moves } from "#app/enums/moves";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils";
import { Type } from "../type";
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>;
/**
* Used by {@link MysteryEncounterOptionBuilder} class to define required/optional properties on the {@link MysteryEncounterOption} class when building.
*
* Should ONLY contain properties that are necessary for {@link MysteryEncounterOption} construction.
* Post-construct and flag data properties are defined in the {@link MysteryEncounterOption} class itself.
*/
export interface IMysteryEncounterOption {
optionMode: MysteryEncounterOptionMode;
hasDexProgress: boolean;
requirements: EncounterSceneRequirement[];
primaryPokemonRequirements: EncounterPokemonRequirement[];
secondaryPokemonRequirements: EncounterPokemonRequirement[];
excludePrimaryFromSecondaryRequirements: boolean;
dialogue?: OptionTextDisplay;
onPreOptionPhase?: OptionPhaseCallback;
onOptionPhase: OptionPhaseCallback;
onPostOptionPhase?: OptionPhaseCallback;
}
export default class MysteryEncounterOption implements IMysteryEncounterOption {
optionMode: MysteryEncounterOptionMode;
hasDexProgress: boolean;
requirements: EncounterSceneRequirement[];
primaryPokemonRequirements: EncounterPokemonRequirement[];
secondaryPokemonRequirements: EncounterPokemonRequirement[];
primaryPokemon?: PlayerPokemon;
secondaryPokemon?: PlayerPokemon[];
excludePrimaryFromSecondaryRequirements: boolean;
/**
* Dialogue object containing all the dialogue, messages, tooltips, etc. for this option
* Will be populated on MysteryEncounter initialization
*/
dialogue?: OptionTextDisplay;
/** Executes before any following dialogue or business logic from option. Usually this will be for calculating dialogueTokens or performing scene/data updates */
onPreOptionPhase?: OptionPhaseCallback;
/** Business logic function for option */
onOptionPhase: OptionPhaseCallback;
/** Executes after the encounter is over. Usually this will be for calculating dialogueTokens or performing data updates */
onPostOptionPhase?: OptionPhaseCallback;
constructor(option: IMysteryEncounterOption | null) {
if (!isNullOrUndefined(option)) {
Object.assign(this, option);
}
this.hasDexProgress = this.hasDexProgress ?? false;
this.requirements = this.requirements ?? [];
this.primaryPokemonRequirements = this.primaryPokemonRequirements ?? [];
this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ?? [];
}
hasRequirements() {
return this.requirements.length > 0 || this.primaryPokemonRequirements.length > 0 || this.secondaryPokemonRequirements.length > 0;
}
meetsRequirements(scene: BattleScene) {
return !this.requirements.some(requirement => !requirement.meetsRequirement(scene)) &&
this.meetsSupportingRequirementAndSupportingPokemonSelected(scene) &&
this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
}
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon) {
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
}
meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene) {
if (!this.primaryPokemonRequirements) {
return true;
}
let qualified: PlayerPokemon[] = scene.getParty();
for (const req of this.primaryPokemonRequirements) {
if (req.meetsRequirement(scene)) {
if (req instanceof EncounterPokemonRequirement) {
const queryParty = req.queryParty(scene.getParty());
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
}
} else {
this.primaryPokemon = undefined;
return false;
}
}
if (qualified.length === 0) {
return false;
}
if (this.excludePrimaryFromSecondaryRequirements && this.secondaryPokemon) {
const truePrimaryPool: PlayerPokemon[] = [];
const overlap: PlayerPokemon[] = [];
for (const qp of qualified) {
if (!this.secondaryPokemon.includes(qp)) {
truePrimaryPool.push(qp);
} else {
overlap.push(qp);
}
}
if (truePrimaryPool.length > 0) {
// always choose from the non-overlapping pokemon first
this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
return true;
} else {
// if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the supporting pokemon pool
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
// is this working?
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)];
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
return true;
}
console.log("Mystery Encounter Edge Case: Requirement not met due to primay pokemon overlapping with support pokemon. There's no valid primary pokemon left.");
return false;
}
} else {
// Just pick the first qualifying Pokemon
this.primaryPokemon = qualified[0];
return true;
}
}
meetsSupportingRequirementAndSupportingPokemonSelected(scene: BattleScene) {
if (!this.secondaryPokemonRequirements) {
this.secondaryPokemon = [];
return true;
}
let qualified: PlayerPokemon[] = scene.getParty();
for (const req of this.secondaryPokemonRequirements) {
if (req.meetsRequirement(scene)) {
if (req instanceof EncounterPokemonRequirement) {
const queryParty = req.queryParty(scene.getParty());
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
}
} else {
this.secondaryPokemon = [];
return false;
}
}
this.secondaryPokemon = qualified;
return true;
}
}
export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterOption> {
optionMode: MysteryEncounterOptionMode = MysteryEncounterOptionMode.DEFAULT;
requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
excludePrimaryFromSecondaryRequirements: boolean = false;
isDisabledOnRequirementsNotMet: boolean = true;
hasDexProgress: boolean = false;
dialogue?: OptionTextDisplay;
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> {
return Object.assign(new MysteryEncounterOptionBuilder(), { optionMode });
}
withHasDexProgress(hasDexProgress: boolean): this & Required<Pick<IMysteryEncounterOption, "hasDexProgress">> {
return Object.assign(this, { hasDexProgress: hasDexProgress });
}
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement.");
}
this.requirements.push(requirement);
return Object.assign(this, { requirements: this.requirements });
}
withSceneMoneyRequirement(requiredMoney?: number, scalingMultiplier?: number) {
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
}
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
return Object.assign(this, { onPreOptionPhase: onPreOptionPhase });
}
withOptionPhase(onOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onOptionPhase">> {
return Object.assign(this, { onOptionPhase: onOptionPhase });
}
withPostOptionPhase(onPostOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPostOptionPhase">> {
return Object.assign(this, { onPostOptionPhase: onPostOptionPhase });
}
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.primaryPokemonRequirements.push(requirement);
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
}
/**
* Player is required to have certain type/s of pokemon in his party (with optional min number of pokemons with that type)
*
* @param type the required type/s
* @param excludeFainted whether to exclude fainted pokemon
* @param minNumberOfPokemon number of pokemons to have that type
* @param invertQuery
* @returns
*/
withPokemonTypeRequirement(type: Type | Type[], excludeFainted?: boolean, minNumberOfPokemon?: number, invertQuery?: boolean) {
return this.withPrimaryPokemonRequirement(new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery));
}
/**
* Player is required to have a pokemon that can learn a certain move/moveset
*
* @param move the required move/moves
* @param options see {@linkcode CanLearnMoveRequirementOptions}
* @returns
*/
withPokemonCanLearnMoveRequirement(move: Moves | Moves[], options?: CanLearnMoveRequirementOptions) {
return this.withPrimaryPokemonRequirement(new CanLearnMoveRequirement(move, options));
}
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.secondaryPokemonRequirements.push(requirement);
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements });
}
/**
* Se the full dialogue object to the option. Will override anything already set
*
* @param dialogue see {@linkcode OptionTextDisplay}
* @returns
*/
withDialogue(dialogue: OptionTextDisplay) {
this.dialogue = dialogue;
return this;
}
build(this: IMysteryEncounterOption) {
return new MysteryEncounterOption(this);
}
}

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