forked from Yonokid/YataiDONNijiiro
Compare commits
186
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc4fc59741 | ||
|
|
d87aef0177 | ||
|
|
605a651a25 | ||
|
|
b9fee6e113 | ||
|
|
88df3fde7f | ||
|
|
9cfd0d9a76 | ||
|
|
6976222461 | ||
|
|
f604f65435 | ||
|
|
200cdc58ed | ||
|
|
c531160071 | ||
|
|
2eccdaecd6 | ||
|
|
e35c5ab854 | ||
|
|
30590cd9b4 | ||
|
|
dcdff8924c | ||
|
|
267d0c2f3e | ||
|
|
fafd8d2778 | ||
|
|
b87ef7a172 | ||
|
|
941afd274c | ||
|
|
3be0239090 | ||
|
|
525a9f36ff | ||
|
|
2f41fe2bbd | ||
|
|
8a75d48555 | ||
|
|
cc3e9308f5 | ||
|
|
5b0412389e | ||
|
|
506c761c3a | ||
|
|
f37375e18a | ||
|
|
fb7c1ba611 | ||
|
|
fd383c7b8b | ||
|
|
8f4b6835c4 | ||
|
|
ec3fa91b0d | ||
|
|
814f21047b | ||
|
|
89618b4c02 | ||
|
|
3ea0910bd5 | ||
|
|
c3d9c9f1b4 | ||
|
|
a340d03a93 | ||
|
|
5b46d889d8 | ||
|
|
366ece5ebe | ||
|
|
06eb0778e4 | ||
|
|
f45fe56a67 | ||
|
|
401d7cf12f | ||
|
|
9999570d4c | ||
|
|
a887052197 | ||
|
|
d121d5ed34 | ||
|
|
4c50c7f2cb | ||
|
|
35b6152aae | ||
|
|
3af3f30ce1 | ||
|
|
828c9eb296 | ||
|
|
463194ccde | ||
|
|
0b62466284 | ||
|
|
c0ecb338ba | ||
|
|
047c3e9f3c | ||
|
|
59c90c3ca1 | ||
|
|
674b253bdd | ||
|
|
633f8b593a | ||
|
|
a5793af2ca | ||
|
|
60c9178c86 | ||
|
|
2994bdc94b | ||
|
|
07092b1cac | ||
|
|
ced2120784 | ||
|
|
c5aeff2158 | ||
|
|
26cab15ab5 | ||
|
|
113e246328 | ||
|
|
f0f6cac997 | ||
|
|
d18a5796c3 | ||
|
|
b74023c829 | ||
|
|
e515e2e0f2 | ||
|
|
45156e3bfc | ||
|
|
43c57b1cfd | ||
|
|
e83a388a4a | ||
|
|
9a8591edb8 | ||
|
|
9019695c30 | ||
|
|
2a605b54aa | ||
|
|
8279a57cbd | ||
|
|
fc5705b024 | ||
|
|
a0a4a29027 | ||
|
|
9a4e078d20 | ||
|
|
04a9920142 | ||
|
|
dfa2ac78a3 | ||
|
|
57250065bb | ||
|
|
346f8e7274 | ||
|
|
6a68be7c36 | ||
|
|
d318cf5e9f | ||
|
|
1c39077a72 | ||
|
|
d4a7e65786 | ||
|
|
d6629d4a38 | ||
|
|
8e8a6d0f28 | ||
|
|
6e94262bdb | ||
|
|
db94fe4f2c | ||
|
|
1281737b79 | ||
|
|
f08a4082bc | ||
|
|
fec174303a | ||
|
|
00cd9bb786 | ||
|
|
1066fa6383 | ||
|
|
7f6e883a09 | ||
|
|
58259f949d | ||
|
|
f63d0901ba | ||
|
|
1b06d309f5 | ||
|
|
ecc50e6c60 | ||
|
|
2b49aad1af | ||
|
|
bffb67099c | ||
|
|
c7acbf8527 | ||
|
|
d4b47f79d9 | ||
|
|
7ce90d46b8 | ||
|
|
037a9c5816 | ||
|
|
96f0f6f7aa | ||
|
|
462e4602c6 | ||
|
|
11417f080f | ||
|
|
0985512f91 | ||
|
|
3a815102a1 | ||
|
|
5ee758f90f | ||
|
|
e1b6f78cb9 | ||
|
|
5ab735c76d | ||
|
|
089ee6d7fc | ||
|
|
26419b4b1c | ||
|
|
809c81957d | ||
|
|
6e2e97d6fe | ||
|
|
c227ea3ce6 | ||
|
|
c7c69dc3fe | ||
|
|
5898df4231 | ||
|
|
ec5e73019b | ||
|
|
2633ff421b | ||
|
|
e1ec601b27 | ||
|
|
3c1f9faad5 | ||
|
|
e0c33eb940 | ||
|
|
aa4967d5a3 | ||
|
|
78dc1281ae | ||
|
|
e2350bc9bc | ||
|
|
dfda066441 | ||
|
|
cd4f675ed1 | ||
|
|
8ff6e2402d | ||
|
|
4184dcbb15 | ||
|
|
0c606013c2 | ||
|
|
ef20f18926 | ||
|
|
9c7111de17 | ||
|
|
bb0bb1cf28 | ||
|
|
10d00c1799 | ||
|
|
0ba0cc1a38 | ||
|
|
aa09e56cfa | ||
|
|
24443674a4 | ||
|
|
45219c8f6b | ||
|
|
1d6b956b69 | ||
|
|
5e96ba9c02 | ||
|
|
c24c3650bc | ||
|
|
188edce33b | ||
|
|
558693399a | ||
|
|
ecdb84701b | ||
|
|
6b9dafeff2 | ||
|
|
5947408c67 | ||
|
|
5d35943d3e | ||
|
|
14bbae37cf | ||
|
|
a87e8921d8 | ||
|
|
e78bd361e1 | ||
|
|
24c1d48543 | ||
|
|
60add28047 | ||
|
|
319603acb7 | ||
|
|
f69e411380 | ||
|
|
85e210f038 | ||
|
|
8874f72738 | ||
|
|
a0c9513f43 | ||
|
|
ea2b88cac9 | ||
|
|
d8eb4ec30e | ||
|
|
05c8bb27f5 | ||
|
|
9f341ac787 | ||
|
|
f19a8ae735 | ||
|
|
561a1ef5c4 | ||
|
|
a0e9ca66a6 | ||
|
|
f50ee6631d | ||
|
|
d459a0f285 | ||
|
|
8ce99e2679 | ||
|
|
94eb92d8c2 | ||
|
|
c64d9874f2 | ||
|
|
2d18433adf | ||
|
|
97daf61279 | ||
|
|
3640b8963b | ||
|
|
4de1d53fb2 | ||
|
|
f0d3de2316 | ||
|
|
dccba9f62d | ||
|
|
1153af1511 | ||
|
|
53a0a99e2f | ||
|
|
6067141adf | ||
|
|
fe649c98ff | ||
|
|
19ad8e43e3 | ||
|
|
8a02b53d66 | ||
|
|
ac550dbfa1 | ||
|
|
f44e0493e6 | ||
|
|
55b11fdb8a |
No files matched your search
@@ -3,3 +3,4 @@
|
||||
*.ogg filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"page1": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"page2": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
@@ -164,5 +164,62 @@
|
||||
}
|
||||
],
|
||||
"comment": "ROUND 17: copied verbatim from song_select/animation.json so the 演奏オプション panel the 段位道場 confirmation dialog's option chip opens runs identically on DAN_SELECT. ModifierSelector's constructor reads 28/29/30/31/32 (+39 optional) and get_animation THROWS on a missing id; a child animation.json replaces the parent's rather than merging, so DAN_SELECT had none of them. option/neiro panel slide OUT (ModifierSelector/NeiroSelector pick this up on confirm when it exists; without it they replay id 28). Arcade MC65 label 'out' = frames 25..44: y 0 -> -75 over 4 frames, then -75 -> 650 over 15. Engine space the drawn y is `attribute + song_select_offset.x` (= -548 in this skin), so attribute runs 0 -> -75 at 4/19 = 0.2105 -> 548."
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "move",
|
||||
"duration": 450,
|
||||
"total_distance": 1920,
|
||||
"ease_in": "quadratic",
|
||||
"comment": "dan transition slide in (PyTaikoGreen x1.5)"
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "move",
|
||||
"duration": 383,
|
||||
"delay": 517,
|
||||
"total_distance": -705,
|
||||
"waypoints": [
|
||||
{
|
||||
"at": 200,
|
||||
"value": -768,
|
||||
"ease_in": "quadratic"
|
||||
},
|
||||
{
|
||||
"at": 283,
|
||||
"value": -689
|
||||
}
|
||||
],
|
||||
"comment": "dan transition don rises from the bottom, overshoots and settles (PyTaikoGreen x1.5)"
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "fade",
|
||||
"duration": 400,
|
||||
"delay": 483,
|
||||
"initial_opacity": 0.0,
|
||||
"final_opacity": 1.0,
|
||||
"ease_out": "quadratic",
|
||||
"comment": "dan transition emblem fade in"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "move",
|
||||
"duration": 500,
|
||||
"delay": 983,
|
||||
"start_position": 0,
|
||||
"total_distance": 90,
|
||||
"ease_in": "quadratic",
|
||||
"comment": "dan transition emblem spin (degrees, not scaled)"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "fade",
|
||||
"duration": 483,
|
||||
"delay": 1000,
|
||||
"initial_opacity": 0.13,
|
||||
"final_opacity": 1.0,
|
||||
"ease_out": "quadratic",
|
||||
"comment": "dan transition white flash; its end is the end of the transition"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,148 @@
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"type": "fade",
|
||||
"duration": 100,
|
||||
"initial_opacity": 0.0,
|
||||
"final_opacity": 1.0,
|
||||
"delay": 20,
|
||||
"lock_input": true,
|
||||
"comment": "fade in the side select box"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "fade",
|
||||
"duration": 500,
|
||||
"initial_opacity": 0.5,
|
||||
"final_opacity": 0.4,
|
||||
"reverse_delay": 0,
|
||||
"loop": true,
|
||||
"comment": "background lights flicker"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "move",
|
||||
"duration": 550,
|
||||
"total_distance": -245,
|
||||
"ease_in": "quadratic",
|
||||
"lock_input": true,
|
||||
"waypoints": [
|
||||
{ "at": 350, "value": -295, "ease_out": "quadratic" }
|
||||
],
|
||||
"comment": "move donchan's drum up/down a little (merged from old ids 2+3)"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "move",
|
||||
"duration": 350,
|
||||
"total_distance": -170,
|
||||
"delay": {
|
||||
"reference_id": 2,
|
||||
"property": "duration"
|
||||
},
|
||||
"ease_out": "quadratic",
|
||||
"lock_input": true,
|
||||
"comment": "move donchan's drum to the left/right"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "texture_resize",
|
||||
"duration": 350,
|
||||
"initial_size": 0.75,
|
||||
"final_size": 1.0,
|
||||
"comment": "initial cloud resize, larger than loop"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "texture_resize",
|
||||
"duration": 200,
|
||||
"initial_size": 1.0,
|
||||
"final_size": 1.2,
|
||||
"reverse_delay": 200,
|
||||
"loop": true,
|
||||
"delay": {
|
||||
"reference_id": 5,
|
||||
"property": "duration"
|
||||
},
|
||||
"comment": "loop resize donchan cloud back and forth"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "texture_change",
|
||||
"duration": 333,
|
||||
"delay": {
|
||||
"init_val": {
|
||||
"reference_id": 4,
|
||||
"property": "duration"
|
||||
},
|
||||
"reference_id": 4,
|
||||
"property": "delay"
|
||||
},
|
||||
"textures": [
|
||||
[0, 83.35, 0],
|
||||
[83.35, 166.7, 1],
|
||||
[166.7, 250, 2],
|
||||
[250, 333, 3]
|
||||
],
|
||||
"comment": "cloud keyframes"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "fade",
|
||||
"duration": 83.35,
|
||||
"delay": {
|
||||
"init_val": {
|
||||
"reference_id": 7,
|
||||
"property": "delay"
|
||||
},
|
||||
"reference_id": 7,
|
||||
"property": "duration"
|
||||
},
|
||||
"comment": "fade out the cloud"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "fade",
|
||||
"duration": 160,
|
||||
"lock_input": true,
|
||||
"comment": "fade out the mode select boxes"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "move",
|
||||
"duration": 166.67,
|
||||
"total_distance": 50,
|
||||
"comment": "box movement"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "move",
|
||||
"duration": 133.34,
|
||||
"total_distance": 54,
|
||||
"delay": 200,
|
||||
"comment": "box opening"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "fade",
|
||||
"duration": 200,
|
||||
"initial_opacity": 0.0,
|
||||
"final_opacity": 1.0,
|
||||
"comment": "nameplate fade in"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "fade",
|
||||
"duration": 1000,
|
||||
"loop": true,
|
||||
"comment": "costume menu blue arrow fade"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "move",
|
||||
"duration": 1000,
|
||||
"total_distance": 25,
|
||||
"loop": true,
|
||||
"comment": "costume menu blue arrow move"
|
||||
}
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"player_entry_en": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"player_entry_ja": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
@@ -1186,5 +1186,22 @@
|
||||
],
|
||||
"loop": true,
|
||||
"comment": "r57: result-gauge tamashii fire cels (replaces the id-20 kusudama-FADE collision; body = id 25, the real 8-cel 400 ms fire cycle)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 76,
|
||||
"type": "texture_resize",
|
||||
"duration": 133.36,
|
||||
"initial_size": 0.0,
|
||||
"final_size": 1.0,
|
||||
"comment": "gogotime lane effect expands from center over 8 frames (16.67ms each)"
|
||||
},
|
||||
{
|
||||
"id": 77,
|
||||
"type": "fade",
|
||||
"duration": 283.3,
|
||||
"initial_opacity": 1.0,
|
||||
"final_opacity": 0.0,
|
||||
"delay": 0,
|
||||
"comment": "judgment fade 3"
|
||||
}
|
||||
]
|
||||
Binary file not shown.
@@ -260,5 +260,11 @@
|
||||
440
|
||||
]
|
||||
]
|
||||
},
|
||||
"lane_effect": {
|
||||
"x": 496,
|
||||
"y": 12,
|
||||
"x2": 1424,
|
||||
"y2": 195
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -641,6 +641,9 @@
|
||||
"x": 0,
|
||||
"y": 110
|
||||
},
|
||||
"yellow_box_bottom": {
|
||||
"y": 974
|
||||
},
|
||||
"dan_level_counter_margin": {
|
||||
"x": 34
|
||||
},
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,244 +0,0 @@
|
||||
{
|
||||
"background": {
|
||||
"x": -6008,
|
||||
"y": 433
|
||||
},
|
||||
"background_2": {
|
||||
"x": -6001,
|
||||
"y": 247
|
||||
},
|
||||
"title": [
|
||||
{
|
||||
"x": -5933,
|
||||
"y": 442
|
||||
},
|
||||
{
|
||||
"x": -5933,
|
||||
"y": 268
|
||||
}
|
||||
],
|
||||
"normal": [
|
||||
{
|
||||
"x": -5977,
|
||||
"y": 491
|
||||
},
|
||||
{
|
||||
"x": -5977,
|
||||
"y": 317
|
||||
}
|
||||
],
|
||||
"normal_ura": [
|
||||
{
|
||||
"x": -5977,
|
||||
"y": 491
|
||||
},
|
||||
{
|
||||
"x": -5977,
|
||||
"y": 317
|
||||
}
|
||||
],
|
||||
"shinuchi": [
|
||||
{
|
||||
"x": -5977,
|
||||
"y": 542
|
||||
},
|
||||
{
|
||||
"x": -5977,
|
||||
"y": 317
|
||||
}
|
||||
],
|
||||
"shinuchi_ura": [
|
||||
{
|
||||
"x": -5977,
|
||||
"y": 542
|
||||
},
|
||||
{
|
||||
"x": -5977,
|
||||
"y": 317
|
||||
}
|
||||
],
|
||||
"pts": [
|
||||
{
|
||||
"x": -5795,
|
||||
"y": 495
|
||||
},
|
||||
{
|
||||
"x": -5808,
|
||||
"y": 321
|
||||
}
|
||||
],
|
||||
"counter": [
|
||||
{
|
||||
"x": -5850,
|
||||
"y": 495,
|
||||
"crop": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
16,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
32,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
48,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
64,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
80,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
96,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
112,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
128,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
],
|
||||
[
|
||||
144,
|
||||
0,
|
||||
16,
|
||||
32
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"x": -5850,
|
||||
"y": 321
|
||||
}
|
||||
],
|
||||
"difficulty": [
|
||||
{
|
||||
"x": -5942,
|
||||
"y": 495
|
||||
},
|
||||
{
|
||||
"x": -5772,
|
||||
"y": 323
|
||||
}
|
||||
],
|
||||
"ura": [
|
||||
{
|
||||
"x": -5974,
|
||||
"y": 495
|
||||
},
|
||||
{
|
||||
"x": -5974,
|
||||
"y": 321
|
||||
}
|
||||
],
|
||||
"judge_good": {
|
||||
"x": -5974,
|
||||
"y": 368
|
||||
},
|
||||
"judge_ok": {
|
||||
"x": -5974,
|
||||
"y": 418
|
||||
},
|
||||
"judge_bad": {
|
||||
"x": -5974,
|
||||
"y": 468
|
||||
},
|
||||
"judge_drumroll": {
|
||||
"x": -6036,
|
||||
"y": 518
|
||||
},
|
||||
"judge_num": {
|
||||
"x": -5752,
|
||||
"y": 318,
|
||||
"crop": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
32,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
64,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
96,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
128,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
160,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
192,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
224,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
256,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
],
|
||||
[
|
||||
288,
|
||||
0,
|
||||
32,
|
||||
40
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -105,8 +105,6 @@ function Background.new(player_num, bpm, scene_preset)
|
||||
self.scene_preset = scene_preset
|
||||
self.is_2p = (player_num == 3) -- PlayerNum::TWO_PLAYER
|
||||
|
||||
tex.load_animations("background")
|
||||
|
||||
-- child-only: arcade soul-gauge overlay, drawn in draw_fore (in front of the HUD)
|
||||
self.arcade_gauge = ArcadeGauge.new()
|
||||
|
||||
@@ -158,6 +156,13 @@ function Background.new(player_num, bpm, scene_preset)
|
||||
return self
|
||||
end
|
||||
|
||||
-- a miss streak darkens the donbg band (each player's own band in 2P)
|
||||
function Background:handle_miss_streak(player_num, on)
|
||||
if self.collab then return end
|
||||
local d = self.is_2p and (player_num == 2 and self.donbg_2 or self.donbg_1) or self.donbg
|
||||
if d then d:set_dark(on) end
|
||||
end
|
||||
|
||||
function Background:update(current_ms, bpm)
|
||||
self.bpm = bpm
|
||||
self.enso_skip:update(current_ms)
|
||||
|
||||
@@ -40,7 +40,7 @@ function A3BGNormal.new()
|
||||
local self = setmetatable({}, A3BGNormal)
|
||||
self.name = "bg_0"
|
||||
tex.load_folder(COLLAB_PATH, "bg_normal/" .. self.name)
|
||||
self.flicker = tex.get_animation(9)
|
||||
self.flicker = tex.get_animation(9, "background")
|
||||
self._id_bg = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/background") or 0
|
||||
self._id_overlay = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/overlay") or 0
|
||||
self._id_curtain = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/curtain") or 0
|
||||
@@ -70,8 +70,8 @@ function A3BGFever.new()
|
||||
self.name = "bg_fever_0"
|
||||
tex.load_folder(COLLAB_PATH, "bg_fever/" .. self.name)
|
||||
self.transitioned = false
|
||||
self.horizontal_move = tex.get_animation(16)
|
||||
self.bg_texture_move = tex.get_animation(17)
|
||||
self.horizontal_move = tex.get_animation(16, "background")
|
||||
self.bg_texture_move = tex.get_animation(17, "background")
|
||||
local petal_info = tex.get_texture_info(self.name, "petals")
|
||||
self.petal_width = petal_info and petal_info.x2[1] or 0
|
||||
self.vertical_move = anim.move(1300, { total_distance = self.petal_width, loop = true })
|
||||
|
||||
@@ -15,7 +15,7 @@ function DanDonBG.new(player_num)
|
||||
local self = setmetatable({}, DanDonBG)
|
||||
local base = DonBGBase.new(1, player_num, COLLAB_PATH)
|
||||
for k, v in pairs(base) do self[k] = v end
|
||||
self.overlay_move = tex.get_animation(2)
|
||||
self.overlay_move = tex.get_animation(2, "background")
|
||||
local bg_overlay_info = tex.get_texture_info(self.name, "bg_overlay")
|
||||
self.bg_overlay_width = bg_overlay_info and bg_overlay_info.x2[1] or 0
|
||||
local bg_overlay_2_info = tex.get_texture_info(self.name, "bg_overlay_2")
|
||||
|
||||
@@ -20,8 +20,8 @@ function FunassyiBGFever.new()
|
||||
self.name = "bg_fever_0"
|
||||
tex.load_folder(COLLAB_PATH, "bg_fever/" .. self.name)
|
||||
self.transitioned = false
|
||||
self.horizontal_move = tex.get_animation(16)
|
||||
self.bg_texture_move = tex.get_animation(17)
|
||||
self.horizontal_move = tex.get_animation(16, "background")
|
||||
self.bg_texture_move = tex.get_animation(17, "background")
|
||||
local overlay_info = tex.get_texture_info(self.name, "overlay")
|
||||
self.overlay_width = overlay_info and overlay_info.x2[1] or 0
|
||||
self._id_bg = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/background") or 0
|
||||
|
||||
@@ -19,7 +19,7 @@ function GumiDonBG.new(index, player_num, path)
|
||||
for k, v in pairs(base) do self[k] = v end
|
||||
-- this skin's donbg/6 is its own (a static band) and leaves out what PyTaikoGreen's DonBG6
|
||||
-- sets for the scrolling overlays that draw_textures below uses
|
||||
self.overlay_move = self.overlay_move or tex.get_animation(2)
|
||||
self.overlay_move = self.overlay_move or tex.get_animation(2, "background")
|
||||
self.overlay_1_texture = self.overlay_1_texture or tex.get_texture_info(self.name, "overlay_1")
|
||||
self.overlay_2_texture = self.overlay_2_texture or tex.get_texture_info(self.name, "overlay_2")
|
||||
self._id_overlay_1 = self._id_overlay_1 or tex.get_texture(self.path .. "/" .. self.name .. "/" .. "overlay_1") or 0
|
||||
|
||||
@@ -13,7 +13,7 @@ function TutorialDonBG.new(index, player_num, path)
|
||||
end
|
||||
self.move = anim.move(6000, { total_distance = -self.bg_texture.x2[1], loop = true })
|
||||
self.move:start()
|
||||
self.overlay_move = tex.get_animation(2)
|
||||
self.overlay_move = tex.get_animation(2, "background")
|
||||
self.overlay_texture = tex.get_texture_info(self.name, "overlay")
|
||||
self.footer_texture = tex.get_texture_info(self.name, "footer")
|
||||
self._id_overlay = tex.get_texture(self.path .. "/" .. self.name .. "/overlay") or 0
|
||||
|
||||
@@ -9,9 +9,6 @@ function ArcadeGauge.new()
|
||||
local self = setmetatable({}, ArcadeGauge)
|
||||
self.p = {}
|
||||
|
||||
tex.unload_folder("result", "gauge")
|
||||
tex.unload_folder("game", "gauge")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ function BGNormal01.new(index, path)
|
||||
local self = setmetatable({}, BGNormal01)
|
||||
local base = BGNormalBase.new(index, path)
|
||||
for k, v in pairs(base) do self[k] = v end
|
||||
self.flicker = tex.get_animation(9)
|
||||
self.flicker = tex.get_animation(9, "background")
|
||||
self._id_overlay = tex.get_texture(self.path .. "/" .. self.name .. "/overlay") or 0
|
||||
return self
|
||||
end
|
||||
@@ -51,7 +51,7 @@ function BGNormal2.new(index, path)
|
||||
local self = setmetatable({}, BGNormal2)
|
||||
local base = BGNormalBase.new(index, path)
|
||||
for k, v in pairs(base) do self[k] = v end
|
||||
self.flicker = tex.get_animation(10)
|
||||
self.flicker = tex.get_animation(10, "background")
|
||||
self._id_center = tex.get_texture(self.path .. "/" .. self.name .. "/center") or 0
|
||||
self._id_overlay = tex.get_texture(self.path .. "/" .. self.name .. "/overlay") or 0
|
||||
self._id_lamps = tex.get_texture(self.path .. "/" .. self.name .. "/lamps") or 0
|
||||
@@ -115,9 +115,9 @@ function BGNormal3.new(index, path)
|
||||
local self = setmetatable({}, BGNormal3)
|
||||
local base = BGNormalBase.new(index, path)
|
||||
for k, v in pairs(base) do self[k] = v end
|
||||
self.flicker = tex.get_animation(11)
|
||||
self.turtle_move = tex.get_animation(12)
|
||||
self.turtle_change = tex.get_animation(13)
|
||||
self.flicker = tex.get_animation(11, "background")
|
||||
self.turtle_move = tex.get_animation(12, "background")
|
||||
self.turtle_change = tex.get_animation(13, "background")
|
||||
self.screen_scale = tex.get_screen_scale()
|
||||
self.screen_width = tex.get_screen_width()
|
||||
self.screen_height = tex.get_screen_height()
|
||||
|
||||
@@ -5,7 +5,7 @@ local DonBG4 = require("background/bg_objects/donbg/4")
|
||||
local DonBG5 = require("background/bg_objects/donbg/5")
|
||||
local DonBG6 = require("background/bg_objects/donbg/6")
|
||||
|
||||
DonBG = {}
|
||||
local DonBG = {}
|
||||
|
||||
function DonBG.create(player_num, path, index)
|
||||
index = index or math.random(1, 6)
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
-- base contract.
|
||||
local DonBGBase = require("background/bg_objects/donbg/base")
|
||||
|
||||
DonBG4 = setmetatable({}, { __index = DonBGBase })
|
||||
local DonBG4 = setmetatable({}, { __index = DonBGBase })
|
||||
DonBG4.__index = DonBG4
|
||||
|
||||
function DonBG4.new(index, player_num, path)
|
||||
local self = setmetatable({}, DonBG4)
|
||||
local base = DonBGBase.new(index, player_num, path)
|
||||
for k, v in pairs(base) do self[k] = v end
|
||||
self.overlay_move = tex.get_animation(2)
|
||||
self.overlay_move = tex.get_animation(2, "background")
|
||||
self.overlay_texture = tex.get_texture_info(self.name, "overlay")
|
||||
self._id_overlay = tex.get_texture(self.path .. "/" .. self.name .. "/overlay") -- nil = missing
|
||||
return self
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- adds the cabinet's three sakura-petal drifters (between the two scrolling layers).
|
||||
local DonBGBase = require("background/bg_objects/donbg/base")
|
||||
|
||||
DonBG6 = setmetatable({}, { __index = DonBGBase })
|
||||
local DonBG6 = setmetatable({}, { __index = DonBGBase })
|
||||
DonBG6.__index = DonBG6
|
||||
|
||||
local PETAL_W, PETAL_H = 391, 208
|
||||
@@ -56,9 +56,9 @@ end
|
||||
|
||||
-- Same shape as DonBGBase:draw(), with the drifters slotted in at their real
|
||||
-- display-list depth: after the depth-0 backdrop, before the depth-4 motif row.
|
||||
function DonBG6:draw(y)
|
||||
function DonBG6:draw_band(y)
|
||||
y = y or 0
|
||||
if not self.use_layers then return DonBGBase.draw(self, y) end
|
||||
if not self.use_layers then return DonBGBase.draw_band(self, y) end
|
||||
|
||||
local normal = self.states.normal or {}
|
||||
local fever = self.states.fever
|
||||
@@ -87,4 +87,8 @@ function DonBG6:draw(y)
|
||||
end
|
||||
end
|
||||
|
||||
function DonBG6:draw(y)
|
||||
self:draw_dark(DonBG6.draw_band, y or 0)
|
||||
end
|
||||
|
||||
return DonBG6
|
||||
@@ -8,7 +8,7 @@ if not ok or type(DATA) ~= "table" then DATA = {} end
|
||||
local ok_anim, DonBGAnim = pcall(require, "background/bg_objects/donbg/anim_player")
|
||||
if not ok_anim or type(DonBGAnim) ~= "table" then DonBGAnim = nil end
|
||||
|
||||
DonBGBase = {}
|
||||
local DonBGBase = {}
|
||||
DonBGBase.__index = DonBGBase
|
||||
|
||||
function DonBGBase.new(index, player_num, path)
|
||||
@@ -33,7 +33,7 @@ function DonBGBase.new(index, player_num, path)
|
||||
end
|
||||
|
||||
self.is_clear = false
|
||||
self.clear_fade = tex.get_animation(1)
|
||||
self.clear_fade = tex.get_animation(1, "background")
|
||||
-- exit is a reverse fade of the same length the bg_fever wipe uses
|
||||
self.exit_fade = nil
|
||||
|
||||
@@ -77,6 +77,27 @@ end
|
||||
|
||||
local EXIT_MS = 29000 / 60
|
||||
|
||||
-- miss streak: the band is multiplied towards black (colour of draw_texture), eased in/out
|
||||
local DARK_MIN, DARK_FADE_MS = 0.65, 300
|
||||
|
||||
function DonBGBase:set_dark(on)
|
||||
self.dark_target = on and 1 or 0
|
||||
end
|
||||
|
||||
function DonBGBase:update_dark(current_time_ms)
|
||||
local dt = current_time_ms - (self.dark_last_ms or current_time_ms)
|
||||
self.dark_last_ms = current_time_ms
|
||||
local d, target = self.dark or 0, self.dark_target or 0
|
||||
local step = dt / DARK_FADE_MS
|
||||
self.dark = d + math.max(-step, math.min(step, target - d))
|
||||
end
|
||||
|
||||
function DonBGBase:dark_color()
|
||||
if not self.dark or self.dark <= 0 then return nil end
|
||||
local v = math.floor(255 * (1 - (1 - DARK_MIN) * self.dark))
|
||||
return { v, v, v, 255 }
|
||||
end
|
||||
|
||||
function DonBGBase:set_clear(is_clear)
|
||||
if is_clear and not self.is_clear then
|
||||
self.is_clear = true
|
||||
@@ -98,6 +119,7 @@ function DonBGBase:set_clear(is_clear)
|
||||
end
|
||||
|
||||
function DonBGBase:update(current_time_ms)
|
||||
self:update_dark(current_time_ms)
|
||||
-- same wall-clock accumulator bg_fever uses; guard drops the load-time gap
|
||||
if self.last_ms then
|
||||
local dt = current_time_ms - self.last_ms
|
||||
@@ -159,7 +181,7 @@ function DonBGBase:draw_textures(fade, y, frame)
|
||||
end
|
||||
end
|
||||
|
||||
function DonBGBase:draw(y)
|
||||
function DonBGBase:draw_band(y)
|
||||
y = y or 0
|
||||
|
||||
-- collab path = parent contract; default pack = DATA-driven layer lists
|
||||
@@ -191,6 +213,26 @@ function DonBGBase:draw(y)
|
||||
end
|
||||
end
|
||||
|
||||
-- Draws the band through `inner`, multiplying every texture it draws by the miss-streak
|
||||
-- darkness. Subclasses draw with their own tex.draw_texture calls, so wrap that once here.
|
||||
function DonBGBase:draw_dark(inner, y)
|
||||
local c = self:dark_color()
|
||||
if not c then return inner(self, y) end
|
||||
local orig = tex.draw_texture
|
||||
tex.draw_texture = function(id, p)
|
||||
p = p or {}
|
||||
p.color = c
|
||||
return orig(id, p)
|
||||
end
|
||||
local ok, err = pcall(inner, self, y)
|
||||
tex.draw_texture = orig
|
||||
if not ok then error(err, 0) end
|
||||
end
|
||||
|
||||
function DonBGBase:draw(y)
|
||||
self:draw_dark(DonBGBase.draw_band, y or 0)
|
||||
end
|
||||
|
||||
function DonBGBase:destroy()
|
||||
tex.unload_folder(self.path, string.format("donbg/%s", self.name))
|
||||
if self.anim then tex.unload_folder(self.path, self.anim.subset) end
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
-- DAN_SELECT paint surface: selection cursor breathing loop and arrow chips,
|
||||
-- sampled from the cabinet's own clips.
|
||||
DanSelect = {}
|
||||
DanSelect.__index = DanSelect
|
||||
-- sampled from the cabinet's own clips. The dan boxes themselves are drawn by the
|
||||
-- parent skin's dan_select.lua, which is loaded first and extended here.
|
||||
|
||||
-- the parent skin's copy of this script: the second match on package.path (the first is this file)
|
||||
local function load_parent(name)
|
||||
local seen = false
|
||||
for tmpl in package.path:gmatch("[^;]+") do
|
||||
local chunk = loadfile((tmpl:gsub("%?", name)))
|
||||
if chunk then
|
||||
if seen then return chunk end
|
||||
seen = true
|
||||
end
|
||||
end
|
||||
end
|
||||
local parent = load_parent("dan_select/dan_select")
|
||||
if parent then parent() end
|
||||
if not DanSelect then
|
||||
DanSelect = {}
|
||||
DanSelect.__index = DanSelect
|
||||
end
|
||||
local parent_new = DanSelect.new
|
||||
|
||||
local ok, anim = pcall(require, "anim/sampler")
|
||||
if not ok then anim = { safe = function()
|
||||
@@ -16,11 +34,12 @@ local CUR = anim.safe("anim/dan_cursor")
|
||||
local ARW = anim.safe("anim/dan_arrow")
|
||||
local ARW_OFF = ARW:label("arrow_off") or 61
|
||||
|
||||
-- No real clip data at all -> no draw_cursor: the engine draws the cursor from its
|
||||
-- inline tables (the boxes are still drawn by the parent's DanSelect).
|
||||
local HAS_CLIPS = CUR:has("#259@0") or ARW:has("#261@0")
|
||||
|
||||
function DanSelect.new()
|
||||
-- No real clip data at all -> hand the whole draw back to the engine's
|
||||
-- inline tables (returning nil leaves fn_draw_cursor invalid, C++ side).
|
||||
if not CUR:has("#259@0") and not ARW:has("#261@0") then return nil end
|
||||
local self = setmetatable({}, DanSelect)
|
||||
local self = parent_new and parent_new() or setmetatable({}, DanSelect)
|
||||
self.t_cursor = tex.get_texture("dan_select/box/cursor")
|
||||
self.t_arrow_r = tex.get_texture("dan_select/box/arrow_r")
|
||||
self.t_arrow_l = tex.get_texture("dan_select/box/arrow_l")
|
||||
@@ -29,7 +48,7 @@ end
|
||||
|
||||
-- pos: selected chip x (the textures' own json positions carry the rest);
|
||||
-- now_ms: engine clock; moved_ms: ms since the last ribbon step (-1 = never).
|
||||
function DanSelect:draw_cursor(pos, now_ms, moved_ms)
|
||||
local function draw_cursor(self, pos, now_ms, moved_ms)
|
||||
if self.t_cursor then
|
||||
local f = (now_ms * 0.06) % 60
|
||||
local a = CUR:get("#259@0", f, "a") or 1
|
||||
@@ -51,3 +70,5 @@ function DanSelect:draw_cursor(pos, now_ms, moved_ms)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if HAS_CLIPS then DanSelect.draw_cursor = draw_cursor end
|
||||
@@ -4,7 +4,6 @@ Entry.__index = Entry
|
||||
function Entry.new()
|
||||
local self = setmetatable({}, Entry)
|
||||
__hss_entry_reset = true
|
||||
tex.load_animations("entry")
|
||||
|
||||
self.bg_flicker = tex.get_animation(1, "entry")
|
||||
self.side_select_fade = tex.get_animation(0, "entry")
|
||||
@@ -45,7 +44,6 @@ function Entry.new()
|
||||
local arcade_cfg = tex.get_skin_config("entry_credit_arcade")
|
||||
self.cr_arcade = (arcade_cfg ~= nil and (arcade_cfg.x or 0) >= 1)
|
||||
self.cr_pending_decide = nil
|
||||
self.global_player_entry = tex.get_texture("entry/global/player_entry")
|
||||
|
||||
self.guide = tex.get_texture("global/indicator/background")
|
||||
self.guide_t0 = self.cr_t0
|
||||
@@ -238,7 +236,6 @@ function Entry:draw_footer(p1_joined, p2_joined)
|
||||
end
|
||||
|
||||
function Entry:draw_player_entry()
|
||||
tex.draw_texture(self.global_player_entry)
|
||||
self:draw_credit_decide()
|
||||
end
|
||||
|
||||
|
||||
@@ -35,13 +35,13 @@ function EntryPlayer.new(side)
|
||||
return self
|
||||
end
|
||||
|
||||
function EntryPlayer:start_animations()
|
||||
-- hold_cloud: login still loading; the cloud keeps puffing/looping until finish_cloud()
|
||||
function EntryPlayer:start_animations(hold_cloud)
|
||||
self.drum_move_1:start()
|
||||
self.drum_move_3:start()
|
||||
self.cloud_resize:start()
|
||||
self.cloud_resize_loop:start()
|
||||
self.cloud_texture_change:start()
|
||||
self.cloud_fade:start()
|
||||
if not hold_cloud then self:finish_cloud() end
|
||||
self.nameplate_fadein:start()
|
||||
end
|
||||
|
||||
@@ -177,3 +177,9 @@ end
|
||||
function EntryPlayer:get_nameplate_fade()
|
||||
return self.nameplate_fadein.attribute
|
||||
end
|
||||
|
||||
-- let the cloud shrink away: texture change + fade out
|
||||
function EntryPlayer:finish_cloud()
|
||||
self.cloud_texture_change:start()
|
||||
self.cloud_fade:start()
|
||||
end
|
||||
@@ -7,11 +7,13 @@ function AllNetIcon.new(online)
|
||||
local self = setmetatable({}, AllNetIcon)
|
||||
self.online = online
|
||||
self.indicator = tex.get_texture("global/overlay/allnet_indicator")
|
||||
self.params = { frame = online and 2 or 0, x = 0, y = 0 } -- reused every draw, no per-frame table garbage
|
||||
return self
|
||||
end
|
||||
|
||||
function AllNetIcon:update(current_ms, online)
|
||||
self.online = online
|
||||
self.params.frame = online and 2 or 0
|
||||
end
|
||||
|
||||
local SONG_MAX_CX, SONG_MAX_CY = 1814, 122
|
||||
@@ -36,20 +38,24 @@ function AllNetIcon:draw_song_max()
|
||||
y = math.floor(SONG_MAX_CY - t.height / 2 + 0.5), fade = fade })
|
||||
end
|
||||
|
||||
function AllNetIcon:draw_icon(x, y)
|
||||
local p = self.params
|
||||
p.x, p.y = x, y
|
||||
tex.draw_texture(self.indicator, p)
|
||||
end
|
||||
|
||||
function AllNetIcon:draw(x, y)
|
||||
local screen = tex.current_screen and tex.current_screen() or ""
|
||||
if screen == "GAME" or screen == "GAME_DAN" then
|
||||
if screen == "GAME" and not (tex.in_transition and tex.in_transition()) then
|
||||
self:draw_song_max()
|
||||
end
|
||||
local frame = self.online and 2 or 0
|
||||
tex.draw_texture(self.indicator, { frame = frame, x = x, y = y })
|
||||
self:draw_icon(x, y)
|
||||
if _G.g_active_enso_skip then _G.g_active_enso_skip:draw() end
|
||||
return
|
||||
end
|
||||
if screen == "LOADING" then
|
||||
return
|
||||
end
|
||||
local frame = self.online and 2 or 0
|
||||
tex.draw_texture(self.indicator, { frame = frame, x = x, y = y })
|
||||
self:draw_icon(x, y)
|
||||
end
|
||||
@@ -91,9 +91,25 @@ function SongTransition:skip_armed()
|
||||
return self.skip_on
|
||||
end
|
||||
|
||||
-- timeline the engine reads (rainbow rise for the title, title fades, done)
|
||||
local RISE_MS, RISE_DIST = 266.0, 1224 -- 816 at 720p, scaled to 1080p
|
||||
function SongTransition:state()
|
||||
local t = self.t or 0
|
||||
local rise = RISE_DIST * clamp(t / RISE_MS, 0, 1) ^ 3
|
||||
if self.is_second then
|
||||
return { rainbow_up = rise, fade_out = clamp(1.0 - t / FADE_OUT_MS, 0, 1),
|
||||
finished = t >= FADE_OUT_MS }
|
||||
end
|
||||
return { rainbow_up = rise, fade = clamp((t - FADE_DELAY) / FADE_MS, 0, 1),
|
||||
finished = t >= SCENE_MS }
|
||||
end
|
||||
|
||||
function SongTransition:start() self.t0 = nil end
|
||||
|
||||
function SongTransition:update(current_ms, rainbow_up, song_info_fade)
|
||||
if not self.t0 then self.t0 = current_ms end
|
||||
local t = clamp(current_ms - self.t0, 0, SCENE_MS)
|
||||
self.t = current_ms - self.t0
|
||||
local t = clamp(self.t, 0, SCENE_MS)
|
||||
if self.is_second then
|
||||
self.f = F_OUT + (F_END - F_OUT) * (t / SCENE_MS)
|
||||
self.info = clamp(1.0 - t / FADE_OUT_MS, 0, 1)
|
||||
|
||||
@@ -3,7 +3,6 @@ BottomCharacters.__index = BottomCharacters
|
||||
|
||||
function BottomCharacters.new()
|
||||
local self = setmetatable({}, BottomCharacters)
|
||||
tex.load_animations("result")
|
||||
|
||||
self.move_up = tex.get_animation(7, "result")
|
||||
self.move_down = tex.get_animation(8, "result")
|
||||
|
||||
@@ -12,7 +12,6 @@ local BAR_BASE_Y = 266
|
||||
|
||||
function HighScoreIndicator.new(score_diff, is_2p)
|
||||
local self = setmetatable({}, HighScoreIndicator)
|
||||
tex.load_animations("result")
|
||||
self.start_ms = nil
|
||||
self.y_offset = 24
|
||||
self.fade_val = 0
|
||||
|
||||
@@ -8,7 +8,6 @@ if not ok_layers then Layers = nil end
|
||||
function ResultFadeIn.new(player_num)
|
||||
local self = setmetatable({}, ResultFadeIn)
|
||||
self.player_num = player_num
|
||||
tex.load_animations("result")
|
||||
__hss_result_bg_clear = 0.0 -- first object the result scene builds; clear last run's state
|
||||
self.fade_in = tex.get_animation(15, "result")
|
||||
self.fade_in:start()
|
||||
|
||||
@@ -99,9 +99,12 @@ function ResultPlayer.new(player_num, is_2p, has_2p,
|
||||
crown_type, score_diff, score_method,
|
||||
mod_display, mod_inverse, mod_random, mod_speed)
|
||||
local self = setmetatable({}, ResultPlayer)
|
||||
tex.load_animations("result")
|
||||
|
||||
self.player_num = player_num
|
||||
local timing = tex.timing_offset(player_num)
|
||||
if timing then
|
||||
self.timing_text = text.create_raw_text(timing, 20 * tex.get_screen_scale(), {255, 255, 255, 255}, {0, 0, 0, 255}, false, 3.0)
|
||||
end
|
||||
self.is_2p = is_2p
|
||||
self.has_2p = has_2p
|
||||
self.gauge_length = gauge_length
|
||||
@@ -327,7 +330,7 @@ function ResultPlayer:update(current_ms, fade_in_finished,
|
||||
self.rank_obj = ClipPlayer.new("game/ending_anim/" .. clip, "game", clip .. "_anim", pos[1], pos[2])
|
||||
if self.rank_obj then
|
||||
self.rank_obj.hold_last = true
|
||||
if rank < 7 then self.rank_obj.shape_map = { [RANK_SHAPES[1]] = RANK_SHAPES[rank] } end
|
||||
if rank < 7 then self.rank_obj.shape_map = { [74] = RANK_SHAPES[rank], [76] = RANK_SHAPES[rank] } end -- clip uses plate 74 then 76
|
||||
self.rank_obj:start(current_ms)
|
||||
-- se_result `scorerank_NN_<seat>`: one sample for every rank, seat like the crown
|
||||
audio.play_sound("scorerank" .. (self.has_2p and (self.is_2p and "_r" or "_l") or "_c"), "sound")
|
||||
@@ -389,6 +392,13 @@ function ResultPlayer:draw()
|
||||
-- board frame (opaque white body), then the red/blue donback panel on top of it, then the difficulty tab
|
||||
if self.t_board then tex.draw_texture(self.t_board, { frame = p_idx, index = p_idx }) end
|
||||
if self.t_donback then tex.draw_texture(self.t_donback, { frame = p_idx, index = p_idx }) end
|
||||
if self.timing_text then
|
||||
local info = tex.get_texture_info("bottom", "chara_0")
|
||||
if info and info.x[p_idx + 1] then
|
||||
local pad = 6 * tex.get_screen_scale()
|
||||
self.timing_text:draw({ x = info.x[p_idx + 1] + pad, y = info.y[p_idx + 1] + pad })
|
||||
end
|
||||
end
|
||||
if self.t_difficulty then tex.draw_texture(self.t_difficulty, { frame = self.selected_difficulty, index = p_idx }) end
|
||||
|
||||
-- option icons
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,199 @@
|
||||
-- Difficulty sort window, split out of song_select.lua.
|
||||
|
||||
local base = require("song_select/file_navigator/box_base")
|
||||
|
||||
local draw_centered = base.draw_centered
|
||||
local draw_title = base.draw_title
|
||||
local draw_guide_label = base.draw_guide_label
|
||||
local outline = base.outline
|
||||
local GUIDE_SELECT_CX, GUIDE_DECIDE_CX = base.GUIDE_SELECT_CX, base.GUIDE_DECIDE_CX
|
||||
|
||||
local SW_ROW_Y = { 410, 551, 692 }
|
||||
local SW_BOARD_W, SW_BOARD_H = 1320, 760
|
||||
local SW_CURSOR_W, SW_CURSOR_H = 1240, 180
|
||||
local SW_PILL_CX = 1124 -- 960 + 164
|
||||
local SW_PILL_W, SW_PILL_H = 792, 104
|
||||
local SW_MARK_CX = 871 -- 960 - 89
|
||||
local SW_ARROW_LX = 786 -- 960 - 174
|
||||
local SW_ARROW_RX = 1462 -- 960 + 502
|
||||
local SW_HEADER_CX = 560 -- 960 - 400
|
||||
local SW_TEXT_DY = -13
|
||||
local SW_GUIDE_Y = 827
|
||||
local SW_GUIDE_W, SW_GUIDE_H = 372, 96
|
||||
local SW_NUM_RIGHT = 1379
|
||||
local SW_NUM_Y = 289
|
||||
local SW_NUM_PITCH = 34
|
||||
local SW_NUM_W, SW_NUM_H = 48, 56
|
||||
local SW_SONGS_X = 1402 -- ink LEFT edge (EditText align = left)
|
||||
local SW_SONGS_Y = 277
|
||||
local SW_TITLE_X = 405
|
||||
local SW_TITLE_Y = 272
|
||||
local SW_TITLE_OUTLINE = { 119, 26, 45, 255 }
|
||||
local SW_HEADER_BOX_W = 310
|
||||
local SW_ITEM_BOX_W = { 620, 430, 430 }
|
||||
local SW_CURSOR_CYCLE = 51.0
|
||||
|
||||
local SW_KEYS = {
|
||||
header = { "sort_window_diff", "sort_window_star", "sort_window_order" },
|
||||
course = { "course_name_0", "course_name_1", "course_name_2", "course_name_3", "course_name_3" },
|
||||
order = { "sort_window_order_0", "sort_window_order_1", "sort_window_order_2", "sort_window_order_3" },
|
||||
title = "folder_sort_select",
|
||||
songs = "sort_window_songs",
|
||||
}
|
||||
|
||||
local function sw_words(group)
|
||||
local k = SW_KEYS[group]
|
||||
if type(k) == "table" then
|
||||
local out = {}
|
||||
for i, key in ipairs(k) do out[i] = tex.skin_text(key) end
|
||||
return out
|
||||
end
|
||||
return tex.skin_text(k)
|
||||
end
|
||||
|
||||
-- OutlinedText cache: key = group .. "\0" .. index.
|
||||
function SongSelect:_sw_text(key, str, size, thickness, outline)
|
||||
self._sw_texts = self._sw_texts or {}
|
||||
local t = self._sw_texts[key]
|
||||
if t == nil then
|
||||
t = text.create_raw_text(str, size, { 255, 255, 255, 255 },
|
||||
outline or { 0, 0, 0, 255 }, false, thickness, 2.0)
|
||||
self._sw_texts[key] = t or false
|
||||
end
|
||||
return t or nil
|
||||
end
|
||||
|
||||
-- Fit-shrink: when a string does not fit its box, re-rasterise it at a smaller (uniformly
|
||||
-- scaled) point size instead of stretching the bitmap, so glyphs keep their proportions.
|
||||
local SW_FIT_MIN_SIZE = 22
|
||||
function SongSelect:_sw_text_fit(key, str, size, thickness, outline, max_w)
|
||||
self._sw_texts = self._sw_texts or {}
|
||||
local t = self._sw_texts[key]
|
||||
if t == nil then
|
||||
t = text.create_raw_text(str, size, { 255, 255, 255, 255 },
|
||||
outline or { 0, 0, 0, 255 }, false, thickness, 2.0)
|
||||
if t and max_w and t.width > max_w then
|
||||
local fit_size = math.max(SW_FIT_MIN_SIZE, math.floor(size * max_w / t.width))
|
||||
if fit_size < size then
|
||||
local fit_thickness = thickness * fit_size / size
|
||||
local t2 = text.create_raw_text(str, fit_size, { 255, 255, 255, 255 },
|
||||
outline or { 0, 0, 0, 255 }, false, fit_thickness, 2.0)
|
||||
if t2 then t = t2 end
|
||||
end
|
||||
end
|
||||
self._sw_texts[key] = t or false
|
||||
end
|
||||
return t or nil
|
||||
end
|
||||
|
||||
-- Draw an OutlinedText with its ink LEFT edge on x.
|
||||
local function draw_text_left(t, x, cy, fade)
|
||||
if not t then return end
|
||||
t:draw({ x = math.floor(x + 0.5), y = math.floor(cy - t.height / 2 + 0.5), fade = fade })
|
||||
end
|
||||
|
||||
-- cursor glow alpha over the 51-frame loop, sampled from anim/sort_cursor.
|
||||
local sort_cursor_ok, sort_cursor_anim = pcall(require, "anim/sampler")
|
||||
local SW_CURSOR_CLIP = (sort_cursor_ok and sort_cursor_anim.safe("anim/sort_cursor"))
|
||||
or { get = function() return nil end, label = function() return nil end }
|
||||
local SW_ON_F = SW_CURSOR_CLIP:label("on") or 9
|
||||
local function sw_cursor_glow(ms)
|
||||
local r = (ms * 0.06) % SW_CURSOR_CYCLE
|
||||
local a = SW_CURSOR_CLIP:get("#3@0", SW_ON_F + r, "a")
|
||||
if a then return a end
|
||||
if r <= 20.0 then return 1.0 - r / 20.0 end
|
||||
return (r - 20.0) / 30.0
|
||||
end
|
||||
|
||||
function SongSelect:_sw_ids()
|
||||
if self._sw_loaded then return end
|
||||
tex.load_folder("song_select", "diff_sort")
|
||||
self._id_sw_board = tex.get_texture("song_select/diff_sort/background") -- 1320x760 window
|
||||
self._id_sw_pill = tex.get_texture("song_select/diff_sort/level_box") -- 792x104 white item pill
|
||||
self._id_sw_cur = tex.get_texture("song_select/diff_sort/box_highlight") -- 1240x180 flat yellow bar
|
||||
self._id_sw_glow = tex.get_texture("song_select/diff_sort/box_outline") -- 1240x180 pulsing glow
|
||||
self._id_sw_mark = tex.get_texture("song_select/diff_sort/diff") -- 96x96, frame = course
|
||||
self._id_sw_arrow = tex.get_texture("song_select/diff_sort/arrow") -- 48x56, frame 1 = rotated
|
||||
self._id_sw_digit = tex.get_texture("song_select/diff_sort/stat_num") -- 48x56, frame = digit
|
||||
self._id_sw_guide = tex.get_texture("song_select/diff_sort/star_select_text") -- s_menfuchi_kp loop
|
||||
self._sw_loaded = true
|
||||
end
|
||||
|
||||
function SongSelect:draw_sort_window(w)
|
||||
self:_sw_ids()
|
||||
local a = w:alpha()
|
||||
if a <= 0.0 or not self._id_sw_board then return end
|
||||
local ms = tex.get_current_ms()
|
||||
local session = w:session()
|
||||
local vals = { w:diff(), w:star(), w:order() }
|
||||
|
||||
draw_centered(self._id_sw_board, 960, 540, SW_BOARD_W, SW_BOARD_H, { fade = a })
|
||||
|
||||
-- the three rows, in the cabinet's own depth order (order, star, diff)
|
||||
for row = 3, 1, -1 do
|
||||
local ry = SW_ROW_Y[row]
|
||||
local idx = row - 1 -- 0-based, matching SortWindow:session()
|
||||
if idx == session and self._id_sw_cur then
|
||||
if self._id_sw_glow then
|
||||
draw_centered(self._id_sw_glow, 960, ry, SW_CURSOR_W, SW_CURSOR_H,
|
||||
{ fade = a * sw_cursor_glow(ms) })
|
||||
end
|
||||
draw_centered(self._id_sw_cur, 960, ry, SW_CURSOR_W, SW_CURSOR_H, { fade = a })
|
||||
end
|
||||
if self._id_sw_pill then
|
||||
draw_centered(self._id_sw_pill, SW_PILL_CX, ry, SW_PILL_W, SW_PILL_H, { fade = a })
|
||||
end
|
||||
-- diff_mark is visible on the むずかしさ row only
|
||||
if row == 1 and self._id_sw_mark then
|
||||
draw_centered(self._id_sw_mark, SW_MARK_CX, ry, 96, 96,
|
||||
{ frame = math.max(0, math.min(4, vals[1] - 1)), fade = a })
|
||||
end
|
||||
if self._id_sw_arrow then
|
||||
local dx = w:arrow_offset(idx)
|
||||
draw_centered(self._id_sw_arrow, SW_ARROW_LX + dx, ry, 48, 56, { frame = 0, fade = a })
|
||||
draw_centered(self._id_sw_arrow, SW_ARROW_RX - dx, ry, 48, 56, { frame = 1, fade = a })
|
||||
end
|
||||
-- item value
|
||||
local str, key
|
||||
if row == 1 then
|
||||
local n = math.max(1, math.min(5, vals[1]))
|
||||
str, key = sw_words("course")[n], "course" .. n
|
||||
elseif row == 2 then
|
||||
str, key = tostring(vals[2]), "star" .. vals[2]
|
||||
else
|
||||
local n = math.max(1, math.min(4, vals[3]))
|
||||
str, key = sw_words("order")[n], "order" .. n
|
||||
end
|
||||
draw_title(self:_sw_text_fit(key, str, 42, outline(6.5), nil, SW_ITEM_BOX_W[row]),
|
||||
SW_PILL_CX, ry + SW_TEXT_DY, a, SW_ITEM_BOX_W[row], 0)
|
||||
-- header
|
||||
draw_title(self:_sw_text_fit("hdr" .. row, sw_words("header")[row], 42, outline(6.5), nil, SW_HEADER_BOX_W),
|
||||
SW_HEADER_CX, ry + SW_TEXT_DY, a, SW_HEADER_BOX_W, 0)
|
||||
end
|
||||
|
||||
-- control guide + its two pill labels
|
||||
if self._id_sw_guide then
|
||||
local cell = math.floor((ms * 0.06) % 210)
|
||||
draw_centered(self._id_sw_guide, 960, SW_GUIDE_Y, SW_GUIDE_W, SW_GUIDE_H,
|
||||
{ frame = cell, fade = a })
|
||||
local labels = self:guide_labels()
|
||||
if labels then
|
||||
draw_guide_label(labels.select, GUIDE_SELECT_CX, SW_GUIDE_Y + 2, a)
|
||||
draw_guide_label(labels.decide, GUIDE_DECIDE_CX, SW_GUIDE_Y + 2, a)
|
||||
end
|
||||
end
|
||||
|
||||
-- song counter: 「N 曲」 (right-aligned digits, then the unit label)
|
||||
local n = math.max(0, math.min(9999, w:song_num()))
|
||||
local digits = tostring(n)
|
||||
for i = 1, #digits do
|
||||
local d = tonumber(digits:sub(i, i))
|
||||
local x = SW_NUM_RIGHT - (#digits - i) * SW_NUM_PITCH
|
||||
draw_centered(self._id_sw_digit, x, SW_NUM_Y, SW_NUM_W, SW_NUM_H, { frame = d, fade = a })
|
||||
end
|
||||
draw_text_left(self:_sw_text("songs", sw_words("songs"), 42, outline(6.5)), SW_SONGS_X, SW_SONGS_Y, a)
|
||||
|
||||
-- board title, with the cabinet's dark-red outline add
|
||||
draw_text_left(self:_sw_text("title", sw_words("title"), 54, outline(7.5), SW_TITLE_OUTLINE),
|
||||
SW_TITLE_X, SW_TITLE_Y, a)
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
-- BackBox rendering ("back" brown board with a label), split out of song_select.lua.
|
||||
|
||||
local base = require("song_select/file_navigator/box_base")
|
||||
|
||||
local folder_leg = base.folder_leg
|
||||
local BOARD_W = base.BOARD_W
|
||||
local CEN_CLOSED_H, CEN_OPEN_H = base.CEN_CLOSED_H, base.CEN_OPEN_H
|
||||
local FOLDER_CLOSE_MS, STALE_TIMER_MS = base.FOLDER_CLOSE_MS, base.STALE_TIMER_MS
|
||||
local BOARD_OUTLINE_BACK = base.BOARD_OUTLINE_BACK
|
||||
local draw_board, draw_glow, glow_pulse = base.draw_board, base.draw_glow, base.glow_pulse
|
||||
local draw_title = base.draw_title
|
||||
local outline = base.outline
|
||||
local box_id = base.box_id
|
||||
local go_get, leg_f, box_fade, fly_offset = base.go_get, base.leg_f, base.box_fade, base.fly_offset
|
||||
local wheel_open_t, wheel_close_step = base.wheel_open_t, base.wheel_close_step
|
||||
local folder_open_timeline, folder_close_timeline = base.folder_open_timeline, base.folder_close_timeline
|
||||
|
||||
function SongSelect:back_label()
|
||||
if not self._back_label then
|
||||
local cfg = tex.get_skin_config("song_back_label")
|
||||
if cfg then
|
||||
self._back_label = text.create_text("song_back_label", { 255, 255, 255, 255 }, BOARD_OUTLINE_BACK, false, outline(5.0), 2.0)
|
||||
else
|
||||
self._back_label = text.create_raw_text("BACK", 44, { 255, 255, 255, 255 }, BOARD_OUTLINE_BACK, false, outline(5.0), 2.0)
|
||||
end
|
||||
end
|
||||
return self._back_label
|
||||
end
|
||||
|
||||
local function back_pb_open(box)
|
||||
local key = "b\0" .. box_id(box)
|
||||
local t = wheel_open_t(key)
|
||||
if t == nil then return 0.0 end
|
||||
return (folder_open_timeline(t))
|
||||
end
|
||||
|
||||
local function back_pb_closed(box)
|
||||
local key = "b\0" .. box_id(box)
|
||||
local e = wheel_close_step(key, FOLDER_CLOSE_MS, STALE_TIMER_MS)
|
||||
if e == nil then return 0.0 end
|
||||
return (folder_close_timeline(e))
|
||||
end
|
||||
|
||||
function SongSelect:draw_back_board(box, p)
|
||||
local bx, by = box:box_x(), box:box_y()
|
||||
local fdx, fdy = fly_offset(box); bx, by = bx + fdx, by + fdy -- wheel fly-in
|
||||
local cen_h = CEN_CLOSED_H + (CEN_OPEN_H - CEN_CLOSED_H) * p
|
||||
local fade = math.min(1.0, box_fade(box))
|
||||
|
||||
local lta = 1.0
|
||||
local lf = (box:draw_state() == "open") and leg_f() or nil
|
||||
if lf and folder_leg.kind == "open" and lf < folder_leg.open_swap - 1 then return end -- not yet
|
||||
if lf then
|
||||
local sx, ba = go_get("song_board_sx", lf, 1), go_get("song_board_sx", lf, 2)
|
||||
if sx and ba then
|
||||
if ba <= 0.0 then return end
|
||||
if sx < 1.0 then
|
||||
draw_board(self._id_bar_back, 0, bx, by, CEN_OPEN_H,
|
||||
fade * ba, false, sx)
|
||||
return
|
||||
end
|
||||
lta = go_get("song_title_a", lf, 1) or 1.0
|
||||
end
|
||||
end
|
||||
|
||||
if p > 0 then draw_glow(self._id_glow, bx, by, cen_h, fade * p * glow_pulse()) end
|
||||
draw_board(self._id_bar_back, 0, bx, by, cen_h, fade)
|
||||
draw_title(self:back_label(), bx, by, fade * lta)
|
||||
end
|
||||
|
||||
function SongSelect:draw_back_closed(box)
|
||||
self:draw_back_board(box, back_pb_closed(box))
|
||||
end
|
||||
|
||||
function SongSelect:draw_back_open(box)
|
||||
self:draw_back_board(box, back_pb_open(box))
|
||||
end
|
||||
@@ -0,0 +1,573 @@
|
||||
-- Shared song-select board chrome, text/colour helpers and the wheel/folder/song
|
||||
-- animation timers, split out of song_select.lua. All positions are 1080p pixels;
|
||||
-- box positions and engine animations are already in screen pixels.
|
||||
|
||||
local DIFF_BACK, DIFF_MODIFIER, DIFF_NEIRO = -3, -2, -1
|
||||
local DIFF_EASY, DIFF_NORMAL, DIFF_HARD, DIFF_ONI, DIFF_URA = 0, 1, 2, 3, 4
|
||||
local CROWN_NONE, CROWN_CLEAR, CROWN_FC, CROWN_DFC = 0, 1, 2, 3
|
||||
|
||||
-- Arcade board geometry (960x352 board texture: top/bottom caps + centre slice).
|
||||
local BOARD_W = 960
|
||||
local CAP_H = 56
|
||||
local CEN_SRC_H = 240
|
||||
local CEN_CLOSED_H = 52
|
||||
local CEN_OPEN_H = 240
|
||||
local GLOW_CAP_H = 80 -- cursor glow 1024x400: 80 / 240 / 80
|
||||
local GLOW_W = 1024
|
||||
|
||||
local GUIDE_SELECT_CX, GUIDE_DECIDE_CX, GUIDE_LABEL_CY = 904, 1077, 980
|
||||
local GUIDE_LABEL_SPACING = 2.0
|
||||
|
||||
-- tex.get_texture tries "<name>" then "<name>_<language>"; fall back to _en/_ja so we
|
||||
-- never hand draw_texture a nil id.
|
||||
local function get_any(subset, name)
|
||||
return tex.get_texture("song_select/" .. subset .. "/" .. name)
|
||||
or tex.get_texture("song_select/" .. subset .. "/" .. name .. "_en")
|
||||
or tex.get_texture("song_select/" .. subset .. "/" .. name .. "_ja")
|
||||
end
|
||||
|
||||
-- draw a w x h texture centred on (cx, cy)
|
||||
local function draw_centered(id, cx, cy, w, h, extra)
|
||||
extra = extra or {}
|
||||
extra.x = cx - w / 2
|
||||
extra.y = cy - h / 2
|
||||
tex.draw_texture(id, extra)
|
||||
end
|
||||
|
||||
-- Same, but subtracts the texture.json base offset the engine adds when it draws in 2P.
|
||||
local function draw_centered_b(id, base, cx, cy, w, h, extra)
|
||||
extra = extra or {}
|
||||
extra.x = cx - w / 2 - base[1]
|
||||
extra.y = cy - h / 2 - base[2]
|
||||
tex.draw_texture(id, extra)
|
||||
end
|
||||
local SEL_BASE_BALLOON = { 683, 308 } -- 1p/2p_balloon(_half) = cursor bubble
|
||||
|
||||
-- Title centred on (bx, cy). max_w (optional): squeeze long text horizontally down to the
|
||||
-- box width, never below min_scale (default 0.75). Destination x/y snap to whole pixels.
|
||||
local function draw_title(text, bx, cy, fade, max_w, min_scale)
|
||||
if not text then return end
|
||||
local w = text.width
|
||||
if max_w and w > max_w then
|
||||
w = math.floor(math.max(max_w, w * (min_scale or 0.75)) + 0.5)
|
||||
end
|
||||
text:draw({ x = math.floor(bx - w / 2 + 0.5), y = math.floor(cy - text.height / 2 + 0.5),
|
||||
x2 = w - text.width, fade = fade })
|
||||
end
|
||||
-- control-guide pill labels
|
||||
local function draw_guide_label(t, cx, cy, fade)
|
||||
if not t then return end
|
||||
t:draw({ x = math.floor(cx - (t.width - GUIDE_LABEL_SPACING) / 2 + 0.5),
|
||||
y = math.floor(cy - t.height / 2 + 0.5), fade = fade })
|
||||
end
|
||||
local SONG_TEXT_BOX_W = 860 -- main_song_board text_title / text_sub box width
|
||||
local DS_TEXT_BOX_W = 1000 -- main_diff text_title_song / text_song_title_sub box width
|
||||
|
||||
-- OutlinedText multiplies the outline thickness by screen_scale, so divide the stage-px
|
||||
-- border back out.
|
||||
local function outline(border) return border / tex.get_screen_scale() end
|
||||
|
||||
-- Board title/subtitle outline colour by genre frame (0..12).
|
||||
local BOARD_OUTLINE = {
|
||||
[0] = { 18, 72, 76, 255 },
|
||||
[1] = { 18, 72, 76, 255 },
|
||||
[2] = { 155, 24, 99, 255 },
|
||||
[3] = { 78, 29, 118, 255 },
|
||||
[4] = { 150, 31, 0, 255 },
|
||||
[5] = { 101, 67, 42, 255 },
|
||||
[6] = { 20, 79, 20, 255 },
|
||||
[7] = { 165, 50, 0, 255 },
|
||||
[8] = { 38, 52, 73, 255 },
|
||||
[9] = { 18, 72, 76, 255 },
|
||||
[10] = { 0, 25, 95, 255 },
|
||||
[11] = { 100, 6, 22, 255 },
|
||||
[12] = { 119, 26, 45, 255 },
|
||||
}
|
||||
local BOARD_OUTLINE_BACK = { 76, 50, 20, 255 }
|
||||
|
||||
local function board_outline(frame)
|
||||
return BOARD_OUTLINE[frame or 0] or BOARD_OUTLINE[0]
|
||||
end
|
||||
|
||||
local function clamp01(v) return math.max(0.0, math.min(1.0, v)) end
|
||||
|
||||
-- Board chrome: 3-slice (cap / centre / cap) from a 960x352 texture, centred on (bx,by).
|
||||
-- sx = optional horizontal scale (the folder-ENTER furl/unfurl); a furled board is drawn
|
||||
-- from flat column slices so its edges stay sharp.
|
||||
local function draw_board(bar_id, frame, bx, by, cen_h, fade, crop_center, sx)
|
||||
local w = BOARD_W
|
||||
if sx and sx ~= 1.0 then w = math.max(2, math.floor(BOARD_W * sx + 0.5)) end
|
||||
local x = math.floor(bx - w / 2 + 0.5)
|
||||
local top = math.floor(by - cen_h / 2 + 0.5) - CAP_H
|
||||
local src_y, src_h = CAP_H, CEN_SRC_H
|
||||
if crop_center ~= false and cen_h < CEN_SRC_H then
|
||||
src_h = math.max(1, math.floor(cen_h + 0.5))
|
||||
src_y = CAP_H + math.floor((CEN_SRC_H - src_h) / 2)
|
||||
cen_h = src_h
|
||||
end
|
||||
local bands = { { top, 0, CAP_H, CAP_H }, { top + CAP_H, src_y, src_h, cen_h },
|
||||
{ top + CAP_H + cen_h, CAP_H + CEN_SRC_H, CAP_H, CAP_H } }
|
||||
if w == BOARD_W then
|
||||
for _, b in ipairs(bands) do
|
||||
tex.draw_texture(bar_id, { frame = frame, x = x, y = b[1], y2 = b[4] - 352, fade = fade,
|
||||
src = { x = 0, y = b[2], width = BOARD_W, height = b[3] } })
|
||||
end
|
||||
return
|
||||
end
|
||||
local CORNER, BAND, PANEL = 40, 43, 794 -- source widths; 40+43+794+43+40 = 960
|
||||
local cols
|
||||
if w >= 2 * CORNER then
|
||||
local corner = CORNER
|
||||
local inner = w - 2 * corner
|
||||
local band = math.floor(inner * BAND / (2 * BAND + PANEL) + 0.5)
|
||||
local panel = inner - 2 * band
|
||||
cols = {
|
||||
{ 0, CORNER, x, corner },
|
||||
{ CORNER + BAND / 2 - 4, 8, x + corner, band },
|
||||
{ BOARD_W / 2 - 4, 8, x + corner + band, panel },
|
||||
{ BOARD_W - CORNER - BAND / 2 - 4, 8, x + corner + band + panel, band },
|
||||
{ BOARD_W - CORNER, CORNER, x + w - corner, corner },
|
||||
}
|
||||
else
|
||||
-- narrower than both corners: draw the outline columns + a flat centre slice
|
||||
local ol = math.max(2, math.min(8, math.floor(w / 6)))
|
||||
local mid = math.max(0, w - 2 * ol)
|
||||
cols = {
|
||||
{ 13, 8, x, ol },
|
||||
{ BOARD_W / 2 - 4, 8, x + ol, mid },
|
||||
{ 940, 8, x + ol + mid, ol },
|
||||
}
|
||||
end
|
||||
for _, b in ipairs(bands) do
|
||||
for _, c in ipairs(cols) do
|
||||
if c[4] > 0 then
|
||||
tex.draw_texture(bar_id, { frame = frame, x = c[3], y = b[1], x2 = c[4] - BOARD_W, y2 = b[4] - 352,
|
||||
fade = fade, src = { x = c[1], y = b[2], width = c[2], height = b[3] } })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Yellow cursor glow around the board.
|
||||
local function draw_glow(glow_id, bx, by, cen_h, fade)
|
||||
local x = math.floor(bx - GLOW_W / 2 + 0.5)
|
||||
local top = math.floor(by - cen_h / 2 + 0.5) - CAP_H - 24
|
||||
local src_h = math.max(1, math.min(240, math.floor(cen_h + 0.5)))
|
||||
cen_h = src_h
|
||||
tex.draw_texture(glow_id, { x = x, y = top, y2 = GLOW_CAP_H - 400, fade = fade,
|
||||
src = { x = 0, y = 0, width = GLOW_W, height = GLOW_CAP_H } })
|
||||
tex.draw_texture(glow_id, { x = x, y = top + GLOW_CAP_H, y2 = cen_h - 400, fade = fade,
|
||||
src = { x = 0, y = GLOW_CAP_H + math.floor((240 - src_h) / 2), width = GLOW_W, height = src_h } })
|
||||
tex.draw_texture(glow_id, { x = x, y = top + GLOW_CAP_H + cen_h, y2 = GLOW_CAP_H - 400, fade = fade,
|
||||
src = { x = 0, y = GLOW_CAP_H + 240, width = GLOW_W, height = GLOW_CAP_H } })
|
||||
end
|
||||
|
||||
local glow_ok, glow_anim = pcall(require, "anim/sampler")
|
||||
if not glow_ok then glow_anim = { safe = function()
|
||||
local n = {}
|
||||
n.at = function() return nil end; n.get = function() return nil end
|
||||
n.label = function() return nil end; n.has = function() return false end
|
||||
n.first = function() return 0 end; n.last = function() return 0 end
|
||||
return n
|
||||
end } end
|
||||
local GLOW_CLIP = glow_anim.safe("anim/cursor_glow")
|
||||
local GLOW_LEN = math.max(1, GLOW_CLIP:last() - GLOW_CLIP:first() + 1) -- 60 frames = 1 s
|
||||
-- song-board oni/ura chip cross-fade loop
|
||||
local URA_LOOP = glow_anim.safe("anim/song_board_ura")
|
||||
local URA_LOOP_F0 = URA_LOOP:first() -- label 'ura' = f10
|
||||
local URA_LOOP_LEN = math.max(1, URA_LOOP:last() - URA_LOOP_F0 + 1) -- 180 frames = 3 s
|
||||
local function glow_pulse()
|
||||
local f = (tex.get_current_ms() * 0.06) % GLOW_LEN
|
||||
return GLOW_CLIP:get("#12@0", f, "a") or 1.0
|
||||
end
|
||||
|
||||
-- Arcade genre board open / close, sampled from anim/folder_board.
|
||||
local folder_anim = glow_anim
|
||||
local FOLDER_CLIP = folder_anim.safe("anim/folder_board")
|
||||
local F_SELECT_ON = FOLDER_CLIP:label("select_on") or 5
|
||||
local F_SELECT_OFF = FOLDER_CLIP:label("select_off") or 30
|
||||
local FOLDER_CLOSE_END_F = 8 -- select_off + 8 = f38, the last frame that moves
|
||||
local FOLDER_CLOSE_MS = FOLDER_CLOSE_END_F / 0.06
|
||||
-- Duration the engine keeps the box "closed" between navigate() and expand_box().
|
||||
local NAV_REOPEN_MS = 166
|
||||
-- A transient navigation closure is < NAV_REOPEN_MS; anything longer resets the open timer.
|
||||
local STALE_TIMER_MS = math.max(FOLDER_CLOSE_MS, NAV_REOPEN_MS) + 50
|
||||
local CABINET_OPEN_WAIT_MS = 61 / 120 * 1000 -- = 508.33 ms
|
||||
local EXTRA_OPEN_HOLD_MS = math.max(0, CABINET_OPEN_WAIT_MS - NAV_REOPEN_MS) -- = 342.33 ms
|
||||
-- centre band scaleY at the two rest states -> 0..1 openness
|
||||
local SY_CLOSED, SY_OPEN = 0.2168, 1.0
|
||||
|
||||
-- Song board timeline, same labels as the genre board.
|
||||
local SONG_CLIP = folder_anim.safe("anim/song_board")
|
||||
local S_SELECT_ON = SONG_CLIP:label("select_on") or 5
|
||||
local S_SELECT_OFF = SONG_CLIP:label("select_off") or 30
|
||||
local SONG_CLOSE_END_F = 13
|
||||
local SONG_CLOSE_MS = SONG_CLOSE_END_F / 0.06 -- = 216.7 ms
|
||||
local SONG_STALE_MS = math.max(SONG_CLOSE_MS, NAV_REOPEN_MS) + 50
|
||||
|
||||
-- Folder enter/exit wheel choreography: slots sweep out along the tilted line, hold, then
|
||||
-- fly back in (sampled from anim/wheel_decide).
|
||||
local WHEEL_FLY = folder_anim.safe("anim/wheel_decide")
|
||||
local FLY_END_F = WHEEL_FLY:label("return") or 150
|
||||
local FLY_TRACKS_UP = { "kanban_genre_2", "kanban_genre_4", "kanban_genre_6", "kanban_genre_8" }
|
||||
local FLY_TRACKS_DOWN = { "kanban_genre_3", "kanban_genre_5", "kanban_genre_7" }
|
||||
local FLY_CENTER_Y, FLY_ROW_GAP, FLY_ROW_PITCH = 540, 120, 135
|
||||
do -- wheel geometry is skin_config-driven; follow it if restated
|
||||
local ok, pitch = pcall(tex.get_skin_config, "song_select_row_pitch")
|
||||
if ok and pitch and pitch.x and pitch.x > 0 then
|
||||
FLY_ROW_PITCH = pitch.x
|
||||
if pitch.y and pitch.y > 0 then FLY_ROW_GAP = pitch.y end
|
||||
end
|
||||
end
|
||||
-- A wheel animation-timer key must be unique per BOX. text_name is not (two charts of one
|
||||
-- song share a title), so prefer box:path() and fall back to title+subtitle+ex_data.
|
||||
local function box_id(box)
|
||||
local ok, p = pcall(function() return box:path() end)
|
||||
if ok and p and p ~= "" then return p end
|
||||
local id = box.text_name or ""
|
||||
local ok_s, sub = pcall(function() return box.text_subtitle end)
|
||||
if ok_s and sub then id = id .. "\1" .. sub end
|
||||
local ok_x, x = pcall(function() return box:ex_data_flag() end)
|
||||
if ok_x and x then id = id .. "\1" .. tostring(x) end
|
||||
return id
|
||||
end
|
||||
|
||||
-- Folder enter/exit leg state (anim/genre_open), driven by the engine's wheel events.
|
||||
local folder_leg = {
|
||||
clip = nil,
|
||||
t0 = nil, f0 = 0, kind = nil, swapped = false,
|
||||
open_f0 = 45, open_swap = 114, open_end = 145,
|
||||
close_f0 = 150, close_swap = 219, close_end = 250,
|
||||
open_furl = 100, -- first frame the decided genre board furls shut
|
||||
seq = nil,
|
||||
have_events = false,
|
||||
snap_once = false, snap_ms = nil,
|
||||
nohold_once = false, nohold_ms = nil,
|
||||
}
|
||||
do
|
||||
local ok, t = pcall(require, "anim/genre_open")
|
||||
if ok and type(t) == "table" and type(t.tracks) == "table" then
|
||||
folder_leg.clip = t
|
||||
for _, k in ipairs({ "open_f0", "open_swap", "open_end",
|
||||
"close_f0", "close_swap", "close_end" }) do
|
||||
if t[k] then folder_leg[k] = t[k] end
|
||||
end
|
||||
local rows = t.tracks and t.tracks.genre_board_sx
|
||||
if rows then
|
||||
for i = 1, #rows do
|
||||
if rows[i][2] < 0.999 then folder_leg.open_furl = rows[i][1]; break end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Linear sampler over the generated {frame, v1, v2...} rows.
|
||||
local function go_get(name, f, col)
|
||||
if not folder_leg.clip then return nil end
|
||||
local rows = folder_leg.clip.tracks[name]
|
||||
if not rows or #rows == 0 then return nil end
|
||||
col = col or 1
|
||||
if f <= rows[1][1] then return rows[1][col + 1] end
|
||||
if f >= rows[#rows][1] then return rows[#rows][col + 1] end
|
||||
for i = 1, #rows - 1 do
|
||||
local a, b = rows[i], rows[i + 1]
|
||||
if f >= a[1] and f <= b[1] then
|
||||
local span = b[1] - a[1]
|
||||
if span <= 0 then return a[col + 1] end
|
||||
local u = (f - a[1]) / span
|
||||
return a[col + 1] + (b[col + 1] - a[col + 1]) * u
|
||||
end
|
||||
end
|
||||
return rows[#rows][col + 1]
|
||||
end
|
||||
|
||||
-- Current clip frame, clamped just short of the swap until the engine signals it.
|
||||
local function leg_f()
|
||||
if not folder_leg.clip or not folder_leg.t0 then return nil end
|
||||
local f = folder_leg.f0 + (tex.get_current_ms() - folder_leg.t0) * 0.06
|
||||
if not folder_leg.swapped then
|
||||
-- hold until the swap: the open leg parks on the furl, the close leg one frame
|
||||
-- short of its swap, so the board stays up as the loading state
|
||||
local hold = (folder_leg.kind == "close") and (folder_leg.close_swap - 1) or (folder_leg.open_furl - 1)
|
||||
if f > hold then f = hold end
|
||||
return f
|
||||
end
|
||||
local fin = (folder_leg.kind == "close") and folder_leg.close_end or folder_leg.open_end
|
||||
if f >= fin then folder_leg.t0 = nil; folder_leg.kind = nil; return nil end
|
||||
return f
|
||||
end
|
||||
|
||||
-- The engine's own box fade, ignored during the pre-swap half of the close leg.
|
||||
local function box_fade(box)
|
||||
if folder_leg.kind == "close" and folder_leg.t0 and not folder_leg.swapped then return 1.0 end
|
||||
return box.fade.attribute
|
||||
end
|
||||
|
||||
-- Start / advance the leg. `begin` puts the clock on the label frame; `swap` re-anchors
|
||||
-- it on the content-swap frame.
|
||||
local function leg_event(id)
|
||||
local now = tex.get_current_ms()
|
||||
if id == 3 then -- OPEN_BEGIN
|
||||
folder_leg.t0, folder_leg.f0, folder_leg.kind, folder_leg.swapped = now, folder_leg.open_f0, "open", false
|
||||
folder_leg.dec_id, folder_leg.dec_box = folder_leg.last_open_id, folder_leg.last_open_box
|
||||
elseif id == 4 then -- OPEN_SWAP
|
||||
local f = (folder_leg.kind == "open" and folder_leg.t0) and (folder_leg.f0 + (now - folder_leg.t0) * 0.06) or nil
|
||||
if not f or f >= folder_leg.open_furl - 1 then
|
||||
folder_leg.t0, folder_leg.f0 = now, folder_leg.open_furl
|
||||
end
|
||||
folder_leg.kind, folder_leg.swapped = "open", true
|
||||
folder_leg.snap_once, folder_leg.snap_ms = true, now
|
||||
elseif id == 5 then -- CLOSE_BEGIN
|
||||
folder_leg.t0, folder_leg.f0, folder_leg.kind, folder_leg.swapped = now, folder_leg.close_f0, "close", false
|
||||
elseif id == 6 then -- CLOSE_SWAP
|
||||
folder_leg.t0, folder_leg.f0, folder_leg.kind, folder_leg.swapped = now, folder_leg.close_swap, "close", true
|
||||
folder_leg.snap_once, folder_leg.snap_ms = true, now
|
||||
elseif id == 1 or id == 7 then -- SCENE_ENTRY / COURSE_BACK
|
||||
folder_leg.nohold_once, folder_leg.nohold_ms = true, now
|
||||
end
|
||||
-- id == 2 (CURSOR_MOVE) needs no flag.
|
||||
end
|
||||
|
||||
-- The wheel clip shares the board clips' labels, so one clock drives all three.
|
||||
local function fly_offset(box)
|
||||
local f = leg_f()
|
||||
if not f then return 0.0, 0.0 end
|
||||
if folder_leg.kind == "open" then
|
||||
if f >= FLY_END_F then return 0.0, 0.0 end
|
||||
elseif folder_leg.kind == "close" then
|
||||
if f >= (WHEEL_FLY:label("genre_deceide2") or 255) - 1 then return 0.0, 0.0 end
|
||||
else
|
||||
return 0.0, 0.0
|
||||
end
|
||||
local st = box:draw_state()
|
||||
if st == "open" or st == "diff_select" then return 0.0, 0.0 end -- the decided board stays
|
||||
local d = box:box_y() - FLY_CENTER_Y
|
||||
local ring = math.floor((math.abs(d) - FLY_ROW_GAP) / FLY_ROW_PITCH + 0.5)
|
||||
if ring < 1 then ring = 1 end
|
||||
local track
|
||||
if d < 0 then track = FLY_TRACKS_UP[math.min(ring, #FLY_TRACKS_UP)]
|
||||
else track = FLY_TRACKS_DOWN[math.min(ring, #FLY_TRACKS_DOWN)] end
|
||||
local x, y = WHEEL_FLY:get(track, f, "tx"), WHEEL_FLY:get(track, f, "ty")
|
||||
if not x or not y then return 0.0, 0.0 end
|
||||
local rx = WHEEL_FLY:get(track, FLY_END_F, "tx") or 0.0
|
||||
local ry = WHEEL_FLY:get(track, FLY_END_F, "ty") or 0.0
|
||||
return x - rx, y - ry
|
||||
end
|
||||
|
||||
-- true while `box` is the genre board the player just decided on (open leg running)
|
||||
local function is_decided(box)
|
||||
return folder_leg.kind == "open" and folder_leg.t0 ~= nil and folder_leg.dec_id ~= nil and box_id(box) == folder_leg.dec_id
|
||||
end
|
||||
|
||||
-- How a fresh open starts: "hold" (cursor move), "snap" (folder enter/exit), "nohold"
|
||||
-- (scene entry / course back). One-shots are armed by the engine's real transitions.
|
||||
-- (diff-seen timestamp lives on folder_leg so box_song can mutate it across the module split.)
|
||||
local OPEN_SNAP_TTL_MS = 2000.0
|
||||
local OPEN_NOHOLD_TTL_MS = 3000.0
|
||||
|
||||
local function open_start_mode(now)
|
||||
if folder_leg.snap_once then
|
||||
folder_leg.snap_once = false
|
||||
if folder_leg.snap_ms and (now - folder_leg.snap_ms) < OPEN_SNAP_TTL_MS then return "snap" end
|
||||
end
|
||||
if folder_leg.nohold_once then
|
||||
folder_leg.nohold_once = false
|
||||
if folder_leg.nohold_ms and (now - folder_leg.nohold_ms) < OPEN_NOHOLD_TTL_MS then return "nohold" end
|
||||
end
|
||||
-- Legacy fallback for a binary without the event channel.
|
||||
if not folder_leg.have_events and folder_leg.diff_seen_ms and (now - folder_leg.diff_seen_ms) < 300.0 then
|
||||
return "nohold"
|
||||
end
|
||||
return "hold"
|
||||
end
|
||||
|
||||
-- Shared wheel-panel open/close wall-clock timers. Keys are "<kind>\0<name>".
|
||||
local wheel_open_start = {}
|
||||
local wheel_open_mode = {}
|
||||
local wheel_open_seen = {}
|
||||
local wheel_close_at = {}
|
||||
local wheel_close_start= {}
|
||||
local wheel_last_draw = {}
|
||||
local wheel_last_fresh_key, wheel_last_fresh_ms = nil, nil
|
||||
|
||||
-- Returns t in ms into the select_on growth window (0..233, clamped), or nil during the
|
||||
-- 508 ms pre-growth hold (draw fully closed).
|
||||
local function wheel_open_t(key)
|
||||
local now = tex.get_current_ms()
|
||||
wheel_close_start[key] = nil
|
||||
wheel_open_seen[key] = now
|
||||
wheel_close_at[key] = nil
|
||||
|
||||
-- off-screen for > NAV_REOPEN_MS with the animation finished -> reset the timer
|
||||
local last = wheel_last_draw[key]
|
||||
wheel_last_draw[key] = now
|
||||
if wheel_open_start[key] and last and (now - last) > NAV_REOPEN_MS then
|
||||
local hold = (wheel_open_mode[key] == "hold") and EXTRA_OPEN_HOLD_MS or 0.0
|
||||
if (now - wheel_open_start[key]) >= (hold + 233.0) then
|
||||
wheel_open_start[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if not wheel_open_start[key] then
|
||||
wheel_open_start[key] = now
|
||||
wheel_open_mode[key] = open_start_mode(now)
|
||||
wheel_last_fresh_key, wheel_last_fresh_ms = key, now
|
||||
end
|
||||
local mode = wheel_open_mode[key]
|
||||
if mode == "snap" then return 233.0 end
|
||||
local elapsed = now - wheel_open_start[key]
|
||||
local hold = (mode == "hold") and EXTRA_OPEN_HOLD_MS or 0.0
|
||||
if elapsed < hold then return nil end
|
||||
return math.min(elapsed - hold, 233.0)
|
||||
end
|
||||
|
||||
-- Closed-state bookkeeping + close animation. Returns e in ms into the select_off
|
||||
-- timeline while closing, else nil (draw at rest).
|
||||
local function wheel_close_step(key, close_ms, stale_ms)
|
||||
local now = tex.get_current_ms()
|
||||
if not wheel_close_start[key] then
|
||||
wheel_close_start[key] = now
|
||||
end
|
||||
if wheel_open_start[key] and (now - wheel_close_start[key]) > stale_ms then
|
||||
wheel_open_start[key] = nil
|
||||
end
|
||||
local seen = wheel_open_seen[key]
|
||||
if seen and now - seen < 100 then -- was drawn open a frame ago -> start closing
|
||||
wheel_close_at[key] = now
|
||||
wheel_open_seen[key] = nil
|
||||
end
|
||||
local c = wheel_close_at[key]
|
||||
if c then
|
||||
local e = now - c
|
||||
if e < close_ms then return e end
|
||||
wheel_close_at[key] = nil
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- `f` is an absolute arcade frame on sprite 73's timeline.
|
||||
-- pb = board progress 0..1, ch_alpha = character alpha, ch_off = character offset,
|
||||
-- ta = intro ("N songs") text alpha.
|
||||
local function folder_sample(f)
|
||||
local sy = FOLDER_CLIP:get("instance_board_center", f, "sy") or SY_CLOSED
|
||||
local pb = clamp01((sy - SY_CLOSED) / (SY_OPEN - SY_CLOSED))
|
||||
local ch_alpha = FOLDER_CLIP:get("instance_character_l", f, "a") or 0.0
|
||||
local ch_x = FOLDER_CLIP:get("instance_character_l", f, "tx") or -340.0
|
||||
local ta = FOLDER_CLIP:get("text_kanban_intro_genre", f, "a") or 0.0
|
||||
return pb, ch_alpha, -ch_x, ta
|
||||
end
|
||||
|
||||
local function folder_open_timeline(t)
|
||||
return folder_sample(F_SELECT_ON + t * 0.06)
|
||||
end
|
||||
|
||||
local function folder_close_timeline(e)
|
||||
return folder_sample(F_SELECT_OFF + e * 0.06)
|
||||
end
|
||||
|
||||
-- Same reader for the song board: pb = board progress, ia = contents alpha, ic = NEW-icon alpha.
|
||||
local function song_sample(f)
|
||||
local sy = SONG_CLIP:get("instance_board_center", f, "sy") or SY_CLOSED
|
||||
local pb = clamp01((sy - SY_CLOSED) / (SY_OPEN - SY_CLOSED))
|
||||
local ia = SONG_CLIP:get("song_kanban_info", f, "a") or (pb >= 1.0 and 1.0 or 0.0)
|
||||
local ic = SONG_CLIP:get("instance_icon", f, "a") or (1.0 - pb)
|
||||
return pb, clamp01(ia), clamp01(ic)
|
||||
end
|
||||
|
||||
local function song_open_timeline(t) return song_sample(S_SELECT_ON + t * 0.06) end
|
||||
local function song_close_timeline(e) return song_sample(S_SELECT_OFF + e * 0.06) end
|
||||
|
||||
-- 演奏オプション header title of the C++ option panel.
|
||||
local MOD_SLIDE, MOD_SLIDE_MS, MOD_JSON_Y, MOD_HEADER_CY, MOD_CX = 548, 333.33, 1080, 49, 215
|
||||
|
||||
-- Difficulty select geometry (shared by the diff-select board and the selector cursor).
|
||||
local DS_SLOT_X = { [DIFF_BACK] = 440, [DIFF_MODIFIER] = 572, [DIFF_NEIRO] = 440,
|
||||
[DIFF_EASY] = 745, [DIFF_NORMAL] = 960, [DIFF_HARD] = 1175, [DIFF_ONI] = 1390 }
|
||||
local DS_BOARD_Y = 583
|
||||
local DS_COURSE_NAME_DY = 49 -- course-name centre below the card origin
|
||||
local URA_ANIM_MS = 1500 -- oni_main change_ura/change_oni: 90 frames @ 60 fps
|
||||
local URA_SWAP_MS = 450 -- board_oni -> board_ura swap
|
||||
local URA_CELLS = 90
|
||||
local DS_BTN_Y = 462
|
||||
local DS_CURSOR_Y = 370
|
||||
local DS_NEIRO_Y = 602
|
||||
|
||||
-- main_diff_mark: 680x680, drawn under the buttons/cards.
|
||||
local DS_MARK_X = { [1] = 200, [2] = 1720 }
|
||||
local DS_MARK_Y = 408
|
||||
-- main_diff_mark 'course_decide_start', one row per frame:
|
||||
-- { mark alpha, mark y-scale, white alpha, white y-scale, white backing alpha }.
|
||||
local DS_MARK_DECIDE = {
|
||||
{ 0.5, 1, 0, 1, 0 },
|
||||
{ 0.5938, 1.0447, 0.1914, 1.0447, 0.1016 },
|
||||
{ 0.6797, 1.0847, 0.3594, 1.0847, 0.1992 },
|
||||
{ 0.7539, 1.12, 0.5117, 1.12, 0.3008 },
|
||||
{ 0.8203, 1.1506, 0.6406, 1.1506, 0.3984 },
|
||||
{ 0.875, 1.1765, 0.75, 1.1765, 0.5 },
|
||||
{ 0.9219, 1.1976, 0.8398, 1.1976, 0.6016 },
|
||||
{ 0.9531, 1.2141, 0.9102, 1.2141, 0.6992 },
|
||||
{ 0.9805, 1.2259, 0.9609, 1.2259, 0.8008 },
|
||||
{ 0.9961, 1.2329, 0.9883, 1.2329, 0.8984 },
|
||||
{ 1, 1.2353, 1, 1.2353, 1 },
|
||||
{ 1, 1.2229, 0.9961, 1.2346, 1 },
|
||||
{ 1, 1.2105, 0.9883, 1.2327, 1 },
|
||||
{ 1, 1.1981, 0.9766, 1.2294, 1 },
|
||||
{ 1, 1.1858, 0.957, 1.2248, 1 },
|
||||
{ 1, 1.1733, 0.9297, 1.219, 1 },
|
||||
{ 1, 1.161, 0.8984, 1.2118, 1 },
|
||||
{ 1, 1.1486, 0.8633, 1.2033, 1 },
|
||||
{ 1, 1.1362, 0.8242, 1.1936, 1 },
|
||||
{ 1, 1.1238, 0.7773, 1.1825, 1 },
|
||||
{ 1, 1.1114, 0.7227, 1.1701, 1 },
|
||||
{ 1, 1.0991, 0.6641, 1.1564, 1 },
|
||||
{ 1, 1.0867, 0.6016, 1.1414, 1 },
|
||||
{ 1, 1.0743, 0.5312, 1.1251, 1 },
|
||||
{ 1, 1.0619, 0.457, 1.1075, 1 },
|
||||
{ 1, 1.0495, 0.375, 1.0886, 1 },
|
||||
{ 1, 1.0371, 0.2891, 1.0684, 1 },
|
||||
{ 1, 1.0247, 0.1992, 1.0469, 1 },
|
||||
{ 1, 1.0124, 0.1016, 1.0241, 1 },
|
||||
{ 1, 1, 0, 1, 1 },
|
||||
}
|
||||
local DS_MARK_DECIDE_MS = 1000 / 60
|
||||
|
||||
return {
|
||||
DIFF_BACK = DIFF_BACK, DIFF_MODIFIER = DIFF_MODIFIER, DIFF_NEIRO = DIFF_NEIRO,
|
||||
DIFF_EASY = DIFF_EASY, DIFF_NORMAL = DIFF_NORMAL, DIFF_HARD = DIFF_HARD,
|
||||
DIFF_ONI = DIFF_ONI, DIFF_URA = DIFF_URA,
|
||||
CROWN_NONE = CROWN_NONE, CROWN_CLEAR = CROWN_CLEAR, CROWN_FC = CROWN_FC, CROWN_DFC = CROWN_DFC,
|
||||
|
||||
BOARD_W = BOARD_W, CAP_H = CAP_H, CEN_SRC_H = CEN_SRC_H,
|
||||
CEN_CLOSED_H = CEN_CLOSED_H, CEN_OPEN_H = CEN_OPEN_H,
|
||||
GLOW_CAP_H = GLOW_CAP_H, GLOW_W = GLOW_W,
|
||||
|
||||
SEL_BASE_BALLOON = SEL_BASE_BALLOON,
|
||||
GUIDE_SELECT_CX = GUIDE_SELECT_CX, GUIDE_DECIDE_CX = GUIDE_DECIDE_CX,
|
||||
GUIDE_LABEL_CY = GUIDE_LABEL_CY, GUIDE_LABEL_SPACING = GUIDE_LABEL_SPACING,
|
||||
SONG_TEXT_BOX_W = SONG_TEXT_BOX_W, DS_TEXT_BOX_W = DS_TEXT_BOX_W,
|
||||
BOARD_OUTLINE = BOARD_OUTLINE, BOARD_OUTLINE_BACK = BOARD_OUTLINE_BACK,
|
||||
|
||||
FOLDER_CLOSE_MS = FOLDER_CLOSE_MS, STALE_TIMER_MS = STALE_TIMER_MS,
|
||||
SONG_CLOSE_MS = SONG_CLOSE_MS, SONG_STALE_MS = SONG_STALE_MS,
|
||||
URA_LOOP = URA_LOOP, URA_LOOP_F0 = URA_LOOP_F0, URA_LOOP_LEN = URA_LOOP_LEN,
|
||||
|
||||
MOD_SLIDE = MOD_SLIDE, MOD_SLIDE_MS = MOD_SLIDE_MS, MOD_JSON_Y = MOD_JSON_Y,
|
||||
MOD_HEADER_CY = MOD_HEADER_CY, MOD_CX = MOD_CX,
|
||||
DS_SLOT_X = DS_SLOT_X, DS_BOARD_Y = DS_BOARD_Y, DS_COURSE_NAME_DY = DS_COURSE_NAME_DY,
|
||||
URA_ANIM_MS = URA_ANIM_MS, URA_SWAP_MS = URA_SWAP_MS, URA_CELLS = URA_CELLS,
|
||||
DS_BTN_Y = DS_BTN_Y, DS_CURSOR_Y = DS_CURSOR_Y, DS_NEIRO_Y = DS_NEIRO_Y,
|
||||
DS_MARK_X = DS_MARK_X, DS_MARK_Y = DS_MARK_Y, DS_MARK_DECIDE = DS_MARK_DECIDE,
|
||||
DS_MARK_DECIDE_MS = DS_MARK_DECIDE_MS,
|
||||
|
||||
get_any = get_any, draw_centered = draw_centered, draw_centered_b = draw_centered_b,
|
||||
draw_title = draw_title, draw_guide_label = draw_guide_label,
|
||||
outline = outline, board_outline = board_outline, clamp01 = clamp01,
|
||||
draw_board = draw_board, draw_glow = draw_glow, glow_pulse = glow_pulse,
|
||||
|
||||
box_id = box_id, go_get = go_get, leg_f = leg_f, box_fade = box_fade,
|
||||
leg_event = leg_event, fly_offset = fly_offset, is_decided = is_decided,
|
||||
open_start_mode = open_start_mode, wheel_open_t = wheel_open_t, wheel_close_step = wheel_close_step,
|
||||
folder_open_timeline = folder_open_timeline, folder_close_timeline = folder_close_timeline,
|
||||
song_open_timeline = song_open_timeline, song_close_timeline = song_close_timeline,
|
||||
|
||||
folder_leg = folder_leg,
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
-- DanBox / other box rendering: a plain board in the arcade "dani" colour. This is the
|
||||
-- `else` branch of the wheel dispatch, split out of song_select.lua.
|
||||
|
||||
local base = require("song_select/file_navigator/box_base")
|
||||
|
||||
local CEN_CLOSED_H, CEN_OPEN_H = base.CEN_CLOSED_H, base.CEN_OPEN_H
|
||||
local FOLDER_CLOSE_MS, STALE_TIMER_MS = base.FOLDER_CLOSE_MS, base.STALE_TIMER_MS
|
||||
local draw_board, draw_glow, glow_pulse = base.draw_board, base.draw_glow, base.glow_pulse
|
||||
local draw_title = base.draw_title
|
||||
local box_id = base.box_id
|
||||
local box_fade, fly_offset = base.box_fade, base.fly_offset
|
||||
local wheel_open_t, wheel_close_step = base.wheel_open_t, base.wheel_close_step
|
||||
local folder_open_timeline, folder_close_timeline = base.folder_open_timeline, base.folder_close_timeline
|
||||
|
||||
function SongSelect:draw_other_board(box, state)
|
||||
-- dan / other boxes: plain board in the arcade "dani" colour
|
||||
local bx, by = box:box_x(), box:box_y()
|
||||
local fdx, fdy = fly_offset(box); bx, by = bx + fdx, by + fdy -- wheel fly-in
|
||||
local key = "o\0" .. box_id(box)
|
||||
local p
|
||||
if state == "open" then
|
||||
local t = wheel_open_t(key)
|
||||
p = t and (folder_open_timeline(t)) or 0.0
|
||||
else
|
||||
local e = wheel_close_step(key, FOLDER_CLOSE_MS, STALE_TIMER_MS)
|
||||
p = e and (folder_close_timeline(e)) or 0.0
|
||||
end
|
||||
local cen_h = CEN_CLOSED_H + (CEN_OPEN_H - CEN_CLOSED_H) * p
|
||||
local fade = math.min(1.0, box_fade(box))
|
||||
if p > 0 then draw_glow(self._id_glow, bx, by, cen_h, fade * p * glow_pulse()) end
|
||||
draw_board((p > 0) and self._id_bar_folder or self._id_bar_genre, 10, bx, by, cen_h, fade, p <= 0 or p >= 1)
|
||||
if p > 0 and p < 1 then draw_board(self._id_bar_genre, 10, bx, by, cen_h, fade * (1.0 - p)) end
|
||||
draw_title(self:folder_title(box), bx, by - 94 * p, fade)
|
||||
end
|
||||
@@ -0,0 +1,172 @@
|
||||
-- FolderBox rendering (genre kanban), split out of song_select.lua. Shared helpers and
|
||||
-- the wheel/folder animation state come from box_base.
|
||||
|
||||
local base = require("song_select/file_navigator/box_base")
|
||||
|
||||
local folder_leg = base.folder_leg
|
||||
local BOARD_W = base.BOARD_W
|
||||
local CAP_H = base.CAP_H
|
||||
local CEN_CLOSED_H, CEN_OPEN_H = base.CEN_CLOSED_H, base.CEN_OPEN_H
|
||||
local FOLDER_CLOSE_MS, STALE_TIMER_MS = base.FOLDER_CLOSE_MS, base.STALE_TIMER_MS
|
||||
local draw_board, draw_glow, glow_pulse = base.draw_board, base.draw_glow, base.glow_pulse
|
||||
local draw_title = base.draw_title
|
||||
local outline, board_outline = base.outline, base.board_outline
|
||||
local box_id = base.box_id
|
||||
local go_get, leg_f, box_fade, fly_offset = base.go_get, base.leg_f, base.box_fade, base.fly_offset
|
||||
local is_decided = base.is_decided
|
||||
local wheel_open_t, wheel_close_step = base.wheel_open_t, base.wheel_close_step
|
||||
local folder_open_timeline, folder_close_timeline = base.folder_open_timeline, base.folder_close_timeline
|
||||
|
||||
-- Event/search folders are matched by display name (the engine folds DEFAULT/RECOMMENDED/
|
||||
-- FAVORITE/DAN/DIFFICULTY into genre frame 0).
|
||||
local EVENT_FOLDER_NAMES = { ["ドドンと!新曲祭り!"] = true, ["New Songs"] = true, ["New"] = true,
|
||||
["おすすめ曲"] = true, ["Recommended"] = true, ["推薦歌曲"] = true }
|
||||
local SORT_FOLDER_NAMES = { ["曲検索"] = true, ["Search Song"] = true, ["搜尋歌曲"] = true,
|
||||
["むずかしさで探す"] = true, ["Difficulty Sort"] = true, ["難易度排序"] = true,
|
||||
["むずかしさからえらぶ"] = true, ["Diff Sort"] = true }
|
||||
local FOLDER_FRAME_EVENT, FOLDER_FRAME_SORT, FOLDER_FRAME_DANI = 11, 12, 10
|
||||
|
||||
local SORT_COLLECTIONS = { SEARCH = true, DIFFICULTY = true }
|
||||
local EVENT_COLLECTIONS = { NEW = true, RECOMMENDED = true }
|
||||
|
||||
local function folder_frame(box)
|
||||
local n = box.text_name
|
||||
if n and EVENT_FOLDER_NAMES[n] then return FOLDER_FRAME_EVENT, true end
|
||||
local col = box.collection
|
||||
if col and col ~= "" then
|
||||
if EVENT_COLLECTIONS[col] then return FOLDER_FRAME_EVENT, true end
|
||||
if SORT_COLLECTIONS[col] then return FOLDER_FRAME_SORT, false end
|
||||
end
|
||||
if box.kind and box:kind() == "dan" then return FOLDER_FRAME_DANI, false end
|
||||
if n and SORT_FOLDER_NAMES[n] then return FOLDER_FRAME_SORT, false end
|
||||
return box:genre_frame(), false
|
||||
end
|
||||
|
||||
-- instance_board_pattern: 960x120 strip centred on the board, y follows the title
|
||||
function SongSelect:draw_pattern(bx, by, p, fade)
|
||||
if not self._id_pattern then return end
|
||||
tex.draw_texture(self._id_pattern, { x = bx - 480, y = by - 94 * p - 60, fade = fade })
|
||||
end
|
||||
|
||||
-- The genre board title, re-made at the skin_config folder_box_name size.
|
||||
local FOLDER_TITLE_SIZE = nil
|
||||
function SongSelect:folder_title(box)
|
||||
if FOLDER_TITLE_SIZE == nil then
|
||||
local cfg = tex.get_skin_config("folder_box_name")
|
||||
FOLDER_TITLE_SIZE = (cfg and cfg.font_size and cfg.font_size > 0) and cfg.font_size or false
|
||||
end
|
||||
if not FOLDER_TITLE_SIZE then return box:name() end
|
||||
local key = box.text_name
|
||||
if not key or key == "" then return box:name() end
|
||||
local ff = folder_frame(box)
|
||||
key = ff .. "\0" .. key
|
||||
local t = self._folder_titles[key]
|
||||
if t == nil then
|
||||
t = text.create_raw_text(box.text_name, FOLDER_TITLE_SIZE, { 255, 255, 255, 255 },
|
||||
board_outline(ff), false, outline(7.5), 2.0)
|
||||
self._folder_titles[key] = t or false
|
||||
end
|
||||
return t or box:name()
|
||||
end
|
||||
|
||||
function SongSelect:draw_folder_board(box, pb, ch_alpha, ch_off, ta, glow_a)
|
||||
local bx, by = box:box_x(), box:box_y()
|
||||
local fdx, fdy = fly_offset(box); bx, by = bx + fdx, by + fdy -- wheel fly-in
|
||||
local cen_h = CEN_CLOSED_H + (CEN_OPEN_H - CEN_CLOSED_H) * pb
|
||||
local fade = box_fade(box)
|
||||
local frame, has_pattern = folder_frame(box)
|
||||
|
||||
-- the genre board furls/unfurls horizontally across the folder transition
|
||||
local ltitle = 1.0
|
||||
local lf = (box:draw_state() == "open" or is_decided(box)) and leg_f() or nil
|
||||
if lf then
|
||||
local sx, ba = go_get("genre_board_sx", lf, 1), go_get("genre_board_sx", lf, 2)
|
||||
if sx and ba then
|
||||
if ba <= 0.0 then return end -- the board is not on screen
|
||||
if sx < 1.0 then -- furl / unfurl: the open slab alone
|
||||
draw_board(self._id_bar_folder, frame, bx, by, CEN_OPEN_H,
|
||||
fade * ba, false, sx)
|
||||
return
|
||||
end
|
||||
ch_alpha = math.min(ch_alpha, go_get("genre_chara_a", lf, 1) or 1.0)
|
||||
ta = math.min(ta, go_get("genre_intro_a", lf, 1) or 1.0)
|
||||
ltitle = go_get("genre_title_a", lf, 1) or 1.0
|
||||
end
|
||||
end
|
||||
|
||||
if glow_a > 0 then draw_glow(self._id_glow, bx, by, cen_h, fade * glow_a * glow_pulse()) end
|
||||
-- closed folder = bar_genre, open folder = folder_graphic (title band on top)
|
||||
if pb <= 0.0 then
|
||||
draw_board(self._id_bar_genre, frame, bx, by, cen_h, fade)
|
||||
else
|
||||
draw_board(self._id_bar_folder, frame, bx, by, cen_h, fade, pb >= 1.0)
|
||||
if pb < 1.0 then draw_board(self._id_bar_genre, frame, bx, by, cen_h, fade * (1.0 - pb)) end
|
||||
end
|
||||
if has_pattern then self:draw_pattern(bx, by, pb, fade) end
|
||||
|
||||
if box:has_box_texture() then
|
||||
-- a library-supplied box.png is fitted (aspect kept, shrink only) into the board rect
|
||||
if ch_alpha > 0 then
|
||||
box:draw_box_texture({ cx = bx, cy = by,
|
||||
max_w = BOARD_W, max_h = CAP_H * 2 + CEN_OPEN_H,
|
||||
fade = fade * ch_alpha })
|
||||
end
|
||||
-- title drawn after the art, matching the cabinet's depth order
|
||||
draw_title(self:folder_title(box), bx, by - 94 * pb, fade * ltitle)
|
||||
return
|
||||
end
|
||||
|
||||
-- characters: texture has L at x=510 and R at x=1410
|
||||
if ch_alpha > 0 then
|
||||
local slide = 450.0 - ch_off
|
||||
local gframe = box:genre_frame()
|
||||
tex.draw_texture(self._id_box_chara, { frame = gframe, x = bx - 960 + slide, y = by - 240, x2 = 960 - 1920, fade = fade * ch_alpha,
|
||||
src = { x = 0, y = 0, width = 960, height = 480 } })
|
||||
tex.draw_texture(self._id_box_chara, { frame = gframe, x = bx - slide, y = by - 240, x2 = 960 - 1920, fade = fade * ch_alpha,
|
||||
src = { x = 960, y = 0, width = 960, height = 480 } })
|
||||
end
|
||||
-- title rises to y-94 when open; the song count stands in for the intro text
|
||||
draw_title(self:folder_title(box), bx, by - 94 * pb, fade * ltitle)
|
||||
local n = box.tja_count
|
||||
if ta > 0 and n and n > 0 then
|
||||
local ckey = frame .. "\0" .. n
|
||||
local label = self._count_cache[ckey]
|
||||
if not label then
|
||||
label = text.create_raw_text(tostring(n) .. " songs", 32, { 255, 255, 255, 255 }, board_outline(frame), false, outline(4.0), 2.0)
|
||||
self._count_cache[ckey] = label
|
||||
end
|
||||
if label then draw_title(label, bx, by + 49 * pb, fade * ta) end
|
||||
end
|
||||
end
|
||||
|
||||
function SongSelect:draw_folder_closed(box)
|
||||
local key = "f\0" .. box_id(box)
|
||||
local e = wheel_close_step(key, FOLDER_CLOSE_MS, STALE_TIMER_MS)
|
||||
if e then
|
||||
local pb, ch_alpha, ch_off, ta = folder_close_timeline(e)
|
||||
self:draw_folder_board(box, pb, ch_alpha, ch_off, ta, pb)
|
||||
return
|
||||
end
|
||||
self:draw_folder_board(box, 0.0, 0.0, 340.0, 0.0, 0.0)
|
||||
end
|
||||
|
||||
function SongSelect:draw_folder_open(box)
|
||||
-- remember the decided board: the engine removes it from the wheel while its songs
|
||||
-- load, and draw_overlays keeps drawing it until it has furled away
|
||||
folder_leg.last_open_id, folder_leg.last_open_box = box_id(box), box
|
||||
if is_decided(box) then
|
||||
self._dec_frame = self._frame_no
|
||||
self:draw_folder_board(box, 1.0, 1.0, 440.0, 1.0, 1.0)
|
||||
return
|
||||
end
|
||||
local key = "f\0" .. box_id(box)
|
||||
local t = wheel_open_t(key)
|
||||
if t == nil then -- 508 ms pre-growth hold: fully closed
|
||||
self:draw_folder_board(box, 0.0, 0.0, 340.0, 0.0, 0.0)
|
||||
return
|
||||
end
|
||||
local pb, ch_alpha, ch_off, ta = folder_open_timeline(t)
|
||||
self:draw_folder_board(box, pb, ch_alpha, ch_off, ta, pb)
|
||||
end
|
||||
|
||||
return { folder_frame = folder_frame }
|
||||
@@ -0,0 +1,426 @@
|
||||
-- SongBox rendering (song board, difficulty plates, badges, diff select), split out of
|
||||
-- song_select.lua. All shared helpers/state come from box_base.
|
||||
|
||||
local base = require("song_select/file_navigator/box_base")
|
||||
|
||||
local folder_leg = base.folder_leg
|
||||
local DIFF_BACK, DIFF_MODIFIER = base.DIFF_BACK, base.DIFF_MODIFIER
|
||||
local DIFF_EASY, DIFF_NORMAL, DIFF_HARD, DIFF_ONI, DIFF_URA =
|
||||
base.DIFF_EASY, base.DIFF_NORMAL, base.DIFF_HARD, base.DIFF_ONI, base.DIFF_URA
|
||||
local CROWN_NONE, CROWN_FC, CROWN_DFC = base.CROWN_NONE, base.CROWN_FC, base.CROWN_DFC
|
||||
local BOARD_W = base.BOARD_W
|
||||
local CEN_CLOSED_H, CEN_OPEN_H = base.CEN_CLOSED_H, base.CEN_OPEN_H
|
||||
local SONG_TEXT_BOX_W, DS_TEXT_BOX_W = base.SONG_TEXT_BOX_W, base.DS_TEXT_BOX_W
|
||||
local SONG_CLOSE_MS, SONG_STALE_MS = base.SONG_CLOSE_MS, base.SONG_STALE_MS
|
||||
local URA_LOOP, URA_LOOP_F0, URA_LOOP_LEN = base.URA_LOOP, base.URA_LOOP_F0, base.URA_LOOP_LEN
|
||||
local DS_SLOT_X, DS_BOARD_Y, DS_COURSE_NAME_DY = base.DS_SLOT_X, base.DS_BOARD_Y, base.DS_COURSE_NAME_DY
|
||||
local URA_ANIM_MS, URA_SWAP_MS, URA_CELLS = base.URA_ANIM_MS, base.URA_SWAP_MS, base.URA_CELLS
|
||||
local DS_BTN_Y, DS_CURSOR_Y, DS_NEIRO_Y = base.DS_BTN_Y, base.DS_CURSOR_Y, base.DS_NEIRO_Y
|
||||
local DS_MARK_X, DS_MARK_Y = base.DS_MARK_X, base.DS_MARK_Y
|
||||
local DS_MARK_DECIDE, DS_MARK_DECIDE_MS = base.DS_MARK_DECIDE, base.DS_MARK_DECIDE_MS
|
||||
local get_any = base.get_any
|
||||
local draw_centered, draw_centered_b = base.draw_centered, base.draw_centered_b
|
||||
local draw_title = base.draw_title
|
||||
local outline, board_outline = base.outline, base.board_outline
|
||||
local draw_board, draw_glow, glow_pulse = base.draw_board, base.draw_glow, base.glow_pulse
|
||||
local box_id = base.box_id
|
||||
local go_get, leg_f, box_fade, fly_offset = base.go_get, base.leg_f, base.box_fade, base.fly_offset
|
||||
local wheel_open_t, wheel_close_step = base.wheel_open_t, base.wheel_close_step
|
||||
local song_open_timeline, song_close_timeline = base.song_open_timeline, base.song_close_timeline
|
||||
|
||||
local SONG_TITLE_OUTLINE = 5.0
|
||||
local SONG_TITLE_SIZE = nil -- resolved once from skin_config song_box_name
|
||||
local SONG_TITLE_CACHE_MAX = 128 -- bound the per-title texture cache
|
||||
|
||||
-- crown / rank badges (72x72) at arcade positions, one column per player.
|
||||
local ICON_ARTS = nil -- name -> texture id (resolved lazily)
|
||||
function SongSelect:song_icon(box)
|
||||
if ICON_ARTS == nil then ICON_ARTS = { icon_new = self._id_new_icon } end
|
||||
local list = {}
|
||||
if box.is_new then list[#list + 1] = "icon_new" end
|
||||
if #list == 0 then return nil end
|
||||
local pick = list[(math.floor(tex.get_current_ms() / 1000.0) % #list) + 1]
|
||||
return ICON_ARTS[pick]
|
||||
end
|
||||
|
||||
function SongSelect:badge_diffs(box, pl)
|
||||
local sel = self._mark_diff and self._mark_diff[pl]
|
||||
local crown_d, rank_d
|
||||
if sel and sel >= DIFF_EASY and sel <= DIFF_URA then
|
||||
local info = box:course_info(sel)
|
||||
if info.has_course then
|
||||
if info.crown ~= CROWN_NONE then crown_d = sel end
|
||||
if (info.rank or 0) > 0 then rank_d = sel end
|
||||
end
|
||||
end
|
||||
if not crown_d or not rank_d then
|
||||
local cbest, rbest
|
||||
for d = DIFF_EASY, DIFF_URA do
|
||||
local info = box:course_info(d)
|
||||
if info.has_course then
|
||||
if info.crown ~= CROWN_NONE then cbest = d end
|
||||
if (info.rank or 0) > 0 then rbest = d end
|
||||
end
|
||||
end
|
||||
crown_d = crown_d or cbest
|
||||
rank_d = rank_d or rbest
|
||||
end
|
||||
return crown_d, rank_d
|
||||
end
|
||||
|
||||
function SongSelect:draw_badges(box, bx, by, p, fade)
|
||||
local scale = 0.75 + 0.25 * p
|
||||
local size = 72 * scale
|
||||
local players = math.min(2, tex.player_num())
|
||||
for pl = 1, players do
|
||||
local cx = bx + ((pl == 1) and -425 or 425)
|
||||
local crown_d, rank_d = self:badge_diffs(box, pl)
|
||||
if crown_d then
|
||||
local info = box:course_info(crown_d)
|
||||
local id = (info.crown == CROWN_DFC) and self._id_crown_dfc
|
||||
or (info.crown == CROWN_FC) and self._id_crown_fc or self._id_crown_clear
|
||||
local cy = by + (-27 + (-114 + 27) * p)
|
||||
draw_centered(id, cx, cy, size, size, { frame = crown_d, x2 = size - 72, y2 = size - 72, fade = fade })
|
||||
end
|
||||
if rank_d then
|
||||
local info = box:course_info(rank_d)
|
||||
local cy = by + (27 + (-42 - 27) * p)
|
||||
draw_centered(self._id_score_rank, cx, cy, size, size,
|
||||
{ frame = (info.rank - 1) * 5 + rank_d, x2 = size - 72, y2 = size - 72, fade = fade })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- SongBox
|
||||
-- pb = board progress (0..1), ia = contents alpha, ic = NEW-icon alpha.
|
||||
function SongSelect:draw_song_board(box, pb, ia, ic)
|
||||
local bx, by = box:box_x(), box:box_y()
|
||||
local fdx, fdy = fly_offset(box); bx, by = bx + fdx, by + fdy -- wheel fly-in
|
||||
local fade = math.min(1.0, box_fade(box))
|
||||
|
||||
-- folder-ENTER/EXIT unfurl/furl: while the slab's sx < 1 only the slab is drawn,
|
||||
-- and the contents alphas come from the clip's own channels.
|
||||
local gof = leg_f()
|
||||
local ltitle = 1.0
|
||||
if gof and folder_leg.kind == "open" and gof < folder_leg.open_swap - 1 and box:draw_state() == "open" then
|
||||
return -- the new content's board does not exist until the swap frame
|
||||
end
|
||||
if gof and box:draw_state() == "open" then
|
||||
local sx, ba = go_get("song_board_sx", gof, 1), go_get("song_board_sx", gof, 2)
|
||||
if sx and ba then
|
||||
if ba <= 0.0 then return end -- board not on screen on this frame
|
||||
if sx < 1.0 then -- unfurl / furl: the slab alone
|
||||
draw_board(self._id_bar_genre, box:genre_frame(), bx, by, CEN_OPEN_H,
|
||||
fade * ba, nil, sx)
|
||||
return
|
||||
end
|
||||
local ia2 = go_get("song_info_a", gof, 1)
|
||||
if ia2 then ia = math.min(ia or 1.0, ia2) end
|
||||
ltitle = go_get("song_title_a", gof, 1) or 1.0
|
||||
local ic2 = go_get("song_icon_a", gof, 1)
|
||||
if ic2 then ic = math.min(ic or 0.0, ic2) end
|
||||
end
|
||||
end
|
||||
|
||||
local cen_h = CEN_CLOSED_H + (CEN_OPEN_H - CEN_CLOSED_H) * pb
|
||||
if pb > 0 then draw_glow(self._id_glow, bx, by, cen_h, fade * pb * glow_pulse()) end
|
||||
draw_board(self._id_bar_genre, box:genre_frame(), bx, by, cen_h, fade)
|
||||
|
||||
if pb <= 0.0 then
|
||||
draw_title(self:song_title(box), bx, by, fade, SONG_TEXT_BOX_W, 0)
|
||||
self:draw_badges(box, bx, by, 0.0, fade)
|
||||
-- instance_icon hangs off the board's left edge (-532)
|
||||
local icon = (ic > 0) and self:song_icon(box) or nil
|
||||
if icon then
|
||||
draw_centered(icon, bx - 532, by + 4, 120, 96, { fade = fade * ic })
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local cfade = fade * ia
|
||||
if cfade > 0 then
|
||||
self:draw_plates(box, bx, by, cfade)
|
||||
self:draw_badges(box, bx, by, pb, cfade)
|
||||
end
|
||||
|
||||
-- title rises from the closed position (y=0) to the open one (y=-70) with the growth
|
||||
draw_title(self:song_title(box), bx, by - 70 * pb, fade * ltitle, SONG_TEXT_BOX_W, 0)
|
||||
local sub = self:song_subtitle(box)
|
||||
if sub and cfade > 0 then draw_title(sub, bx, by - 28, cfade, SONG_TEXT_BOX_W) end
|
||||
|
||||
if tex.display_bpm() and cfade > 0 then
|
||||
local bpm_cfg = tex.get_skin_config("song_box_bpm")
|
||||
box:bpm_text():draw({ x = bx + bpm_cfg.x, y = by + bpm_cfg.y, fade = cfade })
|
||||
end
|
||||
local icon = (ic > 0) and self:song_icon(box) or nil
|
||||
if icon then
|
||||
draw_centered(icon, bx - 532, by + 4, 120, 96, { fade = fade * ic })
|
||||
end
|
||||
-- 新音 / 旧音 / 期間限定 marker, hung off the board's right edge (mirror of the icon)
|
||||
local ok_exd, exd = pcall(function() return box:ex_data_flag() end)
|
||||
if ok_exd and exd and exd > 0 and cfade > 0 then
|
||||
local art = (exd == 1) and get_any("yellow_box", "ex_data_new_audio")
|
||||
or (exd == 2) and get_any("yellow_box", "ex_data_old_audio")
|
||||
or get_any("yellow_box", "ex_data_limited_time")
|
||||
if art then
|
||||
local s = tex.get_screen_scale()
|
||||
draw_centered_b(art, { 458 * s, 120 * s }, bx + 532, by + 4,
|
||||
224 * s, 64 * s, { fade = cfade })
|
||||
end
|
||||
end
|
||||
if box.is_favorite and cfade > 0 then
|
||||
local fav = get_any("yellow_box", "favorite_" .. tex.player_num() .. "p")
|
||||
if fav then tex.draw_texture(fav, { x = bx - 480, y = by - 176, fade = cfade }) end
|
||||
end
|
||||
end
|
||||
|
||||
function SongSelect:draw_song_closed(box)
|
||||
local key = "s\0" .. box_id(box) -- per-BOX, not per-title
|
||||
local e = wheel_close_step(key, SONG_CLOSE_MS, SONG_STALE_MS)
|
||||
if e then
|
||||
self:draw_song_board(box, song_close_timeline(e))
|
||||
return
|
||||
end
|
||||
self:draw_song_board(box, 0.0, 0.0, 1.0)
|
||||
end
|
||||
|
||||
-- Diff plates row (song_kanban_info, board y +75): plate 184x96 with the course name
|
||||
-- under the icon, a star and the level number.
|
||||
function SongSelect:chip_label(diff)
|
||||
local lbl = self._chip_labels[diff]
|
||||
if lbl == nil then
|
||||
local key = "chip_course_name_" .. diff
|
||||
if tex.get_skin_config(key) then
|
||||
lbl = text.create_text(key, { 255, 255, 255, 255 }, { 40, 20, 20, 255 }, false, outline(1.5), 1.0)
|
||||
end
|
||||
self._chip_labels[diff] = lbl or false
|
||||
end
|
||||
return lbl or nil
|
||||
end
|
||||
|
||||
function SongSelect:draw_plates(box, bx, by, fade)
|
||||
local has_ura = box:has_ura()
|
||||
local ura_weight = 0.0
|
||||
local oni_alpha = 1.0
|
||||
if has_ura then
|
||||
local f = URA_LOOP_F0 + (tex.get_current_ms() * 0.06) % URA_LOOP_LEN
|
||||
ura_weight = URA_LOOP:get("ura/text_course_ura", f, "a") or 0.0
|
||||
oni_alpha = URA_LOOP:get("oni/text_course_oni", f, "a") or 1.0
|
||||
end
|
||||
local py = by + 75
|
||||
local function plate(cx, diff, f)
|
||||
local info = box:course_info(diff)
|
||||
if not info.has_course or f <= 0 then return end
|
||||
draw_centered(self._id_plate, cx, py, 184, 96, { frame = diff, fade = f })
|
||||
draw_centered(self._id_star, cx + 27, py, 40, 40, { frame = diff, fade = f })
|
||||
local lv = math.max(1, math.min(11, info.level))
|
||||
draw_centered(self._id_level_number, cx + 61, py, 48, 48, { frame = diff * 11 + (lv - 1), fade = f })
|
||||
local name = self:chip_label(diff)
|
||||
if name then draw_title(name, cx - 42, py + 30, f) end
|
||||
if info.is_branching then
|
||||
draw_centered(self._id_branch, cx - 69, py - 23, 40, 40, { fade = f })
|
||||
end
|
||||
end
|
||||
-- charted columns: easy / normal / hard / (oni|ura)
|
||||
local cols = {}
|
||||
for d = 0, 2 do if box:course_info(d).has_course then cols[#cols + 1] = d end end
|
||||
local has_oni = box:course_info(DIFF_ONI).has_course
|
||||
if has_oni or has_ura then cols[#cols + 1] = DIFF_ONI end
|
||||
local n = #cols
|
||||
for i, d in ipairs(cols) do
|
||||
local cx = bx + (i - 1 - (n - 1) / 2) * 182
|
||||
if d == DIFF_ONI and has_ura then
|
||||
if has_oni then
|
||||
plate(cx, DIFF_ONI, fade * oni_alpha) -- oni below, ura on top
|
||||
plate(cx, DIFF_URA, fade * ura_weight)
|
||||
else
|
||||
plate(cx, DIFF_URA, fade)
|
||||
end
|
||||
else
|
||||
plate(cx, d, fade)
|
||||
end
|
||||
end
|
||||
return ura_weight
|
||||
end
|
||||
|
||||
function SongSelect:draw_song_open(box)
|
||||
local key = "s\0" .. box_id(box)
|
||||
local t = wheel_open_t(key)
|
||||
if t == nil then -- 508 ms pre-growth hold: fully closed
|
||||
self:draw_song_board(box, 0.0, 0.0, 1.0)
|
||||
return
|
||||
end
|
||||
self:draw_song_board(box, song_open_timeline(t))
|
||||
end
|
||||
|
||||
-- Difficulty select
|
||||
function SongSelect:draw_diff_mark_decided(p, dec, fade)
|
||||
local f = math.floor((tex.get_current_ms() - dec.t0) / DS_MARK_DECIDE_MS) + 1
|
||||
local k = DS_MARK_DECIDE[math.min(f, #DS_MARK_DECIDE)]
|
||||
local id, id_w = self._id_mark[dec.course], self._id_mark_w[dec.course]
|
||||
local cx, cy = DS_MARK_X[p], DS_MARK_Y
|
||||
local function stretched(tid, a, sy)
|
||||
if a <= 0 then return end
|
||||
tex.draw_texture(tid, { x = cx, y = cy, y2 = 680 * (sy - 1), origin = { 340, 340 * sy }, fade = a * fade })
|
||||
end
|
||||
stretched(id_w, k[5], 1) -- mark_course_white_2: backing
|
||||
stretched(id, k[1], k[2]) -- mark_course
|
||||
stretched(id_w, k[3], k[4]) -- mark_course_white: the flash on top
|
||||
end
|
||||
|
||||
function SongSelect:draw_song_diff_select_bg(box)
|
||||
local fade = box.diff_fade_in.attribute
|
||||
for p = 1, 2 do
|
||||
local d = self._mark_diff[p]
|
||||
local dec = self._mark_decided[p]
|
||||
if dec and self._id_mark[dec.course] then
|
||||
self:draw_diff_mark_decided(p, dec, fade)
|
||||
elseif self._id_diff_mark and d and d >= DIFF_EASY and DS_MARK_X[p] then
|
||||
draw_centered(self._id_diff_mark, DS_MARK_X[p], DS_MARK_Y, 680, 680, { frame = d, fade = fade })
|
||||
end
|
||||
end
|
||||
tex.draw_texture(self._id_backboard, { frame = box:genre_frame(), x = 960 - 636, y = 440 - 392, fade = fade })
|
||||
end
|
||||
|
||||
function SongSelect:draw_song_diff_select(box)
|
||||
folder_leg.diff_seen_ms = tex.get_current_ms()
|
||||
local fade = box.diff_fade_in.attribute
|
||||
local is_ura = box.is_ura
|
||||
local side = (tex.player_num() == 2) and 1 or -1
|
||||
local now = tex.get_current_ms()
|
||||
|
||||
-- oni<->ura toggle detection
|
||||
if fade < 1.0 then
|
||||
self._ura_prev = is_ura; self._ura_anim = nil
|
||||
elseif self._ura_prev ~= nil and is_ura ~= self._ura_prev then
|
||||
self._ura_anim = { t0 = now, to_ura = is_ura }
|
||||
end
|
||||
self._ura_prev = is_ura
|
||||
local ura_elapsed = self._ura_anim and (now - self._ura_anim.t0) or nil
|
||||
if ura_elapsed and ura_elapsed >= URA_ANIM_MS then self._ura_anim = nil; ura_elapsed = nil end
|
||||
local ura_mid = (not ura_elapsed) or ura_elapsed >= URA_SWAP_MS
|
||||
-- what the oni slot shows: before the swap point the previous course, after it the new one
|
||||
local oni_shown = is_ura
|
||||
if ura_elapsed and not ura_mid then oni_shown = not is_ura end
|
||||
|
||||
-- buttons: return + a small option wrench (the engine cursor can still reach MODIFIER)
|
||||
draw_centered(self._id_btn_back, DS_SLOT_X[DIFF_BACK], DS_BTN_Y, 128, 128, { fade = fade })
|
||||
draw_centered(self._id_btn_option, DS_SLOT_X[DIFF_MODIFIER], DS_BTN_Y, 128, 128, { fade = fade })
|
||||
|
||||
-- diff boards
|
||||
for i = 0, 3 do
|
||||
local cx = DS_SLOT_X[i]
|
||||
local diff = (i == DIFF_ONI and oni_shown) and DIFF_URA or i
|
||||
local info = box:course_info(diff)
|
||||
local f = info.has_course and fade or math.min(fade, 0.4)
|
||||
draw_centered(self._id_diff_board, cx, DS_BOARD_Y, 200, 360, { frame = diff, fade = f })
|
||||
if info.has_course and not (i == DIFF_ONI and ura_elapsed) then
|
||||
local crown = info.crown or 0
|
||||
draw_centered(self._id_small_crown, cx + side * 58, DS_BOARD_Y - 138, 40, 40, { frame = crown, fade = f })
|
||||
if (info.rank or 0) > 0 then
|
||||
draw_centered(self._id_small_rank, cx + side * 20, DS_BOARD_Y - 138, 40, 40, { frame = info.rank - 1, fade = f })
|
||||
end
|
||||
local lv = math.max(1, math.min(11, info.level))
|
||||
tex.draw_texture(self._id_small_star, { frame = 0, x = cx - 40, y = DS_BOARD_Y + 66, fade = f })
|
||||
tex.draw_texture(self._id_small_star, { frame = lv, x = cx - 4, y = DS_BOARD_Y + 68, fade = f })
|
||||
draw_centered(self._id_dot_bar, cx, DS_BOARD_Y + 116, 172, 24, { fade = f })
|
||||
for k = 0, math.min(10, info.level) - 1 do
|
||||
draw_centered(self._id_dot_star, cx - 67 + k * 15, DS_BOARD_Y + 116, 24, 24, { fade = f })
|
||||
end
|
||||
if info.is_branching then
|
||||
draw_centered(self._id_branch_diff, cx, DS_BOARD_Y + 144, 40, 40, { fade = f })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- change animation cells over the oni slot
|
||||
if ura_elapsed then
|
||||
local id = self._ura_anim.to_ura and self._id_ura_to_ura or self._id_ura_to_oni
|
||||
if id then
|
||||
local cell = math.min(URA_CELLS - 1, math.floor(ura_elapsed / URA_ANIM_MS * URA_CELLS))
|
||||
tex.draw_texture(id, { frame = cell, x = DS_SLOT_X[DIFF_ONI] - 170, y = DS_BOARD_Y - 210, fade = fade })
|
||||
end
|
||||
end
|
||||
for i = 0, 3 do
|
||||
local diff = (i == DIFF_ONI and oni_shown) and DIFF_URA or i
|
||||
local info = box:course_info(diff)
|
||||
if info.has_course then
|
||||
local lbl = self:course_label(diff)
|
||||
if lbl then draw_title(lbl, DS_SLOT_X[i], DS_BOARD_Y + DS_COURSE_NAME_DY, fade) end
|
||||
end
|
||||
end
|
||||
|
||||
-- title / subtitle inside the backboard header
|
||||
draw_title(self:header_title(box), 960, 178, fade, DS_TEXT_BOX_W, 0)
|
||||
draw_title(self:header_subtitle(box), 960, 242, fade, DS_TEXT_BOX_W, 0)
|
||||
|
||||
end
|
||||
|
||||
function SongSelect:song_subtitle(box)
|
||||
local str = box.text_subtitle
|
||||
if not str or str == "" then return nil end
|
||||
local gf = self._inside_frame or box:genre_frame()
|
||||
local key = gf .. "\0" .. str
|
||||
if self._sub_key ~= key then
|
||||
self._sub_key = key
|
||||
self._sub_label = text.create_raw_text(str, 24, { 255, 255, 255, 255 }, board_outline(gf), false, outline(3.5), 2.0)
|
||||
end
|
||||
return self._sub_label
|
||||
end
|
||||
|
||||
-- Song board title, arcade-exact: font from skin_config song_box_name, black outline,
|
||||
-- cached per title (the cache is dropped wholesale past SONG_TITLE_CACHE_MAX).
|
||||
function SongSelect:song_title(box)
|
||||
local name = box.text_name
|
||||
if not name or name == "" then return box:name() end
|
||||
local gf = self._inside_frame or box:genre_frame()
|
||||
local key = gf .. "\0" .. name -- the outline follows the genre, so it keys the cache
|
||||
local t = self._song_titles[key]
|
||||
if t == nil then
|
||||
if SONG_TITLE_SIZE == nil then
|
||||
local cfg = tex.get_skin_config("song_box_name")
|
||||
SONG_TITLE_SIZE = (cfg and cfg.font_size and cfg.font_size > 0) and cfg.font_size or 42
|
||||
end
|
||||
if self._song_titles_n >= SONG_TITLE_CACHE_MAX then
|
||||
self._song_titles = {}
|
||||
self._song_titles_n = 0
|
||||
end
|
||||
t = text.create_raw_text(name, SONG_TITLE_SIZE, { 255, 255, 255, 255 }, board_outline(gf),
|
||||
false, outline(SONG_TITLE_OUTLINE), 2.0)
|
||||
self._song_titles[key] = t or false
|
||||
self._song_titles_n = self._song_titles_n + 1
|
||||
end
|
||||
return t or box:name()
|
||||
end
|
||||
|
||||
function SongSelect:header_title(box)
|
||||
local name = box.text_name or ""
|
||||
if self._header_title_key ~= name then
|
||||
self._header_title_key = name
|
||||
self._header_title = text.create_raw_text(name, 48, { 255, 255, 255, 255 }, { 0, 0, 0, 255 }, false, outline(7.0), 2.0)
|
||||
end
|
||||
return self._header_title
|
||||
end
|
||||
|
||||
function SongSelect:header_subtitle(box)
|
||||
local sub = box.text_subtitle
|
||||
if sub == nil then return box:subtitle_large() end -- engine without the binding
|
||||
if sub == "" then return nil end
|
||||
if self._header_sub_key ~= sub then
|
||||
self._header_sub_key = sub
|
||||
self._header_sub = text.create_raw_text(sub, 30, { 255, 255, 255, 255 }, { 0, 0, 0, 255 }, false, outline(4.0), 2.0)
|
||||
end
|
||||
return self._header_sub
|
||||
end
|
||||
|
||||
function SongSelect:course_label(diff)
|
||||
local lbl = self._course_labels[diff]
|
||||
if lbl == nil then
|
||||
local key = "course_name_" .. diff
|
||||
if tex.get_skin_config(key) then
|
||||
lbl = text.create_text(key, { 255, 255, 255, 255 }, { 20, 20, 20, 255 }, false, outline(4.5), 1.0)
|
||||
end
|
||||
self._course_labels[diff] = lbl or false
|
||||
end
|
||||
return lbl or nil
|
||||
end
|
||||
@@ -0,0 +1,188 @@
|
||||
-- 演奏オプション / 音色 board — replaces ModifierSelector::draw() / NeiroSelector::draw().
|
||||
-- Same texture.json bases and skin_config keys the C++ board used, so geometry is exact.
|
||||
-- Split out of song_select.lua.
|
||||
|
||||
local base = require("song_select/file_navigator/box_base")
|
||||
|
||||
local get_any = base.get_any
|
||||
local outline = base.outline
|
||||
|
||||
local OPT_CURSOR_MS = 1000 -- cursor loop = 60 frames @ 60 fps
|
||||
local OPT_CURSOR_MIN = 51 / 256 -- its alpha floor
|
||||
local OPT_GREY = { 166, 168, 171, 255 }
|
||||
local OPT_CHANGED = { 255, 255, 0, 255 }
|
||||
local OPT_SCRIM = { 0, 0, 0, 128 }
|
||||
local OPT_ARROW_NUDGE = 5
|
||||
|
||||
function SongSelect:_opt_ids()
|
||||
if self._opt_ids_t then return self._opt_ids_t end
|
||||
-- Must load these subsets here: script_manager.tex is its own TextureWrapper, so a
|
||||
-- subset the engine loaded elsewhere is not resident in the Lua one.
|
||||
tex.load_folder("song_select", "modifier")
|
||||
tex.load_folder("song_select", "neiro")
|
||||
local t = {}
|
||||
t.top = get_any("modifier", "top")
|
||||
t.bottom = get_any("modifier", "bottom")
|
||||
t.bg = get_any("modifier", "background")
|
||||
t.row = get_any("modifier", "mod_bg")
|
||||
t.row_hl = get_any("modifier", "mod_bg_highlight")
|
||||
t.box = get_any("modifier", "mod_box")
|
||||
t.arrow = get_any("modifier", "blue_arrow")
|
||||
t.p = { get_any("modifier", "1p"), get_any("modifier", "2p") }
|
||||
t.icon = {
|
||||
auto = get_any("modifier", "mod_auto"),
|
||||
speed = get_any("modifier", "mod_baisaku"),
|
||||
sanbai = get_any("modifier", "mod_sanbai"),
|
||||
yonbai = get_any("modifier", "mod_yonbai"),
|
||||
speed_by_value = {
|
||||
[11] = get_any("modifier", "mod_speed_x1_1"), [12] = get_any("modifier", "mod_speed_x1_2"),
|
||||
[13] = get_any("modifier", "mod_speed_x1_3"), [14] = get_any("modifier", "mod_speed_x1_4"),
|
||||
[15] = get_any("modifier", "mod_speed_x1_5"), [16] = get_any("modifier", "mod_speed_x1_6"),
|
||||
[17] = get_any("modifier", "mod_speed_x1_7"), [18] = get_any("modifier", "mod_speed_x1_8"),
|
||||
[19] = get_any("modifier", "mod_speed_x1_9"), [20] = get_any("modifier", "mod_speed_x2"),
|
||||
[25] = get_any("modifier", "mod_speed_x2_5"), [30] = get_any("modifier", "mod_speed_x3"),
|
||||
[35] = get_any("modifier", "mod_speed_x3_5"), [40] = get_any("modifier", "mod_speed_x4"),
|
||||
},
|
||||
display = get_any("modifier", "mod_doron"),
|
||||
inverse = get_any("modifier", "mod_abekobe"),
|
||||
kimagure = get_any("modifier", "mod_kimagure"),
|
||||
detarame = get_any("modifier", "mod_detarame"),
|
||||
}
|
||||
self._opt_ids_t = t
|
||||
return t
|
||||
end
|
||||
|
||||
-- Row label / value strings as OutlinedText, cached by content.
|
||||
function SongSelect:_opt_text(s)
|
||||
if s == nil or s == "" then return nil end
|
||||
if not self._opt_texts then
|
||||
self._opt_texts = {}
|
||||
local cfg = tex.get_skin_config("modifier_text")
|
||||
self._opt_font = (cfg and cfg.font_size ~= 0 and cfg.font_size) or 26
|
||||
end
|
||||
local t = self._opt_texts[s]
|
||||
if t == nil then
|
||||
t = text.create_raw_text(s, self._opt_font, { 255, 255, 255, 255 }, { 0, 0, 0, 255 }, false, outline(3.0), 2.0) or false
|
||||
self._opt_texts[s] = t
|
||||
end
|
||||
return t or nil
|
||||
end
|
||||
|
||||
-- Arcade cursor pulse, sampled from anim/option_cursor; hand triangle is the fallback.
|
||||
local option_cursor_ok, option_cursor_anim = pcall(require, "anim/sampler")
|
||||
local OPT_CURSOR_CLIP = (option_cursor_ok and option_cursor_anim.safe("anim/option_cursor"))
|
||||
or { get = function() return nil end, first = function() return 0 end,
|
||||
last = function() return 0 end }
|
||||
local OPT_CURSOR_LEN = math.max(1, OPT_CURSOR_CLIP:last() - OPT_CURSOR_CLIP:first() + 1)
|
||||
local function opt_cursor_alpha(ms)
|
||||
local f = (ms * 0.06) % OPT_CURSOR_LEN
|
||||
local a = OPT_CURSOR_CLIP:get("#3@0", f, "a")
|
||||
if a then return a end
|
||||
local u = (ms % OPT_CURSOR_MS) / OPT_CURSOR_MS
|
||||
local tri = (u < 0.5) and (u * 2) or ((1 - u) * 2)
|
||||
return 1.0 - (1.0 - OPT_CURSOR_MIN) * tri
|
||||
end
|
||||
|
||||
-- One board. `rows` = {label, value, changed, greyed, name}; `cursor` = 1-based row (nil = none).
|
||||
function SongSelect:draw_option_board(player, offset, rows, cursor, change)
|
||||
local ids = self:_opt_ids()
|
||||
if not ids.top or not ids.row or not ids.box then return end
|
||||
local p = player:player_num()
|
||||
local p2cfg = tex.get_skin_config("option_p2")
|
||||
local x = (p - 1) * ((p2cfg and p2cfg.x) or 1490)
|
||||
local pitch = tex.get_skin_config("modifier_offset")
|
||||
pitch = (pitch and pitch.y ~= 0 and pitch.y) or 61
|
||||
local tcfg = tex.get_skin_config("modifier_text")
|
||||
local ocfg = tex.get_skin_config("modifier_offset_2")
|
||||
local text_x, text_y = (tcfg and tcfg.x) or 300, (tcfg and tcfg.y) or 1178
|
||||
local name_x = (ocfg and ocfg.x) or 44
|
||||
local arrow_span = (ocfg and ocfg.y) or 140
|
||||
local n = #rows
|
||||
local ms = tex.get_current_ms()
|
||||
|
||||
tex.draw_texture(ids.top, { x = x, y = offset })
|
||||
if ids.p[p] then tex.draw_texture(ids.p[p], { x = x, y = offset }) end
|
||||
if ids.bottom then tex.draw_texture(ids.bottom, { x = x, y = offset + n * pitch }) end
|
||||
|
||||
for i = 1, n do
|
||||
local row = rows[i]
|
||||
local row_y = offset + (i - 1) * pitch
|
||||
local is_cur = (cursor == i)
|
||||
|
||||
if ids.bg then tex.draw_texture(ids.bg, { x = x, y = row_y }) end
|
||||
tex.draw_texture(ids.row, { x = x, y = row_y })
|
||||
if row.greyed then
|
||||
-- arcade "grayout": flat grey laid on top of the row plate
|
||||
if ids.row_hl then tex.draw_texture(ids.row_hl, { x = x, y = row_y, color = OPT_GREY }) end
|
||||
elseif is_cur and ids.row_hl then
|
||||
tex.draw_texture(ids.row_hl, { x = x, y = row_y, fade = opt_cursor_alpha(ms) })
|
||||
end
|
||||
tex.draw_texture(ids.box, { x = x, y = row_y, color = row.changed and OPT_CHANGED or nil })
|
||||
|
||||
local label = self:_opt_text(row.label)
|
||||
if label then label:draw({ x = name_x + x, y = text_y + row_y }) end
|
||||
local value = self:_opt_text(row.value)
|
||||
if value then
|
||||
value:draw({ x = math.floor(text_x - value.width / 2 + 0.5) + x, y = text_y + row_y })
|
||||
end
|
||||
|
||||
-- engine row icon; state is the raw value so it never depends on the localised string
|
||||
local icon
|
||||
local st = row.state or 0
|
||||
if row.name == "speed" then
|
||||
if st > 10 then
|
||||
local best
|
||||
for v, id in pairs(ids.icon.speed_by_value or {}) do
|
||||
if id and v <= st and (not best or v > best) then best = v end
|
||||
end
|
||||
icon = best and ids.icon.speed_by_value[best] or nil
|
||||
end
|
||||
if not icon then
|
||||
if st >= 40 then icon = ids.icon.yonbai
|
||||
elseif st >= 30 then icon = ids.icon.sanbai
|
||||
elseif st > 10 then icon = ids.icon.speed end
|
||||
end
|
||||
elseif row.name == "random" then
|
||||
if st == 2 then icon = ids.icon.detarame
|
||||
elseif st == 1 then icon = ids.icon.kimagure end
|
||||
elseif row.changed then
|
||||
icon = ids.icon[row.name]
|
||||
end
|
||||
if icon then tex.draw_texture(icon, { x = x, y = row_y }) end
|
||||
|
||||
if is_cur and ids.arrow then
|
||||
local nl, nr = 0, 0
|
||||
if change and change.active then
|
||||
local amp = OPT_ARROW_NUDGE * math.max(0, math.min(1, change.fade))
|
||||
if change.dir > 0 then nl = amp else nr = amp end
|
||||
end
|
||||
tex.draw_texture(ids.arrow, { x = x - nl, y = row_y })
|
||||
tex.draw_texture(ids.arrow, { mirror = "horizontal", x = x + arrow_span + nr, y = row_y })
|
||||
end
|
||||
|
||||
if row.greyed then tex.draw_texture(ids.row, { x = x, y = row_y, color = OPT_SCRIM }) end
|
||||
end
|
||||
end
|
||||
|
||||
-- Engine entry point. kind: 1 = 演奏オプション, 2 = the standalone 音色 panel.
|
||||
function SongSelect:draw_option_panel(player, kind)
|
||||
if kind == 1 then
|
||||
local rows = player.modifier_rows and player:modifier_rows()
|
||||
local off = player.modifier_offset and player:modifier_offset()
|
||||
if not rows or not off then return end
|
||||
local cursor = player.modifier_index and player:modifier_index() or nil
|
||||
if cursor and (cursor < 1 or cursor > #rows) then cursor = nil end
|
||||
if player.modifier_confirmed and player:modifier_confirmed() then cursor = nil end
|
||||
self._mod_off[player:player_num()] = off
|
||||
self:draw_option_board(player, off, rows, cursor,
|
||||
player.modifier_change and player:modifier_change())
|
||||
else
|
||||
local names = player.neiro_names and player:neiro_names()
|
||||
local off = player.neiro_offset and player:neiro_offset()
|
||||
if not names or not off then return end
|
||||
local idx = (player.neiro_index and player:neiro_index()) or 1
|
||||
local rows = { { name = "neiro", state = idx - 1, value = names[idx] or "",
|
||||
changed = (idx ~= 1), greyed = false } }
|
||||
self:draw_option_board(player, off, rows, 1, nil)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
-- SongSelectPlayer selector cursor (bubble + course frame / button glow), split out of
|
||||
-- song_select.lua. Drawn in two passes (under/over the wheel).
|
||||
|
||||
local base = require("song_select/file_navigator/box_base")
|
||||
|
||||
local DIFF_EASY, DIFF_NEIRO, DIFF_ONI = base.DIFF_EASY, base.DIFF_NEIRO, base.DIFF_ONI
|
||||
local DS_SLOT_X = base.DS_SLOT_X
|
||||
local DS_BOARD_Y, DS_CURSOR_Y, DS_NEIRO_Y, DS_BTN_Y = base.DS_BOARD_Y, base.DS_CURSOR_Y, base.DS_NEIRO_Y, base.DS_BTN_Y
|
||||
local SEL_BASE_BALLOON = base.SEL_BASE_BALLOON
|
||||
local MOD_SLIDE, MOD_SLIDE_MS = base.MOD_SLIDE, base.MOD_SLIDE_MS
|
||||
local draw_centered, draw_centered_b = base.draw_centered, base.draw_centered_b
|
||||
|
||||
function SongSelect:draw_selector(player, is_half, fade_in, pass)
|
||||
local under = (pass == nil) or (pass == 0)
|
||||
local over = (pass == nil) or (pass == 1)
|
||||
local diff = player:selected_difficulty()
|
||||
local p = player:player_num()
|
||||
if under then
|
||||
self._mark_diff[p] = diff
|
||||
local decided = diff >= DIFF_EASY and player.difficulty_decided and player:difficulty_decided()
|
||||
if decided then
|
||||
if not self._mark_decided[p] then
|
||||
self._mark_decided[p] = { t0 = tex.get_current_ms(), course = diff }
|
||||
end
|
||||
else
|
||||
self._mark_decided[p] = nil
|
||||
end
|
||||
end
|
||||
if fade_in < 1.0 then return end
|
||||
local mod_on = player:modifier_active()
|
||||
if player.modifier_offset then
|
||||
self._mod_off[p] = player:modifier_offset()
|
||||
elseif mod_on then
|
||||
if not self._mod_seen[p] then
|
||||
local now = tex.get_current_ms()
|
||||
local prev = self._last_sel_ms or now
|
||||
self._mod_t0[p] = (now - prev < 250) and math.min(now, prev + 16.7) or now
|
||||
end
|
||||
local u = math.max(0.0, math.min(1.0, (tex.get_current_ms() - self._mod_t0[p]) / MOD_SLIDE_MS))
|
||||
self._mod_off[p] = -MOD_SLIDE * u * (2 - u)
|
||||
else
|
||||
self._mod_off[p] = nil
|
||||
end
|
||||
self._last_sel_ms = tex.get_current_ms()
|
||||
self._mod_seen[p] = mod_on
|
||||
local fade = (player:neiro_active() or mod_on) and 0.5 or fade_in
|
||||
-- the cursor offset is +-40 only while both players sit on the same slot
|
||||
local cursor_dx = is_half and ((p == 2) and 40 or -40) or 0
|
||||
local sx = DS_SLOT_X[(diff >= DIFF_EASY) and math.min(DIFF_ONI, diff) or diff]
|
||||
if not sx then return end
|
||||
|
||||
if diff >= DIFF_EASY then
|
||||
local col = math.min(DIFF_ONI, diff)
|
||||
if col == DIFF_ONI and self._ura_anim then return end -- cursor/frame hidden during the change
|
||||
sx = DS_SLOT_X[col]
|
||||
local frame_id = (p == 1) and self._id_course_frame or self._id_course_frame2
|
||||
if under then draw_centered(frame_id, sx, DS_BOARD_Y, 248, 408, { fade = fade }) end
|
||||
if over then
|
||||
draw_centered_b((p == 1) and self._id_bubble_1p or self._id_bubble_2p, SEL_BASE_BALLOON,
|
||||
sx + cursor_dx, DS_CURSOR_Y, 124, 124, { fade = fade })
|
||||
end
|
||||
else
|
||||
local by = (diff == DIFF_NEIRO) and DS_NEIRO_Y or DS_BTN_Y
|
||||
if under then draw_centered(self._id_btn_glow, sx, by, 168, 168, { fade = fade }) end
|
||||
if over then
|
||||
draw_centered_b((p == 1) and self._id_bubble_1p or self._id_bubble_2p, SEL_BASE_BALLOON,
|
||||
sx + cursor_dx, DS_CURSOR_Y, 124, 124, { fade = fade })
|
||||
end
|
||||
end
|
||||
end
|
||||
+53
-1719
File diff suppressed because it is too large.
Load diff
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user