danoniplus 40.5.4 → 40.7.0-a1

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.
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/14
7
+ * Revised : 2025/03/22
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 40.5.4`;
12
- const g_revisedDate = `2025/03/14`;
11
+ const g_version = `Ver 40.7.0`;
12
+ const g_revisedDate = `2025/03/22`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -58,7 +58,7 @@ Object.freeze(g_reservedDomains);
58
58
  // 外部参照を許可するドメイン
59
59
  const g_referenceDomains = [
60
60
  `cwtickle.github.io/danoniplus`,
61
- `cdn.jsdelivr.net/npm`,
61
+ `cdn.jsdelivr.net`,
62
62
  `unpkg.com`,
63
63
  `support-v\\d+--danoniplus.netlify.app`,
64
64
  ];
@@ -66,7 +66,8 @@ Object.freeze(g_referenceDomains);
66
66
 
67
67
  const g_rootPath = current().match(/(^.*\/)/)[0];
68
68
  const g_workPath = new URL(location.href).href.match(/(^.*\/)/)[0];
69
- const g_remoteFlg = g_referenceDomains.some(domain => g_rootPath.match(`^https://${domain}/`) !== null);
69
+ const hasRemoteDomain = _path => g_referenceDomains.some(domain => _path.match(`^https://${domain}/`) !== null);
70
+ const g_remoteFlg = hasRemoteDomain(g_rootPath);
70
71
 
71
72
  const g_randTime = Date.now();
72
73
  const g_isFile = location.href.match(/^file/);
@@ -76,7 +77,7 @@ const g_isDebug = g_isLocal ||
76
77
  getQueryParamVal(`debug`) === `true`;
77
78
  const isLocalMusicFile = _scoreId => g_isFile && !listMatching(getMusicUrl(_scoreId), [`.js`, `.txt`], { suffix: `$` });
78
79
 
79
- window.onload = async () => {
80
+ document.addEventListener('DOMContentLoaded', async () => {
80
81
  g_loadObj.main = true;
81
82
  g_currentPage = `initial`;
82
83
  const links = document.querySelectorAll(`link`);
@@ -89,7 +90,7 @@ window.onload = async () => {
89
90
  await loadScript2(`${g_rootPath}../js/lib/danoni_constants.js?${g_randTime}`);
90
91
  await loadScript2(`${g_rootPath}../js/lib/legacy_functions.js?${g_randTime}`, false);
91
92
  initialControl();
92
- };
93
+ });
93
94
 
94
95
  /*-----------------------------------------------------------*/
95
96
  /* Scene : COMMON [water] */
@@ -524,7 +525,9 @@ const formatObject = (_obj, _indent = 0, { colorFmt = true, rootKey = `` } = {})
524
525
  // カラーコードの色付け処理
525
526
  _value = escapeHtml(_value).replaceAll(`\n`, `<br>`);
526
527
  const colorCodePattern = /(#|0x)(?:[A-Fa-f0-9]{6}(?:[A-Fa-f0-9]{2})?|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{3})/g;
527
- if (colorCodePattern.test(_value)) {
528
+ if (_value === C_FLG_ON) {
529
+ return `<span style="color:#66ff66">&#x2714; ON</span>`;
530
+ } else if (colorCodePattern.test(_value)) {
528
531
  return _value.replace(colorCodePattern, (match) =>
529
532
  `<span style="color:${match.replace(`0x`, `#`)}">◆</span>${match.replace(`0x`, `#`)}`);
530
533
  }
@@ -540,8 +543,9 @@ const formatObject = (_obj, _indent = 0, { colorFmt = true, rootKey = `` } = {})
540
543
  // scrollDirXのスクロール方向表示処理
541
544
  return _value === 1 ? `1|<span style="color:#ff9999">↑</span>` : `-1|<span style="color:#66ff66">↓</span>`;
542
545
 
543
- } else if (_rootKey.startsWith(`keyCtrl`) && !_rootKey.startsWith(`keyCtrlPtn`)) {
544
- // keyCtrlXの対応キー表示処理
546
+ } else if (listMatching(_rootKey, [`keyCtrl`, `keyRetry`, `keyTitleBack`], { prefix: `^` })
547
+ && !_rootKey.startsWith(`keyCtrlPtn`)) {
548
+ // keyCtrlX, keyRetryX, keyTitleBackX の対応キー表示処理
545
549
  return (g_kCd[_value] && _value !== 0) ? `${_value}|<span style="color:#ffff66">${g_kCd[_value]}</span>` : `----`;
546
550
  }
547
551
  } else if (isObj(_value)) {
@@ -1101,6 +1105,9 @@ const loadMultipleFiles2 = async (_fileData, _loadType) => {
1101
1105
  * @returns {string[]} [ファイルキーワード, ルートディレクトリ]
1102
1106
  */
1103
1107
  const getFilePath = (_fileName, _directory = ``) => {
1108
+ if (_fileName.startsWith(`https://`)) {
1109
+ return [_fileName, ``];
1110
+ }
1104
1111
  let fullPath;
1105
1112
  if (_fileName.startsWith(C_MRK_CURRENT_DIRECTORY)) {
1106
1113
  fullPath = `${g_workPath}${_fileName.slice(C_MRK_CURRENT_DIRECTORY.length)}`;
@@ -2598,6 +2605,19 @@ const initialControl = async () => {
2598
2605
  const customKeyList = g_headerObj.keyLists.filter(val =>
2599
2606
  g_keyObj.defaultKeyList.findIndex(key => key === val) < 0);
2600
2607
 
2608
+ if (customKeyList.length === 0) {
2609
+ g_settings.preconditions = g_settings.preconditions.filter(val => !val.includes(`g_editorTmp`));
2610
+ }
2611
+
2612
+ const addNewOrderGroup = (_orgList, _sortRule) => {
2613
+ // インデックスを保持した配列を作成、ルールに従ってソート
2614
+ const indexedList = _orgList.map((value, idx) => ({ value, idx }));
2615
+ const sortedList = [...indexedList].sort(_sortRule);
2616
+ // ソート後の配列のインデックスに基づいて、元のインデックスを取得
2617
+ const newIdxs = sortedList.map(({ idx }) => indexedList.findIndex(({ idx: originalIdx }) => originalIdx === idx));
2618
+ return !newIdxs.every((val, j) => val === j) ? newIdxs : undefined;
2619
+ };
2620
+
2601
2621
  customKeyList.forEach(key => {
2602
2622
  const keyBase = `${key}_0`;
2603
2623
  const keyCtrlPtn = `${g_keyObj.defaultProp}${keyBase}`;
@@ -2606,14 +2626,21 @@ const initialControl = async () => {
2606
2626
  const orgKeyNum = g_keyObj[keyCtrlPtn].length;
2607
2627
  const baseX = Math.floor(Math.random() * (100 - keyGroupList.length));
2608
2628
 
2629
+ const divPos = g_keyObj[`div${keyBase}`];
2630
+ const divMaxPos = g_keyObj[`divMax${keyBase}`] ?? Math.max(...g_keyObj[`pos${keyBase}`]) + 1;
2631
+ const stdPos = Math.max(divPos, divMaxPos - divPos);
2632
+ const [deltaXAbove, deltaXBelow] = [(divPos - stdPos) / 2, (divMaxPos - divPos - stdPos) / 2];
2633
+
2609
2634
  keyGroupList.forEach((keyGroupNo, j) => {
2610
2635
  const keyN = keyGroupNo === `0` ? key : `${key}_${j + 1}`;
2611
2636
  const filterCond = (j) => keyGroup[j].findIndex(val => val === keyGroupNo) >= 0;
2612
2637
  const keyCtrlList = g_keyObj[keyCtrlPtn].filter((val, j) => filterCond(j));
2613
2638
  const charaList = g_keyObj[`chara${keyBase}`].filter((val, j) => filterCond(j));
2614
2639
  const colorList = g_keyObj[`color${keyBase}_0`].filter((val, j) => filterCond(j));
2640
+ const stepRtnList = g_keyObj[`stepRtn${keyBase}_0`].filter((val, j) => filterCond(j));
2615
2641
  const keyNum = g_keyObj[keyCtrlPtn].filter((val, j) => filterCond(j)).length;
2616
2642
 
2643
+ // ---- Dancing☆Onigiri (CW Edition対応)のフォーマット
2617
2644
  g_editorTmp[keyN] = {};
2618
2645
  g_editorTmp[keyN].id = orgKeyNum * 100 + baseX + j;
2619
2646
  g_editorTmp[keyN].num = keyNum;
@@ -2629,6 +2656,77 @@ const initialControl = async () => {
2629
2656
  return `${frzName}_data`;
2630
2657
  });
2631
2658
  g_editorTmp[keyN].colorGroup = colorList.map(val => val % 3);
2659
+
2660
+ // orderGroupsのカスタマイズ
2661
+ if (divMaxPos > divPos) {
2662
+
2663
+ // posXの実際の相対位置を計算
2664
+ const orgPosList = g_keyObj[`pos${keyBase}`].filter((val, j) => filterCond(j));
2665
+ const posList = orgPosList.map(val => val < divPos ? val - deltaXAbove : val - divPos - deltaXBelow);
2666
+
2667
+ g_editorTmp[keyN].orderGroups = [];
2668
+
2669
+ // パターン1: 上下グループ分けして各グループ内で位置順にソート(上下反転)
2670
+ const upDownIdxs = addNewOrderGroup(orgPosList, (a, b) => {
2671
+ const aAbove = a.value < divPos;
2672
+ const bAbove = b.value < divPos;
2673
+ if (aAbove !== bAbove) return Number(aAbove) - Number(bAbove);
2674
+ return a.value - b.value;
2675
+ });
2676
+ if (upDownIdxs !== undefined) {
2677
+ g_editorTmp[keyN].orderGroups.push(upDownIdxs);
2678
+ }
2679
+
2680
+ // パターン2: 単純にステップゾーンのX座標が小さい順にソート
2681
+ const sortedIdxs = addNewOrderGroup(posList, (a, b) => a.value - b.value);
2682
+ if (sortedIdxs !== undefined) {
2683
+ g_editorTmp[keyN].orderGroups.push(sortedIdxs);
2684
+ }
2685
+ if (g_editorTmp[keyN].orderGroups.length === 0) {
2686
+ delete g_editorTmp[keyN].orderGroups;
2687
+ }
2688
+ }
2689
+
2690
+ // ---- ダンおに譜面作成エディタ ver3フォーマット
2691
+
2692
+ // 既存のシャッフルグループからミラー配列を自動生成
2693
+ let k = 0, n = 0, convTxt = ``;
2694
+ let prevMirrorList = [];
2695
+ while (g_keyObj[`shuffle${keyBase}_${k}`] !== undefined) {
2696
+
2697
+ const orgTmpList = []
2698
+ const mirrorTmpList = [];
2699
+ const mirrorList = [];
2700
+ g_keyObj[`shuffle${keyBase}_${k}`].filter((val, m) => filterCond(m))
2701
+ .forEach((_val, _i) => orgTmpList[_val]?.push(_i) || (orgTmpList[_val] = [_i]));
2702
+ orgTmpList.forEach((list, idx) => mirrorTmpList[idx] = list.toReversed());
2703
+ orgTmpList?.forEach((list, a) => list?.forEach((val, b) => mirrorList[orgTmpList[a][b]] = mirrorTmpList[a][b]));
2704
+ if (!mirrorList.every((val, p) => val === prevMirrorList[p])) {
2705
+ convTxt += `\$conv${n + 1}=Mirror${n + 1},${mirrorList.join(',')}<br>`;
2706
+ prevMirrorList = mirrorList.concat();
2707
+ n++;
2708
+ }
2709
+ k++;
2710
+ }
2711
+
2712
+ // 矢印・フリーズアローのヘッダー情報を定義
2713
+ let noteTxt = ``, freezeTxt = ``;
2714
+ g_editorTmp[keyN].noteNames.forEach((val, j) =>
2715
+ noteTxt += `|${val.slice(0, -(`_data`.length))}[i]_data=[a${String(j).padStart(2, `0`)}]|[E]<br>`);
2716
+
2717
+ g_editorTmp[keyN].freezeNames.forEach((val, j) =>
2718
+ freezeTxt += `|${val.slice(0, -(`_data`.length))}[i]_data=[f${String(j).padStart(2, `0`)}]|[E]<br>`);
2719
+
2720
+ g_editorTmp2 += g_editorTmp2Template
2721
+ .replace(`[__KEY__]`, keyN)
2722
+ .replace(`[__MAP__]`, colorList.map(val => val < 3 ? (val + 1) % 3 : val % 7).join(','))
2723
+ .replace(`[__POS__]`, fillArray(keyNum).map((val, j) =>
2724
+ isNaN(parseFloat(stepRtnList[j])) ? 28 : 24).join(`,`))
2725
+ .replace(`[__TXT__]`, g_editorTmp[keyN].chars.map(val => val.replace(`, `, ``)).join(`,`))
2726
+ .replace(`[__CONV__]`, convTxt)
2727
+ .replace(`[__NOTE__]`, noteTxt)
2728
+ .replace(`[__FREEZE__]`, freezeTxt)
2729
+ .replaceAll(`\n`, ``);
2632
2730
  });
2633
2731
  });
2634
2732
  };
@@ -3136,7 +3234,9 @@ const preheaderConvert = _dosObj => {
3136
3234
  });
3137
3235
 
3138
3236
  const convLocalPath = (_file, _type) =>
3139
- g_remoteFlg && hasVal(_file) && !_file.includes(`(..)`) ? `(..)../${_type}/${_file}` : _file;
3237
+ g_remoteFlg && hasVal(_file) && !_file.includes(`(..)`) && !hasRemoteDomain(_file)
3238
+ ? `(..)../${_type}/${_file}`
3239
+ : _file;
3140
3240
 
3141
3241
  // 外部スキンファイルの指定
3142
3242
  const tmpSkinType = _dosObj.skinType ?? g_presetObj.skinType ?? `default`;
@@ -3219,6 +3319,7 @@ const headerConvert = _dosObj => {
3219
3319
  extension: imgTypes[1] || `svg`,
3220
3320
  rotateEnabled: setBoolVal(imgTypes[2], true),
3221
3321
  flatStepHeight: setVal(imgTypes[3], C_ARW_WIDTH, C_TYP_FLOAT),
3322
+ remoteDir: imgTypes[4] || ``,
3222
3323
  };
3223
3324
  g_keycons.imgTypes[j] = (imgTypes[0] === `` ? `Original` : imgTypes[0]);
3224
3325
  });
@@ -3226,7 +3327,7 @@ const headerConvert = _dosObj => {
3226
3327
 
3227
3328
  // 末尾にデフォルト画像セットが入るよう追加
3228
3329
  if (obj.imgType.findIndex(imgSets => imgSets.name === ``) === -1) {
3229
- obj.imgType.push({ name: ``, extension: `svg`, rotateEnabled: true, flatStepHeight: C_ARW_WIDTH });
3330
+ obj.imgType.push({ name: ``, extension: `svg`, rotateEnabled: true, flatStepHeight: C_ARW_WIDTH, remoteDir: `` });
3230
3331
  g_keycons.imgTypes.push(`Original`);
3231
3332
  }
3232
3333
  g_imgType = g_keycons.imgTypes[0];
@@ -3898,6 +3999,7 @@ const getMusicNameMultiLine = _musicName => {
3898
3999
  * @param {object} _imgType
3899
4000
  * @param {string} _imgType.name
3900
4001
  * @param {string} _imgType.extension
4002
+ * @param {string} _imgType.remoteDir
3901
4003
  * @param {boolean} _initFlg
3902
4004
  */
3903
4005
  const updateImgType = (_imgType, _initFlg = false) => {
@@ -3911,9 +4013,13 @@ const updateImgType = (_imgType, _initFlg = false) => {
3911
4013
  Object.keys(g_imgObj).forEach(key => g_imgObj[key] = `${g_rootPath}${orgImgObj[key]}`);
3912
4014
 
3913
4015
  // リモート時は作品ページ側にある画像を優先し、リモートに存在するもののみリモートから取得する
4016
+ // titleArrowについては他のImgTypeから取得するため、remoteDir属性には依存させない
3914
4017
  if (g_remoteFlg) {
3915
4018
  Object.keys(g_imgObj).forEach(key => g_imgObj[key] = `${g_workPath}${orgImgObj[key]}`);
3916
- if (g_defaultSets.imgType.findIndex(val => val === _imgType.name) >= 0) {
4019
+ if (_imgType.remoteDir !== `` && hasRemoteDomain(_imgType.remoteDir)) {
4020
+ g_defaultSets.imgList.filter(val => val !== `titleArrow`)
4021
+ .forEach(key => g_imgObj[key] = `${_imgType.remoteDir}img/${orgImgObj[key]}`);
4022
+ } else if (g_defaultSets.imgType.findIndex(val => val === _imgType.name) >= 0) {
3917
4023
  g_defaultSets.imgList.forEach(key => g_imgObj[key] = `${g_rootPath}${orgImgObj[key]}`);
3918
4024
  }
3919
4025
  }
@@ -5260,9 +5366,14 @@ const preconditionInit = () => {
5260
5366
 
5261
5367
  createDivCss2Label(`lblPrecondView`, viewKeyStorage(`g_rootObj`), g_lblPosObj.lblPrecondView),
5262
5368
  createCss2Button(`btnPrecondView`, g_lblNameObj.b_copyStorage, () =>
5263
- copyTextToClipboard(
5264
- viewKeyStorage(g_settings.preconditions[g_settings.preconditionNum * numOfPrecs + g_settings.preconditionNumSub], ``, false),
5265
- g_msgInfoObj.I_0007),
5369
+ copyTextToClipboard((() => {
5370
+ const key = g_settings.preconditions[g_settings.preconditionNum * numOfPrecs + g_settings.preconditionNumSub];
5371
+ if (key === `g_editorTmp2`) {
5372
+ return g_editorTmp2.replaceAll(`<br>`, `\r\n`).replaceAll(`&nbsp;`, ` `);
5373
+ } else {
5374
+ return viewKeyStorage(key, ``, false);
5375
+ }
5376
+ })(), g_msgInfoObj.I_0007),
5266
5377
  g_lblPosObj.btnPrecondView, g_cssObj.button_Default, g_cssObj.button_ON),
5267
5378
  );
5268
5379
  setUserSelect($id(`lblPrecondView`), `text`);
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2019/11/19
8
- * Revised : 2025/03/12 (v40.5.2)
8
+ * Revised : 2025/03/22 (v40.7.0)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -1179,7 +1179,7 @@ const g_settings = {
1179
1179
  settingWindowNum: 0,
1180
1180
 
1181
1181
  preconditions: [`g_rootObj`, `g_headerObj`, `g_keyObj`, `g_scoreObj`, `g_workObj`,
1182
- `g_detailObj`, `g_stateObj`, `g_attrObj`, `g_editorTmp`],
1182
+ `g_detailObj`, `g_stateObj`, `g_attrObj`, `g_editorTmp`, `g_editorTmp2`],
1183
1183
  preconditionNum: 0,
1184
1184
  preconditionNumSub: 0,
1185
1185
  };
@@ -3187,7 +3187,42 @@ g_keycons.groups.forEach(type => {
3187
3187
  tmpName.forEach(property => g_keyObj[`${property.slice(0, -2)}`] = g_keyObj[property].concat());
3188
3188
  });
3189
3189
 
3190
+ // 外部エディター用テンプレート
3191
+ // g_editorTmp: Dancing☆Onigiriエディター (CW Edition対応)
3192
+ // g_editorTmp2: ダンおに譜面作成エディタ ver3.x
3190
3193
  const g_editorTmp = {};
3194
+ let g_editorTmp2 = ``;
3195
+
3196
+ const g_editorTmp2Template = `
3197
+ <br>
3198
+ \$key=[__KEY__]<br>
3199
+ \$map=[__MAP__]<br>
3200
+ \$pos=[__POS__]<br>
3201
+ \$txt=[__TXT__]<br>
3202
+ [__CONV__]
3203
+ <br>
3204
+ \$dosformat=<br>function externalDosInit() {[E]<br>
3205
+ [E]<br>
3206
+ &nbsp;&nbsp;g_externalDos = \`[E]<br>
3207
+ [E]<br>
3208
+ [header][E]<br>
3209
+ [E]<br>
3210
+ [notestart]<br>
3211
+ [__NOTE__]
3212
+ <br>
3213
+ [__FREEZE__]
3214
+ <br>
3215
+ |speed[i]_data=[speed]|[E]<br>
3216
+ |boost[i]_data=[boost]|[E]<br>
3217
+ [datatext][E]<br>
3218
+ |edit[i]_info=[edit]|[E][E]<br>
3219
+ [noteend]<br>
3220
+ <br>
3221
+ [footer]<br>
3222
+ &nbsp;&nbsp;\`;[E]<br>
3223
+ }<br>
3224
+ <br>
3225
+ `;
3191
3226
 
3192
3227
  // 特殊キーのコピー種 (simple: 代入、multiple: 配列ごと代入)
3193
3228
  // 後でプロパティ削除に影響するため、先頭文字が全く同じ場合は長い方を先に定義する (例: divMax, div)
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  /**
3
3
  * Dancing☆Onigiri 設定用jsファイル
4
- * Template Update: 2025/03/01 (v40.0.0)
4
+ * Template Update: 2025/03/16 (v40.6.0)
5
5
  *
6
6
  * このファイルでは、作品全体に対しての初期設定を行うことができます。
7
7
  * 譜面データ側で個別に同様の項目が設定されている場合は、譜面データ側の設定が優先されます。
@@ -172,16 +172,18 @@ g_presetObj.customDesignUse = {
172
172
 
173
173
  /**
174
174
  デフォルト画像セットの設定
175
- (セット対象のフォルダ名, 拡張子, 画像回転有無(true or false), Flat時ステップ間隔の順に指定)
175
+ (セット対象のフォルダ名, 拡張子, 画像回転有無(true or false), Flat時ステップ間隔, リモート時のディレクトリの順に指定)
176
176
 
177
177
  事前に、[img]フォルダ配下にセット対象のサブフォルダを作成し、その中に一式を入れておく必要あり
178
178
  下記の場合は[classic]フォルダに[png]形式の画像一式をデフォルト画像セットとして使用する
179
+ なお、リモート時のディレクトリ(jsdelivr)を指定した場合はサブフォルダの作成及び格納は不要
179
180
 
180
181
  未指定の場合のデフォルト値は以下の通り
181
182
  セット対象のフォルダ名:`` (imgフォルダ直下)
182
183
  拡張子:`svg`形式
183
184
  画像回転有無:true(回転有り)
184
185
  Flat時ステップ間隔:50(px) ※矢印サイズ
186
+ リモート時のディレクトリ:`` (指定なし。自サーバーの画像を使用する)
185
187
  */
186
188
  //g_presetObj.imageSets = [``, `classic,png`, `classic-thin,png`, `note,svg,false,0`];
187
189
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "40.5.4",
3
+ "version": "40.7.0-a1",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
- "main": "index.js",
5
+ "main": "./js/danoni_main.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },