danoniplus 40.7.1 → 41.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@ a:hover { color:#FF9900; text-decoration: underline; }
37
37
  <body>
38
38
  <table><tr><td>
39
39
  <p style="text-align:center;">
40
- <span style="font-size:32px;">Preview</span>
40
+ <span id="webMusicTitle">Preview</span>
41
41
  </p>
42
42
  <hr>
43
43
 
package/js/danoni_main.js CHANGED
@@ -4,12 +4,12 @@
4
4
  *
5
5
  * Source by tickle
6
6
  * Created : 2018/10/08
7
- * Revised : 2025/03/24
7
+ * Revised : 2025/04/12
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 40.7.1`;
12
- const g_revisedDate = `2025/03/24`;
11
+ const g_version = `Ver 41.0.0`;
12
+ const g_revisedDate = `2025/04/12`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -60,6 +60,7 @@ const g_referenceDomains = [
60
60
  `cwtickle.github.io/danoniplus`,
61
61
  `cdn.jsdelivr.net`,
62
62
  `unpkg.com`,
63
+ `www.unpkg.com`,
63
64
  `support-v\\d+--danoniplus.netlify.app`,
64
65
  ];
65
66
  Object.freeze(g_referenceDomains);
@@ -233,6 +234,7 @@ let g_langStorage = {};
233
234
  // ローカルストレージ設定 (作品別)
234
235
  let g_localStorage;
235
236
  let g_localStorageUrl;
237
+ let g_localStorageUrlOrg;
236
238
  let g_localStorageMgt;
237
239
 
238
240
  // ローカルストレージ設定 (ドメイン・キー別)
@@ -2080,7 +2082,7 @@ const makeSpriteData = (_data, _calcFrame = _frame => _frame) => {
2080
2082
  [spriteData[tmpFrame], dataCnts] =
2081
2083
  checkDuplicatedObjects(spriteData[tmpFrame]);
2082
2084
 
2083
- const emptyPatterns = [``, `[loop]`, `[jump]`];
2085
+ const emptyPatterns = [`[loop]`, `[jump]`];
2084
2086
  const colorObjFlg = tmpSpriteData[2]?.startsWith(`[c]`) || false;
2085
2087
  const spriteFrameData = spriteData[tmpFrame][dataCnts] = {
2086
2088
  depth: tmpDepth,
@@ -2102,8 +2104,10 @@ const makeSpriteData = (_data, _calcFrame = _frame => _frame) => {
2102
2104
  spriteFrameData.colorObjInfo.animationFillMode = tmpObj.animationFillMode;
2103
2105
  }
2104
2106
 
2107
+ } else if (tmpObj.path === ``) {
2108
+ spriteFrameData.command = ``;
2105
2109
  } else if (emptyPatterns.includes(tmpObj.path)) {
2106
- // ループ、フレームジャンプ、空の場合の処理
2110
+ // ループ、フレームジャンプの場合の処理
2107
2111
  spriteFrameData.command = tmpObj.path;
2108
2112
  spriteFrameData.jumpFrame = tmpObj.class;
2109
2113
  spriteFrameData.maxLoop = tmpObj.left;
@@ -2451,8 +2455,8 @@ const initialControl = async () => {
2451
2455
 
2452
2456
  // 共通設定ファイルの指定
2453
2457
  let tmpSettingType = g_rootObj.settingType ?? ``;
2454
- if (g_remoteFlg && !tmpSettingType.includes(`(..)`)) {
2455
- tmpSettingType = `(..)../js/${tmpSettingType}`;
2458
+ if (g_remoteFlg && !tmpSettingType.includes(C_MRK_CURRENT_DIRECTORY)) {
2459
+ tmpSettingType = `${C_MRK_CURRENT_DIRECTORY}../js/${tmpSettingType}`;
2456
2460
  };
2457
2461
  let [settingType, settingRoot] = getFilePath(tmpSettingType);
2458
2462
  if (settingType !== ``) {
@@ -2595,7 +2599,10 @@ const initialControl = async () => {
2595
2599
  }
2596
2600
  }
2597
2601
  g_customJsObj.preTitle.forEach(func => func());
2598
- titleInit();
2602
+ const queryMusicId = getQueryParamVal(`musicId`);
2603
+ g_settings.musicIdxNum = queryMusicId !== null ? Number(queryMusicId) :
2604
+ g_headerObj.musicNos[g_stateObj.scoreId] || g_headerObj.musicNos[0];
2605
+ titleInit(true);
2599
2606
 
2600
2607
  // 未使用のg_keyObjプロパティを削除
2601
2608
  const keyProp = g_keyCopyLists.simple.concat(g_keyCopyLists.multiple, `keyCtrl`, `keyName`, `minWidth`, `ptchara`);
@@ -2613,6 +2620,8 @@ const initialControl = async () => {
2613
2620
  }
2614
2621
  });
2615
2622
 
2623
+ g_stateObj.keyInitial = true;
2624
+
2616
2625
  // エディター用のフォーマッター作成
2617
2626
  const customKeyList = g_headerObj.keyLists.filter(val =>
2618
2627
  g_keyObj.defaultKeyList.findIndex(key => key === val) < 0);
@@ -2745,15 +2754,31 @@ const initialControl = async () => {
2745
2754
 
2746
2755
  /**
2747
2756
  * 作品別ローカルストレージの読み込み・初期設定
2757
+ * @param {string} _musicId 楽曲ID
2748
2758
  */
2749
- const loadLocalStorage = () => {
2750
- // URLからscoreId, h(高さ), debugを削除
2759
+ const loadLocalStorage = (_musicId = ``) => {
2760
+
2761
+ // 作品別ローカルストレージのキー(URL)取得のため、
2762
+ // scoreId, h, debug, musicIdを削除
2763
+ // 選択中の楽曲ID(_musicId)がある場合は、キーとして区別するため追加
2751
2764
  const url = new URL(location.href);
2752
2765
  url.searchParams.delete(`scoreId`);
2753
2766
  url.searchParams.delete(`h`);
2754
2767
  url.searchParams.delete(`debug`);
2768
+ url.searchParams.delete(`musicId`);
2755
2769
  g_localStorageUrl = url.toString();
2756
2770
 
2771
+ // リザルト表示用のURL組み立てのため、_musicIdのないURLを保存
2772
+ g_localStorageUrlOrg = g_localStorageUrl;
2773
+
2774
+ if (_musicId !== ``) {
2775
+ url.searchParams.append(`musicId`, _musicId);
2776
+ g_localStorageUrl = url.toString();
2777
+ if (g_langStorage.safeMode === C_FLG_ON) {
2778
+ return;
2779
+ }
2780
+ }
2781
+
2757
2782
  /**
2758
2783
  * ローカルストレージの初期値設定
2759
2784
  * @param {string} _name
@@ -3246,8 +3271,8 @@ const preheaderConvert = _dosObj => {
3246
3271
  });
3247
3272
 
3248
3273
  const convLocalPath = (_file, _type) =>
3249
- g_remoteFlg && hasVal(_file) && !_file.includes(`(..)`) && !hasRemoteDomain(_file)
3250
- ? `(..)../${_type}/${_file}`
3274
+ g_remoteFlg && hasVal(_file) && !_file.includes(C_MRK_CURRENT_DIRECTORY) && !hasRemoteDomain(_file)
3275
+ ? `${C_MRK_CURRENT_DIRECTORY}../${_type}/${_file}`
3251
3276
  : _file;
3252
3277
 
3253
3278
  // 外部スキンファイルの指定
@@ -3386,14 +3411,16 @@ const headerConvert = _dosObj => {
3386
3411
  obj.musicTitles = [];
3387
3412
  obj.musicTitlesForView = [];
3388
3413
  obj.artistNames = [];
3414
+ obj.artistUrls = [];
3389
3415
  obj.musicNos = [];
3416
+ obj.bpms = [];
3390
3417
 
3391
3418
  const dosMusicTitle = getHeader(_dosObj, `musicTitle`);
3392
3419
  if (hasVal(dosMusicTitle)) {
3393
3420
  const musicData = splitLF2(dosMusicTitle);
3394
3421
 
3395
3422
  if (hasVal(_dosObj.musicNo)) {
3396
- obj.musicNos = _dosObj.musicNo.split(`$`);
3423
+ obj.musicNos = splitLF2(_dosObj.musicNo).map(val => Number(val));
3397
3424
  }
3398
3425
 
3399
3426
  for (let j = 0; j < musicData.length; j++) {
@@ -3402,22 +3429,27 @@ const headerConvert = _dosObj => {
3402
3429
  if (obj.musicNos.length >= j) {
3403
3430
  obj.musicTitles[j] = escapeHtml(getMusicNameSimple(musics[0]));
3404
3431
  obj.musicTitlesForView[j] = escapeHtmlForArray(getMusicNameMultiLine(musics[0]));
3405
- obj.artistNames[j] = escapeHtml(musics[1] ?? ``);
3432
+ obj.artistNames[j] = escapeHtml(musics[1] || obj.artistNames[0] || ``);
3433
+ obj.artistUrls[j] = musics[2] || obj.artistUrls[0] || ``;
3434
+ obj.bpms[j] = musics[4] || obj.bpms[0] || `----`;
3435
+ }
3436
+
3437
+ // 選曲ではなく、単一作品用の項目としての管理変数を定義
3438
+ if (j === 0) {
3439
+ obj.musicTitle = obj.musicTitles[0];
3440
+ obj.musicTitleForView = obj.musicTitlesForView[0];
3441
+ obj.artistName = obj.artistNames[0];
3442
+ if (obj.artistName === ``) {
3443
+ makeWarningWindow(g_msgInfoObj.E_0011);
3444
+ obj.artistName = `artistName`;
3445
+ }
3446
+ obj.artistUrl = obj.artistUrls[0];
3447
+ if (hasVal(musics[3])) {
3448
+ obj.musicTitles[0] = escapeHtml(getMusicNameSimple(musics[3]));
3449
+ obj.musicTitlesForView[0] = escapeHtmlForArray(getMusicNameMultiLine(musics[3]));
3450
+ }
3406
3451
  }
3407
3452
  }
3408
- const musics = splitComma(musicData[0]);
3409
- obj.musicTitle = obj.musicTitles[0];
3410
- obj.musicTitleForView = obj.musicTitlesForView[0];
3411
- obj.artistName = obj.artistNames[0] ?? ``;
3412
- if (obj.artistName === ``) {
3413
- makeWarningWindow(g_msgInfoObj.E_0011);
3414
- obj.artistName = `artistName`;
3415
- }
3416
- obj.artistUrl = musics[2] ?? ``;
3417
- if (musics[3] !== undefined) {
3418
- obj.musicTitles[0] = escapeHtml(getMusicNameSimple(musics[3]));
3419
- obj.musicTitlesForView[0] = escapeHtmlForArray(getMusicNameMultiLine(musics[3]));
3420
- }
3421
3453
 
3422
3454
  } else {
3423
3455
  makeWarningWindow(g_msgInfoObj.E_0012);
@@ -3427,6 +3459,10 @@ const headerConvert = _dosObj => {
3427
3459
  obj.artistUrl = ``;
3428
3460
  }
3429
3461
 
3462
+ // 選曲機能の利用有無
3463
+ obj.packageNames = (_dosObj.packageName || ``).split(`<br>`);
3464
+ obj.musicSelectUse = _dosObj.packageName !== undefined;
3465
+
3430
3466
  // 最小・最大速度の設定
3431
3467
  obj.minSpeed = Math.round(setVal(_dosObj.minSpeed, C_MIN_SPEED, C_TYP_FLOAT) * 4) / 4;
3432
3468
  obj.maxSpeed = Math.round(setVal(_dosObj.maxSpeed, C_MAX_SPEED, C_TYP_FLOAT) * 4) / 4;
@@ -3449,10 +3485,17 @@ const headerConvert = _dosObj => {
3449
3485
 
3450
3486
  // 製作者表示
3451
3487
  const dosTuning = getHeader(_dosObj, `tuning`);
3488
+ obj.tuningNames = [];
3489
+ obj.tuningUrls = [];
3452
3490
  if (hasVal(dosTuning)) {
3453
- const tunings = dosTuning.split(`,`);
3454
- obj.tuning = escapeHtmlForEnabledTag(tunings[0]);
3455
- obj.creatorUrl = (tunings.length > 1 ? tunings[1] : (g_presetObj.tuningUrl ?? ``));
3491
+ splitLF2(dosTuning).forEach(tuning => {
3492
+ const tuningData = tuning.split(`,`);
3493
+ obj.tuningNames.push(escapeHtmlForEnabledTag(tuningData[0]));
3494
+ obj.tuningUrls.push(tuningData[1] ||
3495
+ (getHeader(g_presetObj, `tuning`) === tuningData[0] ? g_presetObj.tuningUrl : ``));
3496
+ });
3497
+ obj.tuning = obj.tuningNames[0];
3498
+ obj.creatorUrl = obj.tuningUrls[0] || g_presetObj.tuningUrl || ``;
3456
3499
  } else {
3457
3500
  obj.tuning = escapeHtmlForEnabledTag(getHeader(g_presetObj, `tuning`) ?? `name`);
3458
3501
  obj.creatorUrl = g_presetObj.tuningUrl ?? ``;
@@ -3536,6 +3579,9 @@ const headerConvert = _dosObj => {
3536
3579
  obj.viewLists = [...Array(obj.keyLabels.length).keys()];
3537
3580
  obj.keyLists = keyLists.sort((a, b) => parseInt(a) - parseInt(b));
3538
3581
  obj.undefinedKeyLists = obj.keyLists.filter(key => g_keyObj[`${g_keyObj.defaultProp}${key}_0`] === undefined);
3582
+ if (obj.musicNos.length === 0) {
3583
+ obj.musicNos = fillArray(obj.keyLabels.length);
3584
+ }
3539
3585
 
3540
3586
  // 譜面変更セレクターの利用有無
3541
3587
  obj.difSelectorUse = getDifSelectorUse(_dosObj.difSelectorUse, obj.viewLists);
@@ -3635,18 +3681,18 @@ const headerConvert = _dosObj => {
3635
3681
  obj.blankFrameDefs = [200];
3636
3682
  if (isNaN(parseFloat(_dosObj.blankFrame))) {
3637
3683
  } else {
3638
- obj.blankFrameDefs = _dosObj.blankFrame.split(`$`).map(val => parseInt(val));
3684
+ obj.blankFrameDefs = splitLF2(_dosObj.blankFrame).map(val => parseInt(val));
3639
3685
  }
3640
3686
  obj.blankFrame = obj.blankFrameDefs[0];
3641
3687
  obj.blankFrameDef = obj.blankFrameDefs[0];
3642
3688
 
3643
3689
  // 開始フレーム数(0以外の場合はフェードインスタート)、終了フレーム数
3644
3690
  [`startFrame`, `endFrame`].filter(tmpParam => hasVal(_dosObj[tmpParam]))
3645
- .forEach(param => obj[param] = _dosObj[param].split(`$`).map(frame => transTimerToFrame(frame)));
3691
+ .forEach(param => obj[param] = splitLF2(_dosObj[param]).map(frame => transTimerToFrame(frame)));
3646
3692
 
3647
3693
  // フェードアウトフレーム数(譜面別)
3648
3694
  if (hasVal(_dosObj.fadeFrame)) {
3649
- const fadeFrames = _dosObj.fadeFrame.split(`$`);
3695
+ const fadeFrames = splitLF2(_dosObj.fadeFrame);
3650
3696
  obj.fadeFrame = [];
3651
3697
  fadeFrames.forEach((fadeInfo, j) => {
3652
3698
  obj.fadeFrame[j] = fadeInfo.split(`,`);
@@ -3691,7 +3737,7 @@ const headerConvert = _dosObj => {
3691
3737
  g_diffObj.frzJdgY = (isNaN(parseFloat(_dosObj.frzJdgY)) ? 0 : parseFloat(_dosObj.frzJdgY));
3692
3738
 
3693
3739
  // musicフォルダ設定
3694
- obj.musicFolder = _dosObj.musicFolder ?? (g_remoteFlg ? `(..)../music` : `music`);
3740
+ obj.musicFolder = _dosObj.musicFolder ?? (g_remoteFlg ? `${C_MRK_CURRENT_DIRECTORY}../music` : `music`);
3695
3741
 
3696
3742
  // 楽曲URL
3697
3743
  if (hasVal(_dosObj.musicUrl)) {
@@ -3801,8 +3847,9 @@ const headerConvert = _dosObj => {
3801
3847
  g_stateObj.excessive = boolToSwitch(obj.excessiveJdgUse);
3802
3848
  g_settings.excessiveNum = Number(obj.excessiveJdgUse);
3803
3849
 
3804
- // 譜面名に制作者名を付加するかどうかのフラグ
3850
+ // 譜面名に制作者名を付加するかどうかのフラグ(選曲用に初期値を退避)
3805
3851
  obj.makerView = setBoolVal(_dosObj.makerView);
3852
+ obj.makerViewOrg = obj.makerView;
3806
3853
 
3807
3854
  // shuffleUse=group 時のみshuffle用配列を組み替える
3808
3855
  if (_dosObj.shuffleUse === `group`) {
@@ -3902,11 +3949,10 @@ const headerConvert = _dosObj => {
3902
3949
  const tmpComment = (_dosObj[`commentVal${g_localeObj.val}`] ?? _dosObj.commentVal ?? ``).split(`\r\n`).join(`\n`);
3903
3950
  obj.commentVal = tmpComment.split(`\n`).join(newlineTag);
3904
3951
 
3905
- // クレジット表示
3906
- if (document.getElementById(`webMusicTitle`) !== null) {
3907
- webMusicTitle.innerHTML =
3908
- `<span style="font-size:${wUnit(32)}">${obj.musicTitleForView.join(`<br>`)}</span><br>
3909
- <span style="font-size:${wUnit(16)}">(Artist: <a href="${obj.artistUrl}" target="_blank">${obj.artistName}</a>)</span>`;
3952
+ const maxMusicNo = Math.max(...obj.musicNos);
3953
+ for (let j = 0; j <= maxMusicNo; j++) {
3954
+ obj[`commentVal${j}`] = (_dosObj[`commentVal${j}`] || ``).split(`\n`)
3955
+ .filter((val, k) => k !== 0 || val !== ``).join(`<br>`);
3910
3956
  }
3911
3957
 
3912
3958
  // コメントの外部化設定
@@ -4775,9 +4821,10 @@ const setKeyDfVal = _ptnName => {
4775
4821
  /*-----------------------------------------------------------*/
4776
4822
 
4777
4823
  /**
4778
- * タイトル画面初期化
4824
+ * タイトル画面初期化
4825
+ * @param {boolean} _initFlg 初期化フラグ
4779
4826
  */
4780
- const titleInit = () => {
4827
+ const titleInit = (_initFlg = false) => {
4781
4828
 
4782
4829
  clearWindow(true);
4783
4830
  g_currentPage = `title`;
@@ -4809,20 +4856,6 @@ const titleInit = () => {
4809
4856
  let buffTime;
4810
4857
  let titleStartTime = performance.now();
4811
4858
 
4812
- // 背景の矢印オブジェクトを表示
4813
- if (!g_headerObj.customTitleArrowUse) {
4814
- divRoot.appendChild(
4815
- createColorObject2(`lblArrow`, {
4816
- x: (g_sWidth - 500) / 2, y: -15 + (g_sHeight - 500) / 2,
4817
- w: 500, h: 500, rotateEnabled: true,
4818
- background: makeColorGradation(g_headerObj.titlearrowgrds[0] || g_headerObj.setColorOrg[0], {
4819
- _defaultColorgrd: [false, `#eeeeee`],
4820
- _objType: `titleArrow`,
4821
- }), rotate: `titleArrow:${g_headerObj.titleArrowRotate}`,
4822
- })
4823
- );
4824
- }
4825
-
4826
4859
  // 背景スプライトを作成
4827
4860
  createMultipleSprite(`backTitleSprite`, g_headerObj.backTitleMaxDepth);
4828
4861
 
@@ -4835,76 +4868,123 @@ const titleInit = () => {
4835
4868
  .replace(/[\t\n]/g, ``), 0, 15, g_cssObj.flex_centering)
4836
4869
  );
4837
4870
 
4838
- // 曲名文字描画(曲名は譜面データから取得)
4839
- if (!g_headerObj.customTitleUse) {
4871
+ // 背景の矢印オブジェクトを表示
4872
+ const tmpCreatorList = [];
4873
+ if (g_headerObj.musicSelectUse) {
4874
+ if (getQueryParamVal(`scoreId`) !== null) {
4875
+ g_headerObj.viewLists = [];
4876
+ g_headerObj.musicNos.forEach((val, j) => {
4877
+ if (val === g_settings.musicIdxNum) {
4878
+ g_headerObj.viewLists.push(j);
4879
+ tmpCreatorList.push(g_headerObj.creatorNames[j]);
4880
+ }
4881
+ });
4882
+ divRoot.appendChild(drawBackArrow(g_headerObj.viewLists[0] + 1));
4883
+ loadLocalStorage(g_settings.musicIdxNum);
4884
+ makeInfoWindow(g_msgInfoObj.W_0041.split(`{0}`).join(g_localStorageUrl), ``,
4885
+ { _backColor: `#333333`, _textColor: `#cccccc`, _pointerEvents: C_DIS_INHERIT });
4886
+ }
4887
+ } else if (!g_headerObj.customTitleArrowUse) {
4888
+ divRoot.appendChild(drawBackArrow());
4889
+ }
4840
4890
 
4841
- // グラデーションの指定がない場合、
4842
- // 矢印色の1番目と3番目を使ってタイトルをグラデーション
4843
- const titlegrd1 = g_headerObj.titlegrds[0] || `${g_headerObj.setColorOrg[0]}:${g_headerObj.setColorOrg[2]}`;
4844
- const titlegrd2 = g_headerObj.titlegrds[1] || titlegrd1;
4891
+ let wheelHandler;
4892
+ if (g_headerObj.musicSelectUse && getQueryParamVal(`scoreId`) === null) {
4845
4893
 
4846
- const titlegrds = [];
4847
- [titlegrd1, titlegrd2].forEach((titlegrd, j) =>
4848
- titlegrds[j] = makeColorGradation(titlegrd, { _defaultColorgrd: false, _objType: `titleMusic` }));
4894
+ // 選曲画面の初期化
4895
+ const wheelCycle = 2;
4896
+ const musicMaxIdx = Math.max(...g_headerObj.musicNos);
4897
+ const musicIdxTmpList = [...Array(musicMaxIdx + 1).keys()];
4898
+
4899
+ /**
4900
+ * メイン以外の選曲ボタンの作成
4901
+ * @param {number} _heightPos
4902
+ * @returns {HTMLDivElement}
4903
+ */
4904
+ const createMSelectBtn = (_heightPos) => createCss2Button(`btnMusicSelect${_heightPos}`,
4905
+ ``, () => changeMSelect(_heightPos), {
4906
+ x: g_btnX(1 / 3) + Math.abs(_heightPos) * 10,
4907
+ y: g_sHeight / 2 + _heightPos * 30 + (_heightPos > 0 ? 1 : -1) * 90,
4908
+ w: g_btnWidth(1 / 2), h: 27, siz: 14, border: `solid 1px #666666`,
4909
+ align: C_ALIGN_LEFT, padding: `0 10px`,
4910
+ }, g_cssObj.button_Default_NoColor, g_cssObj.title_base);
4849
4911
 
4850
- let titlefontsize = 64;
4851
- for (let j = 0; j < g_headerObj.musicTitleForView.length; j++) {
4852
- if (g_headerObj.musicTitleForView[j] !== ``) {
4853
- titlefontsize = getFontSize(g_headerObj.musicTitleForView[j], g_sWidth - 100, g_headerObj.titlefonts[j], titlefontsize);
4912
+ for (let j = -g_settings.mSelectableTerms; j <= g_settings.mSelectableTerms; j++) {
4913
+ if (j !== 0) {
4914
+ divRoot.appendChild(createMSelectBtn(j));
4854
4915
  }
4855
4916
  }
4917
+ createEmptySprite(divRoot, `keyTitleSprite`, g_windowObj.keyTitleSprite);
4918
+ multiAppend(divRoot,
4919
+ createDivCss2Label(`lblMusicSelect`, ``, g_lblPosObj.lblMusicSelect),
4920
+ createDivCss2Label(`lblMusicSelectDetail`, ``, g_lblPosObj.lblMusicSelectDetail),
4921
+ createCss2Button(`btnStart`,
4922
+ `>`, () => {
4923
+ clearTimeout(g_timeoutEvtTitleId);
4924
+ g_handler.removeListener(wheelHandler);
4925
+ g_keyObj.prevKey = `Dummy${g_settings.musicIdxNum}`;
4926
+ }, Object.assign({
4927
+ resetFunc: () => optionInit(),
4928
+ }, g_lblPosObj.btnStart_music), g_cssObj.button_Tweet),
4929
+ createCss2Button(`btnMusicSelectPrev`, `↑`, () => changeMSelect(-1),
4930
+ g_lblPosObj.btnMusicSelectPrev, g_cssObj.button_Setting),
4931
+ createCss2Button(`btnMusicSelectNext`, `↓`, () => changeMSelect(1),
4932
+ g_lblPosObj.btnMusicSelectNext, g_cssObj.button_Setting),
4933
+ createCss2Button(`btnMusicSelectRandom`, `Random`, () =>
4934
+ changeMSelect(Math.floor(Math.random() * musicIdxTmpList.length)),
4935
+ g_lblPosObj.btnMusicSelectRandom, g_cssObj.button_Default),
4936
+ createDivCss2Label(`lblMusicCnt`, ``, g_lblPosObj.lblMusicCnt),
4937
+ createDivCss2Label(`lblComment`, ``, g_lblPosObj.lblComment_music),
4938
+ );
4939
+ changeMSelect(0, _initFlg);
4856
4940
 
4857
- // 変数 titlesize の定義 (使用例: |titlesize=40$20|)
4858
- const titlefontsizes = (g_headerObj.titlesize?.split(`$`).join(`,`).split(`,`) || [titlefontsize, titlefontsize]);
4859
- const titlefontsize1 = setIntVal(titlefontsizes[0], titlefontsize);
4860
- const titlefontsize2 = setIntVal(titlefontsizes[1], titlefontsize1);
4941
+ let wheelCnt = 0;
4942
+ wheelHandler = g_handler.addListener(divRoot, `wheel`, e => {
4943
+ if (g_stateObj.keyInitial && wheelCnt === 0) {
4944
+ e.preventDefault();
4945
+ changeMSelect(e.deltaY > 0 ? 1 : -1);
4946
+ }
4947
+ wheelCnt = (wheelCnt + 1) % wheelCycle;
4948
+ });
4861
4949
 
4862
- // 変数 titlelineheight の定義 (使用例: |titlelineheight=50|)
4863
- const titlelineheight = (g_headerObj.titlelineheight !== `` ? g_headerObj.titlelineheight - (titlefontsize2 + 10) : 0);
4950
+ // 初期表示用 (2秒後に選曲画面を表示)
4951
+ if (_initFlg && !g_headerObj.customTitleUse) {
4952
+ const mSelectTitleSprite = createEmptySprite(divRoot, `mSelectTitleSprite`,
4953
+ g_windowObj.mSelectTitleSprite, g_cssObj.settings_DifSelector);
4954
+ multiAppend(mSelectTitleSprite,
4955
+ drawBackArrow(),
4956
+ drawTitle(g_headerObj.packageNames),
4957
+ );
4864
4958
 
4865
- const txtAnimations = [``, ``];
4866
- if (!g_headerObj.customTitleAnimationUse) {
4867
- for (let j = 0; j < txtAnimations.length; j++) {
4868
- txtAnimations[j] = `animation-name:${g_headerObj.titleAnimationName[j]};
4869
- animation-duration:${g_headerObj.titleAnimationDuration[j]}s;
4870
- animation-delay:${g_headerObj.titleAnimationDelay[j]}s;
4871
- animation-timing-function:${g_headerObj.titleAnimationTimingFunction[j]};`;
4872
- }
4959
+ let spriteOpacity = 1;
4960
+ let fadeOpacity = null;
4961
+ const fadeStartOpacity = setTimeout(() => {
4962
+ clearTimeout(fadeStartOpacity);
4963
+ setOpacity(spriteOpacity);
4964
+ }, 2000);
4965
+
4966
+ const setOpacity = (_opacity) => {
4967
+ if (_opacity <= 0) {
4968
+ clearTimeout(fadeOpacity);
4969
+ mSelectTitleSprite.style.display = C_DIS_NONE;
4970
+ } else {
4971
+ mSelectTitleSprite.style.opacity = _opacity;
4972
+ fadeOpacity = setTimeout(() => {
4973
+ spriteOpacity -= 0.25;
4974
+ setOpacity(spriteOpacity);
4975
+ }, 50);
4976
+ }
4977
+ };
4873
4978
  }
4874
- const lblmusicTitle = createDivCss2Label(`lblmusicTitle`,
4875
- `<div id="lblmusicTitle1" style="
4876
- font-family:${g_headerObj.titlefonts[0]};
4877
- background: ${titlegrds[0]};
4878
- background-clip: text;
4879
- -webkit-background-clip: text;
4880
- color: rgba(255,255,255,0.0);
4881
- ${txtAnimations[0]}
4882
- " class="${g_headerObj.titleAnimationClass[0]}">
4883
- ${g_headerObj.musicTitleForView[0]}
4884
- </div>
4885
- <div id="lblmusicTitle2" style="
4886
- font-size:${wUnit(titlefontsize2)};
4887
- position:relative;left:${wUnit(g_headerObj.titlepos[1][0])};
4888
- top:${wUnit(g_headerObj.titlepos[1][1] + titlelineheight)};
4889
- font-family:${g_headerObj.titlefonts[1]};
4890
- background: ${titlegrds[1]};
4891
- background-clip: text;
4892
- -webkit-background-clip: text;
4893
- color: rgba(255,255,255,0.0);
4894
- ${txtAnimations[1]}
4895
- " class="${g_headerObj.titleAnimationClass[1]}">
4896
- ${g_headerObj.musicTitleForView[1] ?? ``}
4897
- </div>
4898
- `,
4899
- {
4900
- x: Number(g_headerObj.titlepos[0][0]), y: Number(g_headerObj.titlepos[0][1]),
4901
- w: g_sWidth, h: g_sHeight - 40, siz: titlefontsize1,
4902
- display: `flex`, flexDirection: `column`, justifyContent: `center`, alignItems: `center`,
4903
- }
4904
- );
4905
- divRoot.appendChild(lblmusicTitle);
4979
+ } else if (!g_headerObj.customTitleUse) {
4980
+ // 曲名文字描画(曲名は譜面データから取得)
4981
+ divRoot.appendChild(drawTitle(g_headerObj.musicTitlesForView[g_settings.musicIdxNum],
4982
+ g_headerObj.musicSelectUse ? g_headerObj.viewLists[0] + 1 : ``))
4906
4983
  }
4907
4984
 
4985
+ // クレジット表示
4986
+ externalWebTitle();
4987
+
4908
4988
  if (g_errMsgObj.title !== ``) {
4909
4989
  makeWarningWindow();
4910
4990
  }
@@ -4935,17 +5015,38 @@ const titleInit = () => {
4935
5015
  createCss2Button(_id, _text, () => true,
4936
5016
  Object.assign(g_lblPosObj[_id], { siz: getLinkSiz(_text), whiteSpace: `normal`, resetFunc: () => openLink(_url) }), g_cssObj.button_Default);
4937
5017
 
4938
- // ボタン描画
4939
- multiAppend(divRoot,
5018
+ if (g_headerObj.musicSelectUse && getQueryParamVal(`scoreId`) === null) {
5019
+ // 選曲モードではクレジット表示は別で行われているため表示しない
5020
+ } else {
5021
+ if (tmpCreatorList.length === 0) {
5022
+ tmpCreatorList.push(g_headerObj.creatorNames[0]);
5023
+ }
5024
+ const [creatorName, creatorUrl] = getCreatorInfo(tmpCreatorList);
4940
5025
 
4941
- // Click Here
4942
- createCss2Button(`btnStart`, g_lblNameObj.clickHere, () => clearTimeout(g_timeoutEvtTitleId), {
4943
- x: g_btnX(), w: g_btnWidth(), siz: g_limitObj.titleSiz, resetFunc: () => optionInit(),
4944
- }, g_cssObj.button_Start),
5026
+ multiAppend(divRoot,
5027
+
5028
+ // Click Here
5029
+ createCss2Button(`btnStart`, g_lblNameObj.clickHere, () => {
5030
+ clearTimeout(g_timeoutEvtTitleId);
5031
+ g_keyObj.prevKey = `Dummy${g_settings.musicIdxNum}`;
5032
+ }, {
5033
+ x: g_btnX(), w: g_btnWidth(), siz: g_limitObj.titleSiz, resetFunc: () => optionInit(),
5034
+ }, g_cssObj.button_Start),
5035
+
5036
+ // 製作者表示
5037
+ createCreditBtn(`lnkMaker`, `${g_lblNameObj.maker}: ${creatorName}`, creatorUrl),
5038
+
5039
+ // アーティスト表示
5040
+ createCreditBtn(`lnkArtist`, `${g_lblNameObj.artist}: ${g_headerObj.artistNames[g_settings.musicIdxNum]}`, g_headerObj.artistUrls[g_settings.musicIdxNum]),
5041
+ );
5042
+ }
5043
+
5044
+ multiAppend(divRoot,
4945
5045
 
4946
5046
  // Reset
4947
5047
  createCss2Button(`btnReset`, g_lblNameObj.dataReset, () => {
4948
5048
  clearTimeout(g_timeoutEvtTitleId);
5049
+ g_handler.removeListener(wheelHandler);
4949
5050
  dataMgtInit();
4950
5051
  }, g_lblPosObj.btnReset, g_cssObj.button_Reset),
4951
5052
 
@@ -4966,12 +5067,6 @@ const titleInit = () => {
4966
5067
  resetFunc: () => openLink(g_lblNameObj.helpUrl),
4967
5068
  }), g_cssObj.button_Setting),
4968
5069
 
4969
- // 製作者表示
4970
- createCreditBtn(`lnkMaker`, `${g_lblNameObj.maker}: ${g_headerObj.tuningInit}`, g_headerObj.creatorUrl),
4971
-
4972
- // アーティスト表示
4973
- createCreditBtn(`lnkArtist`, `${g_lblNameObj.artist}: ${g_headerObj.artistName}`, g_headerObj.artistUrl),
4974
-
4975
5070
  // バージョン描画
4976
5071
  createCss2Button(`lnkVersion`, versionName, () => true,
4977
5072
  Object.assign(g_lblPosObj.lnkVersion, {
@@ -5041,6 +5136,200 @@ const titleInit = () => {
5041
5136
  g_skinJsObj.title.forEach(func => func());
5042
5137
  };
5043
5138
 
5139
+ /**
5140
+ * 外部のタイトル表示
5141
+ */
5142
+ const externalWebTitle = () => {
5143
+ if (document.getElementById(`webMusicTitle`) !== null) {
5144
+ webMusicTitle.innerHTML =
5145
+ `<span style="font-size:${wUnit(32)}">${g_headerObj.musicTitlesForView[g_settings.musicIdxNum].join(`<br>`)}</span><br>
5146
+ <span style="font-size:${wUnit(16)}">(Artist: <a href="${g_headerObj.artistUrls[g_settings.musicIdxNum]}" target="_blank">${g_headerObj.artistNames[g_settings.musicIdxNum]}</a>)</span>`;
5147
+ }
5148
+ };
5149
+
5150
+ /**
5151
+ * 背景矢印の表示
5152
+ * @param {string|number} _scoreId
5153
+ * @returns {HTMLDivElement}
5154
+ */
5155
+ const drawBackArrow = (_scoreId = ``) =>
5156
+ createColorObject2(`lblArrow`, {
5157
+ x: (g_sWidth - 500) / 2, y: -15 + (g_sHeight - 500) / 2,
5158
+ w: 500, h: 500, rotateEnabled: true,
5159
+ background: makeColorGradation(g_headerObj.titlearrowgrds[0] ||
5160
+ g_headerObj[`setColor${_scoreId}Org`]?.[0] || g_headerObj.setColorOrg[0], {
5161
+ _defaultColorgrd: [false, `#eeeeee`],
5162
+ _objType: `titleArrow`,
5163
+ }), rotate: `titleArrow:${g_headerObj.titleArrowRotate}`,
5164
+ });
5165
+
5166
+ /**
5167
+ * タイトル文字の表示
5168
+ * @param {string[]} _titleName
5169
+ * @returns {HTMLDivElement}
5170
+ */
5171
+ const drawTitle = (_titleName = g_headerObj.musicTitleForView, _scoreId = ``) => {
5172
+
5173
+ // グラデーションの指定がない場合、
5174
+ // 矢印色の1番目と3番目を使ってタイトルをグラデーション
5175
+ const titlegrd1 = g_headerObj.titlegrds[0] || (g_headerObj[`setColor${_scoreId}Org`] ?
5176
+ `${g_headerObj[`setColor${_scoreId}Org`][0]}:${g_headerObj[`setColor${_scoreId}Org`][2]}` : `${g_headerObj.setColorOrg[0]}:${g_headerObj.setColorOrg[2]}`);
5177
+ const titlegrd2 = g_headerObj.titlegrds[1] || titlegrd1;
5178
+
5179
+ const titlegrds = [];
5180
+ [titlegrd1, titlegrd2].forEach((titlegrd, j) =>
5181
+ titlegrds[j] = makeColorGradation(titlegrd, { _defaultColorgrd: false, _objType: `titleMusic` }));
5182
+
5183
+ let titlefontsize = 64;
5184
+ for (let j = 0; j < _titleName.length; j++) {
5185
+ if (_titleName[j] !== ``) {
5186
+ titlefontsize = getFontSize(_titleName[j], g_sWidth - 100, g_headerObj.titlefonts[j], titlefontsize);
5187
+ }
5188
+ }
5189
+
5190
+ // 変数 titlesize の定義 (使用例: |titlesize=40$20|)
5191
+ const titlefontsizes = (g_headerObj.titlesize?.split(`$`).join(`,`).split(`,`) || [titlefontsize, titlefontsize]);
5192
+ const titlefontsize1 = setIntVal(titlefontsizes[0], titlefontsize);
5193
+ const titlefontsize2 = setIntVal(titlefontsizes[1], titlefontsize1);
5194
+
5195
+ // 変数 titlelineheight の定義 (使用例: |titlelineheight=50|)
5196
+ const titlelineheight = (g_headerObj.titlelineheight !== `` ? g_headerObj.titlelineheight - (titlefontsize2 + 10) : 0);
5197
+
5198
+ const txtAnimations = [``, ``];
5199
+ if (!g_headerObj.customTitleAnimationUse) {
5200
+ for (let j = 0; j < txtAnimations.length; j++) {
5201
+ txtAnimations[j] = `animation-name:${g_headerObj.titleAnimationName[j]};
5202
+ animation-duration:${g_headerObj.titleAnimationDuration[j]}s;
5203
+ animation-delay:${g_headerObj.titleAnimationDelay[j]}s;
5204
+ animation-timing-function:${g_headerObj.titleAnimationTimingFunction[j]};`;
5205
+ }
5206
+ }
5207
+ return createDivCss2Label(`lblmusicTitle`,
5208
+ `<div id="lblmusicTitle1" style="
5209
+ font-family:${g_headerObj.titlefonts[0]};
5210
+ background: ${titlegrds[0]};
5211
+ background-clip: text;
5212
+ -webkit-background-clip: text;
5213
+ color: rgba(255,255,255,0.0);
5214
+ ${txtAnimations[0]}
5215
+ " class="${g_headerObj.titleAnimationClass[0]}">
5216
+ ${_titleName[0]}
5217
+ </div>
5218
+ <div id="lblmusicTitle2" style="
5219
+ font-size:${wUnit(titlefontsize2)};
5220
+ position:relative;left:${wUnit(g_headerObj.titlepos[1][0])};
5221
+ top:${wUnit(g_headerObj.titlepos[1][1] + titlelineheight)};
5222
+ font-family:${g_headerObj.titlefonts[1]};
5223
+ background: ${titlegrds[1]};
5224
+ background-clip: text;
5225
+ -webkit-background-clip: text;
5226
+ color: rgba(255,255,255,0.0);
5227
+ ${txtAnimations[1]}
5228
+ " class="${g_headerObj.titleAnimationClass[1]}">
5229
+ ${_titleName[1] ?? ``}
5230
+ </div>
5231
+ `,
5232
+ {
5233
+ x: Number(g_headerObj.titlepos[0][0]), y: Number(g_headerObj.titlepos[0][1]),
5234
+ w: g_sWidth, h: g_sHeight - 40, siz: titlefontsize1,
5235
+ display: `flex`, flexDirection: `column`, justifyContent: `center`, alignItems: `center`,
5236
+ }
5237
+ );
5238
+ };
5239
+
5240
+ /**
5241
+ * 製作者情報の取得
5242
+ * @param {string[]} _creatorList
5243
+ * @returns [string, string, number]
5244
+ */
5245
+ const getCreatorInfo = (_creatorList) => {
5246
+ const creatorName = makeDedupliArray(_creatorList).length === 1 ? _creatorList[0] : `Various`;
5247
+ g_headerObj.makerView = g_headerObj.makerViewOrg ? true : creatorName === `Various`;
5248
+ const creatorIdx = g_headerObj.tuningNames.findIndex(val => val === creatorName);
5249
+ const creatorUrl = creatorIdx >= 0 ? g_headerObj.tuningUrls[creatorIdx] : ``;
5250
+ return [creatorName, creatorUrl, creatorIdx];
5251
+ }
5252
+
5253
+ /**
5254
+ * 選曲ボタンを押したときの処理
5255
+ * @param {number} _num
5256
+ * @param {boolean} _initFlg
5257
+ */
5258
+ const changeMSelect = (_num, _initFlg = false) => {
5259
+ const limitedMLength = 35;
5260
+ const musicMaxIdx = Math.max(...g_headerObj.musicNos);
5261
+ const musicIdxTmpList = [...Array(musicMaxIdx + 1).keys()];
5262
+
5263
+ // 選択方向に合わせて楽曲リスト情報を再取得
5264
+ for (let j = -g_settings.mSelectableTerms; j <= g_settings.mSelectableTerms; j++) {
5265
+ const idx = (j + _num + g_settings.musicIdxNum + musicIdxTmpList.length * 10) % musicIdxTmpList.length;
5266
+ if (j === 0) {
5267
+ } else {
5268
+ document.getElementById(`btnMusicSelect${j}`).style.fontSize =
5269
+ getFontSize(g_headerObj.musicTitles[idx].slice(0, limitedMLength), g_btnWidth(1 / 2), getBasicFont(), 14);
5270
+ document.getElementById(`btnMusicSelect${j}`).innerHTML =
5271
+ `${g_headerObj.musicTitles[idx].slice(0, limitedMLength)}${g_headerObj.musicTitles[idx].length > limitedMLength ? '...' : ''}<br>` +
5272
+ `<span style="font-size:0.7em;line-height:9px"> / ${g_headerObj.artistNames[idx]}</span>`;
5273
+ }
5274
+ }
5275
+ // 現在選択中の楽曲IDを再設定
5276
+ g_settings.musicIdxNum = (g_settings.musicIdxNum + _num + musicIdxTmpList.length) % musicIdxTmpList.length;
5277
+
5278
+ // 選択した楽曲に対応する譜面番号、製作者情報、曲長を取得
5279
+ g_headerObj.viewLists = [];
5280
+ const tmpKeyList = [], tmpCreatorList = [], tmpPlayingFrameList = [];
5281
+ g_headerObj.musicNos.forEach((val, j) => {
5282
+ if (val === (g_settings.musicIdxNum + musicIdxTmpList.length * 20) %
5283
+ musicIdxTmpList.length) {
5284
+ g_headerObj.viewLists.push(j);
5285
+ tmpKeyList.push(g_headerObj.keyLabels[j]);
5286
+ tmpCreatorList.push(g_headerObj.creatorNames[j]);
5287
+ tmpPlayingFrameList.push(g_detailObj.playingFrameWithBlank[j]);
5288
+ }
5289
+ });
5290
+ const playingFrames = makeDedupliArray(tmpPlayingFrameList.sort((a, b) => a - b).map(val => transFrameToTimer(val))).join(`, `);
5291
+ const [creatorName, creatorUrl, creatorIdx] = getCreatorInfo(tmpCreatorList);
5292
+ const creatorLink = creatorIdx >= 0 ?
5293
+ `<a href="${creatorUrl}" target="_blank">${creatorName}</a>` : creatorName;
5294
+
5295
+ // 選択した楽曲の情報表示
5296
+ const idx = g_settings.musicIdxNum;
5297
+ document.getElementById(`lblMusicSelect`).innerHTML =
5298
+ `<span style="font-size:${getFontSize(g_headerObj.musicTitlesForView[idx].join(`<br>`), g_btnWidth(1 / 2), getBasicFont(), 18)}px;` +
5299
+ `font-weight:bold">${g_headerObj.musicTitlesForView[idx].join(`<br>`)}</span>`;
5300
+ document.getElementById(`lblMusicSelectDetail`).innerHTML =
5301
+ `Maker: ${creatorLink} / Artist: <a href="${g_headerObj.artistUrls[idx]}" target="_blank">` +
5302
+ `${g_headerObj.artistNames[idx]}</a><br>Duration: ${playingFrames} / BPM: ${g_headerObj.bpms[idx]}`;
5303
+
5304
+ // 選択した楽曲で使われているキー種の一覧を作成
5305
+ deleteChildspriteAll(`keyTitleSprite`);
5306
+ makeDedupliArray(tmpKeyList).sort((a, b) => parseInt(a) - parseInt(b))
5307
+ .forEach((val, j) => keyTitleSprite.appendChild(
5308
+ createDivCss2Label(`btnKeyTitle${val}`, val,
5309
+ Object.assign({ x: 10 + j * 40 }, g_lblPosObj.btnKeyTitle)
5310
+ )));
5311
+
5312
+
5313
+ // 選択した楽曲の選択位置を表示
5314
+ lblMusicCnt.innerHTML = `${g_settings.musicIdxNum + 1} / ${musicMaxIdx + 1}`;
5315
+
5316
+ // 楽曲別のローカルストレージを再取得
5317
+ loadLocalStorage(g_settings.musicIdxNum);
5318
+ viewKeyStorage.cache = new Map();
5319
+
5320
+ // 初期化もしくは楽曲変更時に速度を初期化
5321
+ if (_initFlg || _num !== 0) {
5322
+ g_stateObj.speed = g_headerObj.initSpeeds[g_headerObj.viewLists[0]];
5323
+ g_settings.speedNum = getCurrentNo(g_settings.speeds, g_stateObj.speed);
5324
+ }
5325
+
5326
+ // コメント文の加工
5327
+ lblComment.innerHTML = convertStrToVal(g_headerObj[`commentVal${g_settings.musicIdxNum}`]);
5328
+
5329
+ // 選曲変更時のカスタム関数実行
5330
+ g_customJsObj.musicSelect.forEach(func => func(g_settings.musicIdxNum));
5331
+ };
5332
+
5044
5333
  /**
5045
5334
  * 警告用ウィンドウ(汎用)を表示
5046
5335
  * @param {string} _text
@@ -5073,10 +5362,12 @@ const makeWarningWindow = (_text = ``, { resetFlg = false, backBtnUse = false }
5073
5362
  * @param {string} _text
5074
5363
  * @param {string} _animationName
5075
5364
  * @param {string} [object._backColor='#ccccff']
5365
+ * @param {string} [object._textColor='#000066']
5366
+ * @param {string} [object._pointerEvents=C_DIS_NONE]
5076
5367
  */
5077
- const makeInfoWindow = (_text, _animationName = ``, { _backColor = `#ccccff` } = {}) => {
5078
- const lblWarning = setWindowStyle(`<p>${_text}</p>`, _backColor, `#000066`, C_ALIGN_CENTER);
5079
- lblWarning.style.pointerEvents = C_DIS_NONE;
5368
+ const makeInfoWindow = (_text, _animationName = ``, { _backColor = `#ccccff`, _textColor = `#000066`, _pointerEvents = C_DIS_NONE } = {}) => {
5369
+ const lblWarning = setWindowStyle(`<p>${_text}</p>`, _backColor, _textColor, C_ALIGN_CENTER);
5370
+ lblWarning.style.pointerEvents = _pointerEvents;
5080
5371
 
5081
5372
  if (_animationName !== ``) {
5082
5373
  lblWarning.style.animationName = _animationName;
@@ -5318,7 +5609,7 @@ const dataMgtInit = () => {
5318
5609
  }
5319
5610
  });
5320
5611
  reloadFlg = true;
5321
- sessionStorage.setItem('resetBackup', JSON.stringify(Array.from(backupData.entries())));
5612
+ sessionStorage.setItem(`resetBackup${g_settings.musicIdxNum}`, JSON.stringify(Array.from(backupData.entries())));
5322
5613
  }
5323
5614
  }, Object.assign(g_lblPosObj.btnResetN, {
5324
5615
  visibility: g_langStorage.safeMode === C_FLG_OFF ? C_DIS_INHERIT : `hidden`,
@@ -5331,7 +5622,7 @@ const dataMgtInit = () => {
5331
5622
 
5332
5623
  // リカバリー用のボタン
5333
5624
  createCss2Button(`btnUndo`, g_lblNameObj.b_undo, () => {
5334
- const backup = JSON.parse(sessionStorage.getItem('resetBackup'));
5625
+ const backup = JSON.parse(sessionStorage.getItem(`resetBackup${g_settings.musicIdxNum}`));
5335
5626
  if (backup && window.confirm(g_msgObj.dataRestoreConfirm)) {
5336
5627
  backup.forEach(([key, data]) => {
5337
5628
  if (g_resetFunc.has(key) || keyList.includes(key.slice(`XX`.length))) {
@@ -5341,12 +5632,12 @@ const dataMgtInit = () => {
5341
5632
  localStorage.setItem(`danonicw-${key}k`, JSON.stringify(data));
5342
5633
  }
5343
5634
  });
5344
- sessionStorage.removeItem('resetBackup');
5635
+ sessionStorage.removeItem(`resetBackup${g_settings.musicIdxNum}`);
5345
5636
  location.reload();
5346
5637
  }
5347
5638
  }, g_lblPosObj.btnUndo, g_cssObj.button_Tweet)
5348
5639
  );
5349
- if (sessionStorage.getItem('resetBackup') === null) {
5640
+ if (sessionStorage.getItem(`resetBackup${g_settings.musicIdxNum}`) === null) {
5350
5641
  btnUndo.style.display = C_DIS_NONE;
5351
5642
  }
5352
5643
 
@@ -5537,6 +5828,15 @@ const optionInit = () => {
5537
5828
  g_currentPage = `option`;
5538
5829
  g_stateObj.filterKeys = ``;
5539
5830
 
5831
+ // 楽曲データの表示
5832
+ let text = `♪` + (g_headerObj.musicSelectUse ? `${g_headerObj.musicTitles[g_settings.musicIdxNum]} / ` : ``) +
5833
+ `BPM: ${g_headerObj.bpms[g_settings.musicIdxNum]}`;
5834
+ if (!g_headerObj.musicSelectUse && g_headerObj.bpms[g_settings.musicIdxNum] === `----`) {
5835
+ text = ``;
5836
+ }
5837
+ divRoot.appendChild(createDivCss2Label(`lblMusicInfo`, text,
5838
+ Object.assign({ siz: getFontSize(text, g_btnWidth(3 / 4), getBasicFont(), 12) }, g_lblPosObj.lblMusicInfo)));
5839
+
5540
5840
  // タイトル文字描画
5541
5841
  divRoot.appendChild(getTitleDivLabel(`lblTitle`, g_lblNameObj.settings, 0, 15, `settings_Title`));
5542
5842
 
@@ -6165,7 +6465,7 @@ const makeHighScore = _scoreId => {
6165
6465
 
6166
6466
  // 結果をクリップボードへコピー (ハイスコア保存分)
6167
6467
  if (g_localStorage.highscores?.[scoreName] !== undefined) {
6168
- const twiturl = new URL(g_localStorageUrl);
6468
+ const twiturl = new URL(g_localStorageUrlOrg);
6169
6469
  twiturl.searchParams.append(`scoreId`, _scoreId);
6170
6470
  const baseTwitUrl = g_isLocal ? `` : `${twiturl.toString()}`.replace(/[\t\n]/g, ``);
6171
6471
 
@@ -9143,6 +9443,7 @@ const scoreConvert = (_dosObj, _scoreId, _preblankFrame, _dummyNo = ``,
9143
9443
  const mergeColorData = (_header = ``) => {
9144
9444
  if (obj[`color${_header}Data`] === undefined) return [];
9145
9445
  const tmpArr = obj[`color${_header}Data`].concat(obj[`acolor${_header}Data`]);
9446
+ delete obj[`acolor${_header}Data`];
9146
9447
  return tmpArr.sort((_a, _b) => _a[0] - _b[0]).flat();
9147
9448
  };
9148
9449
 
@@ -10491,6 +10792,41 @@ const getArrowSettings = () => {
10491
10792
  g_workObj.dividePosDefault = g_workObj.dividePos.concat();
10492
10793
  g_stateObj.layerNum = Math.max(g_stateObj.layerNum, Math.ceil((Math.max(...g_workObj.dividePos) + 1) / 2) * 2);
10493
10794
 
10795
+ // g_workObjの不要なプロパティを削除
10796
+ if (g_stateObj.dummyId === ``) {
10797
+ Object.keys(g_workObj).filter(key => key.startsWith(`dummy`) || key.startsWith(`mkDummy`))
10798
+ .forEach(key => delete g_workObj[key]);
10799
+ }
10800
+ const targetColorKeys = [`mkColor`, `mkColorShadow`];
10801
+ const usedColorKeys = [];
10802
+ targetColorKeys.push(...g_typeLists.frzColor.map(type => `mkFColor${type}`));
10803
+ targetColorKeys.forEach(key => {
10804
+ if (g_workObj[key].length === 0) {
10805
+ delete g_workObj[key];
10806
+ delete g_workObj[`${key}Cd`];
10807
+ } else {
10808
+ usedColorKeys.push(key);
10809
+ }
10810
+ });
10811
+ [`Arrow`, `Step`].forEach(type => {
10812
+ [``, `Dir`, `Layer`].forEach(type2 => {
10813
+ if (g_workObj[`mkScrollch${type}${type2}`].length === 0) {
10814
+ delete g_workObj[`mkScrollch${type}${type2}`];
10815
+ }
10816
+ });
10817
+ });
10818
+ [`Arrow`, `Frz`].forEach(type => {
10819
+ if (g_workObj[`mk${type}ColorChangeAll`].length === 0) {
10820
+ delete g_workObj[`mk${type}ColorChangeAll`];
10821
+ }
10822
+ [``, `Name`].forEach(type2 => {
10823
+ if (g_workObj[`mk${type}CssMotion${type2}`].length === 0) {
10824
+ delete g_workObj[`mk${type}CssMotion${type2}`];
10825
+ }
10826
+ });
10827
+ });
10828
+
10829
+ // 初期位置、ライフ設定の初期化
10494
10830
  Object.keys(g_resultObj).forEach(judgeCnt => g_resultObj[judgeCnt] = 0);
10495
10831
  g_resultObj.spState = ``;
10496
10832
 
@@ -10592,7 +10928,7 @@ const getArrowSettings = () => {
10592
10928
  const _copiedArray = structuredClone(_array);
10593
10929
  return _array.map((_val, _i) => _array[_i] = randArray[_copiedArray[_i]]);
10594
10930
  };
10595
- [`mkColor`, `mkColorShadow`, `mkFColor`, `mkFColorShadow`].forEach(type => {
10931
+ usedColorKeys.forEach(type => {
10596
10932
  if (g_workObj[type] !== undefined) {
10597
10933
  for (let j = 0; j < g_workObj[type].length; j++) {
10598
10934
  if (g_workObj[type][j] === undefined) {
@@ -11218,7 +11554,7 @@ const mainInit = () => {
11218
11554
  * @param {string} _name 通常, ダミー
11219
11555
  */
11220
11556
  const changeArrowColor = (_j, _k, _name) => {
11221
- if (g_workObj[`mk${toCapitalize(_name)}ColorChangeAll`][g_scoreObj.frameNum]) {
11557
+ if (g_workObj[`mk${toCapitalize(_name)}ColorChangeAll`]?.[g_scoreObj.frameNum]) {
11222
11558
 
11223
11559
  /**
11224
11560
  * 全体色の変更処理
@@ -11256,7 +11592,7 @@ const mainInit = () => {
11256
11592
  */
11257
11593
  const changeFrzColor = (_j, _k, _name, _state) => {
11258
11594
 
11259
- if (g_workObj[`mk${toCapitalize(_name)}ColorChangeAll`][g_scoreObj.frameNum]) {
11595
+ if (g_workObj[`mk${toCapitalize(_name)}ColorChangeAll`]?.[g_scoreObj.frameNum]) {
11260
11596
  const frzNo = `${_j}_${_k}`;
11261
11597
  const frzTop = document.getElementById(`${_name}Top${frzNo}`);
11262
11598
  const frzBar = document.getElementById(`${_name}Bar${frzNo}`);
@@ -11863,13 +12199,13 @@ const mainInit = () => {
11863
12199
 
11864
12200
  // 個別・全体色変化 (矢印)
11865
12201
  g_typeLists.arrowColor.forEach(ctype =>
11866
- changeColors(g_workObj[`mk${headerU}Color${ctype}`][currentFrame],
11867
- g_workObj[`mk${headerU}Color${ctype}Cd`][currentFrame], header, `arrow${ctype}`));
12202
+ changeColors(g_workObj[`mk${headerU}Color${ctype}`]?.[currentFrame],
12203
+ g_workObj[`mk${headerU}Color${ctype}Cd`]?.[currentFrame], header, `arrow${ctype}`));
11868
12204
 
11869
12205
  // 個別・全体色変化(フリーズアロー)
11870
12206
  g_typeLists.frzColor.forEach(ctype =>
11871
- changeColors(g_workObj[`mk${headerU}FColor${ctype}`][currentFrame],
11872
- g_workObj[`mk${headerU}FColor${ctype}Cd`][currentFrame], header, `frz${ctype}`));
12207
+ changeColors(g_workObj[`mk${headerU}FColor${ctype}`]?.[currentFrame],
12208
+ g_workObj[`mk${headerU}FColor${ctype}Cd`]?.[currentFrame], header, `frz${ctype}`));
11873
12209
 
11874
12210
  // 矢印モーション
11875
12211
  changeCssMotions(header, `arrow`, currentFrame);
@@ -11892,7 +12228,7 @@ const mainInit = () => {
11892
12228
  changeStepY(currentFrame);
11893
12229
 
11894
12230
  // ダミー矢印生成(背面に表示するため先に処理)
11895
- g_workObj.mkDummyArrow[currentFrame]?.forEach(data =>
12231
+ g_workObj.mkDummyArrow?.[currentFrame]?.forEach(data =>
11896
12232
  makeArrow(data, ++dummyArrowCnts[data.pos], `dummyArrow`, g_workObj.dummyArrowColors[data.pos], g_workObj.dummyArrowShadowColors[data.pos]));
11897
12233
 
11898
12234
  // 矢印生成
@@ -11900,7 +12236,7 @@ const mainInit = () => {
11900
12236
  makeArrow(data, ++arrowCnts[data.pos], `arrow`, g_workObj.arrowColors[data.pos], g_workObj.arrowShadowColors[data.pos]));
11901
12237
 
11902
12238
  // ダミーフリーズアロー生成
11903
- g_workObj.mkDummyFrzArrow[currentFrame]?.forEach(data =>
12239
+ g_workObj.mkDummyFrzArrow?.[currentFrame]?.forEach(data =>
11904
12240
  makeFrzArrow(data, ++dummyFrzCnts[data.pos], `dummyFrz`, g_workObj.dummyFrzNormalColors[data.pos],
11905
12241
  g_workObj.dummyFrzNormalBarColors[data.pos], g_workObj.dummyFrzNormalShadowColors[data.pos]));
11906
12242
 
@@ -12315,7 +12651,7 @@ const changeColors = (_mkColor, _mkColorCd, _header, _name) => {
12315
12651
  */
12316
12652
  const changeCssMotions = (_header, _name, _frameNum) => {
12317
12653
  const camelHeader = _header === `` ? _name : `${_header}${toCapitalize(_name)}`;
12318
- g_workObj[`mk${toCapitalize(camelHeader)}CssMotion`][_frameNum]?.forEach((targetj, j) =>
12654
+ g_workObj[`mk${toCapitalize(camelHeader)}CssMotion`]?.[_frameNum]?.forEach((targetj, j) =>
12319
12655
  g_workObj[`${camelHeader}CssMotions`][targetj] =
12320
12656
  g_workObj[`mk${toCapitalize(camelHeader)}CssMotionName`][_frameNum][2 * j + (g_workObj.dividePos[targetj] % 2)]);
12321
12657
  };
@@ -12325,7 +12661,7 @@ const changeCssMotions = (_header, _name, _frameNum) => {
12325
12661
  * @param {number} _frameNum
12326
12662
  */
12327
12663
  const changeScrollArrowDirs = (_frameNum) =>
12328
- g_workObj.mkScrollchArrow[_frameNum]?.forEach((targetj, j) => {
12664
+ g_workObj.mkScrollchArrow?.[_frameNum]?.forEach((targetj, j) => {
12329
12665
  g_workObj.scrollDir[targetj] = g_workObj.scrollDirDefault[targetj] * g_workObj.mkScrollchArrowDir[_frameNum][j];
12330
12666
  const baseLayer = g_workObj.mkScrollchArrowLayer[_frameNum][j] === -1 ?
12331
12667
  Math.floor(g_workObj.dividePosDefault[targetj] / 2) : g_workObj.mkScrollchArrowLayer[_frameNum][j];
@@ -12337,7 +12673,7 @@ const changeScrollArrowDirs = (_frameNum) =>
12337
12673
  * @param {number} _frameNum
12338
12674
  */
12339
12675
  const changeStepY = (_frameNum) =>
12340
- g_workObj.mkScrollchStep[_frameNum]?.forEach((targetj, j) => {
12676
+ g_workObj.mkScrollchStep?.[_frameNum]?.forEach((targetj, j) => {
12341
12677
  const dividePos = (g_workObj.scrollDirDefault[targetj] * g_workObj.mkScrollchStepDir[_frameNum][j] === 1 ? 0 : 1);
12342
12678
 
12343
12679
  // 移動元のステップゾーンの不透明度、表示・非表示を退避
@@ -13397,7 +13733,7 @@ const resultInit = () => {
13397
13733
  }
13398
13734
  clearTimeout(g_timeoutEvtId);
13399
13735
  clearTimeout(g_timeoutEvtResultId);
13400
- }, Object.assign(_posObj, { resetFunc: _func }), _cssClass);
13736
+ }, Object.assign(_posObj, { resetFunc: () => _func() }), _cssClass);
13401
13737
 
13402
13738
  /**
13403
13739
  * 外部リンクボタンを作成
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2019/11/19
8
- * Revised : 2025/03/22 (v40.7.0)
8
+ * Revised : 2025/04/12 (v41.0.0)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -191,6 +191,8 @@ const getScMsg = {
191
191
  */
192
192
  const updateWindowSiz = () => {
193
193
  Object.assign(g_windowObj, {
194
+ keyTitleSprite: { x: g_btnX(1 / 4), y: g_sHeight / 2 - 5, w: g_btnWidth(1 / 2), h: 16 },
195
+ mSelectTitleSprite: { x: g_btnX(), y: 0, w: g_btnWidth(), h: g_sHeight, clipPath: `inset(12% 0 8% 0)` },
194
196
  optionSprite: { x: (g_sWidth - 450) / 2, y: 65, w: 450, h: 325 },
195
197
  dataSprite: { x: g_btnX() + (g_sWidth - Math.max(g_sWidth - 100, 450)) / 2, y: 65, w: Math.max(g_sWidth - 100, 450), h: 325 },
196
198
  keyListSprite: { x: 0, y: g_limitObj.setLblHeight * 7.5 + 40, w: 150, h: g_sHeight - 380, overflow: C_DIS_AUTO },
@@ -242,6 +244,51 @@ const updateWindowSiz = () => {
242
244
  x: g_btnX(1) - 160, y: (g_sHeight / 2) + 150, w: 140, h: 50, siz: 20, border: `solid 1px #999999`,
243
245
  },
244
246
 
247
+ lblMusicSelect: {
248
+ x: g_btnX(1 / 4), y: g_sHeight / 2 - 90,
249
+ w: g_btnWidth(5 / 8), h: 206, siz: 14, border: `solid 1px #006666`,
250
+ align: C_ALIGN_LEFT, padding: `0 10px`, display: `inline-block`,
251
+ },
252
+ lblMusicSelectDetail: {
253
+ x: g_btnX(1 / 4), y: g_sHeight / 2 - 45,
254
+ w: g_btnWidth(5 / 8), h: 50, siz: 14,
255
+ align: C_ALIGN_LEFT, padding: `0 10px`, display: `inline-block`,
256
+ pointerEvents: C_DIS_INHERIT,
257
+ },
258
+ btnStart_music: {
259
+ x: g_btnX(27 / 32), y: g_sHeight / 2 - 90,
260
+ w: g_btnWidth(1 / 16), h: 206, siz: 24, padding: `0 10px`,
261
+ border: `solid 1px #006666`,
262
+ },
263
+ btnMusicSelectPrev: {
264
+ x: g_btnX(1 / 4), y: g_sHeight / 2 - 134,
265
+ w: 30, h: 40, siz: 20, padding: `0 10px`,
266
+ border: `solid 1px #666600`,
267
+ },
268
+ btnMusicSelectNext: {
269
+ x: g_btnX(1 / 4), y: g_sHeight / 2 + 120,
270
+ w: 30, h: 40, siz: 20, padding: `0 10px`,
271
+ border: `solid 1px #666600`,
272
+ },
273
+ btnMusicSelectRandom: {
274
+ x: g_btnX(1 / 4) - 80, y: g_sHeight / 2 - 134,
275
+ w: 55, h: 40, siz: 14, padding: `0 10px`,
276
+ border: `solid 1px #666666`,
277
+ },
278
+ btnKeyTitle: {
279
+ y: 0, w: 35, h: 16, siz: 14,
280
+ border: `solid 1px #666666`,
281
+ },
282
+ lblMusicCnt: {
283
+ x: g_btnX(1 / 4) - 80, y: g_sHeight / 2 - 90,
284
+ w: 80, h: 20, siz: 14, align: C_ALIGN_CENTER,
285
+ },
286
+ lblComment_music: {
287
+ x: g_btnX(1 / 4) + 10, y: g_sHeight / 2 + 15, w: g_btnWidth(7 / 12) - 5, h: 100,
288
+ siz: g_limitObj.difSelectorSiz, align: C_ALIGN_LEFT,
289
+ overflow: C_DIS_AUTO, whiteSpace: `normal`,
290
+ },
291
+
245
292
  /** データ管理 */
246
293
  btnResetBack: {
247
294
  x: g_btnX(), y: g_sHeight - 20, w: g_btnWidth(1 / 4), h: 16, siz: 12,
@@ -305,8 +352,11 @@ const updateWindowSiz = () => {
305
352
  title: g_msgObj.dataSave, borderStyle: `solid`,
306
353
  },
307
354
 
355
+ lblMusicInfo: {
356
+ x: g_btnX(1 / 4), y: 0, w: g_btnWidth(3 / 4), h: 20, align: C_ALIGN_RIGHT
357
+ },
308
358
  lblBaseSpd: {
309
- x: g_sWidth - 100, y: 0, w: 100, h: 20, siz: 14,
359
+ x: g_sWidth - 100, y: 11, w: 100, h: 20, siz: 13, align: C_ALIGN_RIGHT
310
360
  },
311
361
  btnReverse: {
312
362
  x: 160, y: 0, w: 90, h: 21, siz: g_limitObj.difSelectorSiz, borderStyle: `solid`,
@@ -948,6 +998,7 @@ let C_WOD_FRAME = 30;
948
998
 
949
999
  // 譜面データ持ち回り用
950
1000
  const g_stateObj = {
1001
+ keyInitial: false,
951
1002
  dosDivideFlg: false,
952
1003
  scoreLockFlg: false,
953
1004
  scoreId: 0,
@@ -1063,6 +1114,7 @@ const makeSpeedList = (_minSpd, _maxSpd) => [...Array((_maxSpd - _minSpd) * 20 +
1063
1114
  // 設定系全般管理
1064
1115
  const g_settings = {
1065
1116
 
1117
+ musicIdxNum: 0,
1066
1118
  dataMgtNum: {
1067
1119
  environment: 0,
1068
1120
  highscores: 0,
@@ -1073,6 +1125,8 @@ const g_settings = {
1073
1125
  keyStorages: [`reverse`, `keyCtrl`, `keyCtrlPtn`, `shuffle`, `color`, `stepRtn`],
1074
1126
  colorStorages: [`setColor`, `setShadowColor`, `frzColor`, `frzShadowColor`],
1075
1127
 
1128
+ mSelectableTerms: 3,
1129
+
1076
1130
  speeds: makeSpeedList(C_MIN_SPEED, C_MAX_SPEED),
1077
1131
  speedNum: 0,
1078
1132
  speedTerms: [20, 5, 1],
@@ -2019,6 +2073,8 @@ const g_shortcutObj = {
2019
2073
  ControlLeft_KeyC: { id: `` },
2020
2074
  KeyC: { id: `btnComment` },
2021
2075
  KeyD: { id: `btnReset` },
2076
+ ArrowUp: { id: `btnMusicSelectPrev` },
2077
+ ArrowDown: { id: `btnMusicSelectNext` },
2022
2078
  },
2023
2079
  dataMgt: {
2024
2080
  KeyE: { id: `btnEnvironment` },
@@ -3486,6 +3542,7 @@ const g_lang_msgInfoObj = {
3486
3542
  W_0021: `クリップボードのコピーに失敗しました。`,
3487
3543
  W_0031: `セーフモード適用中です。ローカルストレージ情報を使わない設定になっています。<br>
3488
3544
  「Data Management」から解除が可能です。(W-0031)`,
3545
+ W_0041: `選曲単品モードが有効になっています。<br><a href="{0}">[ 選曲画面へ戻る ]</a>`,
3489
3546
 
3490
3547
  E_0011: `アーティスト名が未入力です。(E-0011)`,
3491
3548
  E_0012: `曲名情報が未設定です。(E-0012)<br>
@@ -3541,6 +3598,7 @@ const g_lang_msgInfoObj = {
3541
3598
  W_0031: `Safe Mode is being applied. <br>
3542
3599
  The setting is set to not use local storage information <br>
3543
3600
  and can be removed from Data Management. (W-0031)`,
3601
+ W_0041: `The single music selection mode is enabled.<br><a href="{0}">[ Return to the original page ]</a>`,
3544
3602
 
3545
3603
  E_0011: `The artist name is not set. (E-0011)`,
3546
3604
  E_0012: `The song title information is not set. (E-0012)<br>
@@ -4175,6 +4233,7 @@ const g_customJsObj = {
4175
4233
  preTitle: [],
4176
4234
  title: [],
4177
4235
  titleEnterFrame: [],
4236
+ musicSelect: [],
4178
4237
  dataMgt: [],
4179
4238
  precondition: [],
4180
4239
  option: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "40.7.1",
3
+ "version": "41.0.0",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
6
  "scripts": {