Merge pull request #139 from AsdarDevelops/part-timer
Part-Timer and Dancing Lessons Encounters
This commit is contained in:
commit
741137d74b
|
@ -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
|
||||
}
|
|
@ -66,7 +66,7 @@ import { Species } from "#enums/species";
|
|||
import { UiTheme } from "#enums/ui-theme";
|
||||
import { TimedEventManager } from "#app/timed-event-manager.js";
|
||||
import i18next from "i18next";
|
||||
import {TrainerType} from "#enums/trainer-type";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import IMysteryEncounter 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";
|
||||
|
|
|
@ -7,6 +7,7 @@ import { BattlerIndex } from "../battle";
|
|||
import { Element } from "json-stable-stringify";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { isNullOrUndefined } from "../utils";
|
||||
import Phaser from "phaser";
|
||||
//import fs from 'vite-plugin-fs/browser';
|
||||
|
||||
export enum AnimFrameTarget {
|
||||
|
@ -111,7 +112,8 @@ export enum CommonAnim {
|
|||
export enum EncounterAnim {
|
||||
MAGMA_BG,
|
||||
MAGMA_SPOUT,
|
||||
SMOKESCREEN
|
||||
SMOKESCREEN,
|
||||
DANCE
|
||||
}
|
||||
|
||||
export class AnimConfig {
|
||||
|
@ -1264,11 +1266,13 @@ export class MoveChargeAnim extends MoveAnim {
|
|||
|
||||
export class EncounterBattleAnim extends BattleAnim {
|
||||
public encounterAnim: EncounterAnim;
|
||||
public oppAnim: boolean;
|
||||
|
||||
constructor(encounterAnim: EncounterAnim, user: Pokemon, target?: Pokemon) {
|
||||
super(user, target || user);
|
||||
constructor(encounterAnim: EncounterAnim, user: Pokemon, target?: Pokemon, oppAnim?: boolean) {
|
||||
super(user, target || user, true);
|
||||
|
||||
this.encounterAnim = encounterAnim;
|
||||
this.oppAnim = oppAnim ?? false;
|
||||
}
|
||||
|
||||
getAnim(): AnimConfig {
|
||||
|
@ -1276,7 +1280,7 @@ export class EncounterBattleAnim extends BattleAnim {
|
|||
}
|
||||
|
||||
isOppAnim(): boolean {
|
||||
return false;
|
||||
return this.oppAnim;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -254,13 +254,12 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
|
|||
};
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true }, null, givePartyPokemonReviverSeeds);
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY],
|
||||
move: new PokemonMove(Moves.STUFF_CHEEKS),
|
||||
ignorePp: true
|
||||
});
|
||||
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]);
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
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 IMysteryEncounter, { 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 { LearnMovePhase, StatChangePhase } from "#app/phases";
|
||||
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";
|
||||
|
||||
/** 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: IMysteryEncounter =
|
||||
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.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(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(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(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(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(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(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 => 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`);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, null, 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();
|
|
@ -0,0 +1,333 @@
|
|||
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 IMysteryEncounter, { 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: IMysteryEncounter =
|
||||
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(new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(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 => {
|
||||
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`);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, null, 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(new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(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 => {
|
||||
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`);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, null, 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(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(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 => {
|
||||
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");
|
||||
});
|
||||
}
|
|
@ -22,6 +22,8 @@ import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/
|
|||
import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter";
|
||||
import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/berries-abound-encounter";
|
||||
import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter";
|
||||
import { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter";
|
||||
import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter";
|
||||
|
||||
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
||||
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
||||
|
@ -135,7 +137,8 @@ export const allMysteryEncounters: { [encounterType: number]: IMysteryEncounter
|
|||
const extremeBiomeEncounters: MysteryEncounterType[] = [];
|
||||
|
||||
const nonExtremeBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.FIELD_TRIP
|
||||
MysteryEncounterType.FIELD_TRIP,
|
||||
MysteryEncounterType.DANCING_LESSONS, // Is also in BADLANDS, DESERT, VOLCANO, WASTELAND, ABYSS
|
||||
];
|
||||
|
||||
const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
|
||||
|
@ -146,7 +149,8 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
|
|||
];
|
||||
|
||||
const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.DEPARTMENT_STORE_SALE
|
||||
MysteryEncounterType.DEPARTMENT_STORE_SALE,
|
||||
MysteryEncounterType.PART_TIMER
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -200,23 +204,32 @@ export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
|
|||
[Biome.LAKE, []],
|
||||
[Biome.SEABED, []],
|
||||
[Biome.MOUNTAIN, []],
|
||||
[Biome.BADLANDS, []],
|
||||
[Biome.BADLANDS, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.CAVE, [
|
||||
MysteryEncounterType.THE_STRONG_STUFF
|
||||
]],
|
||||
[Biome.DESERT, []],
|
||||
[Biome.DESERT, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.ICE_CAVE, []],
|
||||
[Biome.MEADOW, []],
|
||||
[Biome.POWER_PLANT, []],
|
||||
[Biome.VOLCANO, [
|
||||
MysteryEncounterType.FIERY_FALLOUT
|
||||
MysteryEncounterType.FIERY_FALLOUT,
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.GRAVEYARD, []],
|
||||
[Biome.DOJO, []],
|
||||
[Biome.FACTORY, []],
|
||||
[Biome.RUINS, []],
|
||||
[Biome.WASTELAND, []],
|
||||
[Biome.ABYSS, []],
|
||||
[Biome.WASTELAND, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.ABYSS, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.SPACE, []],
|
||||
[Biome.CONSTRUCTION_SITE, []],
|
||||
[Biome.JUNGLE, [
|
||||
|
@ -252,6 +265,8 @@ export function initMysteryEncounters() {
|
|||
allMysteryEncounters[MysteryEncounterType.TRASH_TO_TREASURE] = TrashToTreasureEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.BERRIES_ABOUND] = BerriesAboundEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.CLOWNING_AROUND] = ClowningAroundEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.PART_TIMER] = PartTimerEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.DANCING_LESSONS] = DancingLessonsEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
|
|
|
@ -10,6 +10,36 @@ export const STEALING_MOVES = [
|
|||
Moves.SWITCHEROO
|
||||
];
|
||||
|
||||
export const CHARMING_MOVES = [
|
||||
Moves.CHARM,
|
||||
Moves.FLATTER,
|
||||
Moves.DRAGON_CHEER,
|
||||
Moves.ALLURING_VOICE,
|
||||
Moves.ATTRACT,
|
||||
Moves.SWEET_SCENT,
|
||||
Moves.CAPTIVATE,
|
||||
Moves.AROMATIC_MIST
|
||||
];
|
||||
|
||||
/**
|
||||
* Moves for the Dancer ability
|
||||
*/
|
||||
export const DANCING_MOVES = [
|
||||
Moves.AQUA_STEP,
|
||||
Moves.CLANGOROUS_SOUL,
|
||||
Moves.DRAGON_DANCE,
|
||||
Moves.FEATHER_DANCE,
|
||||
Moves.FIERY_DANCE,
|
||||
Moves.LUNAR_DANCE,
|
||||
Moves.PETAL_DANCE,
|
||||
Moves.REVELATION_DANCE,
|
||||
Moves.QUIVER_DANCE,
|
||||
Moves.SWORDS_DANCE,
|
||||
Moves.TEETER_DANCE,
|
||||
Moves.VICTORY_DANCE,
|
||||
Moves.KNOCK_OFF
|
||||
];
|
||||
|
||||
export const DISTRACTION_MOVES = [
|
||||
Moves.FAKE_OUT,
|
||||
Moves.FOLLOW_ME,
|
||||
|
|
|
@ -423,7 +423,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
|||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
scene.ui.showText("Return to encounter option select.");
|
||||
scene.ui.showText(i18next.t("mysteryEncounter:cancel_option"));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -671,18 +671,22 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
|
|||
export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide: boolean = true, destroy: boolean = true, duration: number = 750): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const introVisuals = scene.currentBattle.mysteryEncounter.introVisuals;
|
||||
const enemyPokemon = scene.getEnemyField();
|
||||
if (enemyPokemon) {
|
||||
scene.currentBattle.enemyParty = [];
|
||||
}
|
||||
if (introVisuals) {
|
||||
if (!hide) {
|
||||
// Make sure visuals are in proper state for showing
|
||||
introVisuals.setVisible(true);
|
||||
introVisuals.x += 16;
|
||||
introVisuals.y -= 16;
|
||||
introVisuals.x = 244;
|
||||
introVisuals.y = 60;
|
||||
introVisuals.alpha = 0;
|
||||
}
|
||||
|
||||
// Transition
|
||||
scene.tweens.add({
|
||||
targets: introVisuals,
|
||||
targets: [introVisuals, enemyPokemon],
|
||||
x: `${hide? "+" : "-"}=16`,
|
||||
y: `${hide ? "-" : "+"}=16`,
|
||||
alpha: hide ? 0 : 1,
|
||||
|
@ -690,9 +694,12 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide:
|
|||
duration,
|
||||
onComplete: () => {
|
||||
if (hide && destroy) {
|
||||
scene.field.remove(introVisuals);
|
||||
introVisuals.setVisible(false);
|
||||
introVisuals.destroy();
|
||||
scene.field.remove(introVisuals, true);
|
||||
|
||||
enemyPokemon.forEach(pokemon => {
|
||||
scene.field.remove(pokemon, true);
|
||||
});
|
||||
|
||||
scene.currentBattle.mysteryEncounter.introVisuals = null;
|
||||
}
|
||||
resolve(true);
|
||||
|
|
|
@ -19,5 +19,7 @@ export enum MysteryEncounterType {
|
|||
A_TRAINERS_TEST,
|
||||
TRASH_TO_TREASURE,
|
||||
BERRIES_ABOUND,
|
||||
CLOWNING_AROUND
|
||||
CLOWNING_AROUND,
|
||||
PART_TIMER,
|
||||
DANCING_LESSONS
|
||||
}
|
||||
|
|
|
@ -19,19 +19,23 @@ import { aTrainersTestDialogue } from "#app/locales/en/mystery-encounters/a-trai
|
|||
import { trashToTreasureDialogue } from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue";
|
||||
import { berriesAboundDialogue } from "#app/locales/en/mystery-encounters/berries-abound-dialogue";
|
||||
import { clowningAroundDialogue } from "#app/locales/en/mystery-encounters/clowning-around-dialogue";
|
||||
import { partTimerDialogue } from "#app/locales/en/mystery-encounters/part-timer-dialogue";
|
||||
import { dancingLessonsDialogue } from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue";
|
||||
|
||||
/**
|
||||
* Patterns that can be used:
|
||||
* '$' will be treated as a new line for Message and Dialogue strings
|
||||
* '@d{<number>}' will add a time delay to text animation for Message and Dialogue strings
|
||||
* Injection patterns that can be used:
|
||||
* - `$` will be treated as a new line for Message and Dialogue strings.
|
||||
* - `@d{<number>}` will add a time delay to text animation for Message and Dialogue strings.
|
||||
* - `@s{<sound_effect_key>}` will play a specified sound effect for Message and Dialogue strings.
|
||||
* - `@f{<number>}` will fade the screen to black for the given duration, then fade back in for Message and Dialogue strings.
|
||||
* - `{{<token>}}` will auto-inject the matching dialogue token value that is stored in {@link IMysteryEncounter.dialogueTokens}.
|
||||
* - (see [i18next interpolations](https://www.i18next.com/translation-function/interpolation)) for more details.
|
||||
* - `@[<TextStyle>]{<text>}` will auto-color the given text to a specified {@link TextStyle} (e.g. `TextStyle.SUMMARY_GREEN`).
|
||||
*
|
||||
* '{{<token>}}' will auto-inject the matching token value for the specified Encounter that is stored in dialogueTokens
|
||||
* (see [i18next interpolations](https://www.i18next.com/translation-function/interpolation))
|
||||
*
|
||||
* '@[<TextStyle>]{<text>}' will auto-color the given text to a specified TextStyle (e.g. TextStyle.SUMMARY_GREEN)
|
||||
*
|
||||
* Any '(+)' or '(-)' type of tooltip will auto-color to green/blue respectively. THIS ONLY OCCURS FOR OPTION TOOLTIPS, NOWHERE ELSE
|
||||
* Other types of '(...)' tooltips will have to specify the text color manually by using '@[SUMMARY_GREEN]{<text>}' pattern
|
||||
* For Option tooltips ({@link OptionTextDisplay.buttonTooltip}):
|
||||
* - Any tooltip that starts with `(+)` or `(-)` at the beginning of a newline will auto-color to green/blue respectively.
|
||||
* - Note, this only occurs for option tooltips, nowhere else.
|
||||
* - Other types of `(...)` tooltips will have to specify the text color manually by using the `@[SUMMARY_GREEN]{<text>}` pattern.
|
||||
*/
|
||||
export const mysteryEncounter = {
|
||||
// DO NOT REMOVE
|
||||
|
@ -41,6 +45,7 @@ export const mysteryEncounter = {
|
|||
"paid_money": "You paid ₽{{amount, number}}.",
|
||||
"receive_money": "You received ₽{{amount, number}}!",
|
||||
"affects_pokedex": "Affects Pokédex Data",
|
||||
"cancel_option": "Return to encounter option select.",
|
||||
|
||||
mysteriousChallengers: mysteriousChallengersDialogue,
|
||||
mysteriousChest: mysteriousChestDialogue,
|
||||
|
@ -62,5 +67,7 @@ export const mysteryEncounter = {
|
|||
aTrainersTest: aTrainersTestDialogue,
|
||||
trashToTreasure: trashToTreasureDialogue,
|
||||
berriesAbound: berriesAboundDialogue,
|
||||
clowningAround: clowningAroundDialogue
|
||||
clowningAround: clowningAroundDialogue,
|
||||
partTimer: partTimerDialogue,
|
||||
dancingLessons: dancingLessonsDialogue
|
||||
} as const;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export const absoluteAvariceDialogue = {
|
||||
intro: "A Greedent ambushed you\nand stole your party's berries!",
|
||||
intro: "A Greedent ambushes you\nand steals your party's berries!",
|
||||
title: "Absolute Avarice",
|
||||
description: "The Greedent has caught you totally off guard now all your berries are gone!\n\nThe Greedent looks like it's about to eat them when it pauses to look at you, interested.",
|
||||
query: "What will you do?",
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
export const dancingLessonsDialogue = {
|
||||
intro: "An Oricorio dances sadly alone, without a partner.",
|
||||
title: "Dancing Lessons",
|
||||
description: "The Oricorio doesn't seem aggressive, if anything it seems sad.\n\nMaybe it just wants someone to dance with...",
|
||||
query: "What will you do?",
|
||||
option: {
|
||||
1: {
|
||||
label: "Battle It",
|
||||
tooltip: "(-) Tough Battle\n(+) Gain a Baton",
|
||||
selected: "The Oricorio is distraught and moves to defend itself!",
|
||||
boss_enraged: "The Oricorio's fear boosted its stats!"
|
||||
},
|
||||
2: {
|
||||
label: "Learn Its Dance",
|
||||
tooltip: "(+) Teach a Pokémon Revelation Dance",
|
||||
selected: `You watch the Oricorio closely as it performs its dance...
|
||||
$@s{level_up_fanfare}Your {{selectedPokemon}} wants to learn Revelation Dance!`,
|
||||
},
|
||||
3: {
|
||||
label: "Show It a Dance",
|
||||
tooltip: "(-) Teach the Oricorio a Dance Move\n(+) The Oricorio Will Like You",
|
||||
disabled_tooltip: "Your Pokémon need to know a Dance move for this.",
|
||||
select_prompt: "Select a Dance type move to use.",
|
||||
selected: `The Oricorio watches in fascination as\n{{selectedPokemon}} shows off {{selectedMove}}!
|
||||
$It loves the display!
|
||||
$@s{level_up_fanfare}The Oricorio wants to join your party!`,
|
||||
},
|
||||
},
|
||||
invalid_selection: "This Pokémon doesn't know a Dance move"
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
export const partTimerDialogue = {
|
||||
intro: "A busy worker flags you down.",
|
||||
speaker: "Worker",
|
||||
intro_dialogue: `You look like someone with lots of capable Pokémon!
|
||||
$We can pay you if you're able to help us with some part-time work!`,
|
||||
title: "Part-Timer",
|
||||
description: "Looks like there are plenty of tasks that need to be done. Depending how well-suited your Pokémon is to a task, they might earn more or less money.",
|
||||
query: "Which job will you choose?",
|
||||
invalid_selection: "Pokémon must be healthy enough.",
|
||||
option: {
|
||||
1: {
|
||||
label: "Make Deliveries",
|
||||
tooltip: "(-) Your Pokémon Uses its Speed\n(+) Earn @[MONEY]{Money}",
|
||||
selected: "Your {{selectedPokemon}} works a shift delivering orders to customers.",
|
||||
},
|
||||
2: {
|
||||
label: "Warehouse Work",
|
||||
tooltip: "(-) Your Pokémon Uses its Strength and Endurance\n(+) Earn @[MONEY]{Money}",
|
||||
selected: "Your {{selectedPokemon}} works a shift moving items around the warehouse.",
|
||||
},
|
||||
3: {
|
||||
label: "Sales Assistant",
|
||||
tooltip: "(-) Your {{option3PrimaryName}} uses {{option3PrimaryMove}}\n(+) Earn @[MONEY]{Money}",
|
||||
disabled_tooltip: "Your Pokémon need to know certain moves for this job",
|
||||
selected: "Your {{option3PrimaryName}} spends the day using {{option3PrimaryMove}} to attract customers to the business!",
|
||||
},
|
||||
},
|
||||
job_complete_good: `Thanks for the assistance!\nYour {{selectedPokemon}} was incredibly helpful!
|
||||
$Here's your check for the day.`,
|
||||
job_complete_bad: `Your {{selectedPokemon}} helped us out a bit!
|
||||
$Here's your check for the day.`,
|
||||
pokemon_tired: "Your {{selectedPokemon}} is worn out!\nThe PP of all its moves was reduced to 2!",
|
||||
outro: "Come back and help out again sometime!"
|
||||
};
|
|
@ -1,11 +1,12 @@
|
|||
import BattleScene, { bypassLogin } from "./battle-scene";
|
||||
import { DamageResult, default as Pokemon, EnemyPokemon, FieldPosition, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "./field/pokemon";
|
||||
import * as Utils from "./utils";
|
||||
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr } from "./data/move";
|
||||
import { isNullOrUndefined } from "./utils";
|
||||
import { allMoves, applyFilteredMoveAttrs, applyMoveAttrs, AttackMove, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, FixedDamageAttr, ForceSwitchOutAttr, getMoveTargets, HealStatusEffectAttr, HitsTagAttr, IncrementMovePriorityAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveEffectTrigger, MoveFlags, MoveTarget, MoveTargetSet, MultiHitAttr, NoEffectAttr, OverrideMoveEffectAttr, PostVictoryStatChangeAttr, PreMoveMessageAttr, SelfStatusMove, VariableTargetAttr } from "./data/move";
|
||||
import { Mode } from "./ui/ui";
|
||||
import { Command } from "./ui/command-ui-handler";
|
||||
import { Stat } from "./data/pokemon-stat";
|
||||
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier, PokemonResetNegativeStatStageModifier } from "./modifier/modifier";
|
||||
import { BerryModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, IvScannerModifier, LapsingPersistentModifier, LapsingPokemonHeldItemModifier, MapModifier, Modifier, MoneyInterestModifier, MoneyMultiplierModifier, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, PokemonMultiHitModifier, PokemonResetNegativeStatStageModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier } from "./modifier/modifier";
|
||||
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
|
||||
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
|
||||
import { CommonAnim, CommonBattleAnim, initEncounterAnims, initMoveAnim, loadEncounterAnimAssets, loadMoveAnimAssets, MoveAnim } from "./data/battle-anims";
|
||||
|
@ -23,10 +24,12 @@ import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, MysteryEncounterP
|
|||
import { getPokemonMessage, getPokemonNameWithAffix } from "./messages";
|
||||
import { Starter } from "./ui/starter-select-ui-handler";
|
||||
import { Gender } from "./data/gender";
|
||||
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
|
||||
import { getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage, Weather, WeatherType } from "./data/weather";
|
||||
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
|
||||
import { CheckTrappedAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability";
|
||||
import { Unlockables, getUnlockableName } from "./system/unlockables";
|
||||
import {
|
||||
AddSecondStrikeAbAttr, AlwaysHitAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostFaintAbAttrs, applyPostKnockOutAbAttrs, applyPostMoveUsedAbAttrs, applyPostStatChangeAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostVictoryAbAttrs, applyPostWeatherLapseAbAttrs, applyPreAttackAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, BlockRedirectAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, CheckTrappedAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, IncreasePpAbAttr, IncrementMovePriorityAbAttr, MaxMultiHitAbAttr, PokemonTypeChangeAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostBiomeChangeAbAttr, PostDefendAbAttr, PostFaintAbAttr, PostKnockOutAbAttr, PostMoveUsedAbAttr, PostStatChangeAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostVictoryAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreventBerryUseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeCopyAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr
|
||||
} from "./data/ability";
|
||||
import { getUnlockableName, Unlockables } from "./system/unlockables";
|
||||
import { getBiomeKey } from "./field/arena";
|
||||
import { BattlerIndex, BattleType, TurnCommand } from "./battle";
|
||||
import { achvs, ChallengeAchv, HealAchv, LevelAchv } from "./system/achv";
|
||||
|
@ -48,7 +51,7 @@ import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run";
|
|||
import { GameMode, GameModes, getGameMode } from "./game-mode";
|
||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./data/pokemon-species";
|
||||
import i18next from "./plugins/i18n";
|
||||
import { TextStyle, addTextObject } from "./ui/text";
|
||||
import { addTextObject, TextStyle } from "./ui/text";
|
||||
import { Type } from "./data/type";
|
||||
import { BerryUsedEvent, EncounterPhaseEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./events/battle-scene";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
|
@ -69,7 +72,6 @@ import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifie
|
|||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import Overrides from "#app/overrides";
|
||||
import { isNullOrUndefined } from "./utils";
|
||||
|
||||
const { t } = i18next;
|
||||
|
||||
|
@ -949,8 +951,7 @@ export class EncounterPhase extends BattlePhase {
|
|||
enemyPokemon.setVisible(false);
|
||||
this.scene.currentBattle.trainer.tint(0, 0.5);
|
||||
} else if (battle.battleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||
enemyPokemon.setVisible(false);
|
||||
this.scene.currentBattle?.trainer?.tint(0, 0.5);
|
||||
// TODO: this may not be necessary, but leaving as placeholder
|
||||
}
|
||||
if (battle.double) {
|
||||
enemyPokemon.setFieldPosition(e ? FieldPosition.RIGHT : FieldPosition.LEFT);
|
||||
|
|
|
@ -18,7 +18,7 @@ import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
|
|||
* @param secondaryOptionSelect -
|
||||
* @param isBattle - if selecting option should lead to battle, set to true
|
||||
*/
|
||||
export async function runMysteryEncounterToEnd(game: GameManager, optionNo: number, secondaryOptionSelect: { pokemonNo: number, optionNo: number } = null, isBattle: boolean = false) {
|
||||
export async function runMysteryEncounterToEnd(game: GameManager, optionNo: number, secondaryOptionSelect: { pokemonNo: number, optionNo?: number } = null, isBattle: boolean = false) {
|
||||
vi.spyOn(EncounterPhaseUtils, "selectPokemonForOption");
|
||||
await runSelectMysteryEncounterOption(game, optionNo, secondaryOptionSelect);
|
||||
|
||||
|
@ -65,7 +65,7 @@ export async function runMysteryEncounterToEnd(game: GameManager, optionNo: numb
|
|||
}
|
||||
}
|
||||
|
||||
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, secondaryOptionSelect: { pokemonNo: number, optionNo: number } = null) {
|
||||
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, secondaryOptionSelect: { pokemonNo: number, optionNo?: number } = null) {
|
||||
// Handle any eventual queued messages (e.g. weather phase, etc.)
|
||||
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
|
||||
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
|
||||
|
@ -112,7 +112,7 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
|
|||
}
|
||||
}
|
||||
|
||||
async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number, optionNo: number) {
|
||||
async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number, optionNo?: number) {
|
||||
// Handle secondary option selections
|
||||
const partyUiHandler = game.scene.ui.handlers[Mode.PARTY] as PartyUiHandler;
|
||||
vi.spyOn(partyUiHandler, "show");
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
import { Biome } from "#app/enums/biome";
|
||||
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { CommandPhase, LearnMovePhase, MovePhase, SelectModifierPhase } from "#app/phases";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
|
||||
const namespace = "mysteryEncounter:dancingLessons";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultBiome = Biome.PLAINS;
|
||||
const defaultWave = 45;
|
||||
|
||||
describe("Dancing Lessons - Mystery Encounter", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
let scene: BattleScene;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
game = new GameManager(phaserGame);
|
||||
scene = game.scene;
|
||||
game.override.mysteryEncounterChance(100);
|
||||
game.override.startingWave(defaultWave);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
game.override.disableTrainerWaves(true);
|
||||
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.PLAINS, [MysteryEncounterType.DANCING_LESSONS]],
|
||||
[Biome.SPACE, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
|
||||
|
||||
expect(DancingLessonsEncounter.encounterType).toBe(MysteryEncounterType.DANCING_LESSONS);
|
||||
expect(DancingLessonsEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
|
||||
expect(DancingLessonsEncounter.dialogue).toBeDefined();
|
||||
expect(DancingLessonsEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
|
||||
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
|
||||
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
|
||||
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
|
||||
expect(DancingLessonsEncounter.options.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should not run below wave 10", async () => {
|
||||
game.override.startingWave(9);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DANCING_LESSONS);
|
||||
});
|
||||
|
||||
it("should not run above wave 179", async () => {
|
||||
game.override.startingWave(181);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not spawn outside of proper biomes", async () => {
|
||||
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
|
||||
game.override.startingBiome(Biome.SPACE);
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(game.scene.currentBattle.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DANCING_LESSONS);
|
||||
});
|
||||
|
||||
describe("Option 1 - Fight the Oricorio", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = DancingLessonsEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should start battle against Oricorio", async () => {
|
||||
const phaseSpy = vi.spyOn(scene, "pushPhase");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||
expect(enemyField.length).toBe(1);
|
||||
expect(enemyField[0].species.speciesId).toBe(Species.ORICORIO);
|
||||
expect(enemyField[0].summonData.battleStats).toEqual([1, 1, 1, 1, 1, 0, 0]);
|
||||
const moveset = enemyField[0].moveset.map(m => m.moveId);
|
||||
expect(moveset.some(m => m === Moves.REVELATION_DANCE)).toBeTruthy();
|
||||
|
||||
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
|
||||
expect(movePhases.length).toBe(1);
|
||||
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.REVELATION_DANCE).length).toBe(1); // Revelation Dance used before battle
|
||||
});
|
||||
|
||||
it("should have a Baton in the rewards after battle", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||
|
||||
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||
expect(modifierSelectHandler.options.length).toEqual(3); // Should fill remaining
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toContain("BATON");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Learn its Dance", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = DancingLessonsEncounter.options[1];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("Should select a pokemon to learn Revelation Dance", async () => {
|
||||
const phaseSpy = vi.spyOn(scene, "unshiftPhase");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
|
||||
|
||||
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof LearnMovePhase).map(p => p[0]);
|
||||
expect(movePhases.length).toBe(1);
|
||||
expect(movePhases.filter(p => (p as LearnMovePhase)["moveId"] === Moves.REVELATION_DANCE).length).toBe(1); // Revelation Dance taught to pokemon
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - Teach it a Dance", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = DancingLessonsEncounter.options[2];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
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`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should add Oricorio to the party", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
|
||||
const partyCountBefore = scene.getParty().length;
|
||||
scene.getParty()[0].moveset = [new PokemonMove(Moves.DRAGON_DANCE)];
|
||||
await runMysteryEncounterToEnd(game, 3, {pokemonNo: 1, optionNo: 1});
|
||||
const partyCountAfter = scene.getParty().length;
|
||||
|
||||
expect(partyCountBefore + 1).toBe(partyCountAfter);
|
||||
const oricorio = scene.getParty()[scene.getParty().length - 1];
|
||||
expect(oricorio.species.speciesId).toBe(Species.ORICORIO);
|
||||
const moveset = oricorio.moveset.map(m => m.moveId);
|
||||
expect(moveset?.some(m => m === Moves.REVELATION_DANCE)).toBeTruthy();
|
||||
expect(moveset?.some(m => m === Moves.DRAGON_DANCE)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should NOT be selectable if the player doesn't have a Dance type move", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
|
||||
const partyCountBefore = scene.getParty().length;
|
||||
scene.getParty().forEach(p => p.moveset = []);
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 3);
|
||||
const partyCountAfter = scene.getParty().length;
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
expect(partyCountBefore).toBe(partyCountAfter);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
|
||||
scene.getParty()[0].moveset = [new PokemonMove(Moves.DRAGON_DANCE)];
|
||||
await runMysteryEncounterToEnd(game, 3, {pokemonNo: 1, optionNo: 1});
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,295 @@
|
|||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
|
||||
const namespace = "mysteryEncounter:partTimer";
|
||||
// Pyukumuku for lowest speed, Regieleki for highest speed, Feebas for lowest "bulk", Melmetal for highest "bulk"
|
||||
const defaultParty = [Species.PYUKUMUKU, Species.REGIELEKI, Species.FEEBAS, Species.MELMETAL];
|
||||
const defaultBiome = Biome.PLAINS;
|
||||
const defaultWave = 37;
|
||||
|
||||
describe("Part-Timer - Mystery Encounter", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
let scene: BattleScene;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
game = new GameManager(phaserGame);
|
||||
scene = game.scene;
|
||||
game.override.mysteryEncounterChance(100);
|
||||
game.override.startingWave(defaultWave);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
game.override.disableTrainerWaves(true);
|
||||
|
||||
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||
]);
|
||||
CIVILIZATION_ENCOUNTER_BIOMES.forEach(biome => {
|
||||
biomeMap.set(biome, [MysteryEncounterType.PART_TIMER]);
|
||||
});
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
|
||||
expect(PartTimerEncounter.encounterType).toBe(MysteryEncounterType.PART_TIMER);
|
||||
expect(PartTimerEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
|
||||
expect(PartTimerEncounter.dialogue).toBeDefined();
|
||||
expect(PartTimerEncounter.dialogue.intro).toStrictEqual([
|
||||
{ text: `${namespace}.intro` },
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
}
|
||||
]);
|
||||
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
|
||||
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
|
||||
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
|
||||
expect(PartTimerEncounter.options.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => {
|
||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||
game.override.startingBiome(Biome.VOLCANO);
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.PART_TIMER);
|
||||
});
|
||||
|
||||
it("should not run below wave 10", async () => {
|
||||
game.override.startingWave(9);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.PART_TIMER);
|
||||
});
|
||||
|
||||
it("should not run above wave 179", async () => {
|
||||
game.override.startingWave(181);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("Option 1 - Make Deliveries", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = PartTimerEncounter.options[0];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it("should give the player 1x money multiplier money with max slowest Pokemon", async () => {
|
||||
vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
// Override party levels to 50 so stats can be fully reflective
|
||||
scene.getParty().forEach(p => {
|
||||
p.level = 50;
|
||||
p.calculateStats();
|
||||
});
|
||||
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 });
|
||||
|
||||
expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(1), true, false);
|
||||
// Expect PP of mon's moves to have been reduced to 2
|
||||
const moves = scene.getParty()[0].moveset;
|
||||
for (const move of moves) {
|
||||
expect(move.getMovePp() - move.ppUsed).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it("should give the player 4x money multiplier money with max fastest Pokemon", async () => {
|
||||
vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
// Override party levels to 50 so stats can be fully reflective
|
||||
scene.getParty().forEach(p => {
|
||||
p.level = 50;
|
||||
p.ivs = [20,20,20,20,20,20];
|
||||
p.calculateStats();
|
||||
});
|
||||
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 2 });
|
||||
|
||||
expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(4), true, false);
|
||||
// Expect PP of mon's moves to have been reduced to 2
|
||||
const moves = scene.getParty()[1].moveset;
|
||||
for (const move of moves) {
|
||||
expect(move.getMovePp() - move.ppUsed).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 });
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Help in the Warehouse", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = PartTimerEncounter.options[1];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it("should give the player 1x money multiplier money with least bulky Pokemon", async () => {
|
||||
vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
// Override party levels to 50 so stats can be fully reflective
|
||||
scene.getParty().forEach(p => {
|
||||
p.level = 50;
|
||||
p.calculateStats();
|
||||
});
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 3 });
|
||||
|
||||
expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(1), true, false);
|
||||
// Expect PP of mon's moves to have been reduced to 2
|
||||
const moves = scene.getParty()[2].moveset;
|
||||
for (const move of moves) {
|
||||
expect(move.getMovePp() - move.ppUsed).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it("should give the player 4x money multiplier money with bulkiest Pokemon", async () => {
|
||||
vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
// Override party levels to 50 so stats can be fully reflective
|
||||
scene.getParty().forEach(p => {
|
||||
p.level = 50;
|
||||
p.ivs = [20,20,20,20,20,20];
|
||||
p.calculateStats();
|
||||
});
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 4 });
|
||||
|
||||
expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(4), true, false);
|
||||
// Expect PP of mon's moves to have been reduced to 2
|
||||
const moves = scene.getParty()[3].moveset;
|
||||
for (const move of moves) {
|
||||
expect(move.getMovePp() - move.ppUsed).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - Assist with Sales", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = PartTimerEncounter.options[2];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it("Should NOT be selectable when requirements are not met", async () => {
|
||||
vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
// Mock movesets
|
||||
scene.getParty().forEach(p => p.moveset = []);
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 3);
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
expect(EncounterPhaseUtils.updatePlayerMoney).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should be selectable and give the player 2.5x money multiplier money with requirements met", async () => {
|
||||
vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
// Mock moveset
|
||||
scene.getParty()[0].moveset = [new PokemonMove(Moves.ATTRACT)];
|
||||
await runMysteryEncounterToEnd(game, 3);
|
||||
|
||||
expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(2.5), true, false);
|
||||
// Expect PP of mon's moves to have been reduced to 2
|
||||
const moves = scene.getParty()[0].moveset;
|
||||
for (const move of moves) {
|
||||
expect(move.getMovePp() - move.ppUsed).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue