MEs to pokerogue beta branch
BIN
public/audio/bgm/mystery_encounter_weird_dream.mp3
Normal file
951
public/battle-anims/encounter-dance.json
Normal file
@ -0,0 +1,951 @@
|
||||
{
|
||||
"id": 686,
|
||||
"graphic": "PRAS- Dragon Dance",
|
||||
"frames": [
|
||||
[
|
||||
{
|
||||
"x": 4,
|
||||
"y": -8,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 70,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -12,
|
||||
"y": -0.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 70,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 12,
|
||||
"y": -12,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 16,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -16,
|
||||
"y": -0.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 24,
|
||||
"y": -12,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 20,
|
||||
"y": 0,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -20,
|
||||
"y": -0.5,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 32,
|
||||
"y": -8,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 24,
|
||||
"y": 0,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -24,
|
||||
"y": -0.5,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 36,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 28,
|
||||
"y": 0,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 70,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -28,
|
||||
"y": -0.5,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 70,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 36,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 36,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 32,
|
||||
"y": -8,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 24,
|
||||
"y": -12,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 12,
|
||||
"y": -12,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 4,
|
||||
"y": -8,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -4,
|
||||
"y": -8,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 70,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -12,
|
||||
"y": -0.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 70,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -12,
|
||||
"y": -12,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 16,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -16,
|
||||
"y": -0.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -24,
|
||||
"y": -12,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 20,
|
||||
"y": 0,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -20,
|
||||
"y": -0.5,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -32,
|
||||
"y": -8,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 24,
|
||||
"y": 0,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -24,
|
||||
"y": -0.5,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 155,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -36,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
},
|
||||
{
|
||||
"x": 28,
|
||||
"y": 0,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 70,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
},
|
||||
{
|
||||
"x": -28,
|
||||
"y": -0.5,
|
||||
"zoomX": 108,
|
||||
"zoomY": 100,
|
||||
"mirror": true,
|
||||
"visible": true,
|
||||
"blendType": 1,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 70,
|
||||
"tone": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"priority": 1,
|
||||
"focus": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -36,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -36,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -32,
|
||||
"y": -8,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -24,
|
||||
"y": -12,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -12,
|
||||
"y": -12,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": -4,
|
||||
"y": -8,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 0,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"locked": true,
|
||||
"priority": 1,
|
||||
"focus": 2
|
||||
}
|
||||
]
|
||||
],
|
||||
"frameTimedEvents": {
|
||||
"0": [
|
||||
{
|
||||
"frameIndex": 0,
|
||||
"resourceName": "PRSFX- Attract.wav",
|
||||
"volume": 100,
|
||||
"pitch": 100,
|
||||
"eventType": "AnimTimedSoundEvent"
|
||||
}
|
||||
],
|
||||
"1": [
|
||||
{
|
||||
"frameIndex": 0,
|
||||
"resourceName": "PRSFX- Ally Switch.wav",
|
||||
"volume": 80,
|
||||
"pitch": 100,
|
||||
"eventType": "AnimTimedSoundEvent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"position": 4,
|
||||
"hue": 0
|
||||
}
|
66
public/battle-anims/encounter-magma-bg.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"frames": [
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
],
|
||||
"frameTimedEvents": {
|
||||
"0": [
|
||||
{
|
||||
"frameIndex": 0,
|
||||
"resourceName": "PRAS- Fire BG",
|
||||
"bgX": 0,
|
||||
"bgY": 0,
|
||||
"opacity": 0,
|
||||
"duration": 35,
|
||||
"eventType": "AnimTimedAddBgEvent"
|
||||
},
|
||||
{
|
||||
"frameIndex": 0,
|
||||
"resourceName": "",
|
||||
"bgX": 0,
|
||||
"bgY": 0,
|
||||
"opacity": 255,
|
||||
"duration": 12,
|
||||
"eventType": "AnimTimedUpdateBgEvent"
|
||||
}
|
||||
],
|
||||
"25": [
|
||||
{
|
||||
"frameIndex": 25,
|
||||
"resourceName": "",
|
||||
"bgX": 0,
|
||||
"bgY": 0,
|
||||
"opacity": 0,
|
||||
"duration": 8,
|
||||
"eventType": "AnimTimedUpdateBgEvent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"position": 1,
|
||||
"hue": 0
|
||||
}
|
902
public/battle-anims/encounter-magma-spout.json
Normal file
@ -0,0 +1,902 @@
|
||||
{
|
||||
"graphic": "PRAS- Magma Storm",
|
||||
"frames": [
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 1,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 1,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 1,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 2,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 2,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 2,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 3,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 3,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 3,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 4,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 4,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 4,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 5,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 5,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 5,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 6,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 6,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 6,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 7,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 7,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 7,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 120,
|
||||
"y": -56,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 8,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 144,
|
||||
"y": -84,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 8,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 100,
|
||||
"y": -86.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 8,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 140,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 9,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 136,
|
||||
"y": -92,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 9,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 108,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 9,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 152,
|
||||
"y": -76,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 10,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 116,
|
||||
"y": -88,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 10,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 128,
|
||||
"y": -62.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 10,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 136,
|
||||
"y": -96,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 7,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 100,
|
||||
"y": -76,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 7,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 148,
|
||||
"y": -66.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 7,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 108,
|
||||
"y": -92,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 8,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 120,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 8,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 144,
|
||||
"y": -86.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 8,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 100,
|
||||
"y": -76,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 9,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 136,
|
||||
"y": -68,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 9,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 128,
|
||||
"y": -94.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 9,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 100.5,
|
||||
"y": -70,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 10,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 144,
|
||||
"y": -66,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 10,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 126,
|
||||
"y": -86.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 10,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 6,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 6,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 6,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 5,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 5,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 5,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 4,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 4,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 4,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 3,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 3,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 3,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 2,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 2,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 2,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 1,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 1,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 1,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 255,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"x": 101,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 130,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 152,
|
||||
"y": -64,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 130,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
},
|
||||
{
|
||||
"x": 124.5,
|
||||
"y": -78.5,
|
||||
"zoomX": 100,
|
||||
"zoomY": 100,
|
||||
"visible": true,
|
||||
"target": 2,
|
||||
"graphicFrame": 0,
|
||||
"opacity": 140,
|
||||
"priority": 4,
|
||||
"focus": 1
|
||||
}
|
||||
]
|
||||
],
|
||||
"frameTimedEvents": {
|
||||
"0": [
|
||||
{
|
||||
"frameIndex": 0,
|
||||
"resourceName": "PRSFX- Magma Storm1.wav",
|
||||
"volume": 100,
|
||||
"pitch": 100,
|
||||
"eventType": "AnimTimedSoundEvent"
|
||||
}
|
||||
],
|
||||
"8": [
|
||||
{
|
||||
"frameIndex": 8,
|
||||
"resourceName": "PRSFX- Magma Storm2.wav",
|
||||
"volume": 100,
|
||||
"pitch": 100,
|
||||
"eventType": "AnimTimedSoundEvent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"position": 1,
|
||||
"hue": 0
|
||||
}
|
1694
public/battle-anims/encounter-smokescreen.json
Normal file
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
BIN
public/images/items/berry_juice.png
Normal file
After Width: | Height: | Size: 318 B |
BIN
public/images/items/black_sludge.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/items/macho_brace.png
Normal file
After Width: | Height: | Size: 372 B |
BIN
public/images/items/old_gateau.png
Normal file
After Width: | Height: | Size: 240 B |
734
public/images/mystery-encounters/b2w2_lady.json
Normal file
@ -0,0 +1,734 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "b2w2_lady.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 399,
|
||||
"h": 360
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 57,
|
||||
"y": 0,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 114,
|
||||
"y": 0,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 9,
|
||||
"y": 8,
|
||||
"w": 55,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 171,
|
||||
"y": 0,
|
||||
"w": 55,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 11,
|
||||
"y": 8,
|
||||
"w": 54,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 228,
|
||||
"y": 0,
|
||||
"w": 54,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0005.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 11,
|
||||
"y": 8,
|
||||
"w": 54,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 285,
|
||||
"y": 0,
|
||||
"w": 54,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0006.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 14,
|
||||
"y": 8,
|
||||
"w": 52,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 342,
|
||||
"y": 0,
|
||||
"w": 52,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0007.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 72,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0008.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 47,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 57,
|
||||
"y": 72,
|
||||
"w": 47,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0009.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 47,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 114,
|
||||
"y": 72,
|
||||
"w": 47,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0010.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 171,
|
||||
"y": 72,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0011.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 228,
|
||||
"y": 72,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0012.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 285,
|
||||
"y": 72,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0013.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 342,
|
||||
"y": 72,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0014.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 49,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 144,
|
||||
"w": 49,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0015.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 49,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 57,
|
||||
"y": 144,
|
||||
"w": 49,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0016.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 49,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 114,
|
||||
"y": 144,
|
||||
"w": 49,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0017.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 49,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 171,
|
||||
"y": 144,
|
||||
"w": 49,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0018.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 228,
|
||||
"y": 144,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0019.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 285,
|
||||
"y": 144,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0020.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 342,
|
||||
"y": 144,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0021.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 22,
|
||||
"y": 8,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 216,
|
||||
"w": 48,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0022.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 8,
|
||||
"w": 50,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 57,
|
||||
"y": 216,
|
||||
"w": 50,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0023.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 18,
|
||||
"y": 8,
|
||||
"w": 51,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 114,
|
||||
"y": 216,
|
||||
"w": 51,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0024.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 18,
|
||||
"y": 8,
|
||||
"w": 51,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 171,
|
||||
"y": 216,
|
||||
"w": 51,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0025.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 15,
|
||||
"y": 8,
|
||||
"w": 53,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 228,
|
||||
"y": 216,
|
||||
"w": 53,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0026.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 10,
|
||||
"y": 8,
|
||||
"w": 57,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 285,
|
||||
"y": 216,
|
||||
"w": 57,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0027.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 10,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 342,
|
||||
"y": 216,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0028.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 10,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 288,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0029.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 9,
|
||||
"y": 8,
|
||||
"w": 55,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 57,
|
||||
"y": 288,
|
||||
"w": 55,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0030.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 114,
|
||||
"y": 288,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0031.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 171,
|
||||
"y": 288,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0032.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 228,
|
||||
"y": 288,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0033.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 285,
|
||||
"y": 288,
|
||||
"w": 56,
|
||||
"h": 72
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:e7f062304401dbd7b3ec79512f0ff4cb:0136dac01331f88892a3df26aeab78f5:1ed1e22abb9b55d76337a5a599835c06$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/b2w2_lady.png
Normal file
After Width: | Height: | Size: 23 KiB |
797
public/images/mystery-encounters/b2w2_veteran_m.json
Normal file
@ -0,0 +1,797 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "b2w2_veteran_m.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 424,
|
||||
"h": 390
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 53,
|
||||
"y": 0,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 106,
|
||||
"y": 0,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 159,
|
||||
"y": 0,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 212,
|
||||
"y": 0,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0005.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 265,
|
||||
"y": 0,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0006.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 318,
|
||||
"y": 0,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0007.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 371,
|
||||
"y": 0,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0008.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 78,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0009.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 53,
|
||||
"y": 78,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0010.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 48,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 106,
|
||||
"y": 78,
|
||||
"w": 48,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0011.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 50,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 159,
|
||||
"y": 78,
|
||||
"w": 50,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0012.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 212,
|
||||
"y": 78,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0013.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 265,
|
||||
"y": 78,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0014.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 52,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 318,
|
||||
"y": 78,
|
||||
"w": 52,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0015.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 51,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 371,
|
||||
"y": 78,
|
||||
"w": 51,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0016.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 52,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 156,
|
||||
"w": 52,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0017.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 52,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 53,
|
||||
"y": 156,
|
||||
"w": 52,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0018.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 106,
|
||||
"y": 156,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0019.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 159,
|
||||
"y": 156,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0020.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 212,
|
||||
"y": 156,
|
||||
"w": 53,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0021.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 52,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 265,
|
||||
"y": 156,
|
||||
"w": 52,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0022.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 51,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 318,
|
||||
"y": 156,
|
||||
"w": 51,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0023.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 51,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 371,
|
||||
"y": 156,
|
||||
"w": 51,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0024.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 51,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 234,
|
||||
"w": 51,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0025.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 50,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 53,
|
||||
"y": 234,
|
||||
"w": 50,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0026.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 48,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 106,
|
||||
"y": 234,
|
||||
"w": 48,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0027.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 46,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 159,
|
||||
"y": 234,
|
||||
"w": 46,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0028.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 46,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 212,
|
||||
"y": 234,
|
||||
"w": 46,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0029.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 265,
|
||||
"y": 234,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0030.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 318,
|
||||
"y": 234,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0031.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 371,
|
||||
"y": 234,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0032.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 312,
|
||||
"w": 44,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0033.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 53,
|
||||
"y": 312,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0034.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 106,
|
||||
"y": 312,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0035.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 159,
|
||||
"y": 312,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0036.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 212,
|
||||
"y": 312,
|
||||
"w": 43,
|
||||
"h": 78
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:4deb068879a8ac195cb4f00c8b17b7f5:b32f0f90436649264b6f3c49b09ac06a:05e903aa75b8e50c28334d9b5e14c85a$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/b2w2_veteran_m.png
Normal file
After Width: | Height: | Size: 19 KiB |
83
public/images/mystery-encounters/bait.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "bait.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 14,
|
||||
"h": 43
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 3,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 3,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 16,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 16
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 5,
|
||||
"w": 11,
|
||||
"h": 11
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 31,
|
||||
"w": 11,
|
||||
"h": 11
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:f0ec04fcd67ac346dce973693711d032:b697e09191c4312b8faaa0a080a309b7:1af241a52e61fa01ca849aa03c112f85$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/bait.png
Normal file
After Width: | Height: | Size: 277 B |
19
public/images/mystery-encounters/buoy.json
Normal file
@ -0,0 +1,19 @@
|
||||
{ "frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 46, "h": 60 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 46, "h": 60 },
|
||||
"sourceSize": { "w": 46, "h": 60 }
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.aseprite.org/",
|
||||
"version": "1.3.7-x64",
|
||||
"image": "buoy-sheet.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 46, "h": 60 },
|
||||
"scale": "1"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/buoy.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
209
public/images/mystery-encounters/chest_blue.json
Normal file
@ -0,0 +1,209 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "chest_blue.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 58,
|
||||
"h": 528
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 14,
|
||||
"y": 30,
|
||||
"w": 48,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 48,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 14,
|
||||
"y": 34,
|
||||
"w": 49,
|
||||
"h": 37
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 44,
|
||||
"w": 49,
|
||||
"h": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 14,
|
||||
"y": 30,
|
||||
"w": 48,
|
||||
"h": 41
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 83,
|
||||
"w": 48,
|
||||
"h": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 14,
|
||||
"y": 23,
|
||||
"w": 48,
|
||||
"h": 48
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 126,
|
||||
"w": 48,
|
||||
"h": 48
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 4,
|
||||
"w": 55,
|
||||
"h": 67
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 176,
|
||||
"w": 55,
|
||||
"h": 67
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0005.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 15,
|
||||
"y": 2,
|
||||
"w": 56,
|
||||
"h": 69
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 245,
|
||||
"w": 56,
|
||||
"h": 69
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0006.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 15,
|
||||
"y": 2,
|
||||
"w": 56,
|
||||
"h": 69
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 316,
|
||||
"w": 56,
|
||||
"h": 69
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0007.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 56,
|
||||
"h": 69
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 387,
|
||||
"w": 56,
|
||||
"h": 69
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0008.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 75,
|
||||
"h": 75
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 2,
|
||||
"w": 56,
|
||||
"h": 69
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 458,
|
||||
"w": 56,
|
||||
"h": 69
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:5f36000f6160ee6f397afe5a6fd60b73:cf6f4b08e23400447813583c322eb6c7:f4f3c064e6c93b8d1290f93bee927f60$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/chest_blue.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
41
public/images/mystery-encounters/chest_red.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "chest_red.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 76,
|
||||
"h": 57
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 76,
|
||||
"h": 57
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 10,
|
||||
"y": 3,
|
||||
"w": 56,
|
||||
"h": 54
|
||||
},
|
||||
"frame": {
|
||||
"x": 8,
|
||||
"y": 0,
|
||||
"w": 56,
|
||||
"h": 54
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/chest_red.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
41
public/images/mystery-encounters/dark_deal_porygon.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "dark_deal_porygon.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 36,
|
||||
"h": 45
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 36,
|
||||
"h": 45
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 44,
|
||||
"h": 44
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 36,
|
||||
"h": 45
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/dark_deal_porygon.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/mystery-encounters/encounter_radar.png
Normal file
After Width: | Height: | Size: 255 B |
BIN
public/images/mystery-encounters/exclaim.png
Normal file
After Width: | Height: | Size: 378 B |
41
public/images/mystery-encounters/girawitch.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "girawitch.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 46,
|
||||
"h": 76
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 46,
|
||||
"h": 76
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 46,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 46,
|
||||
"h": 76
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:e68bbc186f511d505c53b2beec3c3741:7108795fc29d953a1d3729ad93d70936:1661aeeeb2f0e4561c644aff254770b3$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/girawitch.png
Normal file
After Width: | Height: | Size: 1021 B |
41
public/images/mystery-encounters/mad_scientist_m.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "mad_scientist_m.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 46,
|
||||
"h": 76
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 44,
|
||||
"h": 74
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 44,
|
||||
"h": 74
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 44,
|
||||
"h": 74
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:a7f8ff2bbb362868f51125c254eb6681:cf76e61ddd31a8f46af67ced168c44a2:4fc09abe16c0608828269e5da81d0744$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/mad_scientist_m.png
Normal file
After Width: | Height: | Size: 920 B |
104
public/images/mystery-encounters/mud.json
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "mud.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 14,
|
||||
"h": 68
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 20
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 12,
|
||||
"h": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 20
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 12,
|
||||
"h": 14
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 16,
|
||||
"w": 12,
|
||||
"h": 14
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 20
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 12,
|
||||
"h": 16
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 32,
|
||||
"w": 12,
|
||||
"h": 16
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 12,
|
||||
"h": 20
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 3,
|
||||
"w": 12,
|
||||
"h": 17
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 50,
|
||||
"w": 12,
|
||||
"h": 17
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:4f18a8effb8f01eb70f9f25b8294c1bf:ad663a73c51f780bbf45d00a52519553:c64f6b8befc3d5e9f836246d2b9536be$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/mud.png
Normal file
After Width: | Height: | Size: 375 B |
41
public/images/mystery-encounters/pokemon_salesman.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "pokemon_salesman.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 40,
|
||||
"h": 80
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 21,
|
||||
"y": 2,
|
||||
"w": 38,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 38,
|
||||
"h": 78
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:dd57e3db21f3933c15be65bec261f4c1:05c7ef32252a5c2d3ad007b7e26fabd7:ae82f52e471ed81e2558206f05476cd7$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/pokemon_salesman.png
Normal file
After Width: | Height: | Size: 839 B |
41
public/images/mystery-encounters/safari_zone.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "safari_zone.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 120,
|
||||
"h": 84
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 118,
|
||||
"h": 82
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 118,
|
||||
"h": 82
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 118,
|
||||
"h": 82
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:6fad7a61e47043b974153148b4fd3997:5ec4d0890f2f03446daf22c8ae8ba77b:87aa745cd95eef6cbf38935230f4e10f$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/safari_zone.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/mystery-encounters/starry_background.png
Normal file
After Width: | Height: | Size: 10 KiB |
41
public/images/mystery-encounters/teacher.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "teacher.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 43,
|
||||
"h": 74
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 19,
|
||||
"y": 8,
|
||||
"w": 41,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 72
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:506e5a4ce79c134a7b4af90a90aef244:1b81d3d84bf12cedc419805eaff82548:59bc5dd000b5e72588320b473e31c312$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/teacher.png
Normal file
After Width: | Height: | Size: 727 B |
41
public/images/mystery-encounters/training_gear.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "training_gear.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 76,
|
||||
"h": 57
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 76,
|
||||
"h": 57
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 10,
|
||||
"y": 3,
|
||||
"w": 56,
|
||||
"h": 54
|
||||
},
|
||||
"frame": {
|
||||
"x": 8,
|
||||
"y": 0,
|
||||
"w": 56,
|
||||
"h": 54
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/training_gear.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
524
public/images/trainer/buck.json
Normal file
@ -0,0 +1,524 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "buck.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 120,
|
||||
"h": 78
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 4,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 4,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0006.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 4,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0007.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 4,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0010.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 4,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0011.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 4,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0014.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 4,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0015.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 33,
|
||||
"y": 4,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0018.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 18,
|
||||
"y": 8,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 38,
|
||||
"y": 1,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0019.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 18,
|
||||
"y": 8,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 38,
|
||||
"y": 1,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0020.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 15,
|
||||
"y": 8,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 38,
|
||||
"y": 1,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0021.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 15,
|
||||
"y": 8,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 38,
|
||||
"y": 1,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0022.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 8,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 38,
|
||||
"y": 1,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0023.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 8,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
},
|
||||
"frame": {
|
||||
"x": 38,
|
||||
"y": 1,
|
||||
"w": 44,
|
||||
"h": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0005.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0008.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0009.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0012.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0013.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0016.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0017.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 34,
|
||||
"y": 5,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
},
|
||||
"frame": {
|
||||
"x": 84,
|
||||
"y": 1,
|
||||
"w": 35,
|
||||
"h": 75
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:033f3d363b4192f64c92e02c19622c15:0d06141bef5af87ef82da967253207cb:3347efe478119141b0e3e6eccdecd0f5$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/buck.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
398
public/images/trainer/cheryl.json
Normal file
@ -0,0 +1,398 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "cheryl.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 154,
|
||||
"h": 83
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0006.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 25,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0007.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 25,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0008.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 26,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0009.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 26,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0010.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 27,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 44,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0011.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 27,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 44,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0012.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 24,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 44,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0013.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 24,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 44,
|
||||
"y": 1,
|
||||
"w": 41,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0014.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 27,
|
||||
"y": 0,
|
||||
"w": 33,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 87,
|
||||
"y": 1,
|
||||
"w": 33,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0015.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 27,
|
||||
"y": 0,
|
||||
"w": 33,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 87,
|
||||
"y": 1,
|
||||
"w": 33,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0016.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 26,
|
||||
"y": 0,
|
||||
"w": 33,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 87,
|
||||
"y": 1,
|
||||
"w": 33,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0017.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 26,
|
||||
"y": 0,
|
||||
"w": 33,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 87,
|
||||
"y": 1,
|
||||
"w": 33,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 0,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 122,
|
||||
"y": 1,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 0,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 122,
|
||||
"y": 1,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 0,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 122,
|
||||
"y": 1,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 0,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 122,
|
||||
"y": 1,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 21,
|
||||
"y": 0,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 122,
|
||||
"y": 1,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "0005.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 81
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 21,
|
||||
"y": 0,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 122,
|
||||
"y": 1,
|
||||
"w": 31,
|
||||
"h": 81
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:dfcf7aedbd588c4e42427a2e17c171bf:206549943a0e3325d20a017ef01eefee:a233cd27590422717866c66e366b68fb$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/cheryl.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
83
public/images/trainer/marley.json
Normal file
@ -0,0 +1,83 @@
|
||||
{ "frames": [
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 31, "h": 77 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 26, "y": 2, "w": 31, "h": 77 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 31, "h": 77 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 26, "y": 2, "w": 31, "h": 77 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 31, "h": 77 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 26, "y": 2, "w": 31, "h": 77 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 31, "h": 77 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 26, "y": 2, "w": 31, "h": 77 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"frame": { "x": 32, "y": 0, "w": 28, "h": 78 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 28, "y": 1, "w": 28, "h": 78 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0005.png",
|
||||
"frame": { "x": 32, "y": 0, "w": 28, "h": 78 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 28, "y": 1, "w": 28, "h": 78 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0006.png",
|
||||
"frame": { "x": 0, "y": 78, "w": 31, "h": 77 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 28, "y": 2, "w": 31, "h": 77 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0007.png",
|
||||
"frame": { "x": 0, "y": 78, "w": 31, "h": 77 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 28, "y": 2, "w": 31, "h": 77 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.pngprite.org/",
|
||||
"version": "1.3.7-x64",
|
||||
"image": "marley.png",
|
||||
"format": "I8",
|
||||
"size": { "w": 60, "h": 155 },
|
||||
"scale": "1"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/marley.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
209
public/images/trainer/mira.json
Normal file
@ -0,0 +1,209 @@
|
||||
{ "frames": [
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 23, "y": 14, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 23, "y": 14, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 22, "y": 13, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 11, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 11, "w": 53, "h": 63 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0005.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 12, "w": 53, "h": 63 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0006.png",
|
||||
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 13, "y": 11, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0007.png",
|
||||
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 13, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0008.png",
|
||||
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 11, "y": 14, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0009.png",
|
||||
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 13, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0010.png",
|
||||
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 13, "y": 11, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0011.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 11, "w": 53, "h": 63 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0012.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 12, "w": 53, "h": 63 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0013.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 11, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0014.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 22, "y": 13, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0015.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 23, "y": 14, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0016.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 22, "y": 13, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0017.png",
|
||||
"frame": { "x": 53, "y": 0, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 11, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0018.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 11, "w": 53, "h": 63 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0019.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 53, "h": 63 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 21, "y": 12, "w": 53, "h": 63 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0020.png",
|
||||
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 13, "y": 11, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0021.png",
|
||||
"frame": { "x": 0, "y": 63, "w": 44, "h": 65 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 13, "w": 44, "h": 65 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.aseprite.org/",
|
||||
"version": "1.3.7-x64",
|
||||
"image": "mira.png",
|
||||
"format": "I8",
|
||||
"size": { "w": 97, "h": 128 },
|
||||
"scale": "1"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/mira.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
209
public/images/trainer/riley.json
Normal file
@ -0,0 +1,209 @@
|
||||
{ "frames": [
|
||||
{
|
||||
"filename": "0000.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0002.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0003.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0004.png",
|
||||
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 31, "y": 0, "w": 37, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0005.png",
|
||||
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 31, "y": 0, "w": 37, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0006.png",
|
||||
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 30, "y": 0, "w": 37, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0007.png",
|
||||
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 30, "y": 0, "w": 37, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0008.png",
|
||||
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 28, "y": 0, "w": 37, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0009.png",
|
||||
"frame": { "x": 55, "y": 80, "w": 37, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 28, "y": 0, "w": 37, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0010.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 10, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0011.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 10, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0012.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0013.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 11, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0014.png",
|
||||
"frame": { "x": 55, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0015.png",
|
||||
"frame": { "x": 55, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0016.png",
|
||||
"frame": { "x": 0, "y": 80, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0017.png",
|
||||
"frame": { "x": 0, "y": 80, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0018.png",
|
||||
"frame": { "x": 55, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0019.png",
|
||||
"frame": { "x": 55, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0020.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
},
|
||||
{
|
||||
"filename": "0021.png",
|
||||
"frame": { "x": 0, "y": 0, "w": 55, "h": 80 },
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 12, "y": 0, "w": 55, "h": 80 },
|
||||
"sourceSize": { "w": 80, "h": 80 },
|
||||
"duration": 100
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.aseprite.org/",
|
||||
"version": "1.3.7-x64",
|
||||
"image": "riley.png",
|
||||
"format": "I8",
|
||||
"size": { "w": 110, "h": 160 },
|
||||
"scale": "1"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/riley.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
41
public/images/trainer/vicky.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "vicky.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 52,
|
||||
"h": 53
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 27,
|
||||
"w": 52,
|
||||
"h": 53
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 52,
|
||||
"h": 53
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:bf9d2d417a1982282dd711456ac71206:101e07828e3d6e2a2a7a80aebfa802ad:cabe44a4410c334298b1984a219f8160$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/vicky.png
Normal file
After Width: | Height: | Size: 765 B |
41
public/images/trainer/victor.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "victor.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 55,
|
||||
"h": 53
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 12,
|
||||
"y": 27,
|
||||
"w": 55,
|
||||
"h": 53
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 55,
|
||||
"h": 53
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:64eff0f697754cdf9552b46342c9292a:611e0e2cacbd90c1229ce5443b2414f0:0cc0f5a2c1b2eedb46dd8318e8feb1d8$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/victor.png
Normal file
After Width: | Height: | Size: 794 B |
41
public/images/trainer/victoria.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "victoria.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 52,
|
||||
"h": 54
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 14,
|
||||
"y": 26,
|
||||
"w": 52,
|
||||
"h": 54
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 52,
|
||||
"h": 54
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:4dafeae3674d63b12cc4d8044f67b5a3:7834687d784c31169256927f419c7958:cf0eb39e0a3f2e42f23ca29747d73c40$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/victoria.png
Normal file
After Width: | Height: | Size: 813 B |
41
public/images/trainer/vito.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "vito.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 41,
|
||||
"h": 78
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 20,
|
||||
"y": 2,
|
||||
"w": 41,
|
||||
"h": 78
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 41,
|
||||
"h": 78
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:cb988be58fcd5381174e9d120b051e38:4d4723dbbcd9713ee0ed3c2d84ef4bfb:1c7723b536b218346e3138016d865ce9$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/vito.png
Normal file
After Width: | Height: | Size: 765 B |
41
public/images/trainer/vivi.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "vivi.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 48,
|
||||
"h": 69
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 80,
|
||||
"h": 80
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 13,
|
||||
"y": 11,
|
||||
"w": 48,
|
||||
"h": 69
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 48,
|
||||
"h": 69
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:0a51b4df0b2ed0fed7e3bdb5dffd9e28:af1f3b1480023b3e3761c49e49faf5f1:4fc6bf2bec74c4bb8809df38231deb01$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/vivi.png
Normal file
After Width: | Height: | Size: 713 B |
@ -2,7 +2,7 @@ import Phaser from "phaser";
|
||||
import UI from "./ui/ui";
|
||||
import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
|
||||
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
|
||||
import { Constructor } from "#app/utils";
|
||||
import { Constructor, isNullOrUndefined } from "#app/utils";
|
||||
import * as Utils from "./utils";
|
||||
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, TurnHeldItemTransferModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from "./modifier/modifier";
|
||||
import { PokeballType } from "./data/pokeball";
|
||||
@ -11,7 +11,7 @@ import { Phase } from "./phase";
|
||||
import { initGameSpeed } from "./system/game-speed";
|
||||
import { Arena, ArenaBase } from "./field/arena";
|
||||
import { GameData } from "./system/game-data";
|
||||
import { TextStyle, addTextObject, getTextColor } from "./ui/text";
|
||||
import { addTextObject, getTextColor, TextStyle } from "./ui/text";
|
||||
import { allMoves } from "./data/move";
|
||||
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, modifierTypes } from "./modifier/modifier-type";
|
||||
import AbilityBar from "./ui/ability-bar";
|
||||
@ -22,14 +22,14 @@ import { GameMode, GameModes, getGameMode } from "./game-mode";
|
||||
import FieldSpritePipeline from "./pipelines/field-sprite";
|
||||
import SpritePipeline from "./pipelines/sprite";
|
||||
import PartyExpBar from "./ui/party-exp-bar";
|
||||
import { TrainerSlot, trainerConfigs } from "./data/trainer-config";
|
||||
import { trainerConfigs, TrainerSlot } from "./data/trainer-config";
|
||||
import Trainer, { TrainerVariant } from "./field/trainer";
|
||||
import TrainerData from "./system/trainer-data";
|
||||
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||
import { pokemonPrevolutions } from "./data/pokemon-evolutions";
|
||||
import PokeballTray from "./ui/pokeball-tray";
|
||||
import InvertPostFX from "./pipelines/invert";
|
||||
import { Achv, ModifierAchv, MoneyAchv, achvs } from "./system/achv";
|
||||
import { Achv, achvs, ModifierAchv, MoneyAchv } from "./system/achv";
|
||||
import { Voucher, vouchers } from "./system/voucher";
|
||||
import { Gender } from "./data/gender";
|
||||
import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
|
||||
@ -49,8 +49,8 @@ import CandyBar from "./ui/candy-bar";
|
||||
import { Variant, variantData } from "./data/variant";
|
||||
import { Localizable } from "#app/interfaces/locales";
|
||||
import Overrides from "#app/overrides";
|
||||
import {InputsController} from "./inputs-controller";
|
||||
import {UiInputs} from "./ui-inputs";
|
||||
import { InputsController } from "./inputs-controller";
|
||||
import { UiInputs } from "./ui-inputs";
|
||||
import { NewArenaEvent } from "./events/battle-scene";
|
||||
import { ArenaFlyout } from "./ui/arena-flyout";
|
||||
import { EaseType } from "#enums/ease-type";
|
||||
@ -65,7 +65,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 { battleSpecDialogue } from "./data/dialogue";
|
||||
import { LoadingScene } from "./loading-scene";
|
||||
import { LevelCapPhase } from "./phases/level-cap-phase";
|
||||
@ -84,6 +84,13 @@ import { TitlePhase } from "./phases/title-phase";
|
||||
import { ToggleDoublePositionPhase } from "./phases/toggle-double-position-phase";
|
||||
import { TurnInitPhase } from "./phases/turn-init-phase";
|
||||
import { ShopCursorTarget } from "./enums/shop-cursor-target";
|
||||
import MysteryEncounter from "./data/mystery-encounters/mystery-encounter";
|
||||
import { allMysteryEncounters, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
|
||||
import { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
|
||||
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
|
||||
|
||||
@ -235,6 +242,8 @@ export default class BattleScene extends SceneBase {
|
||||
public money: integer;
|
||||
public pokemonInfoContainer: PokemonInfoContainer;
|
||||
private party: PlayerPokemon[];
|
||||
public mysteryEncounterData: MysteryEncounterData = new MysteryEncounterData(null);
|
||||
public lastMysteryEncounter: MysteryEncounter;
|
||||
/** Combined Biome and Wave count text */
|
||||
private biomeWaveText: Phaser.GameObjects.Text;
|
||||
private moneyText: Phaser.GameObjects.Text;
|
||||
@ -388,6 +397,7 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
this.fieldUI = fieldUI;
|
||||
|
||||
// @ts-ignore
|
||||
const transition = this.make.rexTransitionImagePack({
|
||||
x: 0,
|
||||
y: 0,
|
||||
@ -873,6 +883,20 @@ export default class BattleScene extends SceneBase {
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
removePokemonFromPlayerParty(pokemon: PlayerPokemon, destroy: boolean = true) {
|
||||
if (!pokemon) {
|
||||
return;
|
||||
}
|
||||
|
||||
const partyIndex = this.party.indexOf(pokemon);
|
||||
this.party.splice(partyIndex, 1);
|
||||
if (destroy) {
|
||||
this.field.remove(pokemon, true);
|
||||
pokemon.destroy();
|
||||
}
|
||||
this.updateModifiers(true);
|
||||
}
|
||||
|
||||
addPokemonIcon(pokemon: Pokemon, x: number, y: number, originX: number = 0.5, originY: number = 0.5, ignoreOverride: boolean = false): Phaser.GameObjects.Container {
|
||||
const container = this.add.container(x, y);
|
||||
container.setName(`${pokemon.name}-icon`);
|
||||
@ -1064,7 +1088,7 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
}
|
||||
|
||||
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle | null {
|
||||
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounter?: MysteryEncounter): Battle | null {
|
||||
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
|
||||
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
|
||||
let newDouble: boolean | undefined;
|
||||
@ -1112,6 +1136,40 @@ export default class BattleScene extends SceneBase {
|
||||
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
|
||||
this.field.add(newTrainer);
|
||||
}
|
||||
|
||||
// TODO: remove these once ME spawn rates are finalized
|
||||
// let testStartingWeight = 0;
|
||||
// while (testStartingWeight < 3) {
|
||||
// calculateMEAggregateStats(this, testStartingWeight);
|
||||
// testStartingWeight += 2;
|
||||
// }
|
||||
// calculateRareSpawnAggregateStats(this, 14);
|
||||
|
||||
// Check for mystery encounter
|
||||
// Can only occur in place of a standard wild battle, waves 10-180
|
||||
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < 180 && newWaveIndex > 10) {
|
||||
const roll = Utils.randSeedInt(256);
|
||||
|
||||
// Base spawn weight is 1/256, and increases by 5/256 for each missed attempt at spawning an encounter on a valid floor
|
||||
const sessionEncounterRate = !isNullOrUndefined(this.mysteryEncounterData?.encounterSpawnChance) ? this.mysteryEncounterData.encounterSpawnChance : BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||
|
||||
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn
|
||||
// Do the reverse as well
|
||||
// Reduces occurrence of runs with very few (<6) and a ton (>10) of encounters
|
||||
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (180 - 10) * newWaveIndex;
|
||||
const currentRunDiffFromAvg = expectedEncountersByFloor - (this.mysteryEncounterData?.encounteredEvents?.length || 0);
|
||||
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * 5;
|
||||
|
||||
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;
|
||||
|
||||
if (roll < successRate) {
|
||||
newBattleType = BattleType.MYSTERY_ENCOUNTER;
|
||||
// Reset base spawn weight
|
||||
this.mysteryEncounterData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||
} else {
|
||||
this.mysteryEncounterData.encounterSpawnChance = sessionEncounterRate + WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (double === undefined && newWaveIndex > 1) {
|
||||
@ -1144,12 +1202,21 @@ export default class BattleScene extends SceneBase {
|
||||
const maxExpLevel = this.getMaxExpLevel();
|
||||
|
||||
this.lastEnemyTrainer = lastBattle?.trainer ?? null;
|
||||
this.lastMysteryEncounter = lastBattle?.mysteryEncounter ?? null;
|
||||
|
||||
this.executeWithSeedOffset(() => {
|
||||
this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble);
|
||||
}, newWaveIndex << 3, this.waveSeed);
|
||||
this.currentBattle.incrementTurn(this);
|
||||
|
||||
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
|
||||
this.currentBattle.double = false;
|
||||
this.executeWithSeedOffset(() => {
|
||||
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounter);
|
||||
}, this.currentBattle.waveIndex << 4);
|
||||
}
|
||||
|
||||
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
|
||||
|
||||
if (!waveIndex && lastBattle) {
|
||||
@ -1166,14 +1233,16 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
if (resetArenaState) {
|
||||
this.arena.resetArenaEffects();
|
||||
playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p)));
|
||||
if (lastBattle?.mysteryEncounter?.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
|
||||
playerField.forEach((_, p) => this.pushPhase(new ReturnPhase(this, p)));
|
||||
|
||||
for (const pokemon of this.getParty()) {
|
||||
pokemon.resetBattleData();
|
||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
||||
for (const pokemon of this.getParty()) {
|
||||
pokemon.resetBattleData();
|
||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
||||
}
|
||||
|
||||
this.pushPhase(new ShowTrainerPhase(this));
|
||||
}
|
||||
|
||||
this.pushPhase(new ShowTrainerPhase(this));
|
||||
}
|
||||
|
||||
for (const pokemon of this.getParty()) {
|
||||
@ -2391,7 +2460,7 @@ export default class BattleScene extends SceneBase {
|
||||
});
|
||||
}
|
||||
|
||||
generateEnemyModifiers(): Promise<void> {
|
||||
generateEnemyModifiers(heldModifiersConfigs?: HeldModifierConfig[][]): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
|
||||
return resolve();
|
||||
@ -2413,29 +2482,42 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
party.forEach((enemyPokemon: EnemyPokemon, i: integer) => {
|
||||
const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss);
|
||||
let upgradeChance = 32;
|
||||
if (isBoss) {
|
||||
upgradeChance /= 2;
|
||||
}
|
||||
if (isFinalBoss) {
|
||||
upgradeChance /= 8;
|
||||
}
|
||||
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss);
|
||||
let pokemonModifierChance = modifierChance;
|
||||
if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer)
|
||||
pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line
|
||||
let count = 0;
|
||||
for (let c = 0; c < chances; c++) {
|
||||
if (!Utils.randSeedInt(modifierChance)) {
|
||||
count++;
|
||||
if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i] && heldModifiersConfigs[i].length > 0) {
|
||||
heldModifiersConfigs[i].forEach(mt => {
|
||||
const stackCount = mt.stackCount ?? 1;
|
||||
// const isTransferable = mt.isTransferable ?? true;
|
||||
const modifier = mt.modifierType.newModifier(enemyPokemon);
|
||||
modifier.stackCount = stackCount;
|
||||
// TODO: set isTransferable
|
||||
// modifier.setIsTransferable(isTransferable);
|
||||
this.addEnemyModifier(modifier, true);
|
||||
});
|
||||
} else {
|
||||
const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss);
|
||||
let upgradeChance = 32;
|
||||
if (isBoss) {
|
||||
upgradeChance /= 2;
|
||||
}
|
||||
if (isFinalBoss) {
|
||||
upgradeChance /= 8;
|
||||
}
|
||||
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss);
|
||||
let pokemonModifierChance = modifierChance;
|
||||
if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer)
|
||||
pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line
|
||||
let count = 0;
|
||||
for (let c = 0; c < chances; c++) {
|
||||
if (!Utils.randSeedInt(modifierChance)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (isBoss) {
|
||||
count = Math.max(count, Math.floor(chances / 2));
|
||||
}
|
||||
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
|
||||
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
|
||||
}
|
||||
if (isBoss) {
|
||||
count = Math.max(count, Math.floor(chances / 2));
|
||||
}
|
||||
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
|
||||
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
|
||||
return true;
|
||||
});
|
||||
this.updateModifiers(false).then(() => resolve());
|
||||
});
|
||||
@ -2703,4 +2785,114 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
this.shiftPhase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads or generates a mystery encounter
|
||||
* @param override - used to load session encounter when restarting game, etc.
|
||||
* @returns
|
||||
*/
|
||||
getMysteryEncounter(override: MysteryEncounter | undefined): MysteryEncounter {
|
||||
// Loading override or session encounter
|
||||
let encounter: MysteryEncounter | null;
|
||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE!)) {
|
||||
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE!];
|
||||
} else {
|
||||
encounter = override?.encounterType && override.encounterType >= 0 ? allMysteryEncounters[override.encounterType] : null;
|
||||
}
|
||||
|
||||
// Check for queued encounters first
|
||||
if (!encounter && this.mysteryEncounterData?.nextEncounterQueue && this.mysteryEncounterData.nextEncounterQueue.length > 0) {
|
||||
let i = 0;
|
||||
while (i < this.mysteryEncounterData.nextEncounterQueue.length && !!encounter) {
|
||||
const candidate = this.mysteryEncounterData.nextEncounterQueue[i];
|
||||
const forcedChance = candidate[1];
|
||||
if (Utils.randSeedInt(100) < forcedChance) {
|
||||
encounter = allMysteryEncounters[candidate[0]];
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (encounter) {
|
||||
encounter = new MysteryEncounter(encounter);
|
||||
encounter.populateDialogueTokensFromRequirements(this);
|
||||
return encounter;
|
||||
}
|
||||
|
||||
// See Enum values for base tier weights
|
||||
const tierWeights = [MysteryEncounterTier.COMMON, MysteryEncounterTier.GREAT, MysteryEncounterTier.ULTRA, MysteryEncounterTier.ROGUE];
|
||||
|
||||
// Adjust tier weights by previously encountered events to lower odds of only common/uncommons in run
|
||||
this.mysteryEncounterData.encounteredEvents.forEach(val => {
|
||||
const tier = val[1];
|
||||
if (tier === MysteryEncounterTier.COMMON) {
|
||||
tierWeights[0] = tierWeights[0] - 6;
|
||||
} else if (tier === MysteryEncounterTier.GREAT) {
|
||||
tierWeights[1] = tierWeights[1] - 4;
|
||||
}
|
||||
});
|
||||
|
||||
const totalWeight = tierWeights.reduce((a, b) => a + b);
|
||||
const tierValue = Utils.randSeedInt(totalWeight);
|
||||
const commonThreshold = totalWeight - tierWeights[0];
|
||||
const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1];
|
||||
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2];
|
||||
let tier: MysteryEncounterTier | null = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > uncommonThreshold ? MysteryEncounterTier.GREAT : tierValue > rareThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE;
|
||||
|
||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) {
|
||||
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE!;
|
||||
}
|
||||
|
||||
let availableEncounters: MysteryEncounter[] = [];
|
||||
// New encounter will never be the same as the most recent encounter
|
||||
const previousEncounter = this.mysteryEncounterData.encounteredEvents?.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.encounteredEvents.length - 1][0] : null;
|
||||
const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? [];
|
||||
// If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available
|
||||
while (availableEncounters.length === 0 && tier !== null) {
|
||||
availableEncounters = biomeMysteryEncounters
|
||||
.filter((encounterType) => {
|
||||
const encounterCandidate = allMysteryEncounters[encounterType];
|
||||
if (!encounterCandidate) {
|
||||
return false;
|
||||
}
|
||||
if (encounterCandidate.encounterTier !== tier) { // Encounter is in tier
|
||||
return false;
|
||||
}
|
||||
if (!encounterCandidate.meetsRequirements!(this)) { // Meets encounter requirements
|
||||
return false;
|
||||
}
|
||||
if (!isNullOrUndefined(previousEncounter) && encounterType === previousEncounter) { // Previous encounter was not this one
|
||||
return false;
|
||||
}
|
||||
if (this.mysteryEncounterData.encounteredEvents?.length > 0 && // Encounter has not exceeded max allowed encounters
|
||||
(encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters > 0)
|
||||
&& this.mysteryEncounterData.encounteredEvents.filter(e => e[0] === encounterType).length >= encounterCandidate.maxAllowedEncounters) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((m) => (allMysteryEncounters[m]));
|
||||
// Decrement tier
|
||||
if (tier === MysteryEncounterTier.ROGUE) {
|
||||
tier = MysteryEncounterTier.ULTRA;
|
||||
} else if (tier === MysteryEncounterTier.ULTRA) {
|
||||
tier = MysteryEncounterTier.GREAT;
|
||||
} else if (tier === MysteryEncounterTier.GREAT) {
|
||||
tier = MysteryEncounterTier.COMMON;
|
||||
} else {
|
||||
tier = null; // Ends loop
|
||||
}
|
||||
}
|
||||
|
||||
// If absolutely no encounters are available, spawn 0th encounter
|
||||
if (availableEncounters.length === 0) {
|
||||
return allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHALLENGERS];
|
||||
}
|
||||
encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)];
|
||||
// New encounter object to not dirty flags
|
||||
encounter = new MysteryEncounter(encounter);
|
||||
encounter.populateDialogueTokensFromRequirements!(this);
|
||||
return encounter;
|
||||
}
|
||||
}
|
||||
|
@ -14,32 +14,35 @@ import { PlayerGender } from "#enums/player-gender";
|
||||
import { Species } from "#enums/species";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import MysteryEncounter from "./data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
|
||||
export enum BattleType {
|
||||
WILD,
|
||||
TRAINER,
|
||||
CLEAR
|
||||
WILD,
|
||||
TRAINER,
|
||||
CLEAR,
|
||||
MYSTERY_ENCOUNTER
|
||||
}
|
||||
|
||||
export enum BattlerIndex {
|
||||
ATTACKER = -1,
|
||||
PLAYER,
|
||||
PLAYER_2,
|
||||
ENEMY,
|
||||
ENEMY_2
|
||||
ATTACKER = -1,
|
||||
PLAYER,
|
||||
PLAYER_2,
|
||||
ENEMY,
|
||||
ENEMY_2
|
||||
}
|
||||
|
||||
export interface TurnCommand {
|
||||
command: Command;
|
||||
cursor?: integer;
|
||||
move?: QueuedMove;
|
||||
targets?: BattlerIndex[];
|
||||
skip?: boolean;
|
||||
args?: any[];
|
||||
command: Command;
|
||||
cursor?: integer;
|
||||
move?: QueuedMove;
|
||||
targets?: BattlerIndex[];
|
||||
skip?: boolean;
|
||||
args?: any[];
|
||||
}
|
||||
|
||||
interface TurnCommands {
|
||||
[key: integer]: TurnCommand | null
|
||||
[key: integer]: TurnCommand | null
|
||||
}
|
||||
|
||||
export default class Battle {
|
||||
@ -67,6 +70,7 @@ export default class Battle {
|
||||
public lastUsedPokeball: PokeballType | null;
|
||||
public playerFaints: number; // The amount of times pokemon on the players side have fainted
|
||||
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
|
||||
public mysteryEncounter: MysteryEncounter;
|
||||
|
||||
private rngCounter: integer = 0;
|
||||
|
||||
@ -105,7 +109,7 @@ export default class Battle {
|
||||
this.battleSpec = spec;
|
||||
}
|
||||
|
||||
private getLevelForWave(): integer {
|
||||
public getLevelForWave(): integer {
|
||||
const levelWaveIndex = this.gameMode.getWaveForDifficulty(this.waveIndex);
|
||||
const baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2);
|
||||
const bossMultiplier = 1.2;
|
||||
@ -203,7 +207,7 @@ export default class Battle {
|
||||
|
||||
getBgmOverride(scene: BattleScene): string | null {
|
||||
const battlers = this.enemyParty.slice(0, this.getBattlerCount());
|
||||
if (this.battleType === BattleType.TRAINER) {
|
||||
if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
|
||||
if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
|
||||
return `encounter_${this.trainer?.getEncounterBgm()}`;
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import * as Utils from "../utils";
|
||||
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 {
|
||||
@ -102,6 +104,18 @@ export enum CommonAnim {
|
||||
LOCK_ON = 2120
|
||||
}
|
||||
|
||||
/**
|
||||
* Animations used for Mystery Encounters
|
||||
* These are custom animations that may or may not work in any other circumstance
|
||||
* Use at your own risk
|
||||
*/
|
||||
export enum EncounterAnim {
|
||||
MAGMA_BG,
|
||||
MAGMA_SPOUT,
|
||||
SMOKESCREEN,
|
||||
DANCE
|
||||
}
|
||||
|
||||
export class AnimConfig {
|
||||
public id: integer;
|
||||
public graphic: string;
|
||||
@ -303,7 +317,7 @@ abstract class AnimTimedEvent {
|
||||
this.resourceName = resourceName;
|
||||
}
|
||||
|
||||
abstract execute(scene: BattleScene, battleAnim: BattleAnim): integer;
|
||||
abstract execute(scene: BattleScene, battleAnim: BattleAnim, priority?: number): integer;
|
||||
|
||||
abstract getEventType(): string;
|
||||
}
|
||||
@ -321,7 +335,7 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
execute(scene: BattleScene, battleAnim: BattleAnim): integer {
|
||||
execute(scene: BattleScene, battleAnim: BattleAnim, priority?: number): integer {
|
||||
const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) };
|
||||
if (this.resourceName) {
|
||||
try {
|
||||
@ -383,7 +397,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
|
||||
super(frameIndex, resourceName, source);
|
||||
}
|
||||
|
||||
execute(scene: BattleScene, moveAnim: MoveAnim): integer {
|
||||
execute(scene: BattleScene, moveAnim: MoveAnim, priority?: number): integer {
|
||||
const tweenProps = {};
|
||||
if (this.bgX !== undefined) {
|
||||
tweenProps["x"] = (this.bgX * 0.5) - 320;
|
||||
@ -413,7 +427,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
|
||||
super(frameIndex, resourceName, source);
|
||||
}
|
||||
|
||||
execute(scene: BattleScene, moveAnim: MoveAnim): integer {
|
||||
execute(scene: BattleScene, moveAnim: MoveAnim, priority?: number): integer {
|
||||
if (moveAnim.bgSprite) {
|
||||
moveAnim.bgSprite.destroy();
|
||||
}
|
||||
@ -425,7 +439,9 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
|
||||
moveAnim.bgSprite.setAlpha(this.opacity / 255);
|
||||
scene.field.add(moveAnim.bgSprite);
|
||||
const fieldPokemon = scene.getEnemyPokemon() || scene.getPlayerPokemon();
|
||||
if (fieldPokemon?.isOnField()) {
|
||||
if (!isNullOrUndefined(priority)) {
|
||||
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority!);
|
||||
} else if (fieldPokemon?.isOnField()) {
|
||||
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
|
||||
}
|
||||
|
||||
@ -445,6 +461,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
|
||||
export const moveAnims = new Map<Moves, AnimConfig | [AnimConfig, AnimConfig] | null>();
|
||||
export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimConfig] | null>();
|
||||
export const commonAnims = new Map<CommonAnim, AnimConfig>();
|
||||
export const encounterAnims = new Map<EncounterAnim, AnimConfig>();
|
||||
|
||||
export function initCommonAnims(scene: BattleScene): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
@ -515,6 +532,26 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches animation configs to be used in a Mystery Encounter
|
||||
* @param scene
|
||||
* @param encounterAnim - one or more animations to fetch
|
||||
*/
|
||||
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
||||
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
|
||||
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
|
||||
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
|
||||
for (const anim of anims) {
|
||||
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
|
||||
continue;
|
||||
}
|
||||
encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
|
||||
.then(response => response.json())
|
||||
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))));
|
||||
}
|
||||
await Promise.allSettled(encounterAnimFetches);
|
||||
}
|
||||
|
||||
export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
if (chargeAnims.has(chargeAnim)) {
|
||||
@ -569,6 +606,16 @@ export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): P
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads encounter animation assets to scene
|
||||
* MUST be called after [initEncounterAnims()](./battle-anims.ts) to load all required animations properly
|
||||
* @param scene
|
||||
* @param startLoad
|
||||
*/
|
||||
export async function loadEncounterAnimAssets(scene: BattleScene, startLoad?: boolean): Promise<void> {
|
||||
await loadAnimAssets(scene, Array.from(encounterAnims.values()), startLoad);
|
||||
}
|
||||
|
||||
export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
|
||||
@ -678,14 +725,16 @@ export abstract class BattleAnim {
|
||||
public target: Pokemon | null;
|
||||
public sprites: Phaser.GameObjects.Sprite[];
|
||||
public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle;
|
||||
public playOnEmptyField: boolean;
|
||||
|
||||
private srcLine: number[];
|
||||
private dstLine: number[];
|
||||
|
||||
constructor(user?: Pokemon, target?: Pokemon) {
|
||||
constructor(user?: Pokemon, target?: Pokemon, playOnEmptyField: boolean = false) {
|
||||
this.user = user ?? null;
|
||||
this.target = target ?? null;
|
||||
this.sprites = [];
|
||||
this.playOnEmptyField = playOnEmptyField;
|
||||
}
|
||||
|
||||
abstract getAnim(): AnimConfig | null;
|
||||
@ -757,9 +806,9 @@ export abstract class BattleAnim {
|
||||
play(scene: BattleScene, callback?: Function) {
|
||||
const isOppAnim = this.isOppAnim();
|
||||
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
|
||||
const target = !isOppAnim ? this.target : this.user;
|
||||
const target = !isOppAnim ? this.target! : this.user!;
|
||||
|
||||
if (!target?.isOnField()) {
|
||||
if (!target?.isOnField() && !this.playOnEmptyField) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
@ -983,13 +1032,181 @@ export abstract class BattleAnim {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getGraphicFrameDataWithoutTarget(frames: AnimFrame[], targetInitialX: number, targetInitialY: number): Map<integer, Map<AnimFrameTarget, GraphicFrameData>> {
|
||||
const ret: Map<integer, Map<AnimFrameTarget, GraphicFrameData>> = new Map([
|
||||
[AnimFrameTarget.GRAPHIC, new Map<AnimFrameTarget, GraphicFrameData>() ],
|
||||
[AnimFrameTarget.USER, new Map<AnimFrameTarget, GraphicFrameData>() ],
|
||||
[AnimFrameTarget.TARGET, new Map<AnimFrameTarget, GraphicFrameData>() ]
|
||||
]);
|
||||
|
||||
let g = 0;
|
||||
let u = 0;
|
||||
let t = 0;
|
||||
|
||||
for (const frame of frames) {
|
||||
let { x , y } = frame;
|
||||
const scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1);
|
||||
const scaleY = (frame.zoomY / 100);
|
||||
x += targetInitialX;
|
||||
y += targetInitialY;
|
||||
const angle = -frame.angle;
|
||||
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
|
||||
ret.get(frame.target)?.set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle });
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param scene
|
||||
* @param targetInitialX
|
||||
* @param targetInitialY
|
||||
* @param frameTimeMult
|
||||
* @param frameTimedEventPriority
|
||||
* - 0 is behind all other sprites (except BG)
|
||||
* - 1 on top of player field
|
||||
* - 3 is on top of both fields
|
||||
* - 5 is on top of player sprite
|
||||
* @param callback
|
||||
*/
|
||||
playWithoutTargets(scene: BattleScene, targetInitialX: number, targetInitialY: number, frameTimeMult: number, frameTimedEventPriority?: 0 | 1 | 3 | 5, callback?: Function) {
|
||||
const spriteCache: SpriteCache = {
|
||||
[AnimFrameTarget.GRAPHIC]: [],
|
||||
[AnimFrameTarget.USER]: [],
|
||||
[AnimFrameTarget.TARGET]: []
|
||||
};
|
||||
const spritePriorities: integer[] = [];
|
||||
|
||||
const cleanUpAndComplete = () => {
|
||||
for (const ms of Object.values(spriteCache).flat()) {
|
||||
if (ms) {
|
||||
ms.destroy();
|
||||
}
|
||||
}
|
||||
if (this.bgSprite) {
|
||||
this.bgSprite.destroy();
|
||||
}
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
if (!scene.moveAnimations) {
|
||||
return cleanUpAndComplete();
|
||||
}
|
||||
|
||||
const anim = this.getAnim();
|
||||
|
||||
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
|
||||
this.dstLine = [ 150, 75, targetInitialX, targetInitialY ];
|
||||
|
||||
let r = anim!.frames.length;
|
||||
let f = 0;
|
||||
|
||||
const existingFieldSprites = [...scene.field.getAll()];
|
||||
|
||||
scene.tweens.addCounter({
|
||||
duration: Utils.getFrameMs(3) * frameTimeMult,
|
||||
repeat: anim!.frames.length,
|
||||
onRepeat: () => {
|
||||
const spriteFrames = anim!.frames[f];
|
||||
const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[f], targetInitialX, targetInitialY);
|
||||
const u = 0;
|
||||
const t = 0;
|
||||
let g = 0;
|
||||
for (const frame of spriteFrames) {
|
||||
if (frame.target !== AnimFrameTarget.GRAPHIC) {
|
||||
console.log("Encounter animations do not support targets");
|
||||
continue;
|
||||
}
|
||||
|
||||
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
|
||||
if (g === sprites.length) {
|
||||
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1);
|
||||
sprites.push(newSprite);
|
||||
scene.field.add(newSprite);
|
||||
spritePriorities.push(1);
|
||||
}
|
||||
|
||||
const graphicIndex = g++;
|
||||
const moveSprite = sprites[graphicIndex];
|
||||
spritePriorities[graphicIndex] = frame.priority;
|
||||
if (!isNullOrUndefined(frame.priority)) {
|
||||
const setSpritePriority = (priority: integer) => {
|
||||
if (existingFieldSprites.length > priority) {
|
||||
// Move to specified priority index
|
||||
scene.field.moveTo(moveSprite, scene.field.getIndex(existingFieldSprites[priority]));
|
||||
} else {
|
||||
// Move to top of scene
|
||||
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
|
||||
}
|
||||
};
|
||||
setSpritePriority(frame.priority);
|
||||
}
|
||||
moveSprite.setFrame(frame.graphicFrame);
|
||||
|
||||
const graphicFrameData = frameData.get(frame.target)?.get(graphicIndex);
|
||||
if (graphicFrameData) {
|
||||
moveSprite.setPosition(graphicFrameData.x, graphicFrameData.y);
|
||||
moveSprite.setAngle(graphicFrameData.angle);
|
||||
moveSprite.setScale(graphicFrameData.scaleX, graphicFrameData.scaleY);
|
||||
|
||||
moveSprite.setAlpha(frame.opacity / 255);
|
||||
moveSprite.setVisible(frame.visible);
|
||||
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
|
||||
}
|
||||
}
|
||||
if (anim?.frameTimedEvents.get(f)) {
|
||||
for (const event of anim.frameTimedEvents.get(f)!) {
|
||||
r = Math.max((anim.frames.length - f) + event.execute(scene, this, frameTimedEventPriority), r);
|
||||
}
|
||||
}
|
||||
const targets = Utils.getEnumValues(AnimFrameTarget);
|
||||
for (const i of targets) {
|
||||
const count = i === AnimFrameTarget.GRAPHIC ? g : i === AnimFrameTarget.USER ? u : t;
|
||||
if (count < spriteCache[i].length) {
|
||||
const spritesToRemove = spriteCache[i].slice(count, spriteCache[i].length);
|
||||
for (const rs of spritesToRemove) {
|
||||
if (!rs.getData("locked") as boolean) {
|
||||
const spriteCacheIndex = spriteCache[i].indexOf(rs);
|
||||
spriteCache[i].splice(spriteCacheIndex, 1);
|
||||
if (i === AnimFrameTarget.GRAPHIC) {
|
||||
spritePriorities.splice(spriteCacheIndex, 1);
|
||||
}
|
||||
rs.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
f++;
|
||||
r--;
|
||||
},
|
||||
onComplete: () => {
|
||||
for (const ms of Object.values(spriteCache).flat()) {
|
||||
if (ms && !ms.getData("locked")) {
|
||||
ms.destroy();
|
||||
}
|
||||
}
|
||||
if (r) {
|
||||
scene.tweens.addCounter({
|
||||
duration: Utils.getFrameMs(r),
|
||||
onComplete: () => cleanUpAndComplete()
|
||||
});
|
||||
} else {
|
||||
cleanUpAndComplete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class CommonBattleAnim extends BattleAnim {
|
||||
public commonAnim: CommonAnim | null;
|
||||
|
||||
constructor(commonAnim: CommonAnim | null, user: Pokemon, target?: Pokemon) {
|
||||
super(user, target || user);
|
||||
constructor(commonAnim: CommonAnim | null, user: Pokemon, target?: Pokemon, playOnEmptyField: boolean = false) {
|
||||
super(user, target || user, playOnEmptyField);
|
||||
|
||||
this.commonAnim = commonAnim;
|
||||
}
|
||||
@ -1051,6 +1268,26 @@ export class MoveChargeAnim extends MoveAnim {
|
||||
}
|
||||
}
|
||||
|
||||
export class EncounterBattleAnim extends BattleAnim {
|
||||
public encounterAnim: EncounterAnim;
|
||||
public oppAnim: boolean;
|
||||
|
||||
constructor(encounterAnim: EncounterAnim, user: Pokemon, target?: Pokemon, oppAnim?: boolean) {
|
||||
super(user, target || user, true);
|
||||
|
||||
this.encounterAnim = encounterAnim;
|
||||
this.oppAnim = oppAnim ?? false;
|
||||
}
|
||||
|
||||
getAnim(): AnimConfig | null {
|
||||
return encounterAnims.get(this.encounterAnim) ?? null;
|
||||
}
|
||||
|
||||
isOppAnim(): boolean {
|
||||
return this.oppAnim;
|
||||
}
|
||||
}
|
||||
|
||||
export async function populateAnims() {
|
||||
const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase());
|
||||
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/\_/g, ""));
|
||||
|
@ -6,7 +6,7 @@ import { StatusEffect } from "./status-effect";
|
||||
import * as Utils from "../utils";
|
||||
import { ChargeAttr, MoveFlags, allMoves } from "./move";
|
||||
import { Type } from "./type";
|
||||
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability";
|
||||
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
|
||||
import { TerrainType } from "./terrain";
|
||||
import { WeatherType } from "./weather";
|
||||
import { BattleStat } from "./battle-stat";
|
||||
@ -1827,6 +1827,37 @@ export class ExposedTag extends BattlerTag {
|
||||
}
|
||||
}
|
||||
|
||||
export class MysteryEncounterPostSummonTag extends BattlerTag {
|
||||
constructor(sourceMove: Moves) {
|
||||
super(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON, BattlerTagLapseType.CUSTOM, 1, sourceMove);
|
||||
}
|
||||
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
super.onAdd(pokemon);
|
||||
}
|
||||
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
const ret = super.lapse(pokemon, lapseType);
|
||||
|
||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
||||
// Give pokemon +1 stats for battle
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
||||
if (!cancelled.value) {
|
||||
const mysteryEncounterBattleEffects = pokemon.mysteryEncounterBattleEffects;
|
||||
if (mysteryEncounterBattleEffects) {
|
||||
mysteryEncounterBattleEffects(pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
onRemove(pokemon: Pokemon): void {
|
||||
super.onRemove(pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
|
||||
switch (tagType) {
|
||||
@ -1962,6 +1993,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||
case BattlerTagType.GULP_MISSILE_ARROKUDA:
|
||||
case BattlerTagType.GULP_MISSILE_PIKACHU:
|
||||
return new GulpMissileTag(tagType, sourceMove);
|
||||
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
||||
return new MysteryEncounterPostSummonTag(sourceMove);
|
||||
case BattlerTagType.NONE:
|
||||
default:
|
||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||
|
@ -909,6 +909,126 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
||||
]
|
||||
}
|
||||
],
|
||||
[TrainerType.BUCK]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:stat_trainer_buck.encounter.1",
|
||||
"dialogue:stat_trainer_buck.encounter.2"
|
||||
],
|
||||
victory: [
|
||||
"dialogue:stat_trainer_buck.victory.1"
|
||||
],
|
||||
defeat: [
|
||||
"dialogue:stat_trainer_buck.defeat.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
[TrainerType.CHERYL]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:stat_trainer_cheryl.encounter.1",
|
||||
"dialogue:stat_trainer_cheryl.encounter.2"
|
||||
],
|
||||
victory: [
|
||||
"dialogue:stat_trainer_cheryl.victory.1"
|
||||
],
|
||||
defeat: [
|
||||
"dialogue:stat_trainer_cheryl.defeat.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
[TrainerType.MARLEY]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:stat_trainer_marley.encounter.1",
|
||||
"dialogue:stat_trainer_marley.encounter.2"
|
||||
],
|
||||
victory: [
|
||||
"dialogue:stat_trainer_marley.victory.1"
|
||||
],
|
||||
defeat: [
|
||||
"dialogue:stat_trainer_marley.defeat.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
[TrainerType.MIRA]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:stat_trainer_mira.encounter.1",
|
||||
"dialogue:stat_trainer_mira.encounter.2"
|
||||
],
|
||||
victory: [
|
||||
"dialogue:stat_trainer_mira.victory.1"
|
||||
],
|
||||
defeat: [
|
||||
"dialogue:stat_trainer_mira.defeat.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
[TrainerType.RILEY]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:stat_trainer_riley.encounter.1",
|
||||
"dialogue:stat_trainer_riley.encounter.2"
|
||||
],
|
||||
victory: [
|
||||
"dialogue:stat_trainer_riley.victory.1"
|
||||
],
|
||||
defeat: [
|
||||
"dialogue:stat_trainer_riley.defeat.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
[TrainerType.VICTOR]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:winstrates_victor.encounter.1",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:winstrates_victor.victory.1"
|
||||
],
|
||||
}
|
||||
],
|
||||
[TrainerType.VICTORIA]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:winstrates_victoria.encounter.1",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:winstrates_victoria.victory.1"
|
||||
],
|
||||
}
|
||||
],
|
||||
[TrainerType.VIVI]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:winstrates_vivi.encounter.1",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:winstrates_vivi.victory.1"
|
||||
],
|
||||
}
|
||||
],
|
||||
[TrainerType.VICKY]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:winstrates_vicky.encounter.1",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:winstrates_vicky.victory.1"
|
||||
],
|
||||
}
|
||||
],
|
||||
[TrainerType.VITO]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:winstrates_vito.encounter.1",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:winstrates_vito.victory.1"
|
||||
],
|
||||
}
|
||||
],
|
||||
[TrainerType.BROCK]: {
|
||||
encounter: [
|
||||
"dialogue:brock.encounter.1",
|
||||
|
@ -61,7 +61,10 @@ export interface IEggOptions {
|
||||
/** Defines if the egg will hatch with the hidden ability of this species.
|
||||
* If no hidden ability exist, a random one will get choosen.
|
||||
*/
|
||||
overrideHiddenAbility?: boolean
|
||||
overrideHiddenAbility?: boolean,
|
||||
|
||||
/** If Egg is of {@link EggSourceType.EVENT}, can customize the message displayed for where the egg was obtained */
|
||||
eventEggTypeDescriptor?: string;
|
||||
}
|
||||
|
||||
export class Egg {
|
||||
@ -83,6 +86,8 @@ export class Egg {
|
||||
|
||||
private _overrideHiddenAbility: boolean;
|
||||
|
||||
private _eventEggTypeDescriptor?: string;
|
||||
|
||||
////
|
||||
// #endregion
|
||||
////
|
||||
@ -180,6 +185,8 @@ export class Egg {
|
||||
this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
|
||||
this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
|
||||
}
|
||||
|
||||
this._eventEggTypeDescriptor = eggOptions?.eventEggTypeDescriptor;
|
||||
}
|
||||
|
||||
////
|
||||
@ -279,6 +286,8 @@ export class Egg {
|
||||
return i18next.t("egg:gachaTypeShiny");
|
||||
case EggSourceType.GACHA_MOVE:
|
||||
return i18next.t("egg:gachaTypeMove");
|
||||
case EggSourceType.EVENT:
|
||||
return this._eventEggTypeDescriptor ?? i18next.t("egg:eventType");
|
||||
default:
|
||||
console.warn("getEggTypeDescriptor case not defined. Returning default empty string");
|
||||
return "";
|
||||
|
@ -0,0 +1,184 @@
|
||||
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs, } from "#app/data/trainer-config";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { Species } from "#enums/species";
|
||||
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import i18next from "i18next";
|
||||
import { IEggOptions } from "#app/data/egg";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:aTrainersTest";
|
||||
|
||||
/**
|
||||
* A Trainer's Test encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/115 | GitHub Issue #115}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const ATrainersTestEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withSceneWaveRangeRequirement(100, 180)
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Randomly pick from 1 of the 5 stat trainers to spawn
|
||||
let trainerType: TrainerType;
|
||||
let spriteKeys;
|
||||
let trainerNameKey: string;
|
||||
switch (randSeedInt(5)) {
|
||||
default:
|
||||
case 0:
|
||||
trainerType = TrainerType.BUCK;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
|
||||
trainerNameKey = "buck";
|
||||
break;
|
||||
case 1:
|
||||
trainerType = TrainerType.CHERYL;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY);
|
||||
trainerNameKey = "cheryl";
|
||||
break;
|
||||
case 2:
|
||||
trainerType = TrainerType.MARLEY;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE);
|
||||
trainerNameKey = "marley";
|
||||
break;
|
||||
case 3:
|
||||
trainerType = TrainerType.MIRA;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1);
|
||||
trainerNameKey = "mira";
|
||||
break;
|
||||
case 4:
|
||||
trainerType = TrainerType.RILEY;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1);
|
||||
trainerNameKey = "riley";
|
||||
break;
|
||||
}
|
||||
|
||||
// Dialogue and tokens for trainer
|
||||
encounter.dialogue.intro = [
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}.${trainerNameKey}.intro_dialogue`
|
||||
}
|
||||
];
|
||||
encounter.options[0].dialogue!.selected = [
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}.${trainerNameKey}.accept`
|
||||
}
|
||||
];
|
||||
encounter.options[1].dialogue!.selected = [
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}.${trainerNameKey}.decline`
|
||||
}
|
||||
];
|
||||
|
||||
encounter.setDialogueToken("statTrainerName", i18next.t(`trainerNames:${trainerNameKey}`));
|
||||
const eggDescription = i18next.t(`${namespace}.title`) + ":\n" + i18next.t(`trainerNames:${trainerNameKey}`);
|
||||
encounter.misc = { trainerType, trainerNameKey, trainerEggDescription: eggDescription };
|
||||
|
||||
// Trainer config
|
||||
const trainerConfig = trainerConfigs[trainerType].copy();
|
||||
const trainerSpriteKey = trainerConfig.getSpriteKey();
|
||||
encounter.enemyPartyConfigs.push({
|
||||
levelAdditiveMultiplier: 1,
|
||||
trainerConfig: trainerConfig
|
||||
});
|
||||
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: spriteKeys.spriteKey,
|
||||
fileRoot: spriteKeys.fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
x: 22,
|
||||
y: -2,
|
||||
yShadow: -2
|
||||
},
|
||||
{
|
||||
spriteKey: trainerSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
disableAnimation: true,
|
||||
x: -24,
|
||||
y: 4,
|
||||
yShadow: 4
|
||||
}
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withIntroDialogue()
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Spawn standard trainer battle with memory mushroom reward
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
|
||||
const eggOptions: IEggOptions = {
|
||||
scene,
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eventEggTypeDescriptor: encounter.misc.trainerEggDescription,
|
||||
tier: EggTier.ULTRA
|
||||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`));
|
||||
setEncounterRewards(scene, { fillRemaining: true }, [eggOptions]);
|
||||
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Full heal party
|
||||
scene.unshiftPhase(new PartyHealPhase(scene, true));
|
||||
|
||||
const eggOptions: IEggOptions = {
|
||||
scene,
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eventEggTypeDescriptor: encounter.misc.trainerEggDescription,
|
||||
tier: EggTier.GREAT
|
||||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.rare`));
|
||||
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: 0 }, [eggOptions]);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}.outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
@ -0,0 +1,514 @@
|
||||
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { PersistentModifierRequirement } from "../mystery-encounter-requirements";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import { randInt } from "#app/utils";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { PokeballType } from "#app/data/pokeball";
|
||||
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { StatChangePhase } from "#app/phases/stat-change-phase";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:absoluteAvarice";
|
||||
|
||||
/**
|
||||
* Absolute Avarice encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/58 | GitHub Issue #58}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
// This sprite has the shadow
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.GREEDENT,
|
||||
hasShadow: true,
|
||||
alpha: 0.001,
|
||||
repeat: true,
|
||||
x: -5
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.GREEDENT,
|
||||
hasShadow: false,
|
||||
repeat: true,
|
||||
x: -5
|
||||
},
|
||||
{
|
||||
spriteKey: "lum_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: -14,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "salac_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 2,
|
||||
y: 4,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "lansat_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 32,
|
||||
y: 5,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "liechi_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 6,
|
||||
y: -5,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "sitrus_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: 8,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "enigma_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 26,
|
||||
y: -4,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "leppa_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -27,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "petaya_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 30,
|
||||
y: -17,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "ganlon_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -11,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "apicot_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 14,
|
||||
y: -2,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "starf_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 18,
|
||||
y: 9,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
])
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
doGreedentSpriteSteal(scene);
|
||||
doBerrySpritePile(scene);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
}
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
|
||||
scene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
|
||||
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
|
||||
// Sort berries by party member ID to more easily re-add later if necessary
|
||||
const berryItemsMap = new Map<number, BerryModifier[]>();
|
||||
scene.getParty().forEach(pokemon => {
|
||||
const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
|
||||
if (pokemonBerries?.length > 0) {
|
||||
berryItemsMap.set(pokemon.id, pokemonBerries);
|
||||
}
|
||||
});
|
||||
|
||||
encounter.misc = { berryItemsMap };
|
||||
|
||||
// Generates copies of the stolen berries to put on the Greedent
|
||||
const bossModifierConfigs: HeldModifierConfig[] = [];
|
||||
berryItems.forEach(berryMod => {
|
||||
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
|
||||
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
|
||||
for (let i = 0; i < berryMod.stackCount; i++) {
|
||||
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
|
||||
bossModifierConfigs.push({ modifierType });
|
||||
}
|
||||
|
||||
scene.removeModifier(berryMod);
|
||||
});
|
||||
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.GREEDENT),
|
||||
isBoss: true,
|
||||
bossSegments: 3,
|
||||
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF],
|
||||
modifierConfigs: bossModifierConfigs,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Provides 1x Reviver Seed to each party member at end of battle
|
||||
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
|
||||
const givePartyPokemonReviverSeeds = () => {
|
||||
const party = scene.getParty();
|
||||
party.forEach(p => {
|
||||
const seedModifier = revSeed.newModifier(p);
|
||||
scene.addModifier(seedModifier, false, false, false, true);
|
||||
});
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.food_stash`);
|
||||
};
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
|
||||
encounter.startOfBattleEffects.push({
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY],
|
||||
move: new PokemonMove(Moves.STUFF_CHEEKS),
|
||||
ignorePp: true
|
||||
});
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const berryMap = encounter.misc.berryItemsMap;
|
||||
|
||||
// Returns 2/5 of the berries stolen from each Pokemon
|
||||
const party = scene.getParty();
|
||||
party.forEach(pokemon => {
|
||||
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
|
||||
const berryTypesAsArray: BerryType[] = [];
|
||||
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
|
||||
const returnedBerryCount = Math.floor((berryTypesAsArray.length ?? 0) * 2 / 5);
|
||||
|
||||
if (returnedBerryCount > 0) {
|
||||
for (let i = 0; i < returnedBerryCount; i++) {
|
||||
// Shuffle remaining berry types and pop
|
||||
Phaser.Math.RND.shuffle(berryTypesAsArray);
|
||||
const randBerryType = berryTypesAsArray.pop();
|
||||
|
||||
const berryModType = generateModifierType(scene, modifierTypes.BERRY, [randBerryType]) as BerryModifierType;
|
||||
applyModifierTypeToPlayerPokemon(scene, pokemon, berryModType);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Animate berries being eaten
|
||||
doGreedentEatBerries(scene);
|
||||
doBerrySpritePile(scene, true);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Let it have the food
|
||||
// Greedent joins the team, level equal to 2 below highest party member
|
||||
const level = getHighestLevelPlayerPokemon(scene).level - 2;
|
||||
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false);
|
||||
greedent.moveset = [new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF)];
|
||||
greedent.passive = true;
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await catchPokemon(scene, greedent, null, PokeballType.POKEBALL, false);
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
|
||||
function doGreedentSpriteSteal(scene: BattleScene) {
|
||||
const shakeDelay = 50;
|
||||
const slideDelay = 500;
|
||||
|
||||
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals?.getSpriteAtIndex(1);
|
||||
|
||||
scene.playSound("Follow Me");
|
||||
scene.tweens.chain({
|
||||
targets: greedentSprites,
|
||||
tweens: [
|
||||
{ // Slide Greedent diagonally
|
||||
duration: slideDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
y: "+=75",
|
||||
x: "-=65",
|
||||
scale: 1.1
|
||||
},
|
||||
{ // Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Slide Greedent diagonally
|
||||
duration: slideDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
y: "-=75",
|
||||
x: "+=65",
|
||||
scale: 1
|
||||
},
|
||||
{ // Bounce at the end
|
||||
duration: 300,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: "-=20",
|
||||
loop: 1,
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function doGreedentEatBerries(scene: BattleScene) {
|
||||
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals?.getSpriteAtIndex(1);
|
||||
let index = 1;
|
||||
scene.tweens.add({
|
||||
targets: greedentSprites,
|
||||
duration: 150,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: "-=8",
|
||||
loop: 5,
|
||||
onStart: () => {
|
||||
scene.playSound("PRSFX- Bug Bite");
|
||||
},
|
||||
onLoop: () => {
|
||||
if (index % 2 === 0) {
|
||||
scene.playSound("PRSFX- Bug Bite");
|
||||
}
|
||||
index++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param scene
|
||||
* @param isEat - default false. Will "create" pile when false, and remove pile when true.
|
||||
*/
|
||||
function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
|
||||
const berryAddDelay = 150;
|
||||
let animationOrder = ["starf", "sitrus", "lansat", "salac", "apicot", "enigma", "liechi", "ganlon", "lum", "petaya", "leppa"];
|
||||
if (isEat) {
|
||||
animationOrder = animationOrder.reverse();
|
||||
}
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
animationOrder.forEach((berry, i) => {
|
||||
const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry));
|
||||
let sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite;
|
||||
const sprites = encounter.introVisuals?.getSpriteAtIndex(introVisualsIndex);
|
||||
if (sprites) {
|
||||
sprite = sprites[0];
|
||||
tintSprite = sprites[1];
|
||||
}
|
||||
scene.time.delayedCall(berryAddDelay * i + 400, () => {
|
||||
if (sprite) {
|
||||
sprite.setVisible(!isEat);
|
||||
}
|
||||
if (tintSprite) {
|
||||
tintSprite.setVisible(!isEat);
|
||||
}
|
||||
|
||||
// Animate Petaya berry falling off the pile
|
||||
if (berry === "petaya" && sprite && tintSprite && !isEat) {
|
||||
scene.time.delayedCall(200, () => {
|
||||
doBerryBounce(scene, [sprite, tintSprite], 30, 500);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doBerryBounce(scene: BattleScene, berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: integer) {
|
||||
let bouncePower = 1;
|
||||
let bounceYOffset = yd;
|
||||
|
||||
const doBounce = () => {
|
||||
scene.tweens.add({
|
||||
targets: berrySprites,
|
||||
y: "+=" + bounceYOffset,
|
||||
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
|
||||
duration: bouncePower * baseBounceDuration,
|
||||
ease: "Cubic.easeIn",
|
||||
onComplete: () => {
|
||||
bouncePower = bouncePower > 0.01 ? bouncePower * 0.5 : 0;
|
||||
|
||||
if (bouncePower) {
|
||||
bounceYOffset = bounceYOffset * bouncePower;
|
||||
|
||||
scene.tweens.add({
|
||||
targets: berrySprites,
|
||||
y: "-=" + bounceYOffset,
|
||||
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
|
||||
duration: bouncePower * baseBounceDuration,
|
||||
ease: "Cubic.easeOut",
|
||||
onComplete: () => doBounce()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
doBounce();
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements";
|
||||
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:offerYouCantRefuse";
|
||||
|
||||
/**
|
||||
* An Offer You Can't Refuse encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/72 | GitHub Issue #72}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.LIEPARD.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 0,
|
||||
y: -4,
|
||||
yShadow: -4
|
||||
},
|
||||
{
|
||||
spriteKey: "rich_kid_m",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 2,
|
||||
y: 5,
|
||||
yShadow: 5
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
|
||||
const price = scene.getWaveMoneyAmount(10);
|
||||
|
||||
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("price", price.toString());
|
||||
|
||||
// Store pokemon and price
|
||||
encounter.misc = {
|
||||
pokemon: pokemon,
|
||||
price: price
|
||||
};
|
||||
|
||||
// If player meets the combo OR requirements for option 2, populate the token
|
||||
const opt2Req = encounter.options[1].primaryPokemonRequirements[0];
|
||||
if (opt2Req.meetsRequirement(scene)) {
|
||||
const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"];
|
||||
const moveToken = encounter.dialogueTokens["option2PrimaryMove"];
|
||||
if (abilityToken) {
|
||||
encounter.setDialogueToken("moveOrAbility", abilityToken);
|
||||
} else if (moveToken) {
|
||||
encounter.setDialogueToken("moveOrAbility", moveToken);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Update money and remove pokemon from party
|
||||
updatePlayerMoney(scene, encounter.misc.price);
|
||||
scene.removePokemonFromPlayerParty(encounter.misc.pokemon);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Give the player a Shiny charm
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM));
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
|
||||
new MoveRequirement(EXTORTION_MOVES),
|
||||
new AbilityRequirement(EXTORTION_ABILITIES))
|
||||
)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.2.tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Extort the rich kid for money
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Update money and remove pokemon from party
|
||||
updatePlayerMoney(scene, encounter.misc.price);
|
||||
|
||||
setEncounterExp(scene, encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
@ -0,0 +1,343 @@
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import {
|
||||
EnemyPartyConfig, generateModifierType, generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle, setEncounterExp,
|
||||
setEncounterRewards
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import {
|
||||
BerryModifierType,
|
||||
getPartyLuckValue,
|
||||
ModifierPoolType,
|
||||
ModifierTypeOption, modifierTypes,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#app/modifier/modifier-type";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { applyModifierTypeToPlayerPokemon, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { StatChangePhase } from "#app/phases/stat-change-phase";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:berriesAbound";
|
||||
|
||||
/**
|
||||
* Berries Abound encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const BerriesAboundEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "lum_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: -14,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "salac_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 2,
|
||||
y: 4,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "lansat_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 32,
|
||||
y: 5,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "liechi_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 6,
|
||||
y: -5,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "sitrus_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: 8,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "enigma_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 26,
|
||||
y: -4,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "leppa_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -27,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "petaya_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 30,
|
||||
y: -17,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "ganlon_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -11,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "apicot_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 14,
|
||||
y: -2,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "starf_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 18,
|
||||
y: 9,
|
||||
disableAnimation: true
|
||||
},
|
||||
]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Calculate boss mon
|
||||
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, scene.currentBattle.waveIndex, 0, getPartyLuckValue(scene.getParty()), true);
|
||||
const bossPokemon = new EnemyPokemon(scene, bossSpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, true);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
pokemonConfigs: [{
|
||||
species: bossSpecies,
|
||||
dataSource: new PokemonData(bossPokemon),
|
||||
isBoss: true
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Calculate the number of extra berries that player receives
|
||||
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
|
||||
const numBerries =
|
||||
scene.currentBattle.waveIndex > 160 ? 7
|
||||
: scene.currentBattle.waveIndex > 120 ? 5
|
||||
: scene.currentBattle.waveIndex > 40 ? 4 : 2;
|
||||
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
|
||||
encounter.misc = { numBerries };
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
encounter.spriteConfigs.push({
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true
|
||||
});
|
||||
|
||||
// Get fastest party pokemon for option 2
|
||||
const fastestPokemon = getHighestStatPlayerPokemon(scene, Stat.SPD, true);
|
||||
encounter.misc.fastestPokemon = fastestPokemon;
|
||||
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
|
||||
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const numBerries = encounter.misc.numBerries;
|
||||
|
||||
const doBerryRewards = async () => {
|
||||
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
queueEncounterMessage(scene, i18next.t("battle:rewardGain", { modifierName: berryText }));
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it
|
||||
for (let i = 0; i < numBerries; i++) {
|
||||
await tryGiveBerry(scene);
|
||||
}
|
||||
};
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
shopOptions.push(generateModifierTypeOption(scene, modifierTypes.BERRY));
|
||||
}
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
|
||||
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Pick race for berries
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const fastestPokemon = encounter.misc.fastestPokemon;
|
||||
const enemySpeed = encounter.misc.enemySpeed;
|
||||
const speedDiff = fastestPokemon.getStat(Stat.SPD) / enemySpeed;
|
||||
const numBerries = encounter.misc.numBerries;
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
shopOptions.push(generateModifierTypeOption(scene, modifierTypes.BERRY));
|
||||
}
|
||||
|
||||
if (speedDiff < 1) {
|
||||
// Caught and attacked by boss, gets +1 to all stats at start of fight
|
||||
const doBerryRewards = async () => {
|
||||
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
queueEncounterMessage(scene, i18next.t("battle:rewardGain", { modifierName: berryText }));
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it
|
||||
for (let i = 0; i < numBerries; i++) {
|
||||
await tryGiveBerry(scene);
|
||||
}
|
||||
};
|
||||
|
||||
const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
|
||||
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
|
||||
};
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
|
||||
await showEncounterText(scene, `${namespace}.option.2.selected_bad`);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
return;
|
||||
} else {
|
||||
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 1
|
||||
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1)/0.1), numBerries), 1);
|
||||
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
|
||||
const doFasterBerryRewards = async () => {
|
||||
const berryText = numBerriesGrabbed + " " + i18next.t(`${namespace}.berries`);
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
queueEncounterMessage(scene, i18next.t("battle:rewardGain", { modifierName: berryText }));
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
|
||||
for (let i = 0; i < numBerriesGrabbed; i++) {
|
||||
await tryGiveBerry(scene, fastestPokemon);
|
||||
}
|
||||
};
|
||||
|
||||
setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards);
|
||||
await showEncounterText(scene, `${namespace}.option.2.selected`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
|
||||
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
|
||||
const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType;
|
||||
|
||||
const party = scene.getParty();
|
||||
|
||||
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
|
||||
if (prioritizedPokemon) {
|
||||
const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier
|
||||
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the party until berry was successfully given
|
||||
for (const pokemon of party) {
|
||||
const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier
|
||||
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, pokemon, berry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,496 @@
|
||||
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { Species } from "#enums/species";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { Type } from "#app/data/type";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { randSeedInt, randSeedShuffle } from "#app/utils";
|
||||
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import i18next from "i18next";
|
||||
import { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { Ability } from "#app/data/ability";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
import { MoveCategory } from "#app/data/move";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:clowningAround";
|
||||
|
||||
const RANDOM_ABILITY_POOL = [
|
||||
Abilities.STURDY,
|
||||
Abilities.PICKUP,
|
||||
Abilities.INTIMIDATE,
|
||||
Abilities.GUTS,
|
||||
Abilities.DROUGHT,
|
||||
Abilities.DRIZZLE,
|
||||
Abilities.SNOW_WARNING,
|
||||
Abilities.SAND_STREAM,
|
||||
Abilities.ELECTRIC_SURGE,
|
||||
Abilities.PSYCHIC_SURGE,
|
||||
Abilities.GRASSY_SURGE,
|
||||
Abilities.MISTY_SURGE,
|
||||
Abilities.MAGICIAN,
|
||||
Abilities.SHEER_FORCE,
|
||||
Abilities.PRANKSTER
|
||||
];
|
||||
|
||||
/**
|
||||
* Clowning Around encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const ClowningAroundEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(80, 180)
|
||||
.withAnimations(EncounterAnim.SMOKESCREEN)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.MR_MIME.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: -25,
|
||||
tint: 0.3,
|
||||
y: -3,
|
||||
yShadow: -3
|
||||
},
|
||||
{
|
||||
spriteKey: Species.BLACEPHALON.toString(),
|
||||
fileRoot: "pokemon/exp",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 25,
|
||||
tint: 0.3,
|
||||
y: -3,
|
||||
yShadow: -3
|
||||
},
|
||||
{
|
||||
spriteKey: "harlequin",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 0,
|
||||
y: 2,
|
||||
yShadow: 2
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
speaker: `${namespace}.speaker`
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
const clownTrainerType = TrainerType.HARLEQUIN;
|
||||
const clownConfig = trainerConfigs[clownTrainerType].copy();
|
||||
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
|
||||
clownConfig.setPartyTemplates(clownPartyTemplate);
|
||||
clownConfig.setDoubleOnly();
|
||||
// @ts-ignore
|
||||
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
|
||||
|
||||
// Generate random ability for Blacephalon from pool
|
||||
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
|
||||
encounter.setDialogueToken("ability", new Ability(ability, 3).name);
|
||||
encounter.misc = { ability };
|
||||
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: clownConfig,
|
||||
pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon
|
||||
{
|
||||
species: getPokemonSpecies(Species.MR_MIME),
|
||||
isBoss: true,
|
||||
moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC]
|
||||
},
|
||||
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
|
||||
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||
mysteryEncounterData: new MysteryEncounterPokemonData(undefined, ability, undefined, [randSeedInt(18), randSeedInt(18)]),
|
||||
isBoss: true,
|
||||
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
|
||||
},
|
||||
],
|
||||
doubleBattle: true
|
||||
});
|
||||
|
||||
// Load animations/sfx for start of fight moves
|
||||
loadCustomMovesForEncounter(scene, [Moves.ROLE_PLAY, Moves.TAUNT]);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
speaker: `${namespace}.speaker`
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Spawn battle
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
|
||||
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
|
||||
encounter.startOfBattleEffects.push(
|
||||
{ // Mr. Mime copies the Blacephalon's random ability
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY_2],
|
||||
move: new PokemonMove(Moves.ROLE_PLAY),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.TAUNT),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER_2],
|
||||
move: new PokemonMove(Moves.TAUNT),
|
||||
ignorePp: true
|
||||
});
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
// After the battle, offer the player the opportunity to permanently swap ability
|
||||
const abilityWasSwapped = await handleSwapAbility(scene);
|
||||
if (abilityWasSwapped) {
|
||||
await showEncounterText(scene, `${namespace}.option.1.ability_gained`);
|
||||
}
|
||||
|
||||
// Play animations once ability swap is complete
|
||||
// Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals
|
||||
scene.tweens.add({
|
||||
targets: scene.currentBattle.trainer,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 250
|
||||
});
|
||||
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
|
||||
background.playWithoutTargets(scene, 230, 40, 2);
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
speaker: `${namespace}.speaker`
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.2.selected_2`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.2.selected_3`,
|
||||
speaker: `${namespace}.speaker`
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Swap player's items on pokemon with the most items
|
||||
// Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items
|
||||
// So Vitamins, form change items, etc. are not included
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
const party = scene.getParty();
|
||||
let mostHeldItemsPokemon = party[0];
|
||||
let count = mostHeldItemsPokemon.getHeldItems()
|
||||
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
|
||||
party.forEach(pokemon => {
|
||||
const nextCount = pokemon.getHeldItems()
|
||||
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
if (nextCount > count) {
|
||||
mostHeldItemsPokemon = pokemon;
|
||||
count = nextCount;
|
||||
}
|
||||
});
|
||||
|
||||
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
|
||||
|
||||
const items = mostHeldItemsPokemon.getHeldItems();
|
||||
|
||||
// Shuffles Berries (if they have any)
|
||||
let numBerries = 0;
|
||||
items.filter(m => m instanceof BerryModifier)
|
||||
.forEach(m => {
|
||||
numBerries += m.stackCount;
|
||||
scene.removeModifier(m);
|
||||
});
|
||||
|
||||
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
|
||||
|
||||
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||
let numUltra = 0;
|
||||
let numRogue = 0;
|
||||
items.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||
.forEach(m => {
|
||||
const type = m.type.withTierFromPool();
|
||||
const tier = type.tier ?? ModifierTier.ULTRA;
|
||||
if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) {
|
||||
numUltra += m.stackCount;
|
||||
scene.removeModifier(m);
|
||||
} else if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||
numRogue += m.stackCount;
|
||||
scene.removeModifier(m);
|
||||
}
|
||||
});
|
||||
|
||||
generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
|
||||
generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
|
||||
background.playWithoutTargets(scene, 230, 40, 2);
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
speaker: `${namespace}.speaker`
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.3.selected_2`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.3.selected_3`,
|
||||
speaker: `${namespace}.speaker`
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Swap player's types on all party pokemon
|
||||
// If a Pokemon had a single type prior, they will still have a single type after
|
||||
for (const pokemon of scene.getParty()) {
|
||||
const originalTypes = pokemon.getTypes(false, false, true);
|
||||
|
||||
// If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
|
||||
// Makes the "randomness" of the shuffle slightly less punishing
|
||||
let priorityTypes = pokemon.moveset
|
||||
.filter(move => move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS)
|
||||
.map(move => move!.getMove().type);
|
||||
if (priorityTypes?.length > 0) {
|
||||
priorityTypes = [...new Set(priorityTypes)];
|
||||
randSeedShuffle(priorityTypes);
|
||||
}
|
||||
|
||||
let newTypes;
|
||||
if (!originalTypes || originalTypes.length < 1) {
|
||||
newTypes = priorityTypes?.length > 0 ? [priorityTypes.pop()] : [(randSeedInt(18) as Type)];
|
||||
} else {
|
||||
newTypes = originalTypes.map(m => {
|
||||
if (priorityTypes?.length > 0) {
|
||||
const ret = priorityTypes.pop();
|
||||
randSeedShuffle(priorityTypes);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return randSeedInt(18) as Type;
|
||||
});
|
||||
}
|
||||
|
||||
if (!pokemon.mysteryEncounterData) {
|
||||
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes);
|
||||
} else {
|
||||
pokemon.mysteryEncounterData.types = newTypes;
|
||||
}
|
||||
}
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
|
||||
background.playWithoutTargets(scene, 230, 40, 2);
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}.outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
||||
async function handleSwapAbility(scene: BattleScene) {
|
||||
return new Promise<boolean>(async resolve => {
|
||||
await showEncounterDialogue(scene, `${namespace}.option.1.apply_ability_dialogue`, `${namespace}.speaker`);
|
||||
await showEncounterText(scene, `${namespace}.option.1.apply_ability_message`);
|
||||
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
displayYesNoOptions(scene, resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function displayYesNoOptions(scene: BattleScene, resolve) {
|
||||
showEncounterText(scene, `${namespace}.option.1.ability_prompt`, 500, false);
|
||||
const fullOptions = [
|
||||
{
|
||||
label: i18next.t("menu:yes"),
|
||||
handler: () => {
|
||||
onYesAbilitySwap(scene, resolve);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: i18next.t("menu:no"),
|
||||
handler: () => {
|
||||
resolve(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const config: OptionSelectConfig = {
|
||||
options: fullOptions,
|
||||
maxOptions: 7,
|
||||
yOffset: 0
|
||||
};
|
||||
scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
|
||||
}
|
||||
|
||||
function onYesAbilitySwap(scene: BattleScene, resolve) {
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Do ability swap
|
||||
if (!pokemon.mysteryEncounterData) {
|
||||
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, Abilities.AERILATE);
|
||||
}
|
||||
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability;
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
||||
};
|
||||
|
||||
const onPokemonNotSelected = () => {
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
displayYesNoOptions(scene, resolve);
|
||||
});
|
||||
};
|
||||
|
||||
selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected);
|
||||
}
|
||||
|
||||
function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: integer, tier: ModifierTier | "Berries") {
|
||||
// These pools have to be defined at runtime so that modifierTypes exist
|
||||
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
|
||||
// This is to prevent "over-generating" a random item of a certain type during item swaps
|
||||
const ultraPool = [
|
||||
[modifierTypes.REVIVER_SEED, 1],
|
||||
[modifierTypes.GOLDEN_PUNCH, 5],
|
||||
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
|
||||
[modifierTypes.QUICK_CLAW, 3],
|
||||
[modifierTypes.WIDE_LENS, 3]
|
||||
];
|
||||
|
||||
const roguePool = [
|
||||
[modifierTypes.LEFTOVERS, 4],
|
||||
[modifierTypes.SHELL_BELL, 4],
|
||||
[modifierTypes.SOUL_DEW, 10],
|
||||
[modifierTypes.SOOTHE_BELL, 3],
|
||||
[modifierTypes.SCOPE_LENS, 1],
|
||||
[modifierTypes.BATON, 1],
|
||||
[modifierTypes.FOCUS_BAND, 5],
|
||||
[modifierTypes.KINGS_ROCK, 3],
|
||||
[modifierTypes.GRIP_CLAW, 5]
|
||||
];
|
||||
|
||||
const berryPool = [
|
||||
[BerryType.APICOT, 3],
|
||||
[BerryType.ENIGMA, 2],
|
||||
[BerryType.GANLON, 3],
|
||||
[BerryType.LANSAT, 3],
|
||||
[BerryType.LEPPA, 2],
|
||||
[BerryType.LIECHI, 3],
|
||||
[BerryType.LUM, 2],
|
||||
[BerryType.PETAYA, 3],
|
||||
[BerryType.SALAC, 2],
|
||||
[BerryType.SITRUS, 2],
|
||||
[BerryType.STARF, 3]
|
||||
];
|
||||
|
||||
let pool: any[];
|
||||
if (tier === "Berries") {
|
||||
pool = berryPool;
|
||||
} else {
|
||||
pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool;
|
||||
}
|
||||
|
||||
for (let i = 0; i < numItems; i++) {
|
||||
const randIndex = randSeedInt(pool.length);
|
||||
const newItemType = pool[randIndex];
|
||||
let newMod;
|
||||
if (tier === "Berries") {
|
||||
newMod = generateModifierType(scene, modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
|
||||
} else {
|
||||
newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType;
|
||||
}
|
||||
applyModifierTypeToPlayerPokemon(scene, pokemon, newMod);
|
||||
// Decrement max stacks and remove from pool if at max
|
||||
newItemType[1]--;
|
||||
if (newItemType[1] <= 0) {
|
||||
pool.splice(randIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,295 @@
|
||||
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { catchPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { StatChangePhase } from "#app/phases/stat-change-phase";
|
||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:dancingLessons";
|
||||
|
||||
// Fire form
|
||||
const BAILE_STYLE_BIOMES = [
|
||||
Biome.VOLCANO,
|
||||
Biome.BEACH,
|
||||
Biome.ISLAND,
|
||||
Biome.WASTELAND,
|
||||
Biome.MOUNTAIN,
|
||||
Biome.BADLANDS,
|
||||
Biome.DESERT
|
||||
];
|
||||
|
||||
// Electric form
|
||||
const POM_POM_STYLE_BIOMES = [
|
||||
Biome.CONSTRUCTION_SITE,
|
||||
Biome.POWER_PLANT,
|
||||
Biome.FACTORY,
|
||||
Biome.LABORATORY,
|
||||
Biome.SLUM,
|
||||
Biome.METROPOLIS,
|
||||
Biome.DOJO
|
||||
];
|
||||
|
||||
// Psychic form
|
||||
const PAU_STYLE_BIOMES = [
|
||||
Biome.JUNGLE,
|
||||
Biome.FAIRY_CAVE,
|
||||
Biome.MEADOW,
|
||||
Biome.PLAINS,
|
||||
Biome.GRASS,
|
||||
Biome.TALL_GRASS,
|
||||
Biome.FOREST
|
||||
];
|
||||
|
||||
// Ghost form
|
||||
const SENSU_STYLE_BIOMES = [
|
||||
Biome.RUINS,
|
||||
Biome.SWAMP,
|
||||
Biome.CAVE,
|
||||
Biome.ABYSS,
|
||||
Biome.GRAVEYARD,
|
||||
Biome.LAKE,
|
||||
Biome.TEMPLE
|
||||
];
|
||||
|
||||
/**
|
||||
* Dancing Lessons encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/130 | GitHub Issue #130}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DancingLessonsEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals
|
||||
.withAnimations(EncounterAnim.DANCE)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon());
|
||||
danceAnim.play(scene);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
}
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
const species = getPokemonSpecies(Species.ORICORIO);
|
||||
const enemyPokemon = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels![0], TrainerSlot.NONE, false);
|
||||
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
|
||||
if (enemyPokemon.moveset.length < 4) {
|
||||
enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE));
|
||||
} else {
|
||||
enemyPokemon.moveset[0] = new PokemonMove(Moves.REVELATION_DANCE);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the form index based on the biome
|
||||
// Defaults to Baile style if somehow nothing matches
|
||||
const currentBiome = scene.arena.biomeType;
|
||||
if (BAILE_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 0;
|
||||
} else if (POM_POM_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 1;
|
||||
} else if (PAU_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 2;
|
||||
} else if (SENSU_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 3;
|
||||
} else {
|
||||
enemyPokemon.formIndex = 0;
|
||||
}
|
||||
|
||||
const oricorioData = new PokemonData(enemyPokemon);
|
||||
|
||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||
scene.currentBattle.enemyParty[0] = enemyPokemon;
|
||||
scene.field.add(enemyPokemon);
|
||||
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
pokemonConfigs: [{
|
||||
species: species,
|
||||
dataSource: oricorioData,
|
||||
isBoss: true,
|
||||
// Gets +1 to all stats on battle start
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
|
||||
}
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
encounter.misc = {
|
||||
oricorioData
|
||||
};
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
|
||||
encounter.startOfBattleEffects.push({
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.REVELATION_DANCE),
|
||||
ignorePp: true
|
||||
});
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.BATON], fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Learn its Dance
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
scene.unshiftPhase(new LearnMovePhase(scene, scene.getParty().indexOf(pokemon), Moves.REVELATION_DANCE));
|
||||
|
||||
// Play animation again to "learn" the dance
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon());
|
||||
danceAnim.play(scene);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Learn its Dance
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Open menu for selecting pokemon with a Dancing move
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for nature selection
|
||||
return pokemon.moveset
|
||||
.filter(move => move && DANCING_MOVES.includes(move.getMove().id))
|
||||
.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and second option selected
|
||||
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("selectedMove", move.getName());
|
||||
encounter.misc.selectedMove = move;
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that have a Dancing move can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Show the Oricorio a dance, and recruit it
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const oricorio = encounter.misc.oricorioData.toPokemon(scene);
|
||||
oricorio.passive = true;
|
||||
|
||||
// Ensure the Oricorio's moveset gains the Dance move the player used
|
||||
const move = encounter.misc.selectedMove?.getMove().id;
|
||||
if (!oricorio.moveset.some(m => m.getMove().id === move)) {
|
||||
if (oricorio.moveset.length < 4) {
|
||||
oricorio.moveset.push(new PokemonMove(move));
|
||||
} else {
|
||||
oricorio.moveset[3] = new PokemonMove(move);
|
||||
}
|
||||
}
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await catchPokemon(scene, oricorio, null, PokeballType.POKEBALL, false);
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
187
src/data/mystery-encounters/encounters/dark-deal-encounter.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import { Type } from "#app/data/type";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
|
||||
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
const namespace = "mysteryEncounter:darkDeal";
|
||||
|
||||
/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and egg-locked mythicals */
|
||||
const excludedBosses = [
|
||||
Species.NECROZMA,
|
||||
Species.COSMOG,
|
||||
Species.COSMOEM,
|
||||
Species.SOLGALEO,
|
||||
Species.LUNALA,
|
||||
Species.ETERNATUS,
|
||||
Species.NIHILEGO,
|
||||
Species.BUZZWOLE,
|
||||
Species.PHEROMOSA,
|
||||
Species.XURKITREE,
|
||||
Species.CELESTEELA,
|
||||
Species.KARTANA,
|
||||
Species.GUZZLORD,
|
||||
Species.POIPOLE,
|
||||
Species.NAGANADEL,
|
||||
Species.STAKATAKA,
|
||||
Species.BLACEPHALON,
|
||||
Species.GREAT_TUSK,
|
||||
Species.SCREAM_TAIL,
|
||||
Species.BRUTE_BONNET,
|
||||
Species.FLUTTER_MANE,
|
||||
Species.SLITHER_WING,
|
||||
Species.SANDY_SHOCKS,
|
||||
Species.ROARING_MOON,
|
||||
Species.KORAIDON,
|
||||
Species.WALKING_WAKE,
|
||||
Species.GOUGING_FIRE,
|
||||
Species.RAGING_BOLT,
|
||||
Species.IRON_TREADS,
|
||||
Species.IRON_BUNDLE,
|
||||
Species.IRON_HANDS,
|
||||
Species.IRON_JUGULIS,
|
||||
Species.IRON_MOTH,
|
||||
Species.IRON_THORNS,
|
||||
Species.IRON_VALIANT,
|
||||
Species.MIRAIDON,
|
||||
Species.IRON_LEAVES,
|
||||
Species.IRON_BOULDER,
|
||||
Species.IRON_CROWN,
|
||||
Species.MEW,
|
||||
Species.CELEBI,
|
||||
Species.DEOXYS,
|
||||
Species.JIRACHI,
|
||||
Species.PHIONE,
|
||||
Species.MANAPHY,
|
||||
Species.ARCEUS,
|
||||
Species.VICTINI,
|
||||
Species.MELTAN,
|
||||
Species.PECHARUNT,
|
||||
];
|
||||
|
||||
/**
|
||||
* Dark Deal encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/61 | GitHub Issue #61}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DarkDealEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "mad_scientist_m",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "dark_deal_porygon",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withSceneWaveRangeRequirement(30, 180) // waves 30 to 180
|
||||
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
|
||||
.withCatchAllowed(true)
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.1.selected_dialogue`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.1.selected_message`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
|
||||
// Will never return last battle able mon and instead pick fainted/unable to battle
|
||||
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
|
||||
scene.removePokemonFromPlayerParty(removedPokemon);
|
||||
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
|
||||
|
||||
// Store removed pokemon types
|
||||
scene.currentBattle.mysteryEncounter.misc = [
|
||||
removedPokemon.species.type1,
|
||||
];
|
||||
if (removedPokemon.species.type2) {
|
||||
scene.currentBattle.mysteryEncounter.misc.push(removedPokemon.species.type2);
|
||||
}
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Give the player 5 Rogue Balls
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL));
|
||||
|
||||
// Start encounter with random legendary (7-10 starter strength) that has level additive
|
||||
const bossTypes = scene.currentBattle.mysteryEncounter.misc as Type[];
|
||||
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
|
||||
const roll = randSeedInt(100);
|
||||
const starterTier: number | [number, number] =
|
||||
roll > 65 ? 6 : roll > 15 ? 7 : roll > 5 ? 8 : [9, 10];
|
||||
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterTier(starterTier, excludedBosses, bossTypes));
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
};
|
||||
if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) {
|
||||
pokemonConfig.formIndex = 0;
|
||||
}
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [pokemonConfig],
|
||||
};
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}.outro`
|
||||
}
|
||||
])
|
||||
.build();
|
302
src/data/mystery-encounters/encounters/delibirdy-encounter.ts
Normal file
@ -0,0 +1,302 @@
|
||||
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements";
|
||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { HealingBoosterModifier, HiddenAbilityRateBoosterModifier, LevelIncrementBoosterModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:delibirdy";
|
||||
|
||||
/** Berries only */
|
||||
const OPTION_2_ALLOWED_MODIFIERS = ["BerryModifier", "PokemonInstantReviveModifier"];
|
||||
|
||||
/** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */
|
||||
const OPTION_3_DISALLOWED_MODIFIERS = [
|
||||
"BerryModifier",
|
||||
"PokemonInstantReviveModifier",
|
||||
"TerastallizeModifier",
|
||||
"PokemonBaseStatModifier",
|
||||
"PokemonBaseStatTotalModifier"
|
||||
];
|
||||
|
||||
/**
|
||||
* Delibird-y encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/57 | GitHub Issue #57}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DelibirdyEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Must have enough money for it to spawn at the very least
|
||||
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // Must also have either option 2 or 3 available to spawn
|
||||
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
|
||||
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)
|
||||
))
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
startFrame: 38,
|
||||
scale: 0.94
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
scale: 1.06
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
startFrame: 65,
|
||||
x: 1,
|
||||
y: 5,
|
||||
yShadow: 5
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
}
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}.outro`,
|
||||
}
|
||||
])
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, 2.75) // Must have money to spawn
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Give the player an Ability Charm
|
||||
// Check if the player has max stacks of that item already
|
||||
const existing = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.2.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
|
||||
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed
|
||||
if (modifier.type.name.includes("Berry")) {
|
||||
// Check if the player has max stacks of that Candy Jar already
|
||||
const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
|
||||
}
|
||||
} else {
|
||||
// Check if the player has max stacks of that Healing Charm already
|
||||
const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the modifier if its stacks go to 0
|
||||
modifier.stackCount -= 1;
|
||||
if (modifier.stackCount === 0) {
|
||||
scene.removeModifier(modifier);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
|
||||
// Check if the player has max stacks of Berry Pouch already
|
||||
const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
|
||||
}
|
||||
|
||||
// Remove the modifier if its stacks go to 0
|
||||
modifier.stackCount -= 1;
|
||||
if (modifier.stackCount === 0) {
|
||||
scene.removeModifier(modifier);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
@ -0,0 +1,163 @@
|
||||
import {
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, {
|
||||
MysteryEncounterBuilder,
|
||||
} from "../mystery-encounter";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
const namespace = "mysteryEncounter:departmentStoreSale";
|
||||
|
||||
/**
|
||||
* Department Store Sale encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/33 | GitHub Issue #33}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DepartmentStoreSaleEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 100)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "b2w2_lady",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -20,
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.FURFROU,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 30,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Choose TMs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 4) {
|
||||
// 2/2/1 weight on TM rarity
|
||||
const roll = randSeedInt(5);
|
||||
if (roll < 2) {
|
||||
modifiers.push(modifierTypes.TM_COMMON);
|
||||
} else if (roll < 4) {
|
||||
modifiers.push(modifierTypes.TM_GREAT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TM_ULTRA);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Choose Vitamins
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 3) {
|
||||
// 2/1 weight on base stat booster vs PP Up
|
||||
const roll = randSeedInt(3);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.PP_UP);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.BASE_STAT_BOOSTER);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Choose X Items
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 4/1 weight on base stat booster vs Dire Hit
|
||||
const roll = randSeedInt(5);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.DIRE_HIT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TEMP_STAT_BOOSTER);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.4.label`,
|
||||
buttonTooltip: `${namespace}.option.4.tooltip`,
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Choose Pokeballs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 4) {
|
||||
// 10/30/20/5 weight on pokeballs
|
||||
const roll = randSeedInt(65);
|
||||
if (roll < 10) {
|
||||
modifiers.push(modifierTypes.POKEBALL);
|
||||
} else if (roll < 40) {
|
||||
modifiers.push(modifierTypes.GREAT_BALL);
|
||||
} else if (roll < 60) {
|
||||
modifiers.push(modifierTypes.ULTRA_BALL);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.ROGUE_BALL);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}.outro`,
|
||||
}
|
||||
])
|
||||
.build();
|
320
src/data/mystery-encounters/encounters/field-trip-encounter.ts
Normal file
@ -0,0 +1,320 @@
|
||||
import { MoveCategory } from "#app/data/move";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { TempBattleStat } from "#app/data/temp-battle-stat";
|
||||
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
/** i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:fieldTrip";
|
||||
|
||||
/**
|
||||
* Field Trip encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/17 | GitHub Issue #17}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const FieldTripEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "preschooler_m",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "teacher",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "preschooler_f",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.second_option_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
const correctMove = move.getMove().category === MoveCategory.PHYSICAL;
|
||||
encounter.setDialogueToken("moveCategory", "Physical");
|
||||
if (!correctMove) {
|
||||
encounter.options[0].dialogue!.selected = [
|
||||
{
|
||||
text: `${namespace}.option.incorrect`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.lesson_learned`,
|
||||
},
|
||||
];
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}.outro_bad`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
];
|
||||
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||
} else {
|
||||
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("move", move.getName());
|
||||
encounter.options[0].dialogue!.selected = [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
];
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}.outro_good`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
];
|
||||
setEncounterExp(scene, [pokemon.id], 100);
|
||||
}
|
||||
encounter.misc = {
|
||||
correctMove: correctMove,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ATK]),
|
||||
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.DEF]),
|
||||
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]),
|
||||
generateModifierTypeOption(scene, modifierTypes.DIRE_HIT),
|
||||
];
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.second_option_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
const correctMove = move.getMove().category === MoveCategory.SPECIAL;
|
||||
encounter.setDialogueToken("moveCategory", "Special");
|
||||
if (!correctMove) {
|
||||
encounter.options[1].dialogue!.selected = [
|
||||
{
|
||||
text: `${namespace}.option.incorrect`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.lesson_learned`,
|
||||
},
|
||||
];
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}.outro_bad`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
];
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}.outro_bad`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
];
|
||||
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||
} else {
|
||||
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("move", move.getName());
|
||||
encounter.options[1].dialogue!.selected = [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
];
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}.outro_good`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
];
|
||||
setEncounterExp(scene, [pokemon.id], 100);
|
||||
}
|
||||
encounter.misc = {
|
||||
correctMove: correctMove,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPATK]),
|
||||
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPDEF]),
|
||||
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]),
|
||||
generateModifierTypeOption(scene, modifierTypes.DIRE_HIT),
|
||||
];
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.second_option_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
const correctMove = move.getMove().category === MoveCategory.STATUS;
|
||||
encounter.setDialogueToken("moveCategory", "Status");
|
||||
if (!correctMove) {
|
||||
encounter.options[2].dialogue!.selected = [
|
||||
{
|
||||
text: `${namespace}.option.incorrect`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.lesson_learned`,
|
||||
},
|
||||
];
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}.outro_bad`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
];
|
||||
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||
} else {
|
||||
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("move", move.getName());
|
||||
encounter.options[2].dialogue!.selected = [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
];
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}.outro_good`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
];
|
||||
setEncounterExp(scene, [pokemon.id], 100);
|
||||
}
|
||||
encounter.misc = {
|
||||
correctMove: correctMove,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ACC]),
|
||||
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]),
|
||||
generateModifierTypeOption(scene, modifierTypes.GREAT_BALL),
|
||||
generateModifierTypeOption(scene, modifierTypes.IV_SCANNER),
|
||||
];
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
@ -0,0 +1,251 @@
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { TypeRequirement } from "../mystery-encounter-requirements";
|
||||
import { Species } from "#enums/species";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Gender } from "#app/data/gender";
|
||||
import { Type } from "#app/data/type";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
import { WeatherType } from "#app/data/weather";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:fieryFallout";
|
||||
|
||||
/**
|
||||
* Damage percentage taken when suffering the heat.
|
||||
* Can be a number between `0` - `100`.
|
||||
* The higher the more damage taken (100% = instant KO).
|
||||
*/
|
||||
const DAMAGE_PERCENTAGE: number = 20;
|
||||
|
||||
/**
|
||||
* Fiery Fallout encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/88 | GitHub Issue #88}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const FieryFalloutEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(40, 180)
|
||||
.withCatchAllowed(true)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withAnimations(EncounterAnim.MAGMA_BG, EncounterAnim.MAGMA_SPOUT)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Calculate boss mons
|
||||
const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA);
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: volcaronaSpecies,
|
||||
isBoss: false,
|
||||
gender: Gender.MALE
|
||||
},
|
||||
{
|
||||
species: volcaronaSpecies,
|
||||
isBoss: false,
|
||||
gender: Gender.FEMALE
|
||||
}
|
||||
],
|
||||
doubleBattle: true,
|
||||
disableSwitch: true
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Load hidden Volcarona sprites
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.VOLCARONA,
|
||||
repeat: true,
|
||||
hidden: true,
|
||||
hasShadow: true,
|
||||
x: -20,
|
||||
startFrame: 20
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.VOLCARONA,
|
||||
repeat: true,
|
||||
hidden: true,
|
||||
hasShadow: true,
|
||||
x: 20
|
||||
},
|
||||
];
|
||||
|
||||
// Load animations/sfx for Volcarona moves
|
||||
loadCustomMovesForEncounter(scene, [Moves.FIRE_SPIN, Moves.QUIVER_DANCE]);
|
||||
|
||||
scene.arena.trySetWeather(WeatherType.SUNNY, true);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
|
||||
background.playWithoutTargets(scene, 200, 70, 2, 3);
|
||||
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
|
||||
animation.playWithoutTargets(scene, 80, 100, 2);
|
||||
scene.time.delayedCall(600, () => {
|
||||
animation.playWithoutTargets(scene, -20, 100, 2);
|
||||
});
|
||||
scene.time.delayedCall(1200, () => {
|
||||
animation.playWithoutTargets(scene, 140, 150, 2);
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonCharcoal(scene));
|
||||
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.FIRE_SPIN),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER_2],
|
||||
move: new PokemonMove(Moves.FIRE_SPIN),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY],
|
||||
move: new PokemonMove(Moves.QUIVER_DANCE),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.ENEMY_2],
|
||||
move: new PokemonMove(Moves.QUIVER_DANCE),
|
||||
ignorePp: true
|
||||
});
|
||||
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Damage non-fire types and burn 1 random non-fire type member
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const nonFireTypes = scene.getParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE));
|
||||
|
||||
for (const pkm of nonFireTypes) {
|
||||
const percentage = DAMAGE_PERCENTAGE / 100;
|
||||
const damage = Math.floor(pkm.getMaxHp() * percentage);
|
||||
applyDamageToPokemon(scene, pkm, damage);
|
||||
}
|
||||
|
||||
// Burn random member
|
||||
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.BURN);
|
||||
if (burnable?.length > 0) {
|
||||
const roll = randSeedInt(burnable.length);
|
||||
const chosenPokemon = burnable[roll];
|
||||
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
|
||||
// Burn applied
|
||||
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
|
||||
queueEncounterMessage(scene, `${namespace}.option.2.target_burned`);
|
||||
}
|
||||
}
|
||||
|
||||
// No rewards
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3PrimaryName dialogue token automatically
|
||||
.withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
transitionMysteryEncounterIntroVisuals(scene, false, false, 2000);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Fire types help calm the Volcarona
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
transitionMysteryEncounterIntroVisuals(scene);
|
||||
setEncounterRewards(scene,
|
||||
{ fillRemaining: true },
|
||||
undefined,
|
||||
() => {
|
||||
giveLeadPokemonCharcoal(scene);
|
||||
});
|
||||
|
||||
const primary = encounter.options[2].primaryPokemon!;
|
||||
const secondary = encounter.options[2].secondaryPokemon![0];
|
||||
|
||||
setEncounterExp(scene, [primary.id, secondary.id], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
|
||||
function giveLeadPokemonCharcoal(scene: BattleScene) {
|
||||
// Give first party pokemon Charcoal for free at end of battle
|
||||
const leadPokemon = scene.getParty()?.[0];
|
||||
if (leadPokemon) {
|
||||
const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]).type as AttackTypeBoosterModifierType;
|
||||
applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal);
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
|
||||
queueEncounterMessage(scene, `${namespace}.found_charcoal`);
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import {
|
||||
EnemyPartyConfig,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle, setEncounterExp,
|
||||
setEncounterRewards
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { EnemyPokemon } from "#app/field/pokemon";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import {
|
||||
getPartyLuckValue,
|
||||
getPlayerModifierTypeOptions,
|
||||
ModifierPoolType,
|
||||
ModifierTypeOption,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:fightOrFlight";
|
||||
|
||||
/**
|
||||
* Fight or Flight encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const FightOrFlightEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Calculate boss mon
|
||||
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, scene.currentBattle.waveIndex, 0, getPartyLuckValue(scene.getParty()), true);
|
||||
const bossPokemon = new EnemyPokemon(scene, bossSpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, true);
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
pokemonConfigs: [{
|
||||
species: bossSpecies,
|
||||
dataSource: new PokemonData(bossPokemon),
|
||||
isBoss: true
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Calculate item
|
||||
// 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
|
||||
const tier =
|
||||
scene.currentBattle.waveIndex > 160
|
||||
? ModifierTier.MASTER
|
||||
: scene.currentBattle.waveIndex > 120
|
||||
? ModifierTier.ROGUE
|
||||
: scene.currentBattle.waveIndex > 40
|
||||
? ModifierTier.ULTRA
|
||||
: ModifierTier.GREAT;
|
||||
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
|
||||
let item: ModifierTypeOption | null = null;
|
||||
// TMs excluded from possible rewards as they're too swingy in value for a singular item reward
|
||||
while (!item || item.type.id.includes("TM_")) {
|
||||
item = getPlayerModifierTypeOptions(1, scene.getParty(), [], { guaranteedModifierTiers: [tier], allowLuckUpgrades: false })[0];
|
||||
}
|
||||
encounter.setDialogueToken("itemName", item.type.name);
|
||||
encounter.misc = item;
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: item.type.iconImage,
|
||||
fileRoot: "items",
|
||||
hasShadow: false,
|
||||
x: 35,
|
||||
y: -5,
|
||||
scale: 0.75,
|
||||
isItem: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true
|
||||
},
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const item = scene.currentBattle.mysteryEncounter
|
||||
.misc as ModifierTypeOption;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
|
||||
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Pick steal
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
|
||||
|
||||
// Use primaryPokemon to execute the thievery
|
||||
const primaryPokemon = encounter.options[1].primaryPokemon!;
|
||||
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
142
src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species.js";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species.js";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
|
||||
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
const OPTION_1_REQUIRED_MOVE = Moves.SURF;
|
||||
const OPTION_2_REQUIRED_MOVE = Moves.FLY;
|
||||
/**
|
||||
* Damage percentage taken when wandering aimlessly.
|
||||
* Can be a number between `0` - `100`.
|
||||
* The higher the more damage taken (100% = instant KO).
|
||||
*/
|
||||
const DAMAGE_PERCENTAGE: number = 25;
|
||||
/** The i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:lostAtSea";
|
||||
|
||||
/**
|
||||
* Lost at sea encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/9 | GitHub Issue #9}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(11, 179)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "buoy",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
x: 20,
|
||||
y: 3,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([{ text: `${namespace}.intro` }])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const { mysteryEncounter } = scene.currentBattle;
|
||||
|
||||
mysteryEncounter.setDialogueToken("damagePercentage", String(DAMAGE_PERCENTAGE));
|
||||
mysteryEncounter.setDialogueToken("option1RequiredMove", Moves[OPTION_1_REQUIRED_MOVE]);
|
||||
mysteryEncounter.setDialogueToken("option2RequiredMove", Moves[OPTION_2_REQUIRED_MOVE]);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(
|
||||
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
disabledButtonLabel: `${namespace}.option.1.label_disabled`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.1.tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene))
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
//Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
disabledButtonLabel: `${namespace}.option.2.label_disabled`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.2.tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene))
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
// Option 3: Wander aimlessly
|
||||
{
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
const allowedPokemon = scene.getParty().filter((p) => p.isAllowedInBattle());
|
||||
|
||||
for (const pkm of allowedPokemon) {
|
||||
const percentage = DAMAGE_PERCENTAGE / 100;
|
||||
const damage = Math.floor(pkm.getMaxHp() * percentage);
|
||||
applyDamageToPokemon(scene, pkm, damage);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}.outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Generic handler for using a guiding pokemon to guide you back.
|
||||
*
|
||||
* @param scene Battle scene
|
||||
* @param guidePokemon pokemon choosen as a guide
|
||||
*/
|
||||
async function handlePokemonGuidingYouPhase(scene: BattleScene) {
|
||||
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
|
||||
const { mysteryEncounter } = scene.currentBattle;
|
||||
|
||||
if (mysteryEncounter.selectedOption?.primaryPokemon?.id) {
|
||||
setEncounterExp(scene, mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true);
|
||||
} else {
|
||||
console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?");
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
return true;
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
import {
|
||||
EnemyPartyConfig,
|
||||
initBattleWithEnemyConfig,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
trainerConfigs,
|
||||
TrainerPartyCompoundTemplate,
|
||||
TrainerPartyTemplate,
|
||||
trainerPartyTemplates,
|
||||
} from "#app/data/trainer-config";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import * as Utils from "#app/utils";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:mysteriousChallengers";
|
||||
|
||||
/**
|
||||
* Mysterious Challengers encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/41 | GitHub Issue #41}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const MysteriousChallengersEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Calculates what trainers are available for battle in the encounter
|
||||
|
||||
// Normal difficulty trainer is randomly pulled from biome
|
||||
const normalTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
|
||||
const normalConfig = trainerConfigs[normalTrainerType].copy();
|
||||
let female = false;
|
||||
if (normalConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const normalSpriteKey = normalConfig.getSpriteKey(female, normalConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: normalConfig,
|
||||
female: female,
|
||||
});
|
||||
|
||||
// Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config
|
||||
// Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100
|
||||
const hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
|
||||
const hardTemplate = new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true),
|
||||
new TrainerPartyTemplate(
|
||||
Math.min(Math.ceil(scene.currentBattle.waveIndex / 20), 5),
|
||||
PartyMemberStrength.AVERAGE,
|
||||
false,
|
||||
true
|
||||
)
|
||||
);
|
||||
const hardConfig = trainerConfigs[hardTrainerType].copy();
|
||||
hardConfig.setPartyTemplates(hardTemplate);
|
||||
female = false;
|
||||
if (hardConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const hardSpriteKey = hardConfig.getSpriteKey(female, hardConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: hardConfig,
|
||||
levelAdditiveMultiplier: 0.5,
|
||||
female: female,
|
||||
});
|
||||
|
||||
// Brutal trainer is pulled from pool of boss trainers (gym leaders) for the biome
|
||||
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
|
||||
const brutalTrainerType = scene.arena.randomTrainerType(
|
||||
scene.currentBattle.waveIndex,
|
||||
true
|
||||
);
|
||||
const e4Template = trainerPartyTemplates.ELITE_FOUR;
|
||||
const brutalConfig = trainerConfigs[brutalTrainerType].copy();
|
||||
brutalConfig.setPartyTemplates(e4Template);
|
||||
// @ts-ignore
|
||||
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func
|
||||
female = false;
|
||||
if (brutalConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const brutalSpriteKey = brutalConfig.getSpriteKey(female, brutalConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: brutalConfig,
|
||||
levelAdditiveMultiplier: 1,
|
||||
female: female,
|
||||
});
|
||||
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: normalSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
{
|
||||
spriteKey: hardSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
{
|
||||
spriteKey: brutalSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Spawn standard trainer battle with memory mushroom reward
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let ret;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
ret = initBattleWithEnemyConfig(scene, config);
|
||||
}, scene.currentBattle.waveIndex * 10);
|
||||
return ret;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Spawn hard fight with ULTRA/GREAT reward (can improve with luck)
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let ret;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
ret = initBattleWithEnemyConfig(scene, config);
|
||||
}, scene.currentBattle.waveIndex * 100);
|
||||
return ret;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Spawn brutal fight with ROGUE/ULTRA/GREAT reward (can improve with luck)
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2];
|
||||
|
||||
// To avoid player level snowballing from picking this option
|
||||
encounter.expMultiplier = 0.9;
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let ret;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
ret = initBattleWithEnemyConfig(scene, config);
|
||||
}, scene.currentBattle.waveIndex * 1000);
|
||||
return ret;
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}.outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
@ -0,0 +1,141 @@
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { leaveEncounterWithoutBattle, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { randSeedInt } from "#app/utils.js";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
const namespace = "mysteryEncounter:mysteriousChest";
|
||||
|
||||
/**
|
||||
* Mysterious Chest encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/32 | GitHub Issue #32}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const MysteriousChestEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 2 to 180
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "chest_blue",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 10,
|
||||
yShadow: 3,
|
||||
disableAnimation: true, // Re-enabled after option select
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
}
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Play animation
|
||||
const introVisuals =
|
||||
scene.currentBattle.mysteryEncounter.introVisuals!;
|
||||
introVisuals.spriteConfigs[0].disableAnimation = false;
|
||||
introVisuals.playAnim();
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Open the chest
|
||||
const roll = randSeedInt(100);
|
||||
if (roll > 60) {
|
||||
// Choose between 2 COMMON / 2 GREAT tier items (40%)
|
||||
setEncounterRewards(scene, {
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.COMMON,
|
||||
ModifierTier.COMMON,
|
||||
ModifierTier.GREAT,
|
||||
ModifierTier.GREAT,
|
||||
],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.normal`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
} else if (roll > 40) {
|
||||
// Choose between 3 ULTRA tier items (20%)
|
||||
setEncounterRewards(scene, {
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.good`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
} else if (roll > 36) {
|
||||
// Choose between 2 ROGUE tier items (4%)
|
||||
setEncounterRewards(scene, {
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.great`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
} else if (roll > 35) {
|
||||
// Choose 1 MASTER tier item (1%)
|
||||
setEncounterRewards(scene, {
|
||||
guaranteedModifierTiers: [ModifierTier.MASTER],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.amazing`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
} else {
|
||||
// Your highest level unfainted Pok<6F>mon gets OHKO. Progress with no rewards (35%)
|
||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(
|
||||
scene,
|
||||
true
|
||||
);
|
||||
koPlayerPokemon(scene, highestLevelPokemon);
|
||||
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||
// Show which Pokemon was KOed, then leave encounter with no rewards
|
||||
// Does this synchronously so that game over doesn't happen over result message
|
||||
await showEncounterText(scene, `${namespace}.option.1.bad`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
339
src/data/mystery-encounters/encounters/part-timer-encounter.ts
Normal file
@ -0,0 +1,339 @@
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { getEncounterText, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import i18next from "i18next";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:partTimer";
|
||||
|
||||
/**
|
||||
* Part Timer encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/82 | GitHub Issue #82}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const PartTimerEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "worker_f",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: -20
|
||||
},
|
||||
{
|
||||
spriteKey: "training_gear",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 6,
|
||||
x: 20,
|
||||
yShadow: -2
|
||||
}
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
// Load sfx
|
||||
scene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav");
|
||||
scene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav");
|
||||
scene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav");
|
||||
scene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.wav");
|
||||
|
||||
scene.loadSe("PRSFX- Agility", "battle_anims", "PRSFX- Agility.wav");
|
||||
scene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav");
|
||||
scene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav");
|
||||
|
||||
scene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav");
|
||||
scene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav");
|
||||
scene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav");
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
|
||||
// Calculate the "baseline" stat value (90 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
|
||||
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
|
||||
// Calculation from Pokemon.calculateStats
|
||||
const baselineValue = Math.floor(((2 * 90 + 16) * pokemon.level) * 0.01) + 5;
|
||||
const percentDiff = (pokemon.getStat(Stat.SPD) - baselineValue) / baselineValue;
|
||||
const moneyMultiplier = Math.min(Math.max(2.5 * (1+ percentDiff), 1), 4);
|
||||
|
||||
encounter.misc = {
|
||||
moneyMultiplier
|
||||
};
|
||||
|
||||
// Reduce all PP to 2 (if they started at greater than 2)
|
||||
pokemon.moveset.forEach(move => {
|
||||
if (move) {
|
||||
const newPpUsed = move.getMovePp() - 2;
|
||||
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
|
||||
}
|
||||
});
|
||||
|
||||
setEncounterExp(scene, pokemon.id, 100);
|
||||
|
||||
// Hide intro visuals
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, false);
|
||||
// Play sfx for "working"
|
||||
doDeliverySfx(scene);
|
||||
};
|
||||
|
||||
// Only Pokemon non-KOd pokemon can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Pick Deliveries
|
||||
// Bring visuals back in
|
||||
await transitionMysteryEncounterIntroVisuals(scene, false, false);
|
||||
|
||||
const moneyMultiplier = scene.currentBattle.mysteryEncounter.misc.moneyMultiplier;
|
||||
|
||||
// Give money and do dialogue
|
||||
if (moneyMultiplier > 2.5) {
|
||||
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`);
|
||||
} else {
|
||||
await showEncounterDialogue(scene, `${namespace}.job_complete_bad`, `${namespace}.speaker`);
|
||||
}
|
||||
const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier);
|
||||
updatePlayerMoney(scene, moneyChange, true, false);
|
||||
await showEncounterText(scene, i18next.t("mysteryEncounter:receive_money", { amount: moneyChange }));
|
||||
await showEncounterText(scene, `${namespace}.pokemon_tired`);
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
|
||||
// Calculate the "baseline" stat value (75 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
|
||||
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
|
||||
// Calculation from Pokemon.calculateStats
|
||||
const baselineHp = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + pokemon.level + 10;
|
||||
const baselineAtkDef = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + 5;
|
||||
const baselineValue = baselineHp + 1.5 * (baselineAtkDef * 2);
|
||||
const strongestValue = pokemon.getStat(Stat.HP) + 1.5 * (pokemon.getStat(Stat.ATK) + pokemon.getStat(Stat.DEF));
|
||||
const percentDiff = (strongestValue - baselineValue) / baselineValue;
|
||||
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
|
||||
|
||||
encounter.misc = {
|
||||
moneyMultiplier
|
||||
};
|
||||
|
||||
// Reduce all PP to 2 (if they started at greater than 2)
|
||||
pokemon.moveset.forEach(move => {
|
||||
if (move) {
|
||||
const newPpUsed = move.getMovePp() - 2;
|
||||
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
|
||||
}
|
||||
});
|
||||
|
||||
setEncounterExp(scene, pokemon.id, 100);
|
||||
|
||||
// Hide intro visuals
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, false);
|
||||
// Play sfx for "working"
|
||||
doStrongWorkSfx(scene);
|
||||
};
|
||||
|
||||
// Only Pokemon non-KOd pokemon can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Pick Move Warehouse items
|
||||
// Bring visuals back in
|
||||
await transitionMysteryEncounterIntroVisuals(scene, false, false);
|
||||
|
||||
const moneyMultiplier = scene.currentBattle.mysteryEncounter.misc.moneyMultiplier;
|
||||
|
||||
// Give money and do dialogue
|
||||
if (moneyMultiplier > 2.5) {
|
||||
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`);
|
||||
} else {
|
||||
await showEncounterDialogue(scene, `${namespace}.job_complete_bad`, `${namespace}.speaker`);
|
||||
}
|
||||
const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier);
|
||||
updatePlayerMoney(scene, moneyChange, true, false);
|
||||
await showEncounterText(scene, i18next.t("mysteryEncounter:receive_money", { amount: moneyChange }));
|
||||
await showEncounterText(scene, `${namespace}.pokemon_tired`);
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const selectedPokemon = encounter.selectedOption?.primaryPokemon!;
|
||||
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
|
||||
|
||||
// Reduce all PP to 2 (if they started at greater than 2)
|
||||
selectedPokemon.moveset.forEach(move => {
|
||||
if (move) {
|
||||
const newPpUsed = move.getMovePp() - 2;
|
||||
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
|
||||
}
|
||||
});
|
||||
|
||||
setEncounterExp(scene, selectedPokemon.id, 100);
|
||||
|
||||
// Hide intro visuals
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, false);
|
||||
// Play sfx for "working"
|
||||
doSalesSfx(scene);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Assist with Sales
|
||||
// Bring visuals back in
|
||||
await transitionMysteryEncounterIntroVisuals(scene, false, false);
|
||||
|
||||
// Give money and do dialogue
|
||||
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`);
|
||||
const moneyChange = scene.getWaveMoneyAmount(2.5);
|
||||
updatePlayerMoney(scene, moneyChange, true, false);
|
||||
await showEncounterText(scene, i18next.t("mysteryEncounter:receive_money", { amount: moneyChange }));
|
||||
await showEncounterText(scene, `${namespace}.pokemon_tired`);
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.outro`,
|
||||
}
|
||||
])
|
||||
.build();
|
||||
|
||||
function doStrongWorkSfx(scene: BattleScene) {
|
||||
scene.playSound("PRSFX- Horn Drill1");
|
||||
scene.playSound("PRSFX- Horn Drill1");
|
||||
|
||||
scene.time.delayedCall(1000, () => {
|
||||
scene.playSound("PRSFX- Guillotine2");
|
||||
});
|
||||
|
||||
scene.time.delayedCall(2000, () => {
|
||||
scene.playSound("PRSFX- Heavy Slam2");
|
||||
});
|
||||
|
||||
scene.time.delayedCall(2500, () => {
|
||||
scene.playSound("PRSFX- Guillotine2");
|
||||
});
|
||||
}
|
||||
|
||||
function doDeliverySfx(scene: BattleScene) {
|
||||
scene.playSound("PRSFX- Accelerock1");
|
||||
|
||||
scene.time.delayedCall(1500, () => {
|
||||
scene.playSound("PRSFX- Extremespeed1");
|
||||
});
|
||||
|
||||
scene.time.delayedCall(2000, () => {
|
||||
scene.playSound("PRSFX- Extremespeed1");
|
||||
});
|
||||
|
||||
scene.time.delayedCall(2250, () => {
|
||||
scene.playSound("PRSFX- Agility");
|
||||
});
|
||||
}
|
||||
|
||||
function doSalesSfx(scene: BattleScene) {
|
||||
scene.playSound("PRSFX- Captivate");
|
||||
|
||||
scene.time.delayedCall(1500, () => {
|
||||
scene.playSound("PRSFX- Attract2");
|
||||
});
|
||||
|
||||
scene.time.delayedCall(2000, () => {
|
||||
scene.playSound("PRSFX- Aurora Veil2");
|
||||
});
|
||||
|
||||
scene.time.delayedCall(3000, () => {
|
||||
scene.playSound("PRSFX- Attract2");
|
||||
});
|
||||
}
|
501
src/data/mystery-encounters/encounters/safari-zone-encounter.ts
Normal file
@ -0,0 +1,501 @@
|
||||
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier";
|
||||
import { EnemyPokemon } from "#app/field/pokemon";
|
||||
import { PokeballType } from "#app/data/pokeball";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { IntegerHolder, randSeedInt } from "#app/utils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
|
||||
import { SummonPhase } from "#app/phases/summon-phase";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:safariZone";
|
||||
|
||||
const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768];
|
||||
|
||||
/**
|
||||
* Safari Zone encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/39 | GitHub Issue #39}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const SafariZoneEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "safari_zone",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
x: 4,
|
||||
y: 6
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Start safari encounter
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
encounter.continuousEncounter = true;
|
||||
encounter.misc = {
|
||||
safariPokemonRemaining: 3
|
||||
};
|
||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Load bait/mud assets
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
|
||||
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims");
|
||||
scene.loadSe("PRSFX- Taunt2", "battle_anims");
|
||||
scene.loadAtlas("bait", "mystery-encounters");
|
||||
scene.loadAtlas("mud", "mystery-encounters");
|
||||
await summonSafariPokemon(scene);
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, hideDescription: true });
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* SAFARI ZONE MINIGAME OPTIONS
|
||||
*
|
||||
* Catch and flee rate stages are calculated in the same way stat changes are (they range from -6/+6)
|
||||
* https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Great_Marsh_and_Johto_Safari_Zone
|
||||
*
|
||||
* Catch Rate calculation:
|
||||
* catchRate = speciesCatchRate [1 to 255] * catchStageMultiplier [2/8 to 8/2] * ballCatchRate [1.5]
|
||||
*
|
||||
* Flee calculation:
|
||||
* The harder a species is to catch, the higher its flee rate is
|
||||
* (Caps at 50% base chance to flee for the hardest to catch Pokemon, before factoring in flee stage)
|
||||
* fleeRate = ((255^2 - speciesCatchRate^2) / 255 / 2) [0 to 127.5] * fleeStageMultiplier [2/8 to 8/2]
|
||||
* Flee chance = fleeRate / 255
|
||||
*/
|
||||
const safariZoneGameOptions: MysteryEncounterOption[] = [
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.safari.1.label`,
|
||||
buttonTooltip: `${namespace}.safari.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.safari.1.selected`,
|
||||
}
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Throw a ball option
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
const catchResult = await throwPokeball(scene, pokemon);
|
||||
|
||||
if (catchResult) {
|
||||
// You caught pokemon
|
||||
// Check how many safari pokemon left
|
||||
if (encounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon(scene);
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
|
||||
} else {
|
||||
// End safari mode
|
||||
encounter.continuousEncounter = false;
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
}
|
||||
} else {
|
||||
// Pokemon catch failed, end turn
|
||||
await doEndTurn(scene, 0);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.build(),
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.safari.2.label`,
|
||||
buttonTooltip: `${namespace}.safari.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.safari.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Throw bait option
|
||||
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
|
||||
await throwBait(scene, pokemon);
|
||||
|
||||
// 100% chance to increase catch stage +2
|
||||
tryChangeCatchStage(scene, 2);
|
||||
// 80% chance to increase flee stage +1
|
||||
const fleeChangeResult = tryChangeFleeStage(scene, 1, 8);
|
||||
if (!fleeChangeResult) {
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.busy_eating`) ?? "", 1000, false );
|
||||
} else {
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.eating`) ?? "", 1000, false);
|
||||
}
|
||||
|
||||
await doEndTurn(scene, 1);
|
||||
return true;
|
||||
})
|
||||
.build(),
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.safari.3.label`,
|
||||
buttonTooltip: `${namespace}.safari.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.safari.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Throw mud option
|
||||
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
|
||||
await throwMud(scene, pokemon);
|
||||
// 100% chance to decrease flee stage -2
|
||||
tryChangeFleeStage(scene, -2);
|
||||
// 80% chance to decrease catch stage -1
|
||||
const catchChangeResult = tryChangeCatchStage(scene, -1, 8);
|
||||
if (!catchChangeResult) {
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.beside_itself_angry`) ?? "", 1000, false );
|
||||
} else {
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.angry`) ?? "", 1000, false );
|
||||
}
|
||||
|
||||
await doEndTurn(scene, 2);
|
||||
return true;
|
||||
})
|
||||
.build(),
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.safari.4.label`,
|
||||
buttonTooltip: `${namespace}.safari.4.tooltip`,
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Flee option
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
await doPlayerFlee(scene, pokemon);
|
||||
// Check how many safari pokemon left
|
||||
if (encounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon(scene);
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
|
||||
} else {
|
||||
// End safari mode
|
||||
encounter.continuousEncounter = false;
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
];
|
||||
|
||||
async function summonSafariPokemon(scene: BattleScene) {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Message pokemon remaining
|
||||
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
|
||||
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.remaining_count`) ?? "", null, true);
|
||||
|
||||
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
|
||||
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
|
||||
let enemySpecies;
|
||||
let pokemon;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(scene.currentBattle.waveIndex, true, false, scene.gameMode));
|
||||
scene.currentBattle.enemyParty = [];
|
||||
pokemon = scene.addEnemyPokemon(enemySpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, false);
|
||||
|
||||
// Roll shiny twice
|
||||
if (!pokemon.shiny) {
|
||||
pokemon.trySetShiny();
|
||||
}
|
||||
|
||||
// Roll HA twice
|
||||
if (pokemon.species.abilityHidden) {
|
||||
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
||||
if (pokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new IntegerHolder(256);
|
||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
if (hasHiddenAbility) {
|
||||
pokemon.abilityIndex = hiddenIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pokemon.calculateStats();
|
||||
|
||||
scene.currentBattle.enemyParty[0] = pokemon;
|
||||
}, scene.currentBattle.waveIndex * 1000 + encounter.misc.safariPokemonRemaining);
|
||||
|
||||
scene.gameData.setPokemonSeen(pokemon, true);
|
||||
await pokemon.loadAssets();
|
||||
|
||||
// Reset safari catch and flee rates
|
||||
encounter.misc.catchStage = 0;
|
||||
encounter.misc.fleeStage = 0;
|
||||
encounter.misc.pokemon = pokemon;
|
||||
encounter.misc.safariPokemonRemaining -= 1;
|
||||
|
||||
scene.unshiftPhase(new SummonPhase(scene, 0, false));
|
||||
|
||||
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
|
||||
showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", 1500, false)
|
||||
.then(() => {
|
||||
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||
const baseCatchRate = pokemon.species.catchRate;
|
||||
// Catch stage ranges from -6 to +6 (like stat boost stages)
|
||||
const safariCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage;
|
||||
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6)
|
||||
const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
|
||||
// Catch rate same as safari ball
|
||||
const pokeballMultiplier = 1.5;
|
||||
const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier);
|
||||
const ballTwitchRate = Math.round(1048560 / Math.sqrt(Math.sqrt(16711680 / catchRate)));
|
||||
return trainerThrowPokeball(scene, pokemon, PokeballType.POKEBALL, ballTwitchRate);
|
||||
}
|
||||
|
||||
async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
const fpOffset = pokemon.getFieldPositionOffset();
|
||||
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "bait", "0001.png");
|
||||
bait.setOrigin(0.5, 0.625);
|
||||
scene.field.add(bait);
|
||||
|
||||
return new Promise(resolve => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
|
||||
scene.playSound("pb_throw");
|
||||
|
||||
// Trainer throw frames
|
||||
scene.trainer.setFrame("2");
|
||||
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
|
||||
scene.trainer.setFrame("3");
|
||||
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
|
||||
});
|
||||
});
|
||||
|
||||
// Pokeball move and catch logic
|
||||
scene.tweens.add({
|
||||
targets: bait,
|
||||
x: { value: 210 + fpOffset[0], ease: "Linear" },
|
||||
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
|
||||
let index = 1;
|
||||
scene.time.delayedCall(768, () => {
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 150,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: originalY - 5,
|
||||
loop: 6,
|
||||
onStart: () => {
|
||||
scene.playSound("PRSFX- Bug Bite");
|
||||
bait.setFrame("0002.png");
|
||||
},
|
||||
onLoop: () => {
|
||||
if (index % 2 === 0) {
|
||||
scene.playSound("PRSFX- Bug Bite");
|
||||
}
|
||||
if (index === 4) {
|
||||
bait.setFrame("0003.png");
|
||||
}
|
||||
index++;
|
||||
},
|
||||
onComplete: () => {
|
||||
scene.time.delayedCall(256, () => {
|
||||
bait.destroy();
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
const fpOffset = pokemon.getFieldPositionOffset();
|
||||
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 35, "mud", "0001.png");
|
||||
mud.setOrigin(0.5, 0.625);
|
||||
scene.field.add(mud);
|
||||
|
||||
return new Promise(resolve => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
|
||||
scene.playSound("pb_throw");
|
||||
|
||||
// Trainer throw frames
|
||||
scene.trainer.setFrame("2");
|
||||
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
|
||||
scene.trainer.setFrame("3");
|
||||
scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
|
||||
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
|
||||
});
|
||||
});
|
||||
|
||||
// Mud throw and splat
|
||||
scene.tweens.add({
|
||||
targets: mud,
|
||||
x: { value: 230 + fpOffset[0], ease: "Linear" },
|
||||
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
// Mud frame 2
|
||||
scene.playSound("PRSFX- Sludge Bomb2");
|
||||
mud.setFrame("0002.png");
|
||||
// Mud splat
|
||||
scene.time.delayedCall(200, () => {
|
||||
mud.setFrame("0003.png");
|
||||
scene.time.delayedCall(400, () => {
|
||||
mud.setFrame("0004.png");
|
||||
});
|
||||
});
|
||||
|
||||
// Fade mud then angry animation
|
||||
scene.tweens.add({
|
||||
targets: mud,
|
||||
alpha: 0,
|
||||
ease: "Cubic.easeIn",
|
||||
duration: 1000,
|
||||
onComplete: () => {
|
||||
mud.destroy();
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 300,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: originalY - 20,
|
||||
loop: 1,
|
||||
onStart: () => {
|
||||
scene.playSound("PRSFX- Taunt2");
|
||||
},
|
||||
onLoop: () => {
|
||||
scene.playSound("PRSFX- Taunt2");
|
||||
},
|
||||
onComplete: () => {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
|
||||
const speciesCatchRate = pokemon.species.catchRate;
|
||||
const fleeModifier = (2 + Math.min(Math.max(fleeStage, 0), 6)) / (2 - Math.max(Math.min(fleeStage, 0), -6));
|
||||
const fleeRate = (255 * 255 - speciesCatchRate * speciesCatchRate) / 255 / 2 * fleeModifier;
|
||||
console.log("Flee rate: " + fleeRate);
|
||||
const roll = randSeedInt(256);
|
||||
console.log("Roll: " + roll);
|
||||
return roll < fleeRate;
|
||||
}
|
||||
|
||||
function tryChangeFleeStage(scene: BattleScene, change: number, chance?: number): boolean {
|
||||
if (chance && randSeedInt(10) >= chance) {
|
||||
return false;
|
||||
}
|
||||
const currentFleeStage = scene.currentBattle.mysteryEncounter.misc.fleeStage ?? 0;
|
||||
scene.currentBattle.mysteryEncounter.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6);
|
||||
return true;
|
||||
}
|
||||
|
||||
function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number): boolean {
|
||||
if (chance && randSeedInt(10) >= chance) {
|
||||
return false;
|
||||
}
|
||||
const currentCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage ?? 0;
|
||||
scene.currentBattle.mysteryEncounter.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function doEndTurn(scene: BattleScene, cursorIndex: number) {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
const isFlee = isPokemonFlee(pokemon, encounter.misc.fleeStage);
|
||||
if (isFlee) {
|
||||
// Pokemon flees!
|
||||
await doPokemonFlee(scene, pokemon);
|
||||
// Check how many safari pokemon left
|
||||
if (encounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon(scene);
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||
} else {
|
||||
// End safari mode
|
||||
encounter.continuousEncounter = false;
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
}
|
||||
} else {
|
||||
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.watching`) ?? "", 0, null, 1000);
|
||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||
}
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { MoneyRequirement } from "../mystery-encounter-requirements";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:shadyVitaminDealer";
|
||||
|
||||
/**
|
||||
* Shady Vitamin Dealer encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/34 | GitHub Issue #34}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withPrimaryPokemonStatusEffectRequirement([StatusEffect.NONE]) // Pokemon must not have status
|
||||
.withPrimaryPokemonHealthRatioRequirement([0.34, 1]) // Pokemon must have above 1/3rd HP
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.KROOKODILE.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 12,
|
||||
y: -5,
|
||||
yShadow: -5
|
||||
},
|
||||
{
|
||||
spriteKey: "b2w2_veteran_m",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -12,
|
||||
y: 3,
|
||||
yShadow: 3
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, 2) // Wave scaling money multiplier of 2
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Update money
|
||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
|
||||
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
};
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Choose Cheap Option
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Damage and status applied after dealer leaves (to make thematic sense)
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Pokemon takes 1/3 max HP damage
|
||||
applyDamageToPokemon(scene, chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 3));
|
||||
|
||||
// Roll for poison (80%)
|
||||
if (randSeedInt(10) < 8) {
|
||||
if (chosenPokemon.trySetStatus(StatusEffect.TOXIC)) {
|
||||
// Toxic applied
|
||||
queueEncounterMessage(scene, `${namespace}.bad_poison`);
|
||||
} else {
|
||||
// Pokemon immune or something else prevents status
|
||||
queueEncounterMessage(scene, `${namespace}.damage_only`);
|
||||
}
|
||||
} else {
|
||||
queueEncounterMessage(scene, `${namespace}.damage_only`);
|
||||
}
|
||||
|
||||
setEncounterExp(scene, [chosenPokemon.id], 100);
|
||||
|
||||
chosenPokemon.updateInfo();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, 5) // Wave scaling money multiplier of 5
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Update money
|
||||
updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
|
||||
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
};
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Choose Expensive Option
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Status applied after dealer leaves (to make thematic sense)
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Roll for poison (20%)
|
||||
if (randSeedInt(10) < 2) {
|
||||
if (chosenPokemon.trySetStatus(StatusEffect.POISON)) {
|
||||
// Poison applied
|
||||
queueEncounterMessage(scene, `${namespace}.poison`);
|
||||
} else {
|
||||
// Pokemon immune or something else prevents status
|
||||
queueEncounterMessage(scene, `${namespace}.no_bad_effects`);
|
||||
}
|
||||
} else {
|
||||
queueEncounterMessage(scene, `${namespace}.no_bad_effects`);
|
||||
}
|
||||
|
||||
setEncounterExp(scene, [chosenPokemon.id], 100);
|
||||
|
||||
chosenPokemon.updateInfo();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
speaker: `${namespace}.speaker`
|
||||
}
|
||||
]
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
@ -0,0 +1,148 @@
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||
|
||||
/** i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:slumberingSnorlax";
|
||||
|
||||
/**
|
||||
* Sleeping Snorlax encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/103 | GitHub Issue #103}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const SlumberingSnorlaxEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.SNORLAX.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
scale: 1.5,
|
||||
repeat: true,
|
||||
y: 5,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
console.log(encounter);
|
||||
|
||||
// Calculate boss mon
|
||||
const bossSpecies = getPokemonSpecies(Species.SNORLAX);
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
status: [StatusEffect.SLEEP, 5], // Extra turns on timer for Snorlax's start of fight moves
|
||||
moveSet: [Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT]
|
||||
};
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 0.5,
|
||||
pokemonConfigs: [pokemonConfig],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Load animations/sfx for Snorlax fight start moves
|
||||
loadCustomMovesForEncounter(scene, [Moves.SNORE]);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: true});
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.SNORE),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.SNORE),
|
||||
ignorePp: true
|
||||
});
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Fall asleep waiting for Snorlax
|
||||
// Full heal party
|
||||
scene.unshiftPhase(new PartyHealPhase(scene, true));
|
||||
queueEncounterMessage(scene, `${namespace}.option.2.rest_result`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Steal the Snorlax's Leftovers
|
||||
const instance = scene.currentBattle.mysteryEncounter;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: false });
|
||||
// Snorlax exp to Pokemon that did the stealing
|
||||
setEncounterExp(scene, instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
@ -0,0 +1,157 @@
|
||||
import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MoneyRequirement } from "../mystery-encounter-requirements";
|
||||
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { Species } from "#enums/species";
|
||||
import { PokeballType } from "#app/data/pokeball";
|
||||
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:pokemonSalesman";
|
||||
|
||||
const MAX_POKEMON_PRICE_MULTIPLIER = 6;
|
||||
|
||||
/**
|
||||
* Pokemon Salesman encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/36 | GitHub Issue #36}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withSceneRequirement(new MoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "pokemon_salesman",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true
|
||||
}
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
speaker: `${namespace}.speaker`,
|
||||
},
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||
const tries = 0;
|
||||
|
||||
// Reroll any species that don't have HAs
|
||||
while (isNullOrUndefined(species.abilityHidden) && tries < 5) {
|
||||
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||
}
|
||||
|
||||
let pokemon: PlayerPokemon;
|
||||
if (isNullOrUndefined(species.abilityHidden) || randSeedInt(100) === 0) {
|
||||
// If no HA mon found or you roll 1%, give shiny Magikarp
|
||||
species = getPokemonSpecies(Species.MAGIKARP);
|
||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true);
|
||||
} else {
|
||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
|
||||
}
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
|
||||
encounter.spriteConfigs.push({
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true
|
||||
});
|
||||
|
||||
const starterTier = speciesStarters[species.speciesId];
|
||||
// Prices decrease by starter tier less than 5, but only reduces cost by half at max
|
||||
let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5);
|
||||
if (pokemon.shiny) {
|
||||
// Always max price for shiny (flip HA back to normal), and add special messaging
|
||||
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
|
||||
pokemon.abilityIndex = 0;
|
||||
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}.description_shiny`;
|
||||
encounter.options[0].dialogue!.buttonTooltip = `${namespace}.option.1.tooltip_shiny`;
|
||||
}
|
||||
const price = scene.getWaveMoneyAmount(priceMultiplier);
|
||||
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("price", price.toString());
|
||||
encounter.misc = {
|
||||
price: price,
|
||||
pokemon: pokemon
|
||||
};
|
||||
|
||||
pokemon.calculateStats();
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withSceneMoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected_message`,
|
||||
}
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const price = encounter.misc.price;
|
||||
const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon;
|
||||
|
||||
// Update money
|
||||
updatePlayerMoney(scene, -price, true, false);
|
||||
|
||||
// Show dialogue
|
||||
await showEncounterDialogue(scene, `${namespace}.option.1.selected_dialogue`, `${namespace}.speaker`);
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
|
||||
// "Catch" purchased pokemon
|
||||
const data = new PokemonData(purchasedPokemon);
|
||||
data.player = false;
|
||||
await catchPokemon(scene, data.toPokemon(scene) as EnemyPokemon, null, PokeballType.POKEBALL, true, true);
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
@ -0,0 +1,199 @@
|
||||
import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Species } from "#enums/species";
|
||||
import { Nature } from "#app/data/nature";
|
||||
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
import { StatChangePhase } from "#app/phases/stat-change-phase";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:theStrongStuff";
|
||||
|
||||
/**
|
||||
* The Strong Stuff encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/54 | GitHub Issue #54}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TheStrongStuffEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "berry_juice",
|
||||
fileRoot: "items",
|
||||
hasShadow: true,
|
||||
isItem: true,
|
||||
scale: 1.5,
|
||||
x: -15,
|
||||
y: 3,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: Species.SHUCKLE.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
scale: 1.5,
|
||||
x: 20,
|
||||
y: 10,
|
||||
yShadow: 7
|
||||
},
|
||||
]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
disableSwitch: true,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2
|
||||
}
|
||||
],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.stat_boost`);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF, BattleStat.SPDEF], 2));
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
loadCustomMovesForEncounter(scene, [Moves.GASTRO_ACID, Moves.STEALTH_ROCK]);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`
|
||||
}
|
||||
]
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Do blackout and hide intro visuals during blackout
|
||||
scene.time.delayedCall(750, () => {
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 50);
|
||||
});
|
||||
|
||||
// -20 to all base stats of highest BST, +10 to all base stats of rest of party
|
||||
// Get highest BST mon
|
||||
const party = scene.getParty();
|
||||
let highestBst: PlayerPokemon | null = null;
|
||||
let statTotal = 0;
|
||||
for (const pokemon of party) {
|
||||
if (!highestBst) {
|
||||
highestBst = pokemon;
|
||||
statTotal = pokemon.getSpeciesForm().getBaseStatTotal();
|
||||
continue;
|
||||
}
|
||||
|
||||
const total = pokemon.getSpeciesForm().getBaseStatTotal();
|
||||
if (total > statTotal) {
|
||||
highestBst = pokemon;
|
||||
statTotal = total;
|
||||
}
|
||||
}
|
||||
|
||||
if (!highestBst) {
|
||||
highestBst = party[0];
|
||||
}
|
||||
|
||||
modifyPlayerPokemonBST(highestBst, -20);
|
||||
for (const pokemon of party) {
|
||||
if (highestBst.id === pokemon.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
modifyPlayerPokemonBST(pokemon, 10);
|
||||
}
|
||||
|
||||
encounter.setDialogueToken("highBstPokemon", highestBst.getNameToRender());
|
||||
await showEncounterText(scene, `${namespace}.option.1.selected_2`, undefined, true);
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW], fillRemaining: true });
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.GASTRO_ACID),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.STEALTH_ROCK),
|
||||
ignorePp: true
|
||||
});
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.build();
|
@ -0,0 +1,497 @@
|
||||
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { Species } from "#enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { Type } from "#app/data/type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms";
|
||||
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/ability";
|
||||
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
|
||||
import { ReturnPhase } from "#app/phases/return-phase";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:theWinstrateChallenge";
|
||||
|
||||
/**
|
||||
* The Winstrate Challenge encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/136 | GitHub Issue #136}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TheWinstrateChallengeEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withSceneWaveRangeRequirement(80, 180)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "vito",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: false,
|
||||
x: 16,
|
||||
y: -4
|
||||
},
|
||||
{
|
||||
spriteKey: "vivi",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: false,
|
||||
x: -14,
|
||||
y: -4
|
||||
},
|
||||
{
|
||||
spriteKey: "victor",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: -32
|
||||
},
|
||||
{
|
||||
spriteKey: "victoria",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 40,
|
||||
},
|
||||
{
|
||||
spriteKey: "vicky",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 3,
|
||||
y: 5,
|
||||
yShadow: 5
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Loaded back to front for pop() operations
|
||||
encounter.enemyPartyConfigs.push(getVitoTrainerConfig(scene));
|
||||
encounter.enemyPartyConfigs.push(getVickyTrainerConfig(scene));
|
||||
encounter.enemyPartyConfigs.push(getViviTrainerConfig(scene));
|
||||
encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig(scene));
|
||||
encounter.enemyPartyConfigs.push(getVictorTrainerConfig(scene));
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: "trainerNames:victor",
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Spawn 5 trainer battles back to back with Macho Brace in rewards
|
||||
// scene.currentBattle.mysteryEncounter.continuousEncounter = true;
|
||||
scene.currentBattle.mysteryEncounter.doContinueEncounter = (scene: BattleScene) => {
|
||||
return endTrainerBattleAndShowDialogue(scene);
|
||||
};
|
||||
await transitionMysteryEncounterIntroVisuals(scene, true, false);
|
||||
await spawnNextTrainerOrEndEncounter(scene);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
|
||||
scene.unshiftPhase(new PartyHealPhase(scene, true));
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], fillRemaining: false });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const nextConfig = encounter.enemyPartyConfigs.pop();
|
||||
if (!nextConfig) {
|
||||
await transitionMysteryEncounterIntroVisuals(scene, false, false);
|
||||
await showEncounterDialogue(scene, `${namespace}.victory`, `${namespace}.speaker`);
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE], fillRemaining: false });
|
||||
encounter.doContinueEncounter = undefined;
|
||||
leaveEncounterWithoutBattle(scene, false, MysteryEncounterMode.TRAINER_BATTLE);
|
||||
} else {
|
||||
await initBattleWithEnemyConfig(scene, nextConfig);
|
||||
}
|
||||
}
|
||||
|
||||
function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
|
||||
return new Promise(async resolve => {
|
||||
if (scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length === 0) {
|
||||
// Battle is over
|
||||
const trainer = scene.currentBattle.trainer;
|
||||
if (trainer) {
|
||||
scene.tweens.add({
|
||||
targets: trainer,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750,
|
||||
onComplete: () => {
|
||||
scene.field.remove(trainer, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await spawnNextTrainerOrEndEncounter(scene);
|
||||
resolve(); // Wait for all dialogue/post battle stuff to complete before resolving
|
||||
} else {
|
||||
scene.arena.resetArenaEffects();
|
||||
const playerField = scene.getPlayerField();
|
||||
playerField.forEach((_, p) => scene.unshiftPhase(new ReturnPhase(scene, p)));
|
||||
|
||||
for (const pokemon of scene.getParty()) {
|
||||
// Only trigger form change when Eiscue is in Noice form
|
||||
// Hardcoded Eiscue for now in case it is fused with another pokemon
|
||||
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
|
||||
scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
|
||||
}
|
||||
|
||||
pokemon.resetBattleData();
|
||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
||||
}
|
||||
|
||||
scene.unshiftPhase(new ShowTrainerPhase(scene));
|
||||
// Hide the trainer and init next battle
|
||||
const trainer = scene.currentBattle.trainer;
|
||||
// Unassign previous trainer from battle so it isn't destroyed before animation completes
|
||||
scene.currentBattle.trainer = null;
|
||||
await spawnNextTrainerOrEndEncounter(scene);
|
||||
if (trainer) {
|
||||
scene.tweens.add({
|
||||
targets: trainer,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750,
|
||||
onComplete: () => {
|
||||
scene.field.remove(trainer, true);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig {
|
||||
return {
|
||||
trainerType: TrainerType.VICTOR,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.SWELLOW),
|
||||
isBoss: false,
|
||||
abilityIndex: 0, // Guts
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.OBSTAGOON),
|
||||
isBoss: false,
|
||||
abilityIndex: 1, // Guts
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig {
|
||||
return {
|
||||
trainerType: TrainerType.VICTORIA,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.ROSERADE),
|
||||
isBoss: false,
|
||||
abilityIndex: 0, // Natural Cure
|
||||
nature: Nature.CALM,
|
||||
moveSet: [Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.GARDEVOIR),
|
||||
isBoss: false,
|
||||
formIndex: 1,
|
||||
nature: Nature.TIMID,
|
||||
moveSet: [Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.PSYCHIC]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FAIRY]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig {
|
||||
return {
|
||||
trainerType: TrainerType.VIVI,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.SEAKING),
|
||||
isBoss: false,
|
||||
abilityIndex: 3, // Lightning Rod
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
stackCount: 4,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.BRELOOM),
|
||||
isBoss: false,
|
||||
abilityIndex: 1, // Poison Heal
|
||||
nature: Nature.JOLLY,
|
||||
moveSet: [Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
stackCount: 4,
|
||||
isTransferable: false
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.CAMERUPT),
|
||||
isBoss: false,
|
||||
formIndex: 1,
|
||||
nature: Nature.CALM,
|
||||
moveSet: [Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 3,
|
||||
isTransferable: false
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig {
|
||||
return {
|
||||
trainerType: TrainerType.VICKY,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.MEDICHAM),
|
||||
isBoss: false,
|
||||
formIndex: 1,
|
||||
nature: Nature.IMPISH,
|
||||
moveSet: [Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
|
||||
return {
|
||||
trainerType: TrainerType.VITO,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.HISUI_ELECTRODE),
|
||||
isBoss: false,
|
||||
abilityIndex: 0, // Soundproof
|
||||
nature: Nature.MODEST,
|
||||
moveSet: [Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.SWALOT),
|
||||
isBoss: false,
|
||||
abilityIndex: 2, // Gluttony
|
||||
nature: Nature.QUIET,
|
||||
moveSet: [Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.DODRIO),
|
||||
isBoss: false,
|
||||
abilityIndex: 2, // Tangled Feet
|
||||
nature: Nature.JOLLY,
|
||||
moveSet: [Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.ALAKAZAM),
|
||||
isBoss: false,
|
||||
formIndex: 1,
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.DARMANITAN),
|
||||
isBoss: false,
|
||||
abilityIndex: 0, // Sheer Force
|
||||
nature: Nature.IMPISH,
|
||||
moveSet: [Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
@ -0,0 +1,433 @@
|
||||
import { Ability, allAbilities } from "#app/data/ability";
|
||||
import { EnemyPartyConfig, initBattleWithEnemyConfig, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getNatureName, Nature } from "#app/data/nature";
|
||||
import { speciesStarters } from "#app/data/pokemon-species";
|
||||
import { Stat } from "#app/data/pokemon-stat";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { pokemonInfo } from "#app/locales/en/pokemon-info";
|
||||
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { AbilityAttr } from "#app/system/game-data";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { isNullOrUndefined, randSeedShuffle } from "#app/utils";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
|
||||
/** The i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:trainingSession";
|
||||
|
||||
/**
|
||||
* Training Session encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/43 | GitHub Issue #43}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TrainingSessionEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
|
||||
.withHideWildIntroMessage(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "training_gear",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 6,
|
||||
x: 5,
|
||||
yShadow: -2
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
}
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
};
|
||||
};
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
|
||||
// Spawn light training session with chosen pokemon
|
||||
// Every 50 waves, add +1 boss segment, capping at 5
|
||||
const segments = Math.min(
|
||||
2 + Math.floor(scene.currentBattle.waveIndex / 50),
|
||||
5
|
||||
);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(
|
||||
scene,
|
||||
playerPokemon,
|
||||
segments,
|
||||
modifiers
|
||||
);
|
||||
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const getIvName = (index: number) => {
|
||||
switch (index) {
|
||||
case Stat.HP:
|
||||
return pokemonInfo.Stat["HPshortened"];
|
||||
case Stat.ATK:
|
||||
return pokemonInfo.Stat["ATKshortened"];
|
||||
case Stat.DEF:
|
||||
return pokemonInfo.Stat["DEFshortened"];
|
||||
case Stat.SPATK:
|
||||
return pokemonInfo.Stat["SPATKshortened"];
|
||||
case Stat.SPDEF:
|
||||
return pokemonInfo.Stat["SPDEFshortened"];
|
||||
case Stat.SPD:
|
||||
return pokemonInfo.Stat["SPDshortened"];
|
||||
}
|
||||
};
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
encounter.setDialogueToken("stat1", "-");
|
||||
encounter.setDialogueToken("stat2", "-");
|
||||
// Add the pokemon back to party with IV boost
|
||||
const ivIndexes: any[] = [];
|
||||
playerPokemon.ivs.forEach((iv, index) => {
|
||||
if (iv < 31) {
|
||||
ivIndexes.push({ iv: iv, index: index });
|
||||
}
|
||||
});
|
||||
|
||||
// Improves 2 random non-maxed IVs
|
||||
// +10 if IV is < 10, +5 if between 10-20, and +3 if > 20
|
||||
// A 0-4 starting IV will cap in 6 encounters (assuming you always rolled that IV)
|
||||
// 5-14 starting IV caps in 5 encounters
|
||||
// 15-19 starting IV caps in 4 encounters
|
||||
// 20-24 starting IV caps in 3 encounters
|
||||
// 25-27 starting IV caps in 2 encounters
|
||||
let improvedCount = 0;
|
||||
while (ivIndexes.length > 0 && improvedCount < 2) {
|
||||
randSeedShuffle(ivIndexes);
|
||||
const ivToChange = ivIndexes.pop();
|
||||
let newVal = ivToChange.iv;
|
||||
if (improvedCount === 0) {
|
||||
encounter.setDialogueToken(
|
||||
"stat1",
|
||||
getIvName(ivToChange.index) ?? ""
|
||||
);
|
||||
} else {
|
||||
encounter.setDialogueToken(
|
||||
"stat2",
|
||||
getIvName(ivToChange.index) ?? ""
|
||||
);
|
||||
}
|
||||
|
||||
// Corrects required encounter breakpoints to be continuous for all IV values
|
||||
if (ivToChange.iv <= 21 && ivToChange.iv - (1 % 5) === 0) {
|
||||
newVal += 1;
|
||||
}
|
||||
|
||||
newVal += ivToChange.iv <= 10 ? 10 : ivToChange.iv <= 20 ? 5 : 3;
|
||||
newVal = Math.min(newVal, 31);
|
||||
playerPokemon.ivs[ivToChange.index] = newVal;
|
||||
improvedCount++;
|
||||
}
|
||||
|
||||
if (improvedCount > 0) {
|
||||
playerPokemon.calculateStats();
|
||||
scene.gameData.updateSpeciesDexIvs(
|
||||
playerPokemon.species.getRootSpeciesId(true),
|
||||
playerPokemon.ivs
|
||||
);
|
||||
scene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
}
|
||||
|
||||
// Add pokemon and mods back
|
||||
scene.getParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
scene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
scene.updateModifiers(true);
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.finished`);
|
||||
};
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.2.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
// Open menu for selecting pokemon and Nature
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const natures = new Array(25).fill(null).map((val, i) => i as Nature);
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for nature selection
|
||||
return natures.map((nature: Nature) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: getNatureName(nature, true, true, true, scene.uiTheme),
|
||||
handler: () => {
|
||||
// Pokemon and second option selected
|
||||
encounter.setDialogueToken("nature", getNatureName(nature));
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
chosenNature: nature,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
|
||||
// Spawn medium training session with chosen pokemon
|
||||
// Every 40 waves, add +1 boss segment, capping at 6
|
||||
const segments = Math.min(
|
||||
2 + Math.floor(scene.currentBattle.waveIndex / 40),
|
||||
6
|
||||
);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(
|
||||
scene,
|
||||
playerPokemon,
|
||||
segments,
|
||||
modifiers
|
||||
);
|
||||
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
queueEncounterMessage(scene, `${namespace}.option.2.finished`);
|
||||
// Add the pokemon back to party with Nature change
|
||||
playerPokemon.setNature(encounter.misc.chosenNature);
|
||||
scene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
|
||||
// Add pokemon and mods back
|
||||
scene.getParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
scene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
scene.updateModifiers(true);
|
||||
};
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}.option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
// Open menu for selecting pokemon and ability to learn
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for ability selection
|
||||
const speciesForm = !!pokemon.getFusionSpeciesForm()
|
||||
? pokemon.getFusionSpeciesForm()
|
||||
: pokemon.getSpeciesForm();
|
||||
const abilityCount = speciesForm.getAbilityCount();
|
||||
const abilities = new Array(abilityCount)
|
||||
.fill(null)
|
||||
.map((val, i) => allAbilities[speciesForm.getAbility(i)]);
|
||||
return abilities.map((ability: Ability, index) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: ability.name,
|
||||
handler: () => {
|
||||
// Pokemon and ability selected
|
||||
encounter.setDialogueToken("ability", ability.name);
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
abilityIndex: index,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
scene.ui.showText(ability.description);
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
|
||||
// Spawn hard training session with chosen pokemon
|
||||
// Every 30 waves, add +1 boss segment, capping at 6
|
||||
// Also starts with +1 to all stats
|
||||
const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 30), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(scene, playerPokemon, segments, modifiers);
|
||||
config.pokemonConfigs![0].tags = [
|
||||
BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON,
|
||||
];
|
||||
scene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
queueEncounterMessage(scene, `${namespace}.option.3.finished`);
|
||||
// Add the pokemon back to party with ability change
|
||||
const abilityIndex = encounter.misc.abilityIndex;
|
||||
if (!!playerPokemon.getFusionSpeciesForm()) {
|
||||
playerPokemon.fusionAbilityIndex = abilityIndex;
|
||||
if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies!.speciesId)) {
|
||||
scene.gameData.starterData[playerPokemon.fusionSpecies!.speciesId]
|
||||
.abilityAttr |=
|
||||
abilityIndex !== 1 || playerPokemon.fusionSpecies!.ability2
|
||||
? Math.pow(2, playerPokemon.fusionAbilityIndex)
|
||||
: AbilityAttr.ABILITY_HIDDEN;
|
||||
}
|
||||
} else {
|
||||
playerPokemon.abilityIndex = abilityIndex;
|
||||
if (
|
||||
speciesStarters.hasOwnProperty(playerPokemon.species.speciesId)
|
||||
) {
|
||||
scene.gameData.starterData[
|
||||
playerPokemon.species.speciesId
|
||||
].abilityAttr |=
|
||||
abilityIndex !== 1 || playerPokemon.species.ability2
|
||||
? Math.pow(2, playerPokemon.abilityIndex)
|
||||
: AbilityAttr.ABILITY_HIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
playerPokemon.getAbility();
|
||||
playerPokemon.calculateStats();
|
||||
scene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
|
||||
// Add pokemon and mods back
|
||||
scene.getParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
scene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
scene.updateModifiers(true);
|
||||
};
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
return initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
|
||||
function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon,segments: number,modifiers: ModifiersHolder): EnemyPartyConfig {
|
||||
playerPokemon.resetSummonData();
|
||||
|
||||
// Passes modifiers by reference
|
||||
modifiers.value = scene.findModifiers((m) => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === playerPokemon.id) as PokemonHeldItemModifier[];
|
||||
const modifierConfigs = modifiers.value.map((mod) => {
|
||||
return {
|
||||
modifierType: mod.type
|
||||
};
|
||||
}) as HeldModifierConfig[];
|
||||
|
||||
const data = new PokemonData(playerPokemon);
|
||||
return {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: playerPokemon.species,
|
||||
isBoss: true,
|
||||
bossSegments: segments,
|
||||
formIndex: playerPokemon.formIndex,
|
||||
level: playerPokemon.level,
|
||||
dataSource: data,
|
||||
modifierConfigs: modifierConfigs,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
class ModifiersHolder {
|
||||
public value: PokemonHeldItemModifier[] = [];
|
||||
|
||||
constructor() {}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Species } from "#enums/species";
|
||||
import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app/modifier/modifier";
|
||||
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:trashToTreasure";
|
||||
|
||||
const SOUND_EFFECT_WAIT_TIME = 700;
|
||||
|
||||
/**
|
||||
* Trash to Treasure encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/74 | GitHub Issue #74}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TrashToTreasureEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withMaxAllowedEncounters(1)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.GARBODOR.toString() + "-gigantamax",
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: false,
|
||||
disableAnimation: true,
|
||||
scale: 1.5,
|
||||
y: 8,
|
||||
tint: 0.4
|
||||
}
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Calculate boss mon
|
||||
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
formIndex: 1, // Gmax
|
||||
bossSegmentModifier: 1, // +1 Segment from normal
|
||||
moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH]
|
||||
};
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
pokemonConfigs: [pokemonConfig],
|
||||
disableSwitch: true
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Load animations/sfx for Garbodor fight start moves
|
||||
loadCustomMovesForEncounter(scene, [Moves.TOXIC, Moves.AMNESIA]);
|
||||
|
||||
scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
|
||||
scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Play Dig2 and then Venom Drench sfx
|
||||
doGarbageDig(scene);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Gain 2 Leftovers and 2 Shell Bell
|
||||
transitionMysteryEncounterIntroVisuals(scene);
|
||||
await tryApplyDigRewardItems(scene);
|
||||
|
||||
// Give the player the Black Sludge curse
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE));
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Investigate garbage, battle Gmax Garbodor
|
||||
scene.setFieldScale(0.75);
|
||||
await showEncounterText(scene, `${namespace}.option.2.selected_2`);
|
||||
transitionMysteryEncounterIntroVisuals(scene);
|
||||
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.TOXIC),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY],
|
||||
move: new PokemonMove(Moves.AMNESIA),
|
||||
ignorePp: true
|
||||
});
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
|
||||
async function tryApplyDigRewardItems(scene: BattleScene) {
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
const leftovers = generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
|
||||
|
||||
const party = scene.getParty();
|
||||
|
||||
// Iterate over the party until an item was successfully given
|
||||
// First leftovers
|
||||
for (const pokemon of party) {
|
||||
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
|
||||
|
||||
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Second leftovers
|
||||
for (const pokemon of party) {
|
||||
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
|
||||
|
||||
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), undefined, true);
|
||||
|
||||
// First Shell bell
|
||||
for (const pokemon of party) {
|
||||
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
|
||||
|
||||
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Second Shell bell
|
||||
for (const pokemon of party) {
|
||||
const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
|
||||
|
||||
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), undefined, true);
|
||||
}
|
||||
|
||||
async function doGarbageDig(scene: BattleScene) {
|
||||
scene.playSound("PRSFX- Dig2");
|
||||
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
|
||||
scene.playSound("PRSFX- Dig2");
|
||||
scene.playSound("PRSFX- Venom Drench", { volume: 2 });
|
||||
});
|
||||
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => {
|
||||
scene.playSound("PRSFX- Dig2");
|
||||
});
|
||||
}
|
541
src/data/mystery-encounters/encounters/weird-dream-encounter.ts
Normal file
@ -0,0 +1,541 @@
|
||||
import { Type } from "#app/data/type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonBaseStatTotalModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { achvs } from "#app/system/achv";
|
||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { Stat } from "#app/data/pokemon-stat";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||
import { getLevelTotalExp } from "#app/data/exp";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
const namespace = "mysteryEncounter:weirdDream";
|
||||
|
||||
/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, Urshifu, the Poison Chain trio, Ogerpon */
|
||||
const excludedPokemon = [
|
||||
Species.ETERNATUS,
|
||||
/** UBs */
|
||||
Species.NIHILEGO,
|
||||
Species.BUZZWOLE,
|
||||
Species.PHEROMOSA,
|
||||
Species.XURKITREE,
|
||||
Species.CELESTEELA,
|
||||
Species.KARTANA,
|
||||
Species.GUZZLORD,
|
||||
Species.POIPOLE,
|
||||
Species.NAGANADEL,
|
||||
Species.STAKATAKA,
|
||||
Species.BLACEPHALON,
|
||||
/** Paradox */
|
||||
Species.GREAT_TUSK,
|
||||
Species.SCREAM_TAIL,
|
||||
Species.BRUTE_BONNET,
|
||||
Species.FLUTTER_MANE,
|
||||
Species.SLITHER_WING,
|
||||
Species.SANDY_SHOCKS,
|
||||
Species.ROARING_MOON,
|
||||
Species.WALKING_WAKE,
|
||||
Species.GOUGING_FIRE,
|
||||
Species.RAGING_BOLT,
|
||||
Species.IRON_TREADS,
|
||||
Species.IRON_BUNDLE,
|
||||
Species.IRON_HANDS,
|
||||
Species.IRON_JUGULIS,
|
||||
Species.IRON_MOTH,
|
||||
Species.IRON_THORNS,
|
||||
Species.IRON_VALIANT,
|
||||
Species.IRON_LEAVES,
|
||||
Species.IRON_BOULDER,
|
||||
Species.IRON_CROWN,
|
||||
/** These are banned so they don't appear in the < 570 BST pool */
|
||||
Species.COSMOG,
|
||||
Species.MELTAN,
|
||||
Species.KUBFU,
|
||||
Species.COSMOEM,
|
||||
Species.POIPOLE,
|
||||
Species.TERAPAGOS,
|
||||
Species.TYPE_NULL,
|
||||
Species.CALYREX,
|
||||
Species.NAGANADEL,
|
||||
Species.URSHIFU,
|
||||
Species.OGERPON,
|
||||
Species.OKIDOGI,
|
||||
Species.MUNKIDORI,
|
||||
Species.FEZANDIPITI,
|
||||
];
|
||||
|
||||
/**
|
||||
* Weird Dream encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/137 | GitHub Issue #137}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const WeirdDreamEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "girawitch",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
y: 4
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
// Change the bgm
|
||||
scene.fadeOutBgm(3000, false);
|
||||
scene.time.delayedCall(3000, () => {
|
||||
scene.playBgm("mystery_encounter_weird_dream");
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
}
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Play the animation as the player goes through the dialogue
|
||||
scene.time.delayedCall(1000, () => {
|
||||
doShowDreamBackground(scene);
|
||||
});
|
||||
|
||||
// Calculate all the newly transformed Pokemon and begin asset load
|
||||
const teamTransformations = getTeamTransformations(scene);
|
||||
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
|
||||
scene.currentBattle.mysteryEncounter.misc = {
|
||||
teamTransformations,
|
||||
loadAssets
|
||||
};
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue
|
||||
const cutsceneDialoguePromise = showEncounterText(scene, `${namespace}.option.1.cutscene`);
|
||||
|
||||
// Change the entire player's party
|
||||
// Wait for all new Pokemon assets to be loaded before showing transformation animations
|
||||
await Promise.all(scene.currentBattle.mysteryEncounter.misc.loadAssets);
|
||||
const transformations = scene.currentBattle.mysteryEncounter.misc.teamTransformations;
|
||||
|
||||
// If there are 1-3 transformations, do them centered back to back
|
||||
// Otherwise, the first 3 transformations are executed side-by-side, then any remaining 1-3 transformations occur in those same respective positions
|
||||
if (transformations.length <= 3) {
|
||||
for (const transformation of transformations) {
|
||||
const pokemon1 = transformation.previousPokemon;
|
||||
const pokemon2 = transformation.newPokemon;
|
||||
|
||||
await doPokemonTransformationSequence(scene, pokemon1, pokemon2, TransformationScreenPosition.CENTER);
|
||||
}
|
||||
} else {
|
||||
await doSideBySideTransformations(scene, transformations);
|
||||
}
|
||||
|
||||
// Make sure player has finished cutscene dialogue
|
||||
await cutsceneDialoguePromise;
|
||||
|
||||
doHideDreamBackground(scene);
|
||||
await showEncounterText(scene, `${namespace}.option.1.dream_complete`);
|
||||
|
||||
await doNewTeamPostProcess(scene, transformations);
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT]});
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Reduce party levels by 20%
|
||||
for (const pokemon of scene.getParty()) {
|
||||
pokemon.level = Math.max(Math.ceil(0.8 * pokemon.level), 1);
|
||||
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
|
||||
pokemon.levelExp = 0;
|
||||
|
||||
pokemon.calculateStats();
|
||||
pokemon.updateInfo();
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
interface PokemonTransformation {
|
||||
previousPokemon: PlayerPokemon;
|
||||
newSpecies: PokemonSpecies;
|
||||
newPokemon: PlayerPokemon;
|
||||
heldItems: PokemonHeldItemModifier[];
|
||||
}
|
||||
|
||||
function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
|
||||
const party = scene.getParty();
|
||||
// Removes all pokemon from the party
|
||||
const alreadyUsedSpecies: PokemonSpecies[] = [];
|
||||
const pokemonTransformations: PokemonTransformation[] = party.map(p => {
|
||||
return {
|
||||
previousPokemon: p
|
||||
} as PokemonTransformation;
|
||||
});
|
||||
|
||||
// Only 1 Pokemon can be transformed into BST higher than 600
|
||||
let hasPokemonBstHigherThan600 = false;
|
||||
// Only 1 other Pokemon can be transformed into BST between 570-600
|
||||
let hasPokemonBstBetween570And600 = false;
|
||||
|
||||
// First, roll 2 of the party members to new Pokemon at a +90 to +110 BST difference
|
||||
// Then, roll the remainder of the party members at a +40 to +50 BST difference
|
||||
const numPokemon = party.length;
|
||||
for (let i = 0; i < numPokemon; i++) {
|
||||
const removed = party[randSeedInt(party.length)];
|
||||
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
|
||||
pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
scene.removePokemonFromPlayerParty(removed, false);
|
||||
|
||||
const bst = getOriginalBst(scene, removed);
|
||||
let newBstRange;
|
||||
if (i < 2) {
|
||||
newBstRange = [90, 110];
|
||||
} else {
|
||||
newBstRange = [40, 50];
|
||||
}
|
||||
|
||||
const newSpecies = getTransformedSpecies(bst, newBstRange, hasPokemonBstHigherThan600, hasPokemonBstBetween570And600, alreadyUsedSpecies);
|
||||
|
||||
const newSpeciesBst = newSpecies.getBaseStatTotal();
|
||||
if (newSpeciesBst > 600) {
|
||||
hasPokemonBstHigherThan600 = true;
|
||||
}
|
||||
if (newSpeciesBst <= 600 && newSpeciesBst >= 570) {
|
||||
hasPokemonBstBetween570And600 = true;
|
||||
}
|
||||
|
||||
|
||||
pokemonTransformations[index].newSpecies = newSpecies;
|
||||
alreadyUsedSpecies.push(newSpecies);
|
||||
}
|
||||
|
||||
for (const transformation of pokemonTransformations) {
|
||||
const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount());
|
||||
const newPlayerPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined);
|
||||
transformation.newPokemon = newPlayerPokemon;
|
||||
scene.getParty().push(newPlayerPokemon);
|
||||
}
|
||||
|
||||
return pokemonTransformations;
|
||||
}
|
||||
|
||||
async function doNewTeamPostProcess(scene: BattleScene, transformations: PokemonTransformation[]) {
|
||||
let atLeastOneNewStarter = false;
|
||||
for (const transformation of transformations) {
|
||||
const previousPokemon = transformation.previousPokemon;
|
||||
const newPokemon = transformation.newPokemon;
|
||||
const speciesRootForm = newPokemon.species.getRootSpeciesId();
|
||||
|
||||
// Roll HA a second time
|
||||
if (newPokemon.species.abilityHidden) {
|
||||
const hiddenIndex = newPokemon.species.ability2 ? 2 : 1;
|
||||
if (newPokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new IntegerHolder(256);
|
||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
if (hasHiddenAbility) {
|
||||
newPokemon.abilityIndex = hiddenIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Roll IVs a second time
|
||||
newPokemon.ivs = newPokemon.ivs.map(iv => {
|
||||
const newValue = randSeedInt(31);
|
||||
return newValue > iv ? newValue : iv;
|
||||
});
|
||||
|
||||
|
||||
// For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it
|
||||
if (newPokemon.getSpeciesForm().getBaseStatTotal() <= 570 || newPokemon.isShiny()) {
|
||||
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) {
|
||||
scene.validateAchv(achvs.HIDDEN_ABILITY);
|
||||
}
|
||||
|
||||
if (newPokemon.species.subLegendary) {
|
||||
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
|
||||
}
|
||||
|
||||
if (newPokemon.species.legendary) {
|
||||
scene.validateAchv(achvs.CATCH_LEGENDARY);
|
||||
}
|
||||
|
||||
if (newPokemon.species.mythical) {
|
||||
scene.validateAchv(achvs.CATCH_MYTHICAL);
|
||||
}
|
||||
|
||||
scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs);
|
||||
const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false);
|
||||
if (newStarterUnlocked) {
|
||||
atLeastOneNewStarter = true;
|
||||
queueEncounterMessage(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
|
||||
}
|
||||
}
|
||||
|
||||
// If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock)
|
||||
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
|
||||
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
|
||||
});
|
||||
|
||||
// For pokemon that the player owns (including ones just caught), gain a candy
|
||||
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
|
||||
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
|
||||
}
|
||||
|
||||
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move of the new species
|
||||
newPokemon.moveset = previousPokemon.moveset;
|
||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
||||
const eggMoves = speciesEggMoves[speciesRootForm];
|
||||
const eggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove = eggMoves[eggMoveIndex];
|
||||
if (newPokemon.moveset.length < 4) {
|
||||
newPokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
newPokemon.moveset[randSeedInt(4)] = new PokemonMove(randomEggMove);
|
||||
}
|
||||
// For pokemon that the player owns (including ones just caught), unlock the egg move
|
||||
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
|
||||
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), eggMoveIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Randomize the second type of the pokemon
|
||||
// If the pokemon does not normally have a second type, it will gain 1
|
||||
const newTypes = [newPokemon.getTypes()[0]];
|
||||
let newType = randSeedInt(18) as Type;
|
||||
while (newType === newTypes[0]) {
|
||||
newType = randSeedInt(18) as Type;
|
||||
}
|
||||
newTypes.push(newType);
|
||||
if (!newPokemon.mysteryEncounterData) {
|
||||
newPokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes);
|
||||
} else {
|
||||
newPokemon.mysteryEncounterData.types = newTypes;
|
||||
}
|
||||
|
||||
for (const item of transformation.heldItems) {
|
||||
item.pokemonId = newPokemon.id;
|
||||
scene.addModifier(item, false, false, false, true);
|
||||
}
|
||||
|
||||
// Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP, lowest of Atk/SpAtk, and lowest of Def/SpDef
|
||||
if (newPokemon.getSpeciesForm().getBaseStatTotal() <= 450) {
|
||||
const stats: Stat[] = [Stat.HP];
|
||||
const baseStats = newPokemon.getSpeciesForm().baseStats.slice(0);
|
||||
// Attack or SpAtk
|
||||
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
||||
// Def or SpDef
|
||||
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
|
||||
// const mod = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().newModifier(newPokemon, 20, stats);
|
||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().generateType(scene.getParty(), [20, stats]);
|
||||
const modifier = modType?.newModifier(newPokemon);
|
||||
if (modifier) {
|
||||
scene.addModifier(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable passive if previous had it
|
||||
newPokemon.passive = previousPokemon.passive;
|
||||
|
||||
newPokemon.calculateStats();
|
||||
newPokemon.initBattleInfo();
|
||||
}
|
||||
|
||||
// One random pokemon will get its passive unlocked
|
||||
const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive);
|
||||
if (passiveDisabledPokemon?.length > 0) {
|
||||
passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)].passive = true;
|
||||
}
|
||||
|
||||
// If at least one new starter was unlocked, play 1 fanfare
|
||||
if (atLeastOneNewStarter) {
|
||||
scene.playSound("level_up_fanfare");
|
||||
}
|
||||
}
|
||||
|
||||
function getOriginalBst(scene: BattleScene, pokemon: Pokemon) {
|
||||
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
||||
scene.applyModifiers(PokemonBaseStatTotalModifier, true, pokemon, baseStats);
|
||||
if (pokemon.fusionSpecies) {
|
||||
const fusionBaseStats = pokemon.getFusionSpeciesForm().baseStats;
|
||||
for (let s = 0; s < pokemon.stats.length; s++) {
|
||||
baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2);
|
||||
}
|
||||
} else if (scene.gameMode.isSplicedOnly) {
|
||||
for (let s = 0; s < pokemon.stats.length; s++) {
|
||||
baseStats[s] = Math.ceil(baseStats[s] / 2);
|
||||
}
|
||||
}
|
||||
return baseStats.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies {
|
||||
let newSpecies: PokemonSpecies | undefined;
|
||||
while (isNullOrUndefined(newSpecies)) {
|
||||
const bstCap = originalBst + bstSearchRange[1];
|
||||
const bstMin = Math.max(originalBst + bstSearchRange[0], 0);
|
||||
|
||||
// Get any/all species that fall within the Bst range requirements
|
||||
let validSpecies = allSpecies
|
||||
.filter(s => {
|
||||
const speciesBst = s.getBaseStatTotal();
|
||||
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
|
||||
// Checks that a Pokemon has not already been added in the +600 or 570-600 slots;
|
||||
const validBst = (!hasPokemonBstBetween570And600 || (speciesBst < 570 || speciesBst > 600)) &&
|
||||
(!hasPokemonBstHigherThan600 || speciesBst <= 600);
|
||||
return bstInRange && validBst && !excludedPokemon.includes(s.speciesId);
|
||||
});
|
||||
|
||||
// There must be at least 20 species available before it will choose one
|
||||
if (validSpecies?.length > 20) {
|
||||
validSpecies = randSeedShuffle(validSpecies);
|
||||
newSpecies = validSpecies.pop();
|
||||
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) {
|
||||
newSpecies = validSpecies.pop();
|
||||
}
|
||||
} else {
|
||||
// Expands search rand until a Pokemon is found
|
||||
bstSearchRange[0] -= 10;
|
||||
bstSearchRange[1] += 10;
|
||||
}
|
||||
}
|
||||
|
||||
return newSpecies!;
|
||||
}
|
||||
|
||||
function doShowDreamBackground(scene: BattleScene) {
|
||||
const transformationContainer = scene.add.container(0, -scene.game.canvas.height / 6);
|
||||
transformationContainer.name = "Dream Background";
|
||||
|
||||
// In case it takes a bit for video to load
|
||||
const transformationStaticBg = scene.add.rectangle(0, 0, scene.game.canvas.width / 6, scene.game.canvas.height / 6, 0);
|
||||
transformationStaticBg.setName("Black Background");
|
||||
transformationStaticBg.setOrigin(0, 0);
|
||||
transformationContainer.add(transformationStaticBg);
|
||||
transformationStaticBg.setVisible(true);
|
||||
|
||||
const transformationVideoBg: Phaser.GameObjects.Video = scene.add.video(0, 0, "evo_bg").stop();
|
||||
transformationVideoBg.setLoop(true);
|
||||
transformationVideoBg.setOrigin(0, 0);
|
||||
transformationVideoBg.setScale(0.4359673025);
|
||||
transformationContainer.add(transformationVideoBg);
|
||||
|
||||
scene.fieldUI.add(transformationContainer);
|
||||
scene.fieldUI.bringToTop(transformationContainer);
|
||||
transformationVideoBg.play();
|
||||
|
||||
transformationContainer.setVisible(true);
|
||||
transformationContainer.alpha = 0;
|
||||
|
||||
scene.tweens.add({
|
||||
targets: transformationContainer,
|
||||
alpha: 1,
|
||||
duration: 3000,
|
||||
ease: "Sine.easeInOut"
|
||||
});
|
||||
}
|
||||
|
||||
function doHideDreamBackground(scene: BattleScene) {
|
||||
const transformationContainer = scene.fieldUI.getByName("Dream Background");
|
||||
|
||||
scene.tweens.add({
|
||||
targets: transformationContainer,
|
||||
alpha: 0,
|
||||
duration: 3000,
|
||||
ease: "Sine.easeInOut",
|
||||
onComplete: () => {
|
||||
scene.fieldUI.remove(transformationContainer, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doSideBySideTransformations(scene: BattleScene, transformations: PokemonTransformation[]) {
|
||||
return new Promise<void>(resolve => {
|
||||
const allTransformationPromises: Promise<void>[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const delay = i * 4000;
|
||||
scene.time.delayedCall(delay, () => {
|
||||
const transformation = transformations[i];
|
||||
const pokemon1 = transformation.previousPokemon;
|
||||
const pokemon2 = transformation.newPokemon;
|
||||
const screenPosition = i as TransformationScreenPosition;
|
||||
|
||||
const transformationPromise = doPokemonTransformationSequence(scene, pokemon1, pokemon2, screenPosition)
|
||||
.then(() => {
|
||||
if (transformations.length > i + 3) {
|
||||
const nextTransformationAtPosition = transformations[i + 3];
|
||||
const nextPokemon1 = nextTransformationAtPosition.previousPokemon;
|
||||
const nextPokemon2 = nextTransformationAtPosition.newPokemon;
|
||||
|
||||
allTransformationPromises.push(doPokemonTransformationSequence(scene, nextPokemon1, nextPokemon2, screenPosition));
|
||||
}
|
||||
});
|
||||
allTransformationPromises.push(transformationPromise);
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for all transformations to be loaded into promise array
|
||||
const id = setInterval(checkAllPromisesExist, 500);
|
||||
async function checkAllPromisesExist() {
|
||||
if (allTransformationPromises.length === transformations.length) {
|
||||
clearInterval(id);
|
||||
await Promise.all(allTransformationPromises);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
16
src/data/mystery-encounters/mystery-encounter-data.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
|
||||
export class MysteryEncounterData {
|
||||
encounteredEvents: [MysteryEncounterType, MysteryEncounterTier][] = [];
|
||||
encounterSpawnChance: number = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||
nextEncounterQueue: [MysteryEncounterType, integer][] = [];
|
||||
|
||||
constructor(flags: MysteryEncounterData | null) {
|
||||
if (!isNullOrUndefined(flags)) {
|
||||
Object.assign(this, flags);
|
||||
}
|
||||
}
|
||||
}
|
74
src/data/mystery-encounters/mystery-encounter-dialogue.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { TextStyle } from "#app/ui/text";
|
||||
|
||||
export class TextDisplay {
|
||||
speaker?: string;
|
||||
text: string;
|
||||
style?: TextStyle;
|
||||
}
|
||||
|
||||
export class OptionTextDisplay {
|
||||
buttonLabel: string;
|
||||
buttonTooltip?: string;
|
||||
disabledButtonLabel?: string;
|
||||
disabledButtonTooltip?: string;
|
||||
secondOptionPrompt?: string;
|
||||
selected?: TextDisplay[];
|
||||
style?: TextStyle;
|
||||
}
|
||||
|
||||
export class EncounterOptionsDialogue {
|
||||
title?: string;
|
||||
description?: string;
|
||||
query?: string;
|
||||
options?: [...OptionTextDisplay[]]; // Options array with minimum 2 options
|
||||
}
|
||||
|
||||
/**
|
||||
* Example MysteryEncounterDialogue object:
|
||||
*
|
||||
{
|
||||
intro: [
|
||||
{
|
||||
text: "this is a rendered as a message window (no title display)"
|
||||
},
|
||||
{
|
||||
speaker: "John"
|
||||
text: "this is a rendered as a dialogue window (title "John" is displayed above text)"
|
||||
}
|
||||
],
|
||||
encounterOptionsDialogue: {
|
||||
title: "This is the title displayed at top of encounter description box",
|
||||
description: "This is the description in the middle of encounter description box",
|
||||
query: "This is an optional question displayed at the bottom of the description box (keep it short)",
|
||||
options: [
|
||||
{
|
||||
buttonLabel: "Option #1 button label (keep these short)",
|
||||
selected: [ // Optional dialogue windows displayed when specific option is selected and before functional logic for the option is executed
|
||||
{
|
||||
text: "You chose option #1 message"
|
||||
},
|
||||
{
|
||||
speaker: "John"
|
||||
text: "So, you've chosen option #1! It's time to d-d-d-duel!"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
buttonLabel: "Option #2"
|
||||
}
|
||||
],
|
||||
},
|
||||
outro: [
|
||||
{
|
||||
text: "This message will be displayed at the very end of the encounter (i.e. post battle, post reward, etc.)"
|
||||
}
|
||||
],
|
||||
}
|
||||
*
|
||||
*/
|
||||
export default class MysteryEncounterDialogue {
|
||||
intro?: TextDisplay[];
|
||||
encounterOptionsDialogue?: EncounterOptionsDialogue;
|
||||
outro?: TextDisplay[];
|
||||
}
|
||||
|
260
src/data/mystery-encounters/mystery-encounter-option.ts
Normal file
@ -0,0 +1,260 @@
|
||||
import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import * as Utils from "#app/utils";
|
||||
import { Type } from "../type";
|
||||
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
|
||||
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
|
||||
export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>;
|
||||
|
||||
/**
|
||||
* Used by {@link MysteryEncounterOptionBuilder} class to define required/optional properties on the {@link MysteryEncounterOption} class when building.
|
||||
*
|
||||
* Should ONLY contain properties that are necessary for {@link MysteryEncounterOption} construction.
|
||||
* Post-construct and flag data properties are defined in the {@link MysteryEncounterOption} class itself.
|
||||
*/
|
||||
export interface IMysteryEncounterOption {
|
||||
optionMode: MysteryEncounterOptionMode;
|
||||
hasDexProgress: boolean;
|
||||
requirements: EncounterSceneRequirement[];
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[];
|
||||
secondaryPokemonRequirements: EncounterPokemonRequirement[];
|
||||
excludePrimaryFromSecondaryRequirements: boolean;
|
||||
|
||||
dialogue?: OptionTextDisplay;
|
||||
|
||||
onPreOptionPhase?: OptionPhaseCallback;
|
||||
onOptionPhase: OptionPhaseCallback;
|
||||
onPostOptionPhase?: OptionPhaseCallback;
|
||||
}
|
||||
|
||||
export default class MysteryEncounterOption implements IMysteryEncounterOption {
|
||||
optionMode: MysteryEncounterOptionMode;
|
||||
hasDexProgress: boolean;
|
||||
requirements: EncounterSceneRequirement[];
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[];
|
||||
secondaryPokemonRequirements: EncounterPokemonRequirement[];
|
||||
primaryPokemon?: PlayerPokemon;
|
||||
secondaryPokemon?: PlayerPokemon[];
|
||||
excludePrimaryFromSecondaryRequirements: boolean;
|
||||
|
||||
/**
|
||||
* Dialogue object containing all the dialogue, messages, tooltips, etc. for this option
|
||||
* Will be populated on MysteryEncounter initialization
|
||||
*/
|
||||
dialogue?: OptionTextDisplay;
|
||||
|
||||
/** Executes before any following dialogue or business logic from option. Usually this will be for calculating dialogueTokens or performing scene/data updates */
|
||||
onPreOptionPhase?: OptionPhaseCallback;
|
||||
/** Business logic function for option */
|
||||
onOptionPhase: OptionPhaseCallback;
|
||||
/** Executes after the encounter is over. Usually this will be for calculating dialogueTokens or performing data updates */
|
||||
onPostOptionPhase?: OptionPhaseCallback;
|
||||
|
||||
constructor(option: IMysteryEncounterOption | null) {
|
||||
if (!isNullOrUndefined(option)) {
|
||||
Object.assign(this, option);
|
||||
}
|
||||
this.hasDexProgress = this.hasDexProgress ?? false;
|
||||
this.requirements = this.requirements ?? [];
|
||||
this.primaryPokemonRequirements = this.primaryPokemonRequirements ?? [];
|
||||
this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ?? [];
|
||||
}
|
||||
|
||||
hasRequirements() {
|
||||
return this.requirements.length > 0 || this.primaryPokemonRequirements.length > 0 || this.secondaryPokemonRequirements.length > 0;
|
||||
}
|
||||
|
||||
meetsRequirements(scene: BattleScene) {
|
||||
return !this.requirements.some(requirement => !requirement.meetsRequirement(scene)) &&
|
||||
this.meetsSupportingRequirementAndSupportingPokemonSelected(scene) &&
|
||||
this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
|
||||
}
|
||||
|
||||
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon) {
|
||||
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
|
||||
}
|
||||
|
||||
meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene) {
|
||||
if (!this.primaryPokemonRequirements) {
|
||||
return true;
|
||||
}
|
||||
let qualified: PlayerPokemon[] = scene.getParty();
|
||||
for (const req of this.primaryPokemonRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
if (req instanceof EncounterPokemonRequirement) {
|
||||
const queryParty = req.queryParty(scene.getParty());
|
||||
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
|
||||
}
|
||||
} else {
|
||||
this.primaryPokemon = undefined;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (qualified.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.excludePrimaryFromSecondaryRequirements && this.secondaryPokemon) {
|
||||
const truePrimaryPool: PlayerPokemon[] = [];
|
||||
const overlap: PlayerPokemon[] = [];
|
||||
for (const qp of qualified) {
|
||||
if (!this.secondaryPokemon.includes(qp)) {
|
||||
truePrimaryPool.push(qp);
|
||||
} else {
|
||||
overlap.push(qp);
|
||||
}
|
||||
|
||||
}
|
||||
if (truePrimaryPool.length > 0) {
|
||||
// always choose from the non-overlapping pokemon first
|
||||
this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
|
||||
return true;
|
||||
} else {
|
||||
// if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the supporting pokemon pool
|
||||
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
|
||||
// is this working?
|
||||
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)];
|
||||
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
|
||||
return true;
|
||||
}
|
||||
console.log("Mystery Encounter Edge Case: Requirement not met due to primay pokemon overlapping with support pokemon. There's no valid primary pokemon left.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Just pick the first qualifying Pokemon
|
||||
this.primaryPokemon = qualified[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
meetsSupportingRequirementAndSupportingPokemonSelected(scene: BattleScene) {
|
||||
if (!this.secondaryPokemonRequirements) {
|
||||
this.secondaryPokemon = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
let qualified: PlayerPokemon[] = scene.getParty();
|
||||
for (const req of this.secondaryPokemonRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
if (req instanceof EncounterPokemonRequirement) {
|
||||
const queryParty = req.queryParty(scene.getParty());
|
||||
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
|
||||
}
|
||||
} else {
|
||||
this.secondaryPokemon = [];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.secondaryPokemon = qualified;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterOption> {
|
||||
optionMode: MysteryEncounterOptionMode = MysteryEncounterOptionMode.DEFAULT;
|
||||
requirements: EncounterSceneRequirement[] = [];
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
excludePrimaryFromSecondaryRequirements: boolean = false;
|
||||
isDisabledOnRequirementsNotMet: boolean = true;
|
||||
hasDexProgress: boolean = false;
|
||||
dialogue?: OptionTextDisplay;
|
||||
|
||||
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> {
|
||||
return Object.assign(new MysteryEncounterOptionBuilder(), { optionMode });
|
||||
}
|
||||
|
||||
withHasDexProgress(hasDexProgress: boolean): this & Required<Pick<IMysteryEncounterOption, "hasDexProgress">> {
|
||||
return Object.assign(this, { hasDexProgress: hasDexProgress });
|
||||
}
|
||||
|
||||
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
|
||||
if (requirement instanceof EncounterPokemonRequirement) {
|
||||
Error("Incorrectly added pokemon requirement as scene requirement.");
|
||||
}
|
||||
|
||||
this.requirements.push(requirement);
|
||||
return Object.assign(this, { requirements: this.requirements });
|
||||
}
|
||||
|
||||
withSceneMoneyRequirement(requiredMoney?: number, scalingMultiplier?: number) {
|
||||
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
|
||||
}
|
||||
|
||||
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
|
||||
return Object.assign(this, { onPreOptionPhase: onPreOptionPhase });
|
||||
}
|
||||
|
||||
withOptionPhase(onOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onOptionPhase">> {
|
||||
return Object.assign(this, { onOptionPhase: onOptionPhase });
|
||||
}
|
||||
|
||||
withPostOptionPhase(onPostOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPostOptionPhase">> {
|
||||
return Object.assign(this, { onPostOptionPhase: onPostOptionPhase });
|
||||
}
|
||||
|
||||
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
|
||||
if (requirement instanceof EncounterSceneRequirement) {
|
||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||
}
|
||||
|
||||
this.primaryPokemonRequirements.push(requirement);
|
||||
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
|
||||
}
|
||||
|
||||
/**
|
||||
* Player is required to have certain type/s of pokemon in his party (with optional min number of pokemons with that type)
|
||||
*
|
||||
* @param type the required type/s
|
||||
* @param excludeFainted whether to exclude fainted pokemon
|
||||
* @param minNumberOfPokemon number of pokemons to have that type
|
||||
* @param invertQuery
|
||||
* @returns
|
||||
*/
|
||||
withPokemonTypeRequirement(type: Type | Type[], excludeFainted?: boolean, minNumberOfPokemon?: number, invertQuery?: boolean) {
|
||||
return this.withPrimaryPokemonRequirement(new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* Player is required to have a pokemon that can learn a certain move/moveset
|
||||
*
|
||||
* @param move the required move/moves
|
||||
* @param options see {@linkcode CanLearnMoveRequirementOptions}
|
||||
* @returns
|
||||
*/
|
||||
withPokemonCanLearnMoveRequirement(move: Moves | Moves[], options?: CanLearnMoveRequirementOptions) {
|
||||
return this.withPrimaryPokemonRequirement(new CanLearnMoveRequirement(move, options));
|
||||
}
|
||||
|
||||
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
|
||||
if (requirement instanceof EncounterSceneRequirement) {
|
||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||
}
|
||||
|
||||
this.secondaryPokemonRequirements.push(requirement);
|
||||
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
|
||||
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements });
|
||||
}
|
||||
|
||||
/**
|
||||
* Se the full dialogue object to the option. Will override anything already set
|
||||
*
|
||||
* @param dialogue see {@linkcode OptionTextDisplay}
|
||||
* @returns
|
||||
*/
|
||||
withDialogue(dialogue: OptionTextDisplay) {
|
||||
this.dialogue = dialogue;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(this: IMysteryEncounterOption) {
|
||||
return new MysteryEncounterOption(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Type } from "#app/data/type";
|
||||
|
||||
export class MysteryEncounterPokemonData {
|
||||
public spriteScale: number | undefined;
|
||||
public ability: Abilities | undefined;
|
||||
public passive: Abilities | undefined;
|
||||
public types: Type[];
|
||||
|
||||
constructor(spriteScale?: number, ability?: Abilities, passive?: Abilities, types?: Type[]) {
|
||||
this.spriteScale = spriteScale;
|
||||
this.ability = ability;
|
||||
this.passive = passive;
|
||||
this.types = types ?? [];
|
||||
}
|
||||
}
|
964
src/data/mystery-encounters/mystery-encounter-requirements.ts
Normal file
@ -0,0 +1,964 @@
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { Nature } from "../nature";
|
||||
import { EvolutionItem, pokemonEvolutions } from "../pokemon-evolutions";
|
||||
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "../pokemon-forms";
|
||||
import { SpeciesFormKey } from "../pokemon-species";
|
||||
import { StatusEffect } from "../status-effect";
|
||||
import { Type } from "../type";
|
||||
import { WeatherType } from "../weather";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
||||
export interface EncounterRequirement {
|
||||
meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
|
||||
}
|
||||
|
||||
export abstract class EncounterSceneRequirement implements EncounterRequirement {
|
||||
abstract meetsRequirement(scene: BattleScene): boolean;
|
||||
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
|
||||
}
|
||||
|
||||
export class CombinationSceneRequirement extends EncounterSceneRequirement {
|
||||
orRequirements: EncounterSceneRequirement[];
|
||||
|
||||
constructor(... orRequirements: EncounterSceneRequirement[]) {
|
||||
super();
|
||||
this.orRequirements = orRequirements;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
for (const req of this.orRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
for (const req of this.orRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
return req.getDialogueToken(scene, pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
return this.orRequirements[0].getDialogueToken(scene, pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class EncounterPokemonRequirement implements EncounterRequirement {
|
||||
public minNumberOfPokemon: number;
|
||||
public invertQuery: boolean;
|
||||
|
||||
abstract meetsRequirement(scene: BattleScene): boolean;
|
||||
|
||||
/**
|
||||
* Returns all party members that are compatible with this requirement. For non pokemon related requirements, the entire party is returned.
|
||||
* @param partyPokemon
|
||||
*/
|
||||
abstract queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[];
|
||||
|
||||
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
|
||||
}
|
||||
|
||||
export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
|
||||
orRequirements: EncounterPokemonRequirement[];
|
||||
|
||||
constructor(...orRequirements: EncounterPokemonRequirement[]) {
|
||||
super();
|
||||
this.invertQuery = false;
|
||||
this.minNumberOfPokemon = 1;
|
||||
this.orRequirements = orRequirements;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
for (const req of this.orRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
for (const req of this.orRequirements) {
|
||||
const result = req.queryParty(partyPokemon);
|
||||
if (result?.length > 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
for (const req of this.orRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
return req.getDialogueToken(scene, pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
return this.orRequirements[0].getDialogueToken(scene, pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
export class PreviousEncounterRequirement extends EncounterSceneRequirement {
|
||||
previousEncounterRequirement: MysteryEncounterType;
|
||||
|
||||
/**
|
||||
* Used for specifying an encounter that must be seen before this encounter can spawn
|
||||
* @param previousEncounterRequirement
|
||||
*/
|
||||
constructor(previousEncounterRequirement: MysteryEncounterType) {
|
||||
super();
|
||||
this.previousEncounterRequirement = previousEncounterRequirement;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
return scene.mysteryEncounterData.encounteredEvents.some(e => e[0] === this.previousEncounterRequirement);
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["previousEncounter", scene.mysteryEncounterData.encounteredEvents.find(e => e[0] === this.previousEncounterRequirement)?.[0].toString() ?? ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class WaveRangeRequirement extends EncounterSceneRequirement {
|
||||
waveRange: [number, number];
|
||||
|
||||
/**
|
||||
* Used for specifying a unique wave or wave range requirement
|
||||
* If minWaveIndex and maxWaveIndex are equivalent, will check for exact wave number
|
||||
* @param waveRange - [min, max]
|
||||
*/
|
||||
constructor(waveRange: [number, number]) {
|
||||
super();
|
||||
this.waveRange = waveRange;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
if (!isNullOrUndefined(this?.waveRange) && this.waveRange?.[0] <= this.waveRange?.[1]) {
|
||||
const waveIndex = scene.currentBattle.waveIndex;
|
||||
if (waveIndex >= 0 && (this?.waveRange?.[0] >= 0 && this.waveRange?.[0] > waveIndex) || (this?.waveRange?.[1] >= 0 && this.waveRange?.[1] < waveIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["waveCount", scene.currentBattle.waveIndex.toString()];
|
||||
}
|
||||
}
|
||||
|
||||
export class TimeOfDayRequirement extends EncounterSceneRequirement {
|
||||
requiredTimeOfDay: TimeOfDay[];
|
||||
|
||||
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
|
||||
super();
|
||||
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const timeOfDay = scene.arena?.getTimeOfDay();
|
||||
if (!isNullOrUndefined(timeOfDay) && this?.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["timeOfDay", TimeOfDay[scene.arena.getTimeOfDay()].toLocaleLowerCase()];
|
||||
}
|
||||
}
|
||||
|
||||
export class WeatherRequirement extends EncounterSceneRequirement {
|
||||
requiredWeather: WeatherType[];
|
||||
|
||||
constructor(weather: WeatherType | WeatherType[]) {
|
||||
super();
|
||||
this.requiredWeather = Array.isArray(weather) ? weather : [weather];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const currentWeather = scene.arena.weather?.weatherType;
|
||||
if (!isNullOrUndefined(currentWeather) && this?.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const currentWeather = scene.arena.weather?.weatherType;
|
||||
let token = "";
|
||||
if (!isNullOrUndefined(currentWeather)) {
|
||||
token = WeatherType[currentWeather!].replace("_", " ").toLocaleLowerCase();
|
||||
}
|
||||
return ["weather", token];
|
||||
}
|
||||
}
|
||||
|
||||
export class PartySizeRequirement extends EncounterSceneRequirement {
|
||||
partySizeRange: [number, number];
|
||||
excludeFainted: boolean;
|
||||
|
||||
/**
|
||||
* Used for specifying a party size requirement
|
||||
* If min and max are equivalent, will check for exact size
|
||||
* @param partySizeRange - [min, max]
|
||||
* @param excludeFainted
|
||||
*/
|
||||
constructor(partySizeRange: [number, number], excludeFainted: boolean) {
|
||||
super();
|
||||
this.partySizeRange = partySizeRange;
|
||||
this.excludeFainted = excludeFainted;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
if (!isNullOrUndefined(this?.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
|
||||
const partySize = this.excludeFainted ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
|
||||
if (partySize >= 0 && (this?.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this?.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["partySize", scene.getParty().length.toString()];
|
||||
}
|
||||
}
|
||||
|
||||
export class PersistentModifierRequirement extends EncounterSceneRequirement {
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfItems: number;
|
||||
|
||||
constructor(heldItem: string | string[], minNumberOfItems: number = 1) {
|
||||
super();
|
||||
this.minNumberOfItems = minNumberOfItems;
|
||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredHeldItemModifiers?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
let modifierCount = 0;
|
||||
this.requiredHeldItemModifiers.forEach(modifier => {
|
||||
const matchingMods = scene.findModifiers(m => m.constructor.name === modifier);
|
||||
if (matchingMods?.length > 0) {
|
||||
matchingMods.forEach(matchingMod => {
|
||||
modifierCount += matchingMod.stackCount;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return modifierCount >= this.minNumberOfItems;
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["requiredItem", this.requiredHeldItemModifiers[0]];
|
||||
}
|
||||
}
|
||||
|
||||
export class MoneyRequirement extends EncounterSceneRequirement {
|
||||
requiredMoney: number; // Static value
|
||||
scalingMultiplier: number; // Calculates required money based off wave index
|
||||
|
||||
constructor(requiredMoney?: number, scalingMultiplier?: number) {
|
||||
super();
|
||||
this.requiredMoney = requiredMoney ?? 0;
|
||||
this.scalingMultiplier = scalingMultiplier ?? 0;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const money = scene.money;
|
||||
if (isNullOrUndefined(money)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.scalingMultiplier > 0) {
|
||||
this.requiredMoney = scene.getWaveMoneyAmount(this.scalingMultiplier);
|
||||
}
|
||||
return !(this.requiredMoney > 0 && this.requiredMoney > money);
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const value = this.scalingMultiplier > 0 ? scene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString();
|
||||
return ["money", value];
|
||||
}
|
||||
}
|
||||
|
||||
export class SpeciesRequirement extends EncounterPokemonRequirement {
|
||||
requiredSpecies: Species[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(species: Species | Species[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredSpecies = Array.isArray(species) ? species : [species];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredSpecies?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredSpecies.filter((species) => pokemon.species.speciesId === species).length > 0);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed speciess
|
||||
return partyPokemon.filter((pokemon) => this.requiredSpecies.filter((species) => pokemon.species.speciesId === species).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
if (pokemon?.species.speciesId && this.requiredSpecies.includes(pokemon.species.speciesId)) {
|
||||
return ["species", Species[pokemon.species.speciesId]];
|
||||
}
|
||||
return ["species", ""];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class NatureRequirement extends EncounterPokemonRequirement {
|
||||
requiredNature: Nature[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(nature: Nature | Nature[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredNature = Array.isArray(nature) ? nature : [nature];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredNature?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredNature.filter((nature) => pokemon.nature === nature).length > 0);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed natures
|
||||
return partyPokemon.filter((pokemon) => this.requiredNature.filter((nature) => pokemon.nature === nature).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon!.nature)) {
|
||||
return ["nature", Nature[pokemon!.nature]];
|
||||
}
|
||||
return ["nature", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeRequirement extends EncounterPokemonRequirement {
|
||||
requiredType: Type[];
|
||||
excludeFainted: boolean;
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(type: Type | Type[], excludeFainted: boolean = true, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.excludeFainted = excludeFainted;
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredType = Array.isArray(type) ? type : [type];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
let partyPokemon = scene.getParty();
|
||||
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredType?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.excludeFainted) {
|
||||
partyPokemon = partyPokemon.filter((pokemon) => !pokemon.isFainted());
|
||||
}
|
||||
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredType.filter((type) => pokemon.getTypes().includes(type)).length > 0);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed types
|
||||
return partyPokemon.filter((pokemon) => this.requiredType.filter((type) => pokemon.getTypes().includes(type)).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const includedTypes = this.requiredType.filter((ty) => pokemon?.getTypes().includes(ty));
|
||||
if (includedTypes.length > 0) {
|
||||
return ["type", Type[includedTypes[0]]];
|
||||
}
|
||||
return ["type", ""];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class MoveRequirement extends EncounterPokemonRequirement {
|
||||
requiredMoves: Moves[] = [];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(moves: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredMoves = Array.isArray(moves) ? moves : [moves];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredMoves?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length > 0).length > 0);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves
|
||||
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length === 0).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const includedMoves = pokemon?.moveset.filter((move) => move?.moveId && this.requiredMoves.includes(move.moveId));
|
||||
if (includedMoves && includedMoves.length > 0 && includedMoves[0]) {
|
||||
return ["move", includedMoves[0].getName()];
|
||||
}
|
||||
return ["move", ""];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out if Pokemon in the party are able to learn one of many specific moves by TM.
|
||||
* NOTE: Egg moves are not included as learnable.
|
||||
* NOTE: If the Pokemon already knows the move, this requirement will fail, since it's not technically learnable.
|
||||
*/
|
||||
export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
|
||||
requiredMoves: Moves[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(learnableMove: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredMoves?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove)).length > 0);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves
|
||||
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove)).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const includedCompatMoves = this.requiredMoves.filter((reqMove) => pokemon?.compatibleTms.filter((tm) => !pokemon.moveset.find(m => m?.moveId === tm)).includes(reqMove));
|
||||
if (includedCompatMoves.length > 0) {
|
||||
return ["compatibleMove", Moves[includedCompatMoves[0]]];
|
||||
}
|
||||
return ["compatibleMove", ""];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
export class EvolutionTargetSpeciesRequirement extends EncounterPokemonRequirement {
|
||||
requiredEvolutionTargetSpecies: Species[];
|
||||
minNumberOfPokemon:number;
|
||||
invertQuery:boolean;
|
||||
|
||||
constructor(evolutionTargetSpecies: Species | Species[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredEvolutionTargetSpecies = Array.isArray(evolutionTargetSpecies) ? evolutionTargetSpecies : [evolutionTargetSpecies];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredEvolutionTargetSpecies?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredEvolutionTargetSpecies.filter((evolutionTargetSpecies) => pokemon.getEvolution()?.speciesId === evolutionTargetSpecies).length > 0);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionTargetSpeciess
|
||||
return partyPokemon.filter((pokemon) => this.requiredEvolutionTargetSpecies.filter((evolutionTargetSpecies) => pokemon.getEvolution()?.speciesId === evolutionTargetSpecies).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getMatchingDialogueToken(str:string, pokemon: PlayerPokemon): [RegExp, string] {
|
||||
const evos = this.requiredEvolutionTargetSpecies.filter((evolutionTargetSpecies) => pokemon.getEvolution().speciesId === evolutionTargetSpecies);
|
||||
if (evos.length > 0) {
|
||||
return ["evolution", Species[evos[0]]];
|
||||
}
|
||||
return ["evolution", ""];
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
export class AbilityRequirement extends EncounterPokemonRequirement {
|
||||
requiredAbilities: Abilities[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(abilities: Abilities | Abilities[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredAbilities?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredAbilities.some((ability) => pokemon.getAbility().id === ability));
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess
|
||||
return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) {
|
||||
return ["ability", pokemon.getAbility().name];
|
||||
}
|
||||
return ["ability", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
||||
requiredStatusEffect: StatusEffect[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredStatusEffect?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
const x = this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
console.log(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => {
|
||||
return this.requiredStatusEffect.some((statusEffect) => {
|
||||
if (statusEffect === StatusEffect.NONE) {
|
||||
// StatusEffect.NONE also checks for null or undefined status
|
||||
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect;
|
||||
} else {
|
||||
return pokemon.status?.effect === statusEffect;
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects
|
||||
// return partyPokemon.filter((pokemon) => this.requiredStatusEffect.filter((statusEffect) => pokemon.status?.effect === statusEffect).length === 0);
|
||||
return partyPokemon.filter((pokemon) => {
|
||||
return !this.requiredStatusEffect.some((statusEffect) => {
|
||||
if (statusEffect === StatusEffect.NONE) {
|
||||
// StatusEffect.NONE also checks for null or undefined status
|
||||
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect;
|
||||
} else {
|
||||
return pokemon.status?.effect === statusEffect;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const reqStatus = this.requiredStatusEffect.filter((a) => {
|
||||
if (a === StatusEffect.NONE) {
|
||||
return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon!.status!.effect) || pokemon!.status!.effect === a;
|
||||
}
|
||||
return pokemon!.status?.effect === a;
|
||||
});
|
||||
if (reqStatus.length > 0) {
|
||||
return ["status", StatusEffect[reqStatus[0]]];
|
||||
}
|
||||
return ["status", ""];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds if there are pokemon that can form change with a given item.
|
||||
* Notice that we mean specific items, like Charizardite, not the Mega Bracelet.
|
||||
* If you want to trigger the event based on the form change enabler, use PersistentModifierRequirement.
|
||||
*/
|
||||
export class CanFormChangeWithItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredFormChangeItem: FormChangeItem[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(formChangeItem: FormChangeItem | FormChangeItem[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredFormChangeItem?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
filterByForm(pokemon, formChangeItem) {
|
||||
if (pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId)
|
||||
// Get all form changes for this species with an item trigger, including any compound triggers
|
||||
&& pokemonFormChanges[pokemon.species.speciesId].filter(fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger))
|
||||
// Returns true if any form changes match this item
|
||||
.map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
|
||||
.flat().flatMap(fc => fc.item).includes(formChangeItem)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem)).length > 0);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed formChangeItems
|
||||
return partyPokemon.filter((pokemon) => this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem)).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem));
|
||||
if (requiredItems.length > 0) {
|
||||
return ["formChangeItem", FormChangeItem[requiredItems[0]]];
|
||||
}
|
||||
return ["formChangeItem", ""];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredEvolutionItem: EvolutionItem[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredEvolutionItem?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
filterByEvo(pokemon, evolutionItem) {
|
||||
if (pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && pokemonEvolutions[pokemon.species.speciesId].filter(e => e.item === evolutionItem
|
||||
&& (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX)) {
|
||||
return true;
|
||||
} else if (pokemon.isFusion() && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.item === evolutionItem
|
||||
&& (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredEvolutionItem.filter((evolutionItem) => this.filterByEvo(pokemon, evolutionItem)).length > 0);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss
|
||||
return partyPokemon.filter((pokemon) => this.requiredEvolutionItem.filter((evolutionItems) => this.filterByEvo(pokemon, evolutionItems)).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = this.requiredEvolutionItem.filter((evoItem) => this.filterByEvo(pokemon, evoItem));
|
||||
if (requiredItems.length > 0) {
|
||||
return ["evolutionItem", EvolutionItem[requiredItems[0]]];
|
||||
}
|
||||
return ["evolutionItem", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(heldItem: string | string[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredHeldItemModifiers?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => {
|
||||
return pokemon.getHeldItems().some((it) => {
|
||||
return it.constructor.name === heldItem;
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
|
||||
return !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
|
||||
}).length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter((it) => {
|
||||
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
}
|
||||
return ["heldItem", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class LevelRequirement extends EncounterPokemonRequirement {
|
||||
requiredLevelRange: [number, number];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(requiredLevelRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredLevelRange = requiredLevelRange;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
// Party Pokemon inside required level range
|
||||
if (!isNullOrUndefined(this.requiredLevelRange) && this.requiredLevelRange[0] <= this.requiredLevelRange[1]) {
|
||||
const partyPokemon = scene.getParty();
|
||||
const pokemonInRange = this.queryParty(partyPokemon);
|
||||
if (pokemonInRange.length < this.minNumberOfPokemon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => pokemon.level >= this.requiredLevelRange[0] && pokemon.level <= this.requiredLevelRange[1]);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredLevelRanges
|
||||
return partyPokemon.filter((pokemon) => pokemon.level < this.requiredLevelRange[0] || pokemon.level > this.requiredLevelRange[1]);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["level", pokemon?.level.toString() ?? ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class FriendshipRequirement extends EncounterPokemonRequirement {
|
||||
requiredFriendshipRange: [number, number];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(requiredFriendshipRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredFriendshipRange = requiredFriendshipRange;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
// Party Pokemon inside required friendship range
|
||||
if (!isNullOrUndefined(this.requiredFriendshipRange) && this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]) {
|
||||
const partyPokemon = scene.getParty();
|
||||
const pokemonInRange = this.queryParty(partyPokemon);
|
||||
if (pokemonInRange.length < this.minNumberOfPokemon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => pokemon.friendship >= this.requiredFriendshipRange[0] && pokemon.friendship <= this.requiredFriendshipRange[1]);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredFriendshipRanges
|
||||
return partyPokemon.filter((pokemon) => pokemon.friendship < this.requiredFriendshipRange[0] || pokemon.friendship > this.requiredFriendshipRange[1]);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["friendship", pokemon?.friendship.toString() ?? ""];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* .1 -> 10% hp
|
||||
* .5 -> 50% hp
|
||||
* 1 -> 100% hp
|
||||
*/
|
||||
export class HealthRatioRequirement extends EncounterPokemonRequirement {
|
||||
requiredHealthRange: [number, number];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHealthRange = requiredHealthRange;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
// Party Pokemon inside required level range
|
||||
if (!isNullOrUndefined(this.requiredHealthRange) && this.requiredHealthRange[0] <= this.requiredHealthRange[1]) {
|
||||
const partyPokemon = scene.getParty();
|
||||
const pokemonInRange = this.queryParty(partyPokemon);
|
||||
if (pokemonInRange.length < this.minNumberOfPokemon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => {
|
||||
return pokemon.getHpRatio() >= this.requiredHealthRange[0] && pokemon.getHpRatio() <= this.requiredHealthRange[1];
|
||||
});
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredHealthRanges
|
||||
return partyPokemon.filter((pokemon) => pokemon.getHpRatio() < this.requiredHealthRange[0] || pokemon.getHpRatio() > this.requiredHealthRange[1]);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
if (!isNullOrUndefined(pokemon?.getHpRatio())) {
|
||||
return ["healthRatio", Math.floor(pokemon!.getHpRatio() * 100).toString() + "%"];
|
||||
}
|
||||
return ["healthRatio", ""];
|
||||
}
|
||||
}
|
||||
|
||||
export class WeightRequirement extends EncounterPokemonRequirement {
|
||||
requiredWeightRange: [number, number];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(requiredWeightRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredWeightRange = requiredWeightRange;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
// Party Pokemon inside required friendship range
|
||||
if (!isNullOrUndefined(this.requiredWeightRange) && this.requiredWeightRange[0] <= this.requiredWeightRange[1]) {
|
||||
const partyPokemon = scene.getParty();
|
||||
const pokemonInRange = this.queryParty(partyPokemon);
|
||||
if (pokemonInRange.length < this.minNumberOfPokemon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => pokemon.getWeight() >= this.requiredWeightRange[0] && pokemon.getWeight() <= this.requiredWeightRange[1]);
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredWeightRanges
|
||||
return partyPokemon.filter((pokemon) => pokemon.getWeight() < this.requiredWeightRange[0] || pokemon.getWeight() > this.requiredWeightRange[1]);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["weight", pokemon?.getWeight().toString() ?? ""];
|
||||
}
|
||||
}
|
||||
|
||||
|
863
src/data/mystery-encounters/mystery-encounter.ts
Normal file
@ -0,0 +1,863 @@
|
||||
import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
|
||||
import * as Utils from "#app/utils";
|
||||
import { StatusEffect } from "../status-effect";
|
||||
import MysteryEncounterDialogue, { OptionTextDisplay } from "./mystery-encounter-dialogue";
|
||||
import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option";
|
||||
import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { EncounterAnim } from "#app/data/battle-anims";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
export interface EncounterStartOfBattleEffect {
|
||||
sourcePokemon?: Pokemon;
|
||||
sourceBattlerIndex?: BattlerIndex;
|
||||
targets: BattlerIndex[];
|
||||
move: PokemonMove;
|
||||
ignorePp: boolean;
|
||||
followUp?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link MysteryEncounterBuilder} class to define required/optional properties on the {@link MysteryEncounter} class when building.
|
||||
*
|
||||
* Should ONLY contain properties that are necessary for {@link MysteryEncounter} construction.
|
||||
* Post-construct and flag data properties are defined in the {@link MysteryEncounter} class itself.
|
||||
*/
|
||||
export interface IMysteryEncounter {
|
||||
encounterType: MysteryEncounterType;
|
||||
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
|
||||
spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||
encounterTier: MysteryEncounterTier;
|
||||
encounterAnimations?: EncounterAnim[];
|
||||
hideBattleIntroMessage: boolean;
|
||||
autoHideIntroVisuals: boolean;
|
||||
enterIntroVisualsFromRight: boolean;
|
||||
catchAllowed: boolean;
|
||||
continuousEncounter: boolean;
|
||||
maxAllowedEncounters: number;
|
||||
|
||||
onInit?: (scene: BattleScene) => boolean;
|
||||
onVisualsStart?: (scene: BattleScene) => boolean;
|
||||
doEncounterExp?: (scene: BattleScene) => boolean;
|
||||
doEncounterRewards?: (scene: BattleScene) => boolean;
|
||||
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
|
||||
|
||||
requirements: EncounterSceneRequirement[];
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[];
|
||||
secondaryPokemonRequirements: EncounterPokemonRequirement[];
|
||||
excludePrimaryFromSupportRequirements: boolean;
|
||||
|
||||
dialogue: MysteryEncounterDialogue;
|
||||
enemyPartyConfigs: EnemyPartyConfig[];
|
||||
|
||||
dialogueTokens: Record<string, string>;
|
||||
expMultiplier: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* MysteryEncounter class that defines the logic for a single encounter
|
||||
* These objects will be saved as part of session data any time the player is on a floor with an encounter
|
||||
* Unless you know what you're doing, you should use MysteryEncounterBuilder to create an instance for this class
|
||||
*/
|
||||
export default class MysteryEncounter implements IMysteryEncounter {
|
||||
/**
|
||||
* Required params
|
||||
*/
|
||||
encounterType: MysteryEncounterType;
|
||||
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
|
||||
spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||
/**
|
||||
* Optional params
|
||||
*/
|
||||
encounterTier: MysteryEncounterTier;
|
||||
/**
|
||||
* Custom battle animations that are configured for encounter effects and visuals
|
||||
* Specify here so that assets are loaded on initialization of encounter
|
||||
*/
|
||||
encounterAnimations?: EncounterAnim[];
|
||||
/**
|
||||
* If true, hides "A Wild X Appeared" etc. messages
|
||||
* Default true
|
||||
*/
|
||||
hideBattleIntroMessage: boolean;
|
||||
/**
|
||||
* If true, when an option is selected the field visuals will fade out automatically
|
||||
* Default false
|
||||
*/
|
||||
autoHideIntroVisuals: boolean;
|
||||
/**
|
||||
* Intro visuals on the field will slide in from the right instead of the left
|
||||
* Default false
|
||||
*/
|
||||
enterIntroVisualsFromRight: boolean;
|
||||
/**
|
||||
* If true, allows catching a wild pokemon during the encounter
|
||||
* Default false
|
||||
*/
|
||||
catchAllowed: boolean;
|
||||
/**
|
||||
* If true, encounter will continuously run through multiple battles/puzzles/etc. instead of going to next wave
|
||||
* MUST EVENTUALLY BE DISABLED TO CONTINUE TO NEXT WAVE
|
||||
* Default false
|
||||
*/
|
||||
continuousEncounter: boolean;
|
||||
/**
|
||||
* Maximum number of times the encounter can be seen per run
|
||||
* Rogue tier encounters default to 1, others default to 3
|
||||
*/
|
||||
maxAllowedEncounters: number;
|
||||
|
||||
|
||||
/**
|
||||
* Event callback functions
|
||||
*/
|
||||
/** Event when Encounter is first loaded, use it for data conditioning */
|
||||
onInit?: (scene: BattleScene) => boolean;
|
||||
/** Event when battlefield visuals have finished sliding in and the encounter dialogue begins */
|
||||
onVisualsStart?: (scene: BattleScene) => boolean;
|
||||
/** Will provide the player party EXP before rewards are displayed for that wave */
|
||||
doEncounterExp?: (scene: BattleScene) => boolean;
|
||||
/** Will provide the player a rewards shop for that wave */
|
||||
doEncounterRewards?: (scene: BattleScene) => boolean;
|
||||
/** Will execute callback during VictoryPhase of a continuousEncounter */
|
||||
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Requirements
|
||||
*/
|
||||
requirements: EncounterSceneRequirement[];
|
||||
/** Primary Pokemon is a single pokemon randomly selected from the party that meet ALL primary pokemon requirements */
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[];
|
||||
/**
|
||||
* Secondary Pokemon are pokemon that meet ALL secondary pokemon requirements
|
||||
* Note that an individual requirement may require multiple pokemon, but the resulting pokemon after all secondary requirements are met may be lower than expected
|
||||
* If the primary pokemon and secondary pokemon are the same and ExcludePrimaryFromSupportRequirements flag is true, primary pokemon may be promoted from secondary pool
|
||||
*/
|
||||
secondaryPokemonRequirements: EncounterPokemonRequirement[];
|
||||
excludePrimaryFromSupportRequirements: boolean;
|
||||
primaryPokemon?: PlayerPokemon;
|
||||
secondaryPokemon?: PlayerPokemon[];
|
||||
|
||||
/**
|
||||
* Post-construct / Auto-populated params
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter
|
||||
*/
|
||||
dialogue: MysteryEncounterDialogue;
|
||||
/**
|
||||
* Data used for setting up/initializing enemy party in battles
|
||||
* Can store multiple configs so that one can be chosen based on option selected
|
||||
* Should usually be defined in `onInit()` or `onPreOptionPhase()`
|
||||
*/
|
||||
enemyPartyConfigs: EnemyPartyConfig[];
|
||||
/**
|
||||
* Object instance containing sprite data for an encounter when it is being spawned
|
||||
* Otherwise, will be undefined
|
||||
* You probably shouldn't do anything directly with this unless you have a very specific need
|
||||
*/
|
||||
introVisuals?: MysteryEncounterIntroVisuals;
|
||||
|
||||
/**
|
||||
* Flags
|
||||
*/
|
||||
|
||||
/**
|
||||
* Can be set for uses programatic dialogue during an encounter (storing the name of one of the party's pokemon, etc.)
|
||||
* Example use: see MYSTERIOUS_CHEST
|
||||
*/
|
||||
dialogueTokens: Record<string, string>;
|
||||
/**
|
||||
* Should be set depending upon option selected as part of an encounter
|
||||
* For example, if there is no battle as part of the encounter/selected option, should be set to NO_BATTLE
|
||||
* Defaults to DEFAULT
|
||||
*/
|
||||
encounterMode: MysteryEncounterMode;
|
||||
/**
|
||||
* Flag for checking if it's the first time a shop is being shown for an encounter.
|
||||
* Defaults to true so that the first shop does not override the specified rewards.
|
||||
* Will be set to false after a shop is shown (so can't reroll same rarity items for free)
|
||||
*/
|
||||
lockEncounterRewardTiers: boolean;
|
||||
/**
|
||||
* Will be set automatically, indicates special moves in startOfBattleEffects are complete (so will not repeat)
|
||||
*/
|
||||
startOfBattleEffectsComplete: boolean;
|
||||
/**
|
||||
* Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases
|
||||
*/
|
||||
selectedOption?: MysteryEncounterOption;
|
||||
/**
|
||||
* Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases
|
||||
*/
|
||||
startOfBattleEffects: EncounterStartOfBattleEffect[] = [];
|
||||
/**
|
||||
* Can be set higher or lower based on the type of battle or exp gained for an option/encounter
|
||||
* Defaults to 1
|
||||
*/
|
||||
expMultiplier: number;
|
||||
/**
|
||||
* Generic property to set any custom data required for the encounter
|
||||
* Extremely useful for carrying state/data between onPreOptionPhase/onOptionPhase/onPostOptionPhase
|
||||
*/
|
||||
misc?: any;
|
||||
/**
|
||||
* Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave
|
||||
* You should only need to interact via getter/update methods
|
||||
*/
|
||||
private seedOffset?: any;
|
||||
|
||||
constructor(encounter: IMysteryEncounter | null) {
|
||||
if (!isNullOrUndefined(encounter)) {
|
||||
Object.assign(this, encounter);
|
||||
}
|
||||
this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON;
|
||||
this.dialogue = this.dialogue ?? {};
|
||||
this.spriteConfigs = this.spriteConfigs ? [...this.spriteConfigs] : [];
|
||||
// Default max is 1 for ROGUE encounters, 3 for others
|
||||
this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? 1 : 3;
|
||||
this.encounterMode = MysteryEncounterMode.DEFAULT;
|
||||
this.requirements = this.requirements ? this.requirements : [];
|
||||
this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false;
|
||||
this.autoHideIntroVisuals = this.autoHideIntroVisuals ?? true;
|
||||
this.enterIntroVisualsFromRight = this.enterIntroVisualsFromRight ?? false;
|
||||
this.continuousEncounter = this.continuousEncounter ?? false;
|
||||
|
||||
// Reset any dirty flags or encounter data
|
||||
this.startOfBattleEffectsComplete = false;
|
||||
this.lockEncounterRewardTiers = true;
|
||||
this.dialogueTokens = {};
|
||||
this.enemyPartyConfigs = [];
|
||||
// this.startOfBattleEffects = [];
|
||||
this.introVisuals = undefined;
|
||||
this.misc = null;
|
||||
this.expMultiplier = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current scene state meets the requirements for the MysteryEncounter to spawn
|
||||
* This is used to filter the pool of encounters down to only the ones with all requirements met
|
||||
* @param scene
|
||||
* @returns
|
||||
*/
|
||||
meetsRequirements(scene: BattleScene) {
|
||||
const sceneReq = !this.requirements.some(requirement => !requirement.meetsRequirement(scene));
|
||||
const secReqs = this.meetsSecondaryRequirementAndSecondaryPokemonSelected(scene); // secondary is checked first to handle cases of primary overlapping with secondary
|
||||
const priReqs = this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
|
||||
|
||||
return sceneReq && secReqs && priReqs;
|
||||
}
|
||||
|
||||
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon) {
|
||||
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
|
||||
}
|
||||
|
||||
meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
|
||||
if (this.primaryPokemonRequirements.length === 0) {
|
||||
const activeMon = scene.getParty().filter(p => p.isActive(true));
|
||||
if (activeMon.length > 0) {
|
||||
this.primaryPokemon = activeMon[0];
|
||||
} else {
|
||||
this.primaryPokemon = scene.getParty().filter(p => !p.isFainted())[0];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
let qualified: PlayerPokemon[] = scene.getParty();
|
||||
for (const req of this.primaryPokemonRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
qualified = qualified.filter(pkmn => req.queryParty(scene.getParty()).includes(pkmn));
|
||||
} else {
|
||||
this.primaryPokemon = undefined;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (qualified.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.excludePrimaryFromSupportRequirements && this.secondaryPokemon) {
|
||||
const truePrimaryPool: PlayerPokemon[] = [];
|
||||
const overlap: PlayerPokemon[] = [];
|
||||
for (const qp of qualified) {
|
||||
if (!this.secondaryPokemon.includes(qp)) {
|
||||
truePrimaryPool.push(qp);
|
||||
} else {
|
||||
overlap.push(qp);
|
||||
}
|
||||
|
||||
}
|
||||
if (truePrimaryPool.length > 0) {
|
||||
// Always choose from the non-overlapping pokemon first
|
||||
this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
|
||||
return true;
|
||||
} else {
|
||||
// If there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool
|
||||
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
|
||||
// is this working?
|
||||
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)];
|
||||
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
|
||||
return true;
|
||||
}
|
||||
console.log("Mystery Encounter Edge Case: Requirement not met due to primary pokemon overlapping with secondary pokemon. There's no valid primary pokemon left.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// this means we CAN have the same pokemon be a primary and secondary pokemon, so just choose any qualifying one randomly.
|
||||
this.primaryPokemon = qualified[Utils.randSeedInt(qualified.length, 0)];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean {
|
||||
if (!this.secondaryPokemonRequirements) {
|
||||
this.secondaryPokemon = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
let qualified: PlayerPokemon[] = scene.getParty();
|
||||
for (const req of this.secondaryPokemonRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
qualified = qualified.filter(pkmn => req.queryParty(scene.getParty()).includes(pkmn));
|
||||
} else {
|
||||
this.secondaryPokemon = [];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.secondaryPokemon = qualified;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes encounter intro sprites based on the sprite configs defined in spriteConfigs
|
||||
* @param scene
|
||||
*/
|
||||
initIntroVisuals(scene: BattleScene) {
|
||||
this.introVisuals = new MysteryEncounterIntroVisuals(scene, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-pushes dialogue tokens from the encounter (and option) requirements.
|
||||
* Will use the first support pokemon in list
|
||||
* For multiple support pokemon in the dialogue token, it will have to be overridden.
|
||||
*/
|
||||
populateDialogueTokensFromRequirements(scene: BattleScene) {
|
||||
this.meetsRequirements(scene);
|
||||
if (this.requirements?.length > 0) {
|
||||
for (const req of this.requirements) {
|
||||
const dialogueToken = req.getDialogueToken(scene);
|
||||
if (dialogueToken?.length === 2) {
|
||||
this.setDialogueToken(...dialogueToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.primaryPokemon && this.primaryPokemon.length > 0) {
|
||||
this.setDialogueToken("primaryName", this.primaryPokemon.getNameToRender());
|
||||
for (const req of this.primaryPokemonRequirements) {
|
||||
if (!req.invertQuery) {
|
||||
const value = req.getDialogueToken(scene, this.primaryPokemon);
|
||||
if (value?.length === 2) {
|
||||
this.setDialogueToken("primary" + capitalizeFirstLetter(value[0]), value[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.secondaryPokemonRequirements?.length > 0 && this.secondaryPokemon && this.secondaryPokemon.length > 0) {
|
||||
this.setDialogueToken("secondaryName", this.secondaryPokemon[0].getNameToRender());
|
||||
for (const req of this.secondaryPokemonRequirements) {
|
||||
if (!req.invertQuery) {
|
||||
const value = req.getDialogueToken(scene, this.secondaryPokemon[0]);
|
||||
if (value?.length === 2) {
|
||||
this.setDialogueToken("primary" + capitalizeFirstLetter(value[0]), value[1]);
|
||||
}
|
||||
this.setDialogueToken("secondary" + capitalizeFirstLetter(value[0]), value[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dialogue tokens for options
|
||||
for (let i = 0; i < this.options.length; i++) {
|
||||
const opt = this.options[i];
|
||||
opt.meetsRequirements(scene);
|
||||
const j = i + 1;
|
||||
if (opt.requirements.length > 0) {
|
||||
for (const req of opt.requirements) {
|
||||
const dialogueToken = req.getDialogueToken(scene);
|
||||
if (dialogueToken?.length === 2) {
|
||||
this.setDialogueToken("option" + j + capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (opt.primaryPokemonRequirements.length > 0 && opt.primaryPokemon) {
|
||||
this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.getNameToRender());
|
||||
for (const req of opt.primaryPokemonRequirements) {
|
||||
if (!req.invertQuery) {
|
||||
const value = req.getDialogueToken(scene, opt.primaryPokemon);
|
||||
if (value?.length === 2) {
|
||||
this.setDialogueToken("option" + j + "Primary" + capitalizeFirstLetter(value[0]), value[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (opt.secondaryPokemonRequirements?.length > 0 && opt.secondaryPokemon && opt.secondaryPokemon.length > 0) {
|
||||
this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].getNameToRender());
|
||||
for (const req of opt.secondaryPokemonRequirements) {
|
||||
if (!req.invertQuery) {
|
||||
const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]);
|
||||
if (value?.length === 2) {
|
||||
this.setDialogueToken("option" + j + "Secondary" + capitalizeFirstLetter(value[0]), value[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDialogueToken(key: string, value: string): void {
|
||||
this.dialogueTokens[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If an encounter uses {@link MysteryEncounterMode.continuousEncounter},
|
||||
* should rely on this value for seed offset instead of wave index.
|
||||
*
|
||||
* This offset is incremented for each new {@link MysteryEncounterPhase} that occurs,
|
||||
* so multi-encounter RNG will be consistent on resets and not be affected by number of turns, move RNG, etc.
|
||||
*/
|
||||
getSeedOffset() {
|
||||
return this.seedOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintains seed offset for RNG consistency
|
||||
* Increments if the same MysteryEncounter has multiple option select cycles
|
||||
* @param scene
|
||||
*/
|
||||
updateSeedOffset(scene: BattleScene) {
|
||||
const currentOffset = this.seedOffset ?? scene.currentBattle.waveIndex * 1000;
|
||||
this.seedOffset = currentOffset + 512;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for creating a MysteryEncounter
|
||||
* must call `build()` at the end after specifying all params for the MysteryEncounter
|
||||
*/
|
||||
export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
|
||||
enemyPartyConfigs: EnemyPartyConfig[] = [];
|
||||
|
||||
dialogue: MysteryEncounterDialogue = {};
|
||||
requirements: EncounterSceneRequirement[] = [];
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
excludePrimaryFromSupportRequirements: boolean = true;
|
||||
dialogueTokens: Record<string, string> = {};
|
||||
|
||||
hideBattleIntroMessage: boolean = false;
|
||||
autoHideIntroVisuals: boolean = true;
|
||||
enterIntroVisualsFromRight: boolean = false;
|
||||
continuousEncounter: boolean = false;
|
||||
catchAllowed: boolean = false;
|
||||
lockEncounterRewardTiers: boolean = false;
|
||||
startOfBattleEffectsComplete: boolean = false;
|
||||
maxAllowedEncounters: number = 3;
|
||||
expMultiplier: number = 1;
|
||||
|
||||
/**
|
||||
* REQUIRED
|
||||
*/
|
||||
|
||||
/**
|
||||
* @statif Defines the type of encounter which is used as an identifier, should be tied to a unique MysteryEncounterType
|
||||
* NOTE: if new functions are added to MysteryEncounter class
|
||||
* @param encounterType
|
||||
* @returns this
|
||||
*/
|
||||
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> {
|
||||
return Object.assign(new MysteryEncounterBuilder(), { encounterType });
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an option for the encounter.
|
||||
* Use for complex options.
|
||||
* There should be at least 2 options defined and no more than 4.
|
||||
*
|
||||
* @param option - MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance
|
||||
* @returns
|
||||
*/
|
||||
withOption(option: MysteryEncounterOption): this & Pick<IMysteryEncounter, "options"> {
|
||||
if (!this.options) {
|
||||
const options = [option];
|
||||
return Object.assign(this, { options });
|
||||
} else {
|
||||
this.options.push(option);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an option + phasefor the encounter.
|
||||
* Use for easy/streamlined options.
|
||||
* There should be at least 2 options defined and no more than 4.
|
||||
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
|
||||
*
|
||||
* @param hasDexProgress -
|
||||
* @param dialogue - {@linkcode OptionTextDisplay}
|
||||
* @param callback - {@linkcode OptionPhaseCallback}
|
||||
* @returns
|
||||
*/
|
||||
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
|
||||
return this.withOption(MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an option + phasefor the encounter.
|
||||
* Use for easy/streamlined options.
|
||||
* There should be at least 2 options defined and no more than 4.
|
||||
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
|
||||
*
|
||||
* @param dialogue - {@linkcode OptionTextDisplay}
|
||||
* @param callback - {@linkcode OptionPhaseCallback}
|
||||
* @returns
|
||||
*/
|
||||
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
|
||||
return this.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue(dialogue)
|
||||
.withOptionPhase(callback).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the sprites that will be shown on the enemy field when the encounter spawns
|
||||
* Can be one or more sprites, recommended not to exceed 4
|
||||
* @param spriteConfigs
|
||||
* @returns
|
||||
*/
|
||||
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<IMysteryEncounter, "spriteConfigs"> {
|
||||
return Object.assign(this, { spriteConfigs: spriteConfigs });
|
||||
}
|
||||
|
||||
withIntroDialogue(dialogue: MysteryEncounterDialogue["intro"] = []): this {
|
||||
this.dialogue = {...this.dialogue, intro: dialogue };
|
||||
return this;
|
||||
}
|
||||
|
||||
withIntro({spriteConfigs, dialogue} : {spriteConfigs: MysteryEncounterSpriteConfig[], dialogue?: MysteryEncounterDialogue["intro"]}) {
|
||||
return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue);
|
||||
}
|
||||
|
||||
/**
|
||||
* OPTIONAL
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the rarity tier for an encounter
|
||||
* If not specified, defaults to COMMON
|
||||
* Tiers are:
|
||||
* COMMON 32/64 odds
|
||||
* GREAT 16/64 odds
|
||||
* ULTRA 10/64 odds
|
||||
* ROGUE 6/64 odds
|
||||
* ULTRA_RARE Not currently used
|
||||
* @param encounterTier
|
||||
* @returns
|
||||
*/
|
||||
withEncounterTier(encounterTier: MysteryEncounterTier): this & Pick<IMysteryEncounter, "encounterTier"> {
|
||||
return Object.assign(this, { encounterTier: encounterTier });
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines any EncounterAnim animations that are intended to be used during the encounter
|
||||
* EncounterAnims are custom battle animations (think Ice Beam) that can be played at any point during an encounter or callback
|
||||
* They just need to be specified here so that resources are loaded on encounter init
|
||||
* @param encounterAnimations
|
||||
* @returns
|
||||
*/
|
||||
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
|
||||
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
|
||||
return Object.assign(this, { encounterAnimations: animations });
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, encounter will continuously run through multiple battles/puzzles/etc. instead of going to next wave
|
||||
* MUST EVENTUALLY BE DISABLED TO CONTINUE TO NEXT WAVE
|
||||
* Default false
|
||||
* @param continuousEncounter
|
||||
*/
|
||||
withContinuousEncounter(continuousEncounter: boolean): this & Required<Pick<IMysteryEncounter, "continuousEncounter">> {
|
||||
return Object.assign(this, { continuousEncounter: continuousEncounter });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of times that an encounter can spawn in a given Classic run
|
||||
* @param maxAllowedEncounters
|
||||
* @returns
|
||||
*/
|
||||
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> {
|
||||
return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters });
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a requirement for an encounter
|
||||
* For example, passing requirement as "new WaveCountRequirement([2, 180])" would create a requirement that the encounter can only be spawned between waves 2 and 180
|
||||
* Existing Requirement objects are defined in mystery-encounter-requirements.ts, and more can always be created to meet a requirement need
|
||||
* @param requirement
|
||||
* @returns
|
||||
*/
|
||||
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
if (requirement instanceof EncounterPokemonRequirement) {
|
||||
Error("Incorrectly added pokemon requirement as scene requirement.");
|
||||
}
|
||||
this.requirements.push(requirement);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a wave range requirement for an encounter.
|
||||
*
|
||||
* @param min min wave (or exact wave if only min is given)
|
||||
* @param max optional max wave. If not given, defaults to min => exact wave
|
||||
* @returns
|
||||
*/
|
||||
withSceneWaveRangeRequirement(min: number, max?: number): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new WaveRangeRequirement([min, max ?? min]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a party size requirement for an encounter.
|
||||
*
|
||||
* @param min min wave (or exact size if only min is given)
|
||||
* @param max optional max size. If not given, defaults to min => exact wave
|
||||
* @param excludeFainted - if true, only counts unfainted mons
|
||||
* @returns
|
||||
*/
|
||||
withScenePartySizeRequirement(min: number, max?: number, excludeFainted: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a primary pokemon requirement
|
||||
*
|
||||
* @param requirement {@linkcode EncounterPokemonRequirement}
|
||||
* @returns
|
||||
*/
|
||||
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
if (requirement instanceof EncounterSceneRequirement) {
|
||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||
}
|
||||
|
||||
this.primaryPokemonRequirements.push(requirement);
|
||||
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a primary pokemon status effect requirement
|
||||
*
|
||||
* @param statusEffect the status effect/s to check
|
||||
* @param minNumberOfPokemon minimum number of pokemon to have the effect
|
||||
* @param invertQuery if true will invert the query
|
||||
* @returns
|
||||
*/
|
||||
withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
return this.withPrimaryPokemonRequirement(new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a primary pokemon health ratio requirement
|
||||
*
|
||||
* @param requiredHealthRange the health range to check
|
||||
* @param minNumberOfPokemon minimum number of pokemon to have the health range
|
||||
* @param invertQuery if true will invert the query
|
||||
* @returns
|
||||
*/
|
||||
withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
return this.withPrimaryPokemonRequirement(new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery));
|
||||
}
|
||||
|
||||
// TODO: Maybe add an optional parameter for excluding primary pokemon from the support cast?
|
||||
// ex. if your only grass type pokemon, a snivy, is chosen as primary, if the support pokemon requires a grass type, the event won't trigger because
|
||||
// it's already been
|
||||
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> {
|
||||
if (requirement instanceof EncounterSceneRequirement) {
|
||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||
}
|
||||
|
||||
this.secondaryPokemonRequirements.push(requirement);
|
||||
this.excludePrimaryFromSupportRequirements = excludePrimaryFromSecondaryRequirements;
|
||||
return Object.assign(this, { excludePrimaryFromSecondaryRequirements: this.excludePrimaryFromSupportRequirements, secondaryPokemonRequirements: this.secondaryPokemonRequirements });
|
||||
}
|
||||
|
||||
/**
|
||||
* Can set custom encounter rewards via this callback function
|
||||
* If rewards are always deterministic for an encounter, this is a good way to set them
|
||||
*
|
||||
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
|
||||
* It may be better to programmatically set doEncounterRewards elsewhere.
|
||||
* There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set rewards
|
||||
* @param doEncounterRewards - synchronous callback function to perform during rewards phase of the encounter
|
||||
* @returns
|
||||
*/
|
||||
withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterRewards">> {
|
||||
return Object.assign(this, { doEncounterRewards: doEncounterRewards });
|
||||
}
|
||||
|
||||
/**
|
||||
* Can set custom encounter exp via this callback function
|
||||
* If exp always deterministic for an encounter, this is a good way to set them
|
||||
*
|
||||
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
|
||||
* It may be better to programmatically set doEncounterExp elsewhere.
|
||||
* There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set rewards
|
||||
* @param doEncounterExp - synchronous callback function to perform during rewards phase of the encounter
|
||||
* @returns
|
||||
*/
|
||||
withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterExp">> {
|
||||
return Object.assign(this, { doEncounterExp: doEncounterExp });
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to perform init logic before intro visuals are shown and before the MysteryEncounterPhase begins
|
||||
* Useful for performing things like procedural generation of intro sprites, etc.
|
||||
*
|
||||
* @param onInit - synchronous callback function to perform as soon as the encounter is selected for the next phase
|
||||
* @returns
|
||||
*/
|
||||
withOnInit(onInit: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onInit">> {
|
||||
return Object.assign(this, { onInit });
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to perform some extra logic (usually animations) when the enemy field is finished sliding in
|
||||
*
|
||||
* @param onVisualsStart - synchronous callback function to perform as soon as the enemy field finishes sliding in
|
||||
* @returns
|
||||
*/
|
||||
withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onVisualsStart">> {
|
||||
return Object.assign(this, { onVisualsStart: onVisualsStart });
|
||||
}
|
||||
|
||||
/**
|
||||
* Can set whether catching is allowed or not on the encounter
|
||||
* This flag can also be programmatically set inside option event functions or elsewhere
|
||||
* @param catchAllowed - if true, allows enemy pokemon to be caught during the encounter
|
||||
* @returns
|
||||
*/
|
||||
withCatchAllowed(catchAllowed: boolean): this & Required<Pick<IMysteryEncounter, "catchAllowed">> {
|
||||
return Object.assign(this, { catchAllowed: catchAllowed });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hideBattleIntroMessage - if true, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
|
||||
* @returns
|
||||
*/
|
||||
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
|
||||
return Object.assign(this, { hideBattleIntroMessage: hideBattleIntroMessage });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param autoHideIntroVisuals - if false, will not hide the intro visuals that are displayed at the beginning of encounter
|
||||
* @returns
|
||||
*/
|
||||
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
|
||||
return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param enterIntroVisualsFromRight - If true, will slide in intro visuals from the right side of the screen. If false, slides in from left, as normal
|
||||
* Default false
|
||||
* @returns
|
||||
*/
|
||||
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> {
|
||||
return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a title for the encounter
|
||||
*
|
||||
* @param title - title of the encounter
|
||||
* @returns
|
||||
*/
|
||||
withTitle(title: string): this {
|
||||
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
||||
|
||||
this.dialogue = {
|
||||
...this.dialogue,
|
||||
encounterOptionsDialogue: {
|
||||
...encounterOptionsDialogue,
|
||||
title,
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a description of the encounter
|
||||
*
|
||||
* @param description - description of the encounter
|
||||
* @returns
|
||||
*/
|
||||
withDescription(description: string): this {
|
||||
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
||||
|
||||
this.dialogue = {
|
||||
...this.dialogue,
|
||||
encounterOptionsDialogue: {
|
||||
...encounterOptionsDialogue,
|
||||
description,
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a query for the encounter
|
||||
*
|
||||
* @param query - query to use for the encounter
|
||||
* @returns
|
||||
*/
|
||||
withQuery(query: string): this {
|
||||
const encounterOptionsDialogue = this.dialogue.encounterOptionsDialogue ?? {};
|
||||
|
||||
this.dialogue = {
|
||||
...this.dialogue,
|
||||
encounterOptionsDialogue: {
|
||||
...encounterOptionsDialogue,
|
||||
query,
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add outro dialogue/s for the encounter
|
||||
*
|
||||
* @param dialogue - outro dialogue/s
|
||||
* @returns
|
||||
*/
|
||||
withOutroDialogue(dialogue: MysteryEncounterDialogue["outro"] = []): this {
|
||||
this.dialogue = {...this.dialogue, outro: dialogue };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the mystery encounter
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
build(this: IMysteryEncounter): MysteryEncounter {
|
||||
return new MysteryEncounter(this);
|
||||
}
|
||||
}
|
322
src/data/mystery-encounters/mystery-encounters.ts
Normal file
@ -0,0 +1,322 @@
|
||||
import { Biome } from "#enums/biome";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { DarkDealEncounter } from "./encounters/dark-deal-encounter";
|
||||
import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale-encounter";
|
||||
import { FieldTripEncounter } from "./encounters/field-trip-encounter";
|
||||
import { FightOrFlightEncounter } from "./encounters/fight-or-flight-encounter";
|
||||
import { LostAtSeaEncounter } from "./encounters/lost-at-sea-encounter";
|
||||
import { MysteriousChallengersEncounter } from "./encounters/mysterious-challengers-encounter";
|
||||
import { MysteriousChestEncounter } from "./encounters/mysterious-chest-encounter";
|
||||
import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
|
||||
import { SlumberingSnorlaxEncounter } from "./encounters/slumbering-snorlax-encounter";
|
||||
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
|
||||
import MysteryEncounter from "./mystery-encounter";
|
||||
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
|
||||
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
|
||||
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
|
||||
import { ThePokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/the-pokemon-salesman-encounter";
|
||||
import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter";
|
||||
import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter";
|
||||
import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounters/absolute-avarice-encounter";
|
||||
import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter";
|
||||
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";
|
||||
import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/weird-dream-encounter";
|
||||
import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-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;
|
||||
export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 5;
|
||||
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 15;
|
||||
|
||||
export const EXTREME_ENCOUNTER_BIOMES = [
|
||||
Biome.SEA,
|
||||
Biome.SEABED,
|
||||
Biome.BADLANDS,
|
||||
Biome.DESERT,
|
||||
Biome.ICE_CAVE,
|
||||
Biome.VOLCANO,
|
||||
Biome.WASTELAND,
|
||||
Biome.ABYSS,
|
||||
Biome.SPACE,
|
||||
Biome.END
|
||||
];
|
||||
|
||||
export const NON_EXTREME_ENCOUNTER_BIOMES = [
|
||||
Biome.TOWN,
|
||||
Biome.PLAINS,
|
||||
Biome.GRASS,
|
||||
Biome.TALL_GRASS,
|
||||
Biome.METROPOLIS,
|
||||
Biome.FOREST,
|
||||
Biome.SWAMP,
|
||||
Biome.BEACH,
|
||||
Biome.LAKE,
|
||||
Biome.MOUNTAIN,
|
||||
Biome.CAVE,
|
||||
Biome.MEADOW,
|
||||
Biome.POWER_PLANT,
|
||||
Biome.GRAVEYARD,
|
||||
Biome.DOJO,
|
||||
Biome.FACTORY,
|
||||
Biome.RUINS,
|
||||
Biome.CONSTRUCTION_SITE,
|
||||
Biome.JUNGLE,
|
||||
Biome.FAIRY_CAVE,
|
||||
Biome.TEMPLE,
|
||||
Biome.SLUM,
|
||||
Biome.SNOWY_FOREST,
|
||||
Biome.ISLAND,
|
||||
Biome.LABORATORY
|
||||
];
|
||||
|
||||
/**
|
||||
* Places where you could very reasonably expect to encounter a single human
|
||||
*
|
||||
* Diff from NON_EXTREME_ENCOUNTER_BIOMES:
|
||||
* + BADLANDS
|
||||
* + DESERT
|
||||
* + ICE_CAVE
|
||||
*/
|
||||
export const HUMAN_TRANSITABLE_BIOMES = [
|
||||
Biome.TOWN,
|
||||
Biome.PLAINS,
|
||||
Biome.GRASS,
|
||||
Biome.TALL_GRASS,
|
||||
Biome.METROPOLIS,
|
||||
Biome.FOREST,
|
||||
Biome.SWAMP,
|
||||
Biome.BEACH,
|
||||
Biome.LAKE,
|
||||
Biome.MOUNTAIN,
|
||||
Biome.BADLANDS,
|
||||
Biome.CAVE,
|
||||
Biome.DESERT,
|
||||
Biome.ICE_CAVE,
|
||||
Biome.MEADOW,
|
||||
Biome.POWER_PLANT,
|
||||
Biome.GRAVEYARD,
|
||||
Biome.DOJO,
|
||||
Biome.FACTORY,
|
||||
Biome.RUINS,
|
||||
Biome.CONSTRUCTION_SITE,
|
||||
Biome.JUNGLE,
|
||||
Biome.FAIRY_CAVE,
|
||||
Biome.TEMPLE,
|
||||
Biome.SLUM,
|
||||
Biome.SNOWY_FOREST,
|
||||
Biome.ISLAND,
|
||||
Biome.LABORATORY
|
||||
];
|
||||
|
||||
/**
|
||||
* Places where you could expect a town or city, some form of large civilization
|
||||
*/
|
||||
export const CIVILIZATION_ENCOUNTER_BIOMES = [
|
||||
Biome.TOWN,
|
||||
Biome.PLAINS,
|
||||
Biome.GRASS,
|
||||
Biome.TALL_GRASS,
|
||||
Biome.METROPOLIS,
|
||||
Biome.BEACH,
|
||||
Biome.LAKE,
|
||||
Biome.MEADOW,
|
||||
Biome.POWER_PLANT,
|
||||
Biome.GRAVEYARD,
|
||||
Biome.DOJO,
|
||||
Biome.FACTORY,
|
||||
Biome.CONSTRUCTION_SITE,
|
||||
Biome.SLUM,
|
||||
Biome.ISLAND
|
||||
];
|
||||
|
||||
export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {};
|
||||
|
||||
|
||||
const extremeBiomeEncounters: MysteryEncounterType[] = [];
|
||||
|
||||
const nonExtremeBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.FIELD_TRIP,
|
||||
MysteryEncounterType.DANCING_LESSONS, // Is also in BADLANDS, DESERT, VOLCANO, WASTELAND, ABYSS
|
||||
];
|
||||
|
||||
const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
|
||||
MysteryEncounterType.SHADY_VITAMIN_DEALER,
|
||||
MysteryEncounterType.THE_POKEMON_SALESMAN,
|
||||
MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
|
||||
MysteryEncounterType.THE_WINSTRATE_CHALLENGE
|
||||
];
|
||||
|
||||
const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.DEPARTMENT_STORE_SALE,
|
||||
MysteryEncounterType.PART_TIMER
|
||||
];
|
||||
|
||||
/**
|
||||
* To add an encounter to every biome possible, use this array
|
||||
*/
|
||||
const anyBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.FIGHT_OR_FLIGHT,
|
||||
MysteryEncounterType.DARK_DEAL,
|
||||
MysteryEncounterType.MYSTERIOUS_CHEST,
|
||||
MysteryEncounterType.TRAINING_SESSION,
|
||||
MysteryEncounterType.DELIBIRDY,
|
||||
MysteryEncounterType.A_TRAINERS_TEST,
|
||||
MysteryEncounterType.TRASH_TO_TREASURE,
|
||||
MysteryEncounterType.BERRIES_ABOUND,
|
||||
MysteryEncounterType.CLOWNING_AROUND,
|
||||
MysteryEncounterType.WEIRD_DREAM
|
||||
];
|
||||
|
||||
/**
|
||||
* ENCOUNTER BIOME MAPPING
|
||||
* To add an Encounter to a biome group, instead of cluttering the map, use the biome group arrays above
|
||||
*
|
||||
* Adding specific Encounters to the mysteryEncountersByBiome map is for specific cases and special circumstances
|
||||
* that biome groups do not cover
|
||||
*/
|
||||
export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.TOWN, []],
|
||||
[Biome.PLAINS, [
|
||||
MysteryEncounterType.SLUMBERING_SNORLAX,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[Biome.GRASS, [
|
||||
MysteryEncounterType.SLUMBERING_SNORLAX,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[Biome.TALL_GRASS, [
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[Biome.METROPOLIS, []],
|
||||
[Biome.FOREST, [
|
||||
MysteryEncounterType.SAFARI_ZONE,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
|
||||
[Biome.SEA, [
|
||||
MysteryEncounterType.LOST_AT_SEA
|
||||
]],
|
||||
[Biome.SWAMP, [
|
||||
MysteryEncounterType.SAFARI_ZONE
|
||||
]],
|
||||
[Biome.BEACH, []],
|
||||
[Biome.LAKE, []],
|
||||
[Biome.SEABED, []],
|
||||
[Biome.MOUNTAIN, []],
|
||||
[Biome.BADLANDS, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.CAVE, [
|
||||
MysteryEncounterType.THE_STRONG_STUFF
|
||||
]],
|
||||
[Biome.DESERT, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.ICE_CAVE, []],
|
||||
[Biome.MEADOW, []],
|
||||
[Biome.POWER_PLANT, []],
|
||||
[Biome.VOLCANO, [
|
||||
MysteryEncounterType.FIERY_FALLOUT,
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.GRAVEYARD, []],
|
||||
[Biome.DOJO, []],
|
||||
[Biome.FACTORY, []],
|
||||
[Biome.RUINS, []],
|
||||
[Biome.WASTELAND, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.ABYSS, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[Biome.SPACE, []],
|
||||
[Biome.CONSTRUCTION_SITE, []],
|
||||
[Biome.JUNGLE, [
|
||||
MysteryEncounterType.SAFARI_ZONE
|
||||
]],
|
||||
[Biome.FAIRY_CAVE, []],
|
||||
[Biome.TEMPLE, []],
|
||||
[Biome.SLUM, []],
|
||||
[Biome.SNOWY_FOREST, []],
|
||||
[Biome.ISLAND, []],
|
||||
[Biome.LABORATORY, []]
|
||||
]);
|
||||
|
||||
export function initMysteryEncounters() {
|
||||
allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHALLENGERS] = MysteriousChallengersEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHEST] = MysteriousChestEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.DARK_DEAL] = DarkDealEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.FIGHT_OR_FLIGHT] = FightOrFlightEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.TRAINING_SESSION] = TrainingSessionEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.SLUMBERING_SNORLAX] = SlumberingSnorlaxEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.SAFARI_ZONE] = SafariZoneEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.LOST_AT_SEA] = LostAtSeaEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.FIERY_FALLOUT] = FieryFalloutEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.THE_STRONG_STUFF] = TheStrongStuffEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.THE_POKEMON_SALESMAN] = ThePokemonSalesmanEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE] = AnOfferYouCantRefuseEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.DELIBIRDY] = DelibirdyEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.ABSOLUTE_AVARICE] = AbsoluteAvariceEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.A_TRAINERS_TEST] = ATrainersTestEncounter;
|
||||
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;
|
||||
allMysteryEncounters[MysteryEncounterType.WEIRD_DREAM] = WeirdDreamEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.THE_WINSTRATE_CHALLENGE] = TheWinstrateChallengeEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
EXTREME_ENCOUNTER_BIOMES.forEach(biome => {
|
||||
const encountersForBiome = mysteryEncountersByBiome.get(biome);
|
||||
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
|
||||
encountersForBiome.push(encounter);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Add non-extreme encounters to biome map
|
||||
nonExtremeBiomeEncounters.forEach(encounter => {
|
||||
NON_EXTREME_ENCOUNTER_BIOMES.forEach(biome => {
|
||||
const encountersForBiome = mysteryEncountersByBiome.get(biome);
|
||||
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
|
||||
encountersForBiome.push(encounter);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Add human encounters to biome map
|
||||
humanTransitableBiomeEncounters.forEach(encounter => {
|
||||
HUMAN_TRANSITABLE_BIOMES.forEach(biome => {
|
||||
const encountersForBiome = mysteryEncountersByBiome.get(biome);
|
||||
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
|
||||
encountersForBiome.push(encounter);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Add civilization encounters to biome map
|
||||
civilizationBiomeEncounters.forEach(encounter => {
|
||||
CIVILIZATION_ENCOUNTER_BIOMES.forEach(biome => {
|
||||
const encountersForBiome = mysteryEncountersByBiome.get(biome);
|
||||
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
|
||||
encountersForBiome.push(encounter);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add ANY biome encounters to biome map
|
||||
mysteryEncountersByBiome.forEach(biomeEncounters => {
|
||||
anyBiomeEncounters.forEach(encounter => {
|
||||
if (!biomeEncounters.includes(encounter)) {
|
||||
biomeEncounters.push(encounter);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|