danoniplus 50.4.0 → 50.5.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.
@@ -0,0 +1,3093 @@
1
+ /**
2
+ * Dancing☆Onigiri (CW Edition)
3
+ * 譜面データを分割してグローバル変数に格納する処理群
4
+ * - ページ: initial
5
+ *
6
+ * Source by tickle
7
+ * Created : 2026/09/13
8
+ * Revised :
9
+ *
10
+ * https://github.com/cwtickle/danoniplus
11
+ */
12
+
13
+ /*-----------------------------------------------------------*/
14
+ /* Scene : INITIALIZE [peach] */
15
+ /*-----------------------------------------------------------*/
16
+
17
+ const initialControl = async () => {
18
+
19
+ const stage = document.getElementById(`canvas-frame`);
20
+ const divRoot = createEmptySprite(stage, `divRoot`, g_windowObj.divRoot);
21
+ g_workPath = validatePath(document.getElementById(`jsRootUrl`)?.value,
22
+ new URL(location.href).href.match(/(^.*\/)/)[0]);
23
+
24
+ // 背景の表示
25
+ if (document.getElementById(`layer0`) !== null) {
26
+ const layer0 = document.getElementById(`layer0`);
27
+ makeBgCanvas(layer0.getContext(`2d`));
28
+ } else {
29
+ createEmptySprite(divRoot, `divBack`, g_windowObj.divBack);
30
+ }
31
+
32
+ // Now Loadingを表示
33
+ divRoot.appendChild(getLoadingLabel());
34
+
35
+ // 譜面初期情報ロード許可フラグ
36
+ g_canLoadDifInfoFlg = true;
37
+
38
+ // 譜面データの読み込みオプション
39
+ g_enableAmpersandSplit = setBoolVal(document.getElementById(`enableAmpersandSplit`)?.value, true);
40
+ g_enableDecodeURI = setBoolVal(document.getElementById(`enableDecodeURI`)?.value);
41
+
42
+ // 作品別ローカルストレージの読み込み
43
+ loadLocalStorage();
44
+
45
+ // 譜面データの読み込み(1ファイル目)
46
+ await loadChartFile(0);
47
+
48
+ // 共通設定ファイルの指定
49
+ let tmpSettingType = g_rootObj.settingType ?? ``;
50
+ if (g_remoteFlg && !tmpSettingType.includes(C_MRK_CURRENT_DIRECTORY)) {
51
+ tmpSettingType = `${C_MRK_CURRENT_DIRECTORY}../js/${tmpSettingType}`;
52
+ };
53
+ let [settingType, settingRoot] = getFilePath(tmpSettingType);
54
+ if (settingType !== ``) {
55
+ settingType = `_${settingType}`;
56
+ }
57
+
58
+ // 共通設定ファイルの読込
59
+ await loadScript2(`${settingRoot}danoni_setting${settingType}.js?${g_randTime}`, false);
60
+ loadLegacySettingFunc();
61
+ deleteDiv(divRoot, `lblLoading`);
62
+
63
+ // クエリで譜面番号が指定されていればセット
64
+ g_stateObj.scoreId = setIntVal(getQueryParamVal(`scoreId`));
65
+
66
+ // 譜面ヘッダーの読込
67
+ Object.assign(g_headerObj, preheaderConvert(g_rootObj));
68
+
69
+ // CSSファイル内のbackgroundを取得するために再描画
70
+ if (document.getElementById(`layer0`) === null) {
71
+ deleteDiv(divRoot, `divBack`);
72
+ createEmptySprite(divRoot, `divBack`);
73
+ } else if (!g_headerObj.defaultSkinFlg && !g_headerObj.customBackUse) {
74
+ createEmptySprite(divRoot, `divBack`);
75
+ }
76
+
77
+ // CSSファイルの読み込み
78
+ const skinList = g_headerObj.jsData.filter(file => file[0].indexOf(`danoni_skin`) !== -1);
79
+ await loadMultipleFiles2(skinList, `css`);
80
+
81
+ // JSファイルの読み込み
82
+ await loadMultipleFiles2(g_headerObj.jsData, `js`);
83
+ loadLegacyCustomFunc();
84
+
85
+ // 譜面ヘッダー、特殊キー情報の読込
86
+ Object.assign(g_headerObj, headerConvert(g_rootObj));
87
+ g_headerObj.undefinedKeyListFinal = [];
88
+ const importKeysData = _data => {
89
+ keysConvert(dosConvert(_data));
90
+ g_headerObj.undefinedKeyLists = g_headerObj.undefinedKeyLists.filter(key => g_keyObj[`${g_keyObj.defaultProp}${key}_0`] === undefined);
91
+ };
92
+ g_presetObj.keysDataLib.forEach(list => importKeysData(list));
93
+ if (g_presetObj.keysData !== undefined) {
94
+ g_presetObj.keysDataLocal.unshift(g_presetObj.keysData);
95
+ }
96
+ g_presetObj.keysDataLocal.forEach(list => importKeysData(list));
97
+ g_headerObj.keyExtraList = keysConvert(g_rootObj, {
98
+ keyExtraList: makeDedupliArray(g_headerObj.undefinedKeyLists, g_rootObj.keyExtraList?.split(`,`)),
99
+ });
100
+
101
+ // キー定義でエラーになる場合は強制的にデフォルトキーへ変更して続行
102
+ let hasUndefinedKey = false;
103
+ for (let j = 0; j < g_headerObj.keyLabels.length; j++) {
104
+ if (g_headerObj.undefinedKeyListFinal.includes(g_headerObj.keyLabels[j])) {
105
+ g_headerObj.keyLists = g_headerObj.keyLists.filter(key => key !== g_headerObj.keyLabels[j]);
106
+ g_headerObj.keyLabels[j] = g_keyObj.initKeyLabel;
107
+ hasUndefinedKey = true;
108
+ }
109
+ }
110
+ if (hasUndefinedKey) {
111
+ g_headerObj.keyLists = makeDedupliArray(g_headerObj.keyLists, [g_keyObj.initKeyLabel])
112
+ .sort((a, b) => parseInt(a) - parseInt(b));
113
+ }
114
+
115
+ // ラベルテキスト、オンマウステキスト、確認メッセージ定義の上書き設定
116
+ Object.assign(g_lblNameObj, g_lang_lblNameObj[g_localeObj.val], g_presetObj.lblName?.[g_localeObj.val]);
117
+ Object.assign(g_msgObj, g_lang_msgObj[g_localeObj.val], g_presetObj.msg?.[g_localeObj.val]);
118
+
119
+ // デフォルトのカラー・シャッフルグループ設定を退避
120
+ g_keycons.groups.forEach(type =>
121
+ Object.keys(g_keyObj).filter(val => val.startsWith(type))
122
+ .forEach(property => g_keyObj[`${property}d`] = structuredClone(g_keyObj[property])));
123
+
124
+ // 自動横幅拡張設定
125
+ if (g_headerObj.autoSpread) {
126
+ g_sWidth = Math.max(g_sWidth, g_presetObj.autoMinWidth ?? g_keyObj.minWidth);
127
+ g_headerObj.keyLists.forEach(key => {
128
+ g_sWidth = Math.max(g_sWidth, g_keyObj[`minWidth${key}`] ?? g_keyObj.minWidthDefault);
129
+
130
+ // 別キーモード有効時は、別キーモード毎の横幅を拡張対象へ追加
131
+ if (g_headerObj.transKeyUse) {
132
+ for (let k = 1; hasVal(g_keyObj[`keyCtrl${key}_${k}`]); k++) {
133
+ const anotherKey = g_keyObj[`transKey${key}_${k}`] ?? ``;
134
+ if (anotherKey !== ``) {
135
+ g_sWidth = Math.max(g_sWidth, g_keyObj[`minWidth${anotherKey}`] ?? g_keyObj.minWidthDefault);
136
+ }
137
+ }
138
+ }
139
+ });
140
+
141
+ $id(`canvas-frame`).width = wUnit(g_sWidth);
142
+ $id(`divRoot`).width = wUnit(g_sWidth);
143
+ }
144
+ if (g_headerObj.playingWidth === `default`) {
145
+ g_headerObj.playingWidth = g_sWidth;
146
+ }
147
+
148
+ // 可変ウィンドウサイズを更新
149
+ updateWindowSiz();
150
+
151
+ // キー数情報を初期化
152
+ g_keyObj.currentKey = g_headerObj.keyLabels[g_stateObj.scoreId];
153
+ g_keyObj.currentPtn = 0;
154
+
155
+ // 画像ファイルの読み込み
156
+ g_imgInitList.forEach(img => preloadFile(`image`, g_imgObj[img]));
157
+
158
+ // その他の画像ファイルの読み込み
159
+ g_headerObj.preloadImages.filter(image => hasVal(image)).forEach(preloadImage => {
160
+
161
+ // Pattern A: |preloadImages=file.png|
162
+ // Pattern B: |preloadImages=file*.png@10| -> file01.png ~ file10.png
163
+ // Pattern C: |preloadImages=file*.png@2-9| -> file2.png ~ file9.png
164
+ // Pattern D: |preloadImages=file*.png@003-018| -> file003.png ~ file018.png
165
+
166
+ const tmpPreloadImages = preloadImage.split(`@`);
167
+ if (tmpPreloadImages.length === 1) {
168
+ // Pattern Aの場合
169
+ preloadFile(`image`, preloadImage);
170
+ } else {
171
+ const termRoopCnts = tmpPreloadImages[1].split(`-`);
172
+ let startCnt = 1;
173
+ let lastCnt;
174
+ let paddingLen;
175
+
176
+ if (termRoopCnts.length === 1) {
177
+ // Pattern Bの場合
178
+ lastCnt = setIntVal(tmpPreloadImages[1], 1);
179
+ paddingLen = String(setVal(tmpPreloadImages[1], 1)).length;
180
+ } else {
181
+ // Pattern C, Dの場合
182
+ startCnt = setIntVal(termRoopCnts[0], 1);
183
+ lastCnt = setIntVal(termRoopCnts[1], 1);
184
+ paddingLen = String(setVal(termRoopCnts[1], 1)).length;
185
+ }
186
+ for (let k = startCnt; k <= lastCnt; k++) {
187
+ preloadFile(`image`, tmpPreloadImages[0].replaceAll(`*`, String(k).padStart(paddingLen, `0`)));
188
+ }
189
+ }
190
+ });
191
+
192
+ // ローカルファイル起動時に各種警告文を表示
193
+ if (g_isFile) {
194
+ makeWarningWindow(g_msgInfoObj.W_0011);
195
+ if (!listMatching(getMusicUrl(g_stateObj.scoreId), [`.js`, `.txt`], { suffix: `$` })) {
196
+ if (g_userAgent.indexOf(`firefox`) !== -1) {
197
+ makeWarningWindow(g_msgInfoObj.W_0001);
198
+ }
199
+ makeWarningWindow(g_msgInfoObj.W_0012);
200
+ }
201
+ }
202
+
203
+ if (g_loadObj.main) {
204
+
205
+ // 譜面分割、譜面番号固定かどうかをチェック
206
+ g_stateObj.dosDivideFlg = setBoolVal(document.getElementById(`externalDosDivide`)?.value ?? getQueryParamVal(`dosDivide`));
207
+ g_stateObj.scoreLockFlg = setBoolVal(document.getElementById(`externalDosLock`)?.value ?? getQueryParamVal(`dosLock`));
208
+
209
+ // 非分割時は resetGaugeSetting が全難易度を一括構築するため、初回のみで十分
210
+ const loopCount = g_stateObj.dosDivideFlg ? g_headerObj.keyLabels.length : 1;
211
+
212
+ for (let j = 0; j < g_headerObj.keyLabels.length; j++) {
213
+
214
+ // 譜面ファイルが分割されている場合、譜面詳細情報取得のために譜面をロード
215
+ if (g_stateObj.dosDivideFlg) {
216
+ await loadChartFile(j);
217
+ resetColorSetting(j);
218
+ }
219
+ getScoreDetailData(j);
220
+ if (j < loopCount) {
221
+ // 分割時は各譜面ごとに上書き・補完、非分割時は初回のみ実行
222
+ resetGaugeSetting(j);
223
+ }
224
+ }
225
+ }
226
+ safeExecuteCustomHooks(`g_customJsObj.preTitle`, g_customJsObj.preTitle);
227
+ const queryMusicId = getQueryParamVal(`musicId`);
228
+ g_settings.musicIdxNum = queryMusicId !== null ? Number(queryMusicId) :
229
+ g_headerObj.musicGroups?.[g_headerObj.musicNos[g_stateObj.scoreId]] ??
230
+ g_headerObj.musicNos[g_stateObj.scoreId] ?? g_headerObj.musicNos[0];
231
+ titleInit(true);
232
+
233
+ // 未使用のg_keyObjプロパティを削除
234
+ const keyProp = g_keyCopyLists.simple.concat(
235
+ g_keyCopyLists.multiple,
236
+ `keyCtrl`, `keyName`, `minWidth`, `movLock`, `initManual`, `ptchara`
237
+ );
238
+ const delKeyPropList = [`ptchara7`, `dfPtnNum`, `minKeyCtrlNum`, `minPatterns`];
239
+ Object.keys(g_keyObj).forEach(key => {
240
+ const type = keyProp.find(prop => key.startsWith(prop)) || ``;
241
+ if (type !== ``) {
242
+ const keyName = String(key.split(`_`)[0].slice(type.length));
243
+ if (!g_headerObj.keyLists.includes(keyName) && keyName !== `` && keyName !== `Default`) {
244
+ delete g_keyObj[key];
245
+ }
246
+ }
247
+ if (key.match(/^chara7_[a-z]/) || delKeyPropList.includes(key) || g_keyObj[key] === undefined) {
248
+ delete g_keyObj[key];
249
+ }
250
+ });
251
+
252
+ g_stateObj.keyInitial = true;
253
+
254
+ // エディター用のフォーマッター作成
255
+ const customKeyList = g_headerObj.keyLists.filter(val =>
256
+ g_keyObj.defaultKeyList.findIndex(key => key === val) < 0);
257
+
258
+ if (customKeyList.length === 0) {
259
+ g_settings.preconditions = g_settings.preconditions.filter(val => !val.includes(`g_editorTmp`));
260
+ }
261
+
262
+ const addNewOrderGroup = (_orgList, _sortRule) => {
263
+ // インデックスを保持した配列を作成、ルールに従ってソート
264
+ const indexedList = _orgList.map((value, idx) => ({ value, idx }));
265
+ const sortedList = [...indexedList].sort(_sortRule);
266
+ // ソート後の配列のインデックスに基づいて、元のインデックスを取得
267
+ const newIdxs = sortedList.map(({ idx }) => indexedList.findIndex(({ idx: originalIdx }) => originalIdx === idx));
268
+ return !newIdxs.every((val, j) => val === j) ? newIdxs : undefined;
269
+ };
270
+
271
+ customKeyList.forEach(key => {
272
+ const keyBase = `${key}_0`;
273
+ const keyCtrlPtn = `${g_keyObj.defaultProp}${keyBase}`;
274
+ const keyGroup = g_keyObj[`keyGroup${keyBase}`];
275
+ const keyGroupList = makeDedupliArray(keyGroup.flat());
276
+ const orgKeyNum = g_keyObj[keyCtrlPtn].length;
277
+ const baseX = Math.floor(Math.random() * (100 - keyGroupList.length));
278
+
279
+ const divPos = g_keyObj[`div${keyBase}`];
280
+ const divMaxPos = g_keyObj[`divMax${keyBase}`] ?? Math.max(...g_keyObj[`pos${keyBase}`]) + 1;
281
+ const stdPos = Math.max(divPos, divMaxPos - divPos);
282
+ const [deltaXAbove, deltaXBelow] = [(divPos - stdPos) / 2, (divMaxPos - divPos - stdPos) / 2];
283
+
284
+ keyGroupList.forEach((keyGroupNo, j) => {
285
+ const keyN = keyGroupNo === `0` ? key : `${key}_${j + 1}`;
286
+ const filterCond = (r) => keyGroup[r].findIndex(val => val === keyGroupNo) >= 0;
287
+ const keyCtrlList = g_keyObj[keyCtrlPtn].filter((val, r) => filterCond(r));
288
+ const charaList = g_keyObj[`chara${keyBase}`].filter((val, r) => filterCond(r));
289
+ const colorList = g_keyObj[`color${keyBase}_0`].filter((val, r) => filterCond(r));
290
+ const stepRtnList = g_keyObj[`stepRtn${keyBase}_0`].filter((val, r) => filterCond(r));
291
+ const keyNum = g_keyObj[keyCtrlPtn].filter((val, r) => filterCond(r)).length;
292
+
293
+ // ---- Dancing☆Onigiri (CW Edition対応)のフォーマット
294
+ g_editorTmp[keyN] = {};
295
+ g_editorTmp[keyN].id = orgKeyNum * 100 + baseX + j;
296
+ g_editorTmp[keyN].num = keyNum;
297
+ g_editorTmp[keyN].chars = keyCtrlList.map(val => g_kCd[val[0]]);
298
+ g_editorTmp[keyN].keys = keyCtrlList.map(val => g_kCdN[val[0]]).map(val => replaceStr(val, g_escapeStr.editorKey));
299
+ g_editorTmp[keyN].alternativeKeys = keyCtrlList.map(val => val[1] === 0 ? `` : g_kCdN[val[1]]).map(val => replaceStr(val, g_escapeStr.editorKey));
300
+ g_editorTmp[keyN].noteNames = charaList.map(val => `${val}_data`);
301
+ g_editorTmp[keyN].freezeNames = charaList.map(val => {
302
+ let frzName = replaceStr(val, g_escapeStr.frzName);
303
+ if (frzName.indexOf(`frz`) === -1 && frzName.indexOf(`foni`) === -1) {
304
+ frzName = frzName.replaceAll(frzName, `frz${toCapitalize(frzName)}`);
305
+ }
306
+ return `${frzName}_data`;
307
+ });
308
+ g_editorTmp[keyN].colorGroup = colorList.map(val => val % 3);
309
+
310
+ // orderGroupsのカスタマイズ
311
+ if (divMaxPos > divPos) {
312
+
313
+ // posXの実際の相対位置を計算
314
+ const orgPosList = g_keyObj[`pos${keyBase}`].filter((val, r) => filterCond(r));
315
+ const posList = orgPosList.map(val => val < divPos ? val - deltaXAbove : val - divPos - deltaXBelow);
316
+
317
+ g_editorTmp[keyN].orderGroups = [];
318
+
319
+ // パターン1: 上下グループ分けして各グループ内で位置順にソート(上下反転)
320
+ const upDownIdxs = addNewOrderGroup(orgPosList, (a, b) => {
321
+ const aAbove = a.value < divPos;
322
+ const bAbove = b.value < divPos;
323
+ if (aAbove !== bAbove) return Number(aAbove) - Number(bAbove);
324
+ return a.value - b.value;
325
+ });
326
+ if (upDownIdxs !== undefined) {
327
+ g_editorTmp[keyN].orderGroups.push(upDownIdxs);
328
+ }
329
+
330
+ // パターン2: 単純にステップゾーンのX座標が小さい順にソート
331
+ const sortedIdxs = addNewOrderGroup(posList, (a, b) => a.value - b.value);
332
+ if (sortedIdxs !== undefined) {
333
+ g_editorTmp[keyN].orderGroups.push(sortedIdxs);
334
+ }
335
+ if (g_editorTmp[keyN].orderGroups.length === 0) {
336
+ delete g_editorTmp[keyN].orderGroups;
337
+ }
338
+ }
339
+
340
+ // ---- ダンおに譜面作成エディタ ver3フォーマット
341
+
342
+ // 既存のシャッフルグループからミラー配列を自動生成
343
+ let k = 0, n = 0, convTxt = ``;
344
+ let prevMirrorList = [];
345
+ while (g_keyObj[`shuffle${keyBase}_${k}`] !== undefined) {
346
+
347
+ const orgTmpList = []
348
+ const mirrorTmpList = [];
349
+ const mirrorList = [];
350
+ g_keyObj[`shuffle${keyBase}_${k}`].filter((val, m) => filterCond(m))
351
+ .forEach((_val, _i) => orgTmpList[_val]?.push(_i) || (orgTmpList[_val] = [_i]));
352
+ orgTmpList.forEach((list, idx) => mirrorTmpList[idx] = list.toReversed());
353
+ orgTmpList?.forEach((list, a) => list?.forEach((val, b) => mirrorList[orgTmpList[a][b]] = mirrorTmpList[a][b]));
354
+ if (!mirrorList.every((val, p) => val === prevMirrorList[p])) {
355
+ convTxt += `\$conv${n + 1}=Mirror${n + 1},${mirrorList.join(',')}<br>`;
356
+ prevMirrorList = mirrorList.concat();
357
+ n++;
358
+ }
359
+ k++;
360
+ }
361
+
362
+ // 矢印・フリーズアローのヘッダー情報を定義
363
+ const noteTxt = g_editorTmp[keyN].noteNames.map((val, r) =>
364
+ `|${val.slice(0, -(`_data`.length))}[i]_data=[a${String(r).padStart(2, `0`)}]|[E]<br>`).join(``);
365
+
366
+ const freezeTxt = g_editorTmp[keyN].freezeNames.map((val, r) =>
367
+ `|${val.slice(0, -(`_data`.length))}[i]_data=[f${String(r).padStart(2, `0`)}]|[E]<br>`).join(``);
368
+
369
+ g_editorTmp2 += g_editorTmp2Template
370
+ .replace(`[__KEY__]`, keyN)
371
+ .replace(`[__MAP__]`, colorList.map(val => val < 3 ? (val + 1) % 3 : val % 7).join(','))
372
+ .replace(`[__POS__]`, fillArray(keyNum).map((val, r) =>
373
+ isNaN(parseFloat(stepRtnList[r])) ? 28 : 24).join(`,`))
374
+ .replace(`[__TXT__]`, g_editorTmp[keyN].chars.map(val => val.replace(`, `, ``)).join(`,`))
375
+ .replace(`[__CONV__]`, convTxt)
376
+ .replace(`[__NOTE__]`, noteTxt)
377
+ .replace(`[__FREEZE__]`, freezeTxt)
378
+ .replaceAll(`\n`, ``);
379
+ });
380
+ });
381
+ };
382
+
383
+ /**
384
+ * 作品別ローカルストレージの読み込み・初期設定
385
+ * @param {string} _musicId 楽曲ID
386
+ */
387
+ const loadLocalStorage = (_musicId = ``) => {
388
+
389
+ // 作品別ローカルストレージのキー(URL)取得のため、
390
+ // scoreId, h, debug, musicIdを削除
391
+ // 選択中の楽曲ID(_musicId)がある場合は、キーとして区別するため追加
392
+ const url = new URL(location.href);
393
+ url.searchParams.delete(`scoreId`);
394
+ url.searchParams.delete(`h`);
395
+ url.searchParams.delete(`debug`);
396
+ url.searchParams.delete(`musicId`);
397
+ g_localStorageUrl = url.toString();
398
+
399
+ // リザルト表示用のURL組み立てのため、_musicIdのないURLを保存
400
+ g_localStorageUrlOrg = g_localStorageUrl;
401
+
402
+ if (_musicId !== ``) {
403
+ url.searchParams.append(`musicId`, _musicId);
404
+ g_localStorageUrl = url.toString();
405
+ if (g_langStorage.safeMode === C_FLG_ON) {
406
+ return;
407
+ }
408
+ }
409
+
410
+ /**
411
+ * ローカルストレージの初期値設定
412
+ * @param {string} _name
413
+ * @param {string} _type
414
+ * @param {number} _defaultPos
415
+ */
416
+ const checkLocalParam = (_name, _type = C_TYP_STRING, _defaultPos = 0) => {
417
+ const defaultVal = g_settings[`${_name}s`][_defaultPos];
418
+ if (g_localStorage[_name] !== undefined) {
419
+ g_stateObj[_name] = setVal(g_localStorage[_name], defaultVal, _type);
420
+ g_settings[`${_name}Num`] = roundZero(g_settings[`${_name}s`].findIndex(val => val === g_stateObj[_name]), defaultVal);
421
+ } else {
422
+ g_localStorage[_name] = defaultVal;
423
+ }
424
+ };
425
+
426
+ // ロケールの読込、警告メッセージの入替
427
+ g_langStorage = parseStorageData(`danoni-locale`);
428
+ if (g_langStorage.locale !== undefined) {
429
+ g_localeObj.val = g_langStorage.locale;
430
+ g_localeObj.num = g_localeObj.list.findIndex(val => val === g_localeObj.val);
431
+ }
432
+ if (g_langStorage.safeMode === undefined) {
433
+ g_langStorage.safeMode = C_FLG_OFF;
434
+ }
435
+ if (g_langStorage.bgmVolume === undefined) {
436
+ g_langStorage.bgmVolume = 50;
437
+ }
438
+ g_stateObj.bgmVolume = g_langStorage.bgmVolume;
439
+ g_settings.bgmVolumeNum = g_settings.volumes.findIndex(val => val === g_stateObj.bgmVolume);
440
+ Object.assign(g_msgInfoObj, g_lang_msgInfoObj[g_localeObj.val]);
441
+ Object.assign(g_kCd, g_lang_kCd[g_localeObj.val]);
442
+
443
+ // 作品別ローカルストレージの読込
444
+ if (g_langStorage.safeMode === C_FLG_OFF) {
445
+ g_localStorage = parseStorageData(g_localStorageUrl, {
446
+ adjustment: 0, hitPosition: 0, volume: 100, highscores: {},
447
+ });
448
+ } else {
449
+ g_localStorage = {};
450
+ g_stateObj.dataSaveFlg = false;
451
+ makeWarningWindow(g_msgInfoObj.W_0031);
452
+ }
453
+
454
+ // Adjustment, Volume, Appearance, Opacity, HitPosition初期値設定
455
+ checkLocalParam(`adjustment`, C_TYP_FLOAT, g_settings.adjustmentNum);
456
+ checkLocalParam(`volume`, C_TYP_NUMBER, g_settings.volumes.length - 1);
457
+ checkLocalParam(`appearance`);
458
+ checkLocalParam(`opacity`, C_TYP_NUMBER, g_settings.opacitys.length - 1);
459
+ checkLocalParam(`hitPosition`, C_TYP_FLOAT, g_settings.hitPositionNum);
460
+
461
+ // ハイスコア取得準備
462
+ if (g_localStorage.highscores === undefined) {
463
+ g_localStorage.highscores = {};
464
+ }
465
+
466
+ // 廃棄済みリストからデータを消去
467
+ g_storeSettingsEx.filter(val => g_localStorage[val] !== undefined)
468
+ .forEach(val => delete g_localStorage[val]);
469
+ };
470
+
471
+ /**
472
+ * 譜面データを分割して値を取得
473
+ * @param {string} _dos 譜面データ
474
+ * @returns
475
+ */
476
+ const dosConvert = (_dos = ``) => {
477
+
478
+ const obj = {};
479
+ const paramsTmp = g_enableAmpersandSplit ? _dos.split(`&`).join(`|`) : _dos;
480
+ paramsTmp.split(`|`).filter(param => param.indexOf(`=`) > 0).forEach(param => {
481
+ const pos = param.indexOf(`=`);
482
+ const pKey = param.substring(0, pos);
483
+ const pValue = param.substring(pos + 1);
484
+
485
+ if (pKey !== undefined) {
486
+ obj[pKey] = g_enableDecodeURI ? decodeURIComponent(pValue) : pValue;
487
+ }
488
+ });
489
+ return obj;
490
+ };
491
+
492
+ /**
493
+ * 譜面読込
494
+ * @param {number} _scoreId 譜面番号
495
+ */
496
+ const loadChartFile = async (_scoreId = g_stateObj.scoreId) => {
497
+
498
+ const dosInput = document.getElementById(`dos`);
499
+ const divRoot = document.getElementById(`divRoot`);
500
+ const queryDos = getQueryParamVal(`dos`) !== null ?
501
+ `dos/${getQueryParamVal('dos')}.txt` : encodeURI(document.getElementById(`externalDos`)?.value ?? ``);
502
+
503
+ if (dosInput === null && queryDos === ``) {
504
+ makeWarningWindow(g_msgInfoObj.E_0023);
505
+ g_loadObj.main = false;
506
+ return;
507
+ }
508
+
509
+ // 譜面分割あり、譜面番号固定時のみ譜面データを一時クリア
510
+ if (queryDos !== `` && g_stateObj.dosDivideFlg && g_stateObj.scoreLockFlg) {
511
+ Object.keys(g_rootObj).filter(data => fuzzyListMatching(data, g_checkStr.resetDosHeader, g_checkStr.resetDosFooter))
512
+ .forEach(scoredata => g_rootObj[scoredata] = ``);
513
+ }
514
+
515
+ // HTML埋め込みdos
516
+ if (dosInput !== null && _scoreId === 0) {
517
+ Object.assign(g_rootObj, dosConvert(dosInput.value));
518
+ }
519
+
520
+ // 外部dos読み込み
521
+ if (queryDos !== ``) {
522
+ const charset = document.getElementById(`externalDosCharset`)?.value ?? document.characterSet;
523
+ const fileBase = queryDos.match(/.+\..*/)[0];
524
+ const fileExtension = fileBase.split(`.`).pop();
525
+ const fileCommon = fileBase.split(`.${fileExtension}`)[0];
526
+ const filename = `${fileCommon}${g_stateObj.dosDivideFlg ?
527
+ setDosIdHeader(_scoreId, g_stateObj.scoreLockFlg) : ''}.${fileExtension}`;
528
+
529
+ await loadScript2(`${filename}?${Date.now()}`, false, charset);
530
+ if (typeof externalDosInit === C_TYP_FUNCTION) {
531
+
532
+ // 外部データを読込(ファイルが見つからなかった場合は譜面追記をスキップ)
533
+ externalDosInit();
534
+ if (g_loadObj[filename]) {
535
+ Object.assign(g_rootObj, dosConvert(g_externalDos));
536
+ }
537
+
538
+ } else {
539
+ makeWarningWindow(g_msgInfoObj.E_0022);
540
+ }
541
+ }
542
+ };
543
+
544
+ /**
545
+ * 譜面をファイルで分割している場合に初期色を追加取得
546
+ * @param {string} _scoreId
547
+ */
548
+ const resetColorSetting = _scoreId => {
549
+ // 初期矢印・フリーズアロー色の再定義
550
+ if (g_stateObj.scoreLockFlg) {
551
+ Object.assign(g_rootObj, copySetColor(g_rootObj, _scoreId));
552
+
553
+ // 分割先のファイルで初期色が未定義の場合はデフォルト値を適用
554
+ [``, `Shadow`].forEach(pattern =>
555
+ [`set`, `frz`].forEach(arrow => {
556
+ // frzShadowColorStrのみ、空で構成された初期配列があるためその条件を追加して除外条件とする
557
+ if (!hasVal(g_rootObj[`${arrow}${pattern}Color${_scoreId + 1}`])
558
+ && g_headerObj[`${arrow}${pattern}ColorStr`]?.flat()?.some(val => hasVal(val))) {
559
+ g_rootObj[`${arrow}${pattern}Color`] = g_headerObj[`${arrow}${pattern}ColorStr`].join(`,`);
560
+ }
561
+ })
562
+ );
563
+ }
564
+ Object.assign(g_headerObj, resetBaseColorList(g_headerObj, g_rootObj, { scoreId: _scoreId, scoreLockFlg: false }));
565
+ };
566
+
567
+ /**
568
+ * 譜面をファイルで分割している場合にゲージ情報を追加取得
569
+ * @param {string} _scoreId
570
+ */
571
+ const resetGaugeSetting = _scoreId => {
572
+ // ライフ設定のカスタム部分再取得(譜面ヘッダー加味)
573
+ Object.assign(g_gaugeOptionObj, resetCustomGauge(g_rootObj, { scoreId: _scoreId }));
574
+ Object.keys(g_gaugeOptionObj.customFulls).forEach(gaugePtn => getGaugeSetting(g_rootObj, gaugePtn, g_headerObj.difLabels.length, { scoreId: _scoreId }));
575
+ };
576
+
577
+ /**
578
+ * 譜面番号固定かつ譜面ファイル分割時に初期色情報を他譜面へコピー
579
+ * @param {object} _baseObj
580
+ * @param {number} _scoreId
581
+ * @returns
582
+ */
583
+ const copySetColor = (_baseObj, _scoreId) => {
584
+ const obj = {};
585
+ // Todo: dosIdを引数にして、dosIdと一致するscoreIdを算出
586
+ // 算出したscoreIdすべてに対して下記処理を実行
587
+ const srcIdHeader = setScoreIdHeader(_scoreId, g_stateObj.scoreLockFlg, true);
588
+ const targetIdHeader = setScoreIdHeader(_scoreId, false, true);
589
+ [``, `Shadow`].forEach(pattern =>
590
+ [`set`, `frz`].filter(arrow => hasVal(_baseObj[`${arrow}${pattern}Color${srcIdHeader}`] || _baseObj[`${arrow}${pattern}Color`]))
591
+ .forEach(arrow => obj[`${arrow}${pattern}Color${targetIdHeader}`] =
592
+ _baseObj[`${arrow}${pattern}Color${srcIdHeader}`] || _baseObj[`${arrow}${pattern}Color`]));
593
+ return obj;
594
+ };
595
+
596
+ /**
597
+ * MusicUrlの基本情報を取得
598
+ * @param {number} _scoreId
599
+ * @returns {string}
600
+ */
601
+ const getMusicUrl = _scoreId =>
602
+ g_headerObj.musicUrls?.[g_headerObj.musicNos[_scoreId]] ?? g_headerObj.musicUrls?.[0] ?? `nosound.mp3`;
603
+
604
+ /**
605
+ * 音源データの実際のパスを取得
606
+ * @param {string} _musicUrl
607
+ * @returns {string}
608
+ */
609
+ const getFullMusicUrl = (_musicUrl = ``) => {
610
+ let baseMusicUrl = _musicUrl;
611
+ let baseDir = `../${g_headerObj.musicFolder}/`;
612
+
613
+ if (_musicUrl.includes(C_MRK_CURRENT_DIRECTORY)) {
614
+ // musicUrl, musicFolder両方にカレントパス指定がある場合は、musicUrlの値を優先
615
+
616
+ } else if (g_headerObj.musicFolder.includes(C_MRK_CURRENT_DIRECTORY)) {
617
+ // musicFolderにカレントパス指定がある場合は、ファイル名にmusicFolderの値も含める
618
+ baseMusicUrl = `${g_headerObj.musicFolder}/${_musicUrl}`;
619
+ }
620
+ if (g_headerObj.musicFolder.includes(C_MRK_CURRENT_DIRECTORY)) {
621
+ // musicFolderにカレントパス指定がある場合は、ディレクトリは指定しない
622
+ baseDir = ``;
623
+ }
624
+ const [musicFile, musicPath] = getFilePath(baseMusicUrl, baseDir);
625
+ return `${musicPath}${musicFile}`;
626
+ };
627
+
628
+ /**
629
+ * 譜面ファイル読込後処理(譜面詳細情報取得用)
630
+ * @param {number} _scoreId
631
+ */
632
+ const getScoreDetailData = _scoreId => {
633
+ const keyCtrlPtn = `${g_headerObj.keyLabels[_scoreId]}_0`;
634
+ storeBaseData(_scoreId, scoreConvert(g_rootObj, _scoreId, 0, ``, keyCtrlPtn, true), keyCtrlPtn);
635
+ };
636
+
637
+ /**
638
+ * 譜面詳細データの格納
639
+ * @param {number} _scoreId
640
+ * @param {object} _scoreObj
641
+ * @param {number} _keyCtrlPtn
642
+ */
643
+ const storeBaseData = (_scoreId, _scoreObj, _keyCtrlPtn) => {
644
+ const lastFrame = getLastFrame(_scoreObj, _keyCtrlPtn) + 1;
645
+ const startFrame = getStartFrame(lastFrame, 0, _scoreId);
646
+ const firstArrowFrame = getFirstArrowFrame(_scoreObj, _keyCtrlPtn);
647
+ const playingFrame = lastFrame - firstArrowFrame;
648
+ const keyNum = g_keyObj[`${g_keyObj.defaultProp}${_keyCtrlPtn}`].length;
649
+
650
+ // 譜面密度グラフ用のデータ作成
651
+ const noteCnt = { arrow: [], frz: [] };
652
+ const densityData = fillArray(g_limitObj.densityDivision);
653
+ let allData = 0;
654
+
655
+ const types = [`arrow`, `frz`];
656
+ let fullData = [];
657
+ for (let j = 0; j < keyNum; j++) {
658
+ noteCnt.arrow[j] = 0;
659
+ noteCnt.frz[j] = 0;
660
+
661
+ const tmpFrzData = _scoreObj.frzData[j].filter((data, k) => k % 2 === 0);
662
+ [_scoreObj.arrowData[j], tmpFrzData].forEach((typeData, m) =>
663
+ typeData.forEach(note => {
664
+ if (isNaN(parseFloat(note))) {
665
+ return;
666
+ }
667
+ const point = Math.floor((note - firstArrowFrame) / playingFrame * g_limitObj.densityDivision);
668
+ if (point >= 0) {
669
+ densityData[point]++;
670
+ noteCnt[types[m]][j]++;
671
+ allData++;
672
+ }
673
+ }));
674
+ fullData = fullData.concat(..._scoreObj.arrowData[j], ...tmpFrzData);
675
+ }
676
+
677
+ fullData = fullData.filter(val => !isNaN(parseFloat(val))).sort((a, b) => a - b);
678
+ let pushCnt = 1;
679
+ const density2PushData = fillArray(g_limitObj.densityDivision);
680
+ const density3PushData = fillArray(g_limitObj.densityDivision);
681
+ fullData.forEach((note, j) => {
682
+ if (fullData[j] === fullData[j + 1]) {
683
+ pushCnt++;
684
+ } else {
685
+ const point = Math.floor((note - firstArrowFrame) / playingFrame * g_limitObj.densityDivision);
686
+ if (point >= 0) {
687
+ if (pushCnt >= 2) {
688
+ density2PushData[point] += pushCnt;
689
+ if (pushCnt >= 3) {
690
+ density3PushData[point] += pushCnt;
691
+ }
692
+ }
693
+ }
694
+ pushCnt = 1;
695
+ }
696
+ });
697
+
698
+ g_detailObj.toolDif[_scoreId] = calcLevel(_scoreObj);
699
+ g_detailObj.speedData[_scoreId] = _scoreObj.speedData.concat();
700
+ g_detailObj.boostData[_scoreId] = _scoreObj.boostData.concat();
701
+
702
+ const storeDensity = _densityData => {
703
+ const dataList = [];
704
+ for (let j = 0; j < g_limitObj.densityDivision; j++) {
705
+ dataList.push(allData === 0 ? 0 : Math.round(_densityData[j] / allData * g_limitObj.densityDivision * 10000) / 100);
706
+ }
707
+ return dataList;
708
+ };
709
+ const diffArray = (_array1, _array2) => {
710
+ const list = [];
711
+ _array1.forEach((val, j) => list.push(_array1[j] - _array2[j]));
712
+ return list;
713
+ };
714
+ g_detailObj.densityData[_scoreId] = storeDensity(densityData);
715
+ g_detailObj.density2PushData[_scoreId] = storeDensity(density2PushData);
716
+ g_detailObj.density3PushData[_scoreId] = storeDensity(density3PushData);
717
+
718
+ g_detailObj.densityDiff[_scoreId] = diffArray(g_detailObj.densityData[_scoreId], g_detailObj.density2PushData[_scoreId]);
719
+ g_detailObj.density2PushDiff[_scoreId] = diffArray(g_detailObj.density2PushData[_scoreId], g_detailObj.density3PushData[_scoreId]);
720
+ g_detailObj.density3PushDiff[_scoreId] = g_detailObj.density3PushData[_scoreId].concat();
721
+
722
+ g_detailObj.maxDensity[_scoreId] = getMaxValIdxs(densityData, g_limitObj.densityMaxVals).flat();
723
+
724
+ g_detailObj.arrowCnt[_scoreId] = noteCnt.arrow.concat();
725
+ g_detailObj.frzCnt[_scoreId] = noteCnt.frz.map((val, k) => _scoreObj.frzData[k].length % 2 === 0 ? val : val - 0.5);
726
+ g_detailObj.startFrame[_scoreId] = startFrame;
727
+ g_detailObj.playingFrame[_scoreId] = playingFrame;
728
+ g_detailObj.playingFrameWithBlank[_scoreId] = lastFrame - startFrame;
729
+
730
+ // --- ミニマップ設定 ---
731
+ g_detailObj.miniMapParams[_scoreId] = {
732
+ _scoreId, _scoreObj, _keyNum: keyNum,
733
+ _playingFrame: playingFrame,
734
+ _firstArrowFrame: firstArrowFrame,
735
+ _keyCtrlPtn,
736
+ config: {
737
+ scale: 1.5,
738
+ timeMargin: 35,
739
+ mmWidthBase: (g_sWidth - 500) / 2 + 290,
740
+ mmMarginY: 2,
741
+ get laneWidth() {
742
+ return Math.min((this.mmWidthBase - this.timeMargin) / keyNum, 40);
743
+ },
744
+ get logicalWidth() {
745
+ const logicalWidth = this.timeMargin + (this.laneWidth * keyNum);
746
+ return Math.ceil(logicalWidth * g_dpr) / g_dpr;
747
+ }
748
+ },
749
+ };
750
+
751
+ // Canvas保存用配列を空で初期化
752
+ g_detailObj.scoreMinimap[_scoreId] = null;
753
+ g_detailObj.scoreMinimapReverse[_scoreId] = null;
754
+ };
755
+
756
+ /**
757
+ * ツール計算
758
+ * @param {object} _scoreObj
759
+ * @param {number[][]} _scoreObj.arrowData 矢印データ
760
+ * @param {number[][]} _scoreObj.frzData フリーズデータ
761
+ * @returns {{tool: string, tate: number, douji: number, push3Cnt: number, push3: number[]}}
762
+ */
763
+ const calcLevel = _scoreObj => {
764
+ //--------------------------------------------------------------
765
+ //<フリーズデータ分解>
766
+ // フリーズデータを分解し、矢印データに組み込む
767
+ //
768
+ // (イメージ)
769
+ // &left_data=400,500,700&
770
+ // &frzLeft_data=550,650&
771
+ // ⇒
772
+ // left_data=[400,500,550,700]; // フリーズの始点を組込
773
+ // frzStartData=[550]; // フリーズ始点
774
+ // frzEndData =[650]; // フリーズ終点
775
+ //--------------------------------------------------------------
776
+ const frzStartData = [];
777
+ const frzEndData = [];
778
+
779
+ _scoreObj.frzData.forEach((frzs, j) => {
780
+ if (frzs.length > 1) {
781
+ for (let k = 0; k < frzs.length; k += 2) {
782
+ _scoreObj.arrowData[j].push(frzs[k]);
783
+ frzStartData.push(frzs[k]);
784
+ frzEndData.push(frzs[k + 1]);
785
+ }
786
+ }
787
+ _scoreObj.arrowData[j] = _scoreObj.arrowData[j].sort((a, b) => a - b)
788
+ .filter((x, i, self) => self.indexOf(x) === i && !isNaN(parseFloat(x)));
789
+ })
790
+
791
+ frzStartData.sort((a, b) => a - b);
792
+ frzEndData.sort((a, b) => a - b);
793
+
794
+ //--------------------------------------------------------------
795
+ //<データ結合・整理>
796
+ // 矢印データを連結してソートする。
797
+ //
798
+ // 重複は後の同時押し補正で使用する。
799
+ // 後の同時押し補正の都合上、firstFrame-100, lastFrame+100 のデータを末尾に追加。
800
+ //
801
+ // (イメージ)
802
+ // |left_data=300,400,550| // フリーズデータ(始点)を含む
803
+ // |down_data=500|
804
+ // |up_data=600|
805
+ // |right_data=700,800|
806
+ // |space_data=200,300,1000|
807
+ // frzEndData = [650]; // フリーズデータ(終点) ※allScorebook対象外
808
+ // ⇒
809
+ // allScorebook = [100,200,300,300,400,500,550,600,700,800,1000,1100];
810
+ //
811
+ //--------------------------------------------------------------
812
+ let allScorebook = [];
813
+ _scoreObj.arrowData.forEach(data => allScorebook = allScorebook.concat(data));
814
+
815
+ allScorebook.sort((a, b) => a - b);
816
+ allScorebook.unshift(allScorebook[0] - 100);
817
+ allScorebook.push(allScorebook.at(-1) + 100);
818
+ const allCnt = allScorebook.length;
819
+
820
+ frzEndData.push(allScorebook.at(-1));
821
+
822
+ //--------------------------------------------------------------
823
+ //<間隔フレーム数の調和平均計算+いろいろ補正>
824
+ // レベル計算メイン。
825
+ //
826
+ // [レベル計算ツール++ ver1.18] 3つ押し以上でも同時押し補正ができるよう調整
827
+ //--------------------------------------------------------------
828
+ let levelcount = 0; // 難易度レベル
829
+ let freezenum = 0; // フリーズアロー数
830
+ let pushCnt = 1; // 同時押し数カウント
831
+ let twoPushCount = 0; // 同時押し補正値
832
+ const push3List = []; // 3つ押し判定数
833
+
834
+ for (let i = 1; i < allCnt - 2; i++) {
835
+ // フリーズ始点の検索
836
+ while (frzStartData[0] === allScorebook[i]) {
837
+ // 同時押しの場合
838
+ if (allScorebook[i] === allScorebook[i + 1]) {
839
+ break;
840
+ }
841
+
842
+ // 現フレームに存在するフリーズ数を1増やす
843
+ // (フリーズアローの同時チェック開始)
844
+ frzStartData.shift();
845
+ freezenum++;
846
+ }
847
+
848
+ // フリーズ終点の検索
849
+ while (frzEndData[0] < allScorebook[i + 1]) {
850
+ // 現フレームに存在するフリーズ数を1減らす
851
+ frzEndData.shift();
852
+ freezenum--;
853
+ }
854
+
855
+ // 同時押し補正処理(フリーズアローが絡まない場合)
856
+ if (allScorebook[i + 1] === allScorebook[i] && !freezenum) {
857
+
858
+ const chk = (allScorebook[i + 2] - allScorebook[i + 1]) * (allScorebook[i] - allScorebook[i - pushCnt]);
859
+ if (chk !== 0) {
860
+ twoPushCount += 40 / chk;
861
+ } else {
862
+ // 3つ押しが絡んだ場合は加算しない
863
+ push3List.push(allScorebook[i]);
864
+ }
865
+ pushCnt++;
866
+
867
+ } else {
868
+ // 単押し+フリーズアローの補正処理(フリーズアロー中の矢印)
869
+ pushCnt = 1;
870
+ const chk2 = (2 - freezenum) * (allScorebook[i + 1] - allScorebook[i]);
871
+ if (chk2 > 0) {
872
+ levelcount += 2 / chk2;
873
+ } else {
874
+ // 3つ押しが絡んだ場合は加算しない
875
+ push3List.push(allScorebook[i]);
876
+ }
877
+ }
878
+ }
879
+ levelcount += twoPushCount;
880
+ const leveltmp = levelcount;
881
+
882
+ //--------------------------------------------------------------
883
+ //<同方向連打補正>
884
+ // 同方向矢印(フリーズアロー)の隣接間隔が10フレーム未満の場合に加算する。
885
+ //--------------------------------------------------------------
886
+ _scoreObj.arrowData.forEach(arrows =>
887
+ arrows.forEach((val, k) => {
888
+ if (arrows[k + 1] - arrows[k] < 10) {
889
+ levelcount += 10 / Math.pow(arrows[k + 1] - arrows[k], 2) - 1 / 10;
890
+ }
891
+ }));
892
+
893
+ //--------------------------------------------------------------
894
+ //<表示>
895
+ // 曲長、3つ押し補正を行い、最終的な難易度レベル値を表示する。
896
+ //--------------------------------------------------------------
897
+ const push3Cnt = push3List.length;
898
+ const calcArrowCnt = allCnt - push3Cnt - 3;
899
+ const toDecimal2 = num => Math.round(num * 100) / 100;
900
+ const calcDifLevel = num => calcArrowCnt > 0 ? toDecimal2(num / Math.sqrt(calcArrowCnt) * 4) : 0;
901
+
902
+ const baseDifLevel = calcDifLevel(levelcount);
903
+ const difLevel = toDecimal2(baseDifLevel * (allCnt - 3) / calcArrowCnt);
904
+
905
+ //--------------------------------------------------------------
906
+ //<計算結果を格納>
907
+ //--------------------------------------------------------------
908
+ return {
909
+ // 難易度レベル
910
+ tool: (allCnt === 3 ? `0.01` : `${difLevel.toFixed(2)}${(push3Cnt > 0 ? "*" : "")}`),
911
+ // 縦連打補正
912
+ tate: toDecimal2(baseDifLevel - calcDifLevel(leveltmp)),
913
+ // 同時押し補正
914
+ douji: calcDifLevel(twoPushCount),
915
+ // 3つ押し数
916
+ push3cnt: push3Cnt,
917
+ // 3つ押しリスト
918
+ push3: makeDedupliArray(push3List),
919
+ };
920
+ };
921
+
922
+ /**
923
+ * ロケールを含んだヘッダーの優先度設定
924
+ * @param {object} _obj
925
+ * @param {...any} [_params]
926
+ * @returns {string}
927
+ */
928
+ const getHeader = (_obj, ..._params) => {
929
+ let headerLocale, headerDf;
930
+ Object.keys(_params).forEach(j => {
931
+ headerLocale ??= _obj[`${_params[j]}${g_localeObj.val}`];
932
+ headerDf ??= _obj[_params[j]];
933
+ });
934
+ return headerLocale ?? headerDf;
935
+ };
936
+
937
+ /**
938
+ * ヘッダー名の互換設定
939
+ * @param {string} _param
940
+ * @returns {string[]}
941
+ */
942
+ const getHname = _param => [_param, _param.toLowerCase()];
943
+
944
+ /**
945
+ * 譜面ヘッダーの分解(スキン、jsファイルなどの設定)
946
+ * @param {object} _dosObj
947
+ * @returns {object}
948
+ */
949
+ const preheaderConvert = _dosObj => {
950
+
951
+ // ヘッダー群の格納先
952
+ const obj = {};
953
+
954
+ // ウィンドウ位置の設定
955
+ const align = _dosObj.windowAlign ?? g_presetObj.windowAlign;
956
+ if (align !== undefined) {
957
+ g_windowAlign[align]();
958
+ }
959
+
960
+ obj.jsData = [];
961
+ obj.stepRtnUse = true;
962
+
963
+ const setJsFiles = (_files, _defaultDir, _type = `custom`) =>
964
+ _files.filter(file => hasVal(file)).forEach(file => {
965
+ const [jsFile, jsDir] = getFilePath(file, _defaultDir);
966
+ obj.jsData.push([_type === `skin` ? `danoni_skin_${jsFile}.js` : jsFile, jsDir]);
967
+ });
968
+
969
+ const convLocalPath = (_file, _type) =>
970
+ g_remoteFlg && hasVal(_file) && !_file.includes(C_MRK_CURRENT_DIRECTORY) && !hasRemoteDomain(_file)
971
+ ? `${C_MRK_CURRENT_DIRECTORY}../${_type}/${_file}`
972
+ : _file;
973
+
974
+ // 外部スキンファイルの指定
975
+ const tmpSkinType = _dosObj.skinType ?? g_presetObj.skinType ?? `default`;
976
+ const tmpSkinTypes = tmpSkinType.split(`,`).map(file => {
977
+
978
+ // スキンタイプを取得(ディレクトリパス、カレント指定(..)を除去)
979
+ const match = file.match(/.*\/(.+)|\(\.\.\)([^/]+)|(.+)/);
980
+ const skinName = match[1] || match[2] || match[3];
981
+
982
+ // デフォルトセット以外はリモート先のデータを使用しない
983
+ return g_defaultSets.skinType.findIndex(val => val === skinName) < 0 ?
984
+ convLocalPath(file, `skin`) : file;
985
+ });
986
+ obj.defaultSkinFlg = tmpSkinTypes.includes(`default`) && setBoolVal(_dosObj.bgCanvasUse ?? g_presetObj.bgCanvasUse, true);
987
+ setJsFiles(tmpSkinTypes, C_DIR_SKIN, `skin`);
988
+
989
+ // 外部jsファイルの指定
990
+ const tmpCustomjs = getHeader(_dosObj, ...getHname(`customJs`)) ?? g_presetObj.customJs ?? C_JSF_CUSTOM;
991
+ setJsFiles(tmpCustomjs.replaceAll(`*`, g_presetObj.customJs).split(`,`)
992
+ .map(file => convLocalPath(file, `js`)), C_DIR_JS);
993
+
994
+ // 外部cssファイルの指定
995
+ const tmpCustomcss = getHeader(_dosObj, ...getHname(`customCss`)) ?? g_presetObj.customCss ?? ``;
996
+ setJsFiles(tmpCustomcss.replaceAll(`*`, g_presetObj.customCss).split(`,`)
997
+ .map(file => convLocalPath(file, `css`)), C_DIR_CSS);
998
+
999
+ // デフォルト曲名表示、背景、Ready表示の利用有無
1000
+ g_titleLists.init.forEach(objName => {
1001
+ const objUpper = toCapitalize(objName);
1002
+ obj[`custom${objUpper}Use`] =
1003
+ setBoolVal(_dosObj[`custom${objUpper}Use`] ?? g_presetObj.customDesignUse?.[objName]);
1004
+ });
1005
+
1006
+ // 背景・マスクモーションのパス指定方法を他の設定に合わせる設定
1007
+ obj.syncBackPath = setBoolVal(_dosObj.syncBackPath ?? g_presetObj.syncBackPath);
1008
+
1009
+ return obj;
1010
+ };
1011
+
1012
+ /**
1013
+ * 譜面ヘッダーの分解(その他の設定)
1014
+ * @param {object} _dosObj 譜面データオブジェクト
1015
+ * @returns {object}
1016
+ */
1017
+ const headerConvert = _dosObj => {
1018
+
1019
+ // ヘッダー群の格納先
1020
+ const obj = {};
1021
+
1022
+ // 自動プリロードの設定
1023
+ obj.autoPreload = setBoolVal(_dosObj.autoPreload, true);
1024
+ g_headerObj.autoPreload = obj.autoPreload;
1025
+
1026
+ // デフォルトスタイルのバックアップ
1027
+ getCssCustomProperties();
1028
+
1029
+ // 初期で変更するカスタムプロパティを設定
1030
+ Object.keys(_dosObj).filter(val => val.startsWith(`--`) && hasVal(_dosObj[val])).forEach(prop => {
1031
+ g_cssBkProperties[prop] = getCssCustomProperty(prop, _dosObj[prop]);
1032
+ document.documentElement.style.setProperty(prop, g_cssBkProperties[prop]);
1033
+ });
1034
+
1035
+ // フォントの設定
1036
+ obj.customFont = _dosObj.customFont ?? ``;
1037
+ g_headerObj.customFont = obj.customFont;
1038
+
1039
+ // 画像ルートパス、拡張子の設定 (サーバ上のみ)
1040
+ obj.imgType = [];
1041
+ if (!g_isFile) {
1042
+ let tmpImgTypes = [];
1043
+ if (hasVal(_dosObj.imgType)) {
1044
+ tmpImgTypes = splitLF2(_dosObj.imgType);
1045
+ } else if (g_presetObj.imageSets !== undefined) {
1046
+ tmpImgTypes = g_presetObj.imageSets.concat();
1047
+ }
1048
+ tmpImgTypes.forEach((tmpImgType, j) => {
1049
+ const imgTypes = tmpImgType.split(`,`);
1050
+ obj.imgType[j] = {
1051
+ name: imgTypes[0],
1052
+ extension: imgTypes[1] || `svg`,
1053
+ rotateEnabled: setBoolVal(imgTypes[2], true),
1054
+ flatStepHeight: setVal(imgTypes[3], C_ARW_WIDTH, C_TYP_FLOAT),
1055
+ remoteDir: imgTypes[4] || ``,
1056
+ };
1057
+ g_keycons.imgTypes[j] = (imgTypes[0] === `` ? `Original` : imgTypes[0]);
1058
+ });
1059
+ }
1060
+
1061
+ // 末尾にデフォルト画像セットが入るよう追加
1062
+ if (obj.imgType.findIndex(imgSets => imgSets.name === ``) === -1) {
1063
+ obj.imgType.push({ name: ``, extension: `svg`, rotateEnabled: true, flatStepHeight: C_ARW_WIDTH, remoteDir: `` });
1064
+ g_keycons.imgTypes.push(`Original`);
1065
+ }
1066
+ g_imgType = g_keycons.imgTypes[0];
1067
+ g_stateObj.rotateEnabled = obj.imgType[0].rotateEnabled;
1068
+ g_stateObj.flatStepHeight = obj.imgType[0].flatStepHeight;
1069
+ changeSettingListsForImg();
1070
+
1071
+ const [titleArrowName, titleArrowRotate] = padArray(_dosObj.titleArrowName?.split(`:`), [`Original`, 180]);
1072
+ obj.titleArrowNo = roundZero(g_keycons.imgTypes.findIndex(imgType => imgType === titleArrowName));
1073
+ obj.titleArrowRotate = titleArrowRotate;
1074
+
1075
+ // サーバ上の場合、画像セットを再読込(ローカルファイル時は読込済みのためスキップ)
1076
+ if (!g_isFile) {
1077
+ updateImgType(obj.imgType[obj.titleArrowNo], true);
1078
+ updateImgType(obj.imgType[0]);
1079
+ } else {
1080
+ g_imgObj.titleArrow = C_IMG_ARROW;
1081
+ }
1082
+
1083
+ // 自動横幅拡張設定
1084
+ obj.autoSpread = setBoolVal(_dosObj.autoSpread, g_presetObj.autoSpread ?? true);
1085
+
1086
+ // 横幅設定
1087
+ if (hasVal(_dosObj.windowWidth)) {
1088
+ g_sWidth = Math.max(setIntVal(_dosObj.windowWidth, g_sWidth), g_sWidth);
1089
+ $id(`canvas-frame`).width = wUnit(g_sWidth);
1090
+ }
1091
+ // 高さ設定
1092
+ obj.heightVariable = getQueryParamVal(`h`) !== null && (_dosObj.heightVariable || g_presetObj.heightVariable || false);
1093
+ if (hasVal(_dosObj.windowHeight || g_presetObj.autoMinHeight) || obj.heightVariable) {
1094
+ g_sHeight = Math.max(setIntVal(_dosObj.windowHeight, g_presetObj.autoMinHeight ?? g_sHeight),
1095
+ setIntVal(getQueryParamVal(`h`), g_sHeight), g_sHeight);
1096
+ $id(`canvas-frame`).height = wUnit(g_sHeight);
1097
+ }
1098
+
1099
+ // 曲名
1100
+ obj.musicTitles = [`musicName`];
1101
+ obj.musicTitlesForView = [[`musicName`]];
1102
+ obj.artistNames = [``];
1103
+ obj.artistUrls = [``];
1104
+ obj.bpms = [`----`];
1105
+ obj.musicNos = hasVal(_dosObj.musicNo)
1106
+ ? splitLF2(_dosObj.musicNo).map(Number).map(val => isNaN(val) ? 0 : val)
1107
+ : fillArray(_dosObj.difData?.split(`$`).length ?? 1);
1108
+
1109
+ const dosMusicTitle = getHeader(_dosObj, `musicTitle`);
1110
+ let alternativeTitle;
1111
+ if (hasVal(dosMusicTitle)) {
1112
+ const musicData = splitLF2(dosMusicTitle);
1113
+
1114
+ const lastIdx = Math.max(...obj.musicNos, musicData.length - 1);
1115
+ for (let j = 0; j <= lastIdx; j++) {
1116
+ const tmpMusicData = musicData[j] ?? ``;
1117
+ const musics = splitComma(tmpMusicData);
1118
+
1119
+ obj.musicTitles[j] = hasVal(musics[0])
1120
+ ? escapeHtml(getMusicNameSimple(musics[0]))
1121
+ : obj.musicTitles[0];
1122
+ obj.musicTitlesForView[j] = hasVal(musics[0])
1123
+ ? escapeHtmlForArray(getMusicNameMultiLine(musics[0]))
1124
+ : obj.musicTitlesForView[0];
1125
+ obj.artistNames[j] = hasVal(musics[1])
1126
+ ? escapeHtml(musics[1])
1127
+ : obj.artistNames[0];
1128
+ obj.artistUrls[j] = musics[2] || obj.artistUrls[0];
1129
+ obj.bpms[j] = musics[4] || obj.bpms[0];
1130
+
1131
+ // 代替タイトル名
1132
+ if (j === 0 && hasVal(_dosObj.musicNo)) {
1133
+ alternativeTitle = musics[3];
1134
+ }
1135
+ }
1136
+
1137
+ } else {
1138
+ makeWarningWindow(g_msgInfoObj.E_0012);
1139
+ }
1140
+
1141
+ // 単一作品用の項目としての管理変数
1142
+ obj.musicTitle = obj.musicTitles[0];
1143
+ obj.musicTitleForView = obj.musicTitlesForView[0];
1144
+ obj.artistName = obj.artistNames[0];
1145
+ if (obj.artistName === ``) {
1146
+ makeWarningWindow(g_msgInfoObj.E_0011);
1147
+ obj.artistName = `artistName`;
1148
+ }
1149
+ obj.artistUrl = obj.artistUrls[0];
1150
+
1151
+ // 代替タイトル名は曲名定義の後に設定する(複数曲を束ねる名前であり、曲名ではないため)
1152
+ if (hasVal(alternativeTitle)) {
1153
+ obj.musicTitles[0] = escapeHtml(getMusicNameSimple(alternativeTitle));
1154
+ obj.musicTitlesForView[0] = escapeHtmlForArray(getMusicNameMultiLine(alternativeTitle));
1155
+ }
1156
+
1157
+ // 選曲機能の利用有無(最後のカンマ後の文字をBGM利用フラグとして利用)
1158
+ const rawPackageName = _dosObj.packageName || ``;
1159
+ const packageNameParts = rawPackageName.split(`,`);
1160
+ const bgmUseSwitch = setVal(trimStr(packageNameParts.at(-1)), ``, C_TYP_SWITCH);
1161
+ const packageName = bgmUseSwitch === ``
1162
+ ? rawPackageName
1163
+ : packageNameParts.slice(0, -1).join(`,`);
1164
+ obj.packageNames = (packageName || ``).split(`<br>`);
1165
+ obj.musicSelectUse = _dosObj.packageName !== undefined;
1166
+ obj.bgmUseFlg = bgmUseSwitch === C_FLG_ON;
1167
+
1168
+ if (!obj.bgmUseFlg) {
1169
+ g_stateObj.bgmMuteFlg = true;
1170
+ }
1171
+
1172
+ // 最小・最大速度の設定
1173
+ obj.minSpeed = Math.round(setVal(_dosObj.minSpeed, C_MIN_SPEED, C_TYP_FLOAT) * 4) / 4;
1174
+ obj.maxSpeed = Math.round(setVal(_dosObj.maxSpeed, C_MAX_SPEED, C_TYP_FLOAT) * 4) / 4;
1175
+ if (obj.minSpeed > obj.maxSpeed || obj.minSpeed < 0.5 || obj.maxSpeed < 0.5) {
1176
+ obj.minSpeed = C_MIN_SPEED;
1177
+ obj.maxSpeed = C_MAX_SPEED;
1178
+ }
1179
+ g_settings.speeds = makeSpeedList(obj.minSpeed, obj.maxSpeed);
1180
+
1181
+ // プレイ中のショートカットキー
1182
+ obj.keyRetry = setIntVal(getKeyCtrlVal(_dosObj.keyRetry), C_KEY_RETRY);
1183
+ obj.keyRetryDef = obj.keyRetry;
1184
+ obj.keyRetryDef2 = obj.keyRetry;
1185
+ obj.keyTitleBack = setIntVal(getKeyCtrlVal(_dosObj.keyTitleBack), C_KEY_TITLEBACK);
1186
+ obj.keyTitleBackDef = obj.keyTitleBack;
1187
+ obj.keyTitleBackDef2 = obj.keyTitleBack;
1188
+ obj.keyPause = setIntVal(getKeyCtrlVal(_dosObj.keyPause), C_KEY_PAUSE);
1189
+ obj.keyPauseDef = obj.keyPause;
1190
+ obj.keyPauseDef2 = obj.keyPause;
1191
+
1192
+ // フリーズアローの許容フレーム数設定
1193
+ obj.frzAttempt = setIntVal(_dosObj.frzAttempt, C_FRM_FRZATTEMPT);
1194
+
1195
+ // 製作者表示
1196
+ const dosTuning = getHeader(_dosObj, `tuning`);
1197
+ obj.tuningNames = [];
1198
+ obj.tuningUrls = [];
1199
+ if (hasVal(dosTuning)) {
1200
+ splitLF2(dosTuning).forEach(tuning => {
1201
+ const tuningData = tuning.split(`,`);
1202
+ obj.tuningNames.push(escapeHtmlForEnabledTag(tuningData[0]));
1203
+ obj.tuningUrls.push(tuningData[1] ||
1204
+ (getHeader(g_presetObj, `tuning`) === tuningData[0] ? g_presetObj.tuningUrl : ``));
1205
+ });
1206
+ obj.tuning = obj.tuningNames[0];
1207
+ obj.creatorUrl = obj.tuningUrls[0] || g_presetObj.tuningUrl || ``;
1208
+ } else {
1209
+ obj.tuning = escapeHtmlForEnabledTag(getHeader(g_presetObj, `tuning`) ?? `name`);
1210
+ obj.creatorUrl = g_presetObj.tuningUrl ?? ``;
1211
+ }
1212
+ obj.tuningInit = obj.tuning;
1213
+
1214
+ obj.dosNos = [];
1215
+ obj.scoreNos = [];
1216
+ if (hasVal(_dosObj.dosNo)) {
1217
+ splitLF2(_dosObj.dosNo).map((val, j) => [obj.dosNos[j], obj.scoreNos[j]] = val.split(`,`));
1218
+ const dosNoCnt = {};
1219
+ obj.dosNos.forEach((val, j) => {
1220
+ if (dosNoCnt[val] === undefined) {
1221
+ dosNoCnt[val] = 0;
1222
+ }
1223
+ if (obj.scoreNos[j] === undefined) {
1224
+ dosNoCnt[val]++;
1225
+ obj.scoreNos[j] = dosNoCnt[val];
1226
+ } else {
1227
+ dosNoCnt[val] = Number(obj.scoreNos[j]);
1228
+ }
1229
+ });
1230
+ }
1231
+
1232
+ // 譜面情報
1233
+ if (hasVal(_dosObj.difData)) {
1234
+ const difs = splitLF2(_dosObj.difData);
1235
+ const difpos = {
1236
+ Key: 0, Name: 1, Speed: 2, Border: 3, Recovery: 4, Damage: 5, Init: 6,
1237
+ };
1238
+ obj.keyLabels = [];
1239
+ obj.difLabels = [];
1240
+ obj.initSpeeds = [];
1241
+ obj.lifeBorders = [];
1242
+ obj.lifeRecoverys = [];
1243
+ obj.lifeDamages = [];
1244
+ obj.lifeInits = [];
1245
+ obj.creatorNames = [];
1246
+ obj.difficulties = [];
1247
+ g_stateObj.scoreId = (g_stateObj.scoreId < difs.length ? g_stateObj.scoreId : 0);
1248
+
1249
+ difs.forEach(dif => {
1250
+ const difDetails = dif.split(`,`);
1251
+ const lifeData = (_type, _default) =>
1252
+ difDetails[difpos[_type]] || g_presetObj.gauge?.[_type] || _default;
1253
+
1254
+ // ライフ:ノルマ、回復量、ダメージ量、初期値の設定
1255
+ obj.lifeBorders.push(lifeData(`Border`, `x`));
1256
+ obj.lifeRecoverys.push(lifeData(`Recovery`, 6));
1257
+ obj.lifeDamages.push(lifeData(`Damage`, 40));
1258
+ obj.lifeInits.push(lifeData(`Init`, 25));
1259
+
1260
+ // キー数
1261
+ const keyLabel = difDetails[difpos.Key] || g_keyObj.initKeyLabel;
1262
+ obj.keyLabels.push(g_keyObj.keyTransPattern[keyLabel] ?? keyLabel);
1263
+
1264
+ // 譜面名、制作者名
1265
+ if (hasVal(difDetails[difpos.Name])) {
1266
+ const difNameInfo = difDetails[difpos.Name].split(`::`);
1267
+ obj.difLabels.push(escapeHtml(difNameInfo[0] ?? `Normal`));
1268
+ obj.creatorNames.push(setVal(escapeHtml(difNameInfo[1]), obj.tuning));
1269
+ obj.difficulties.push(setIntVal(difNameInfo[2], 0));
1270
+ } else {
1271
+ obj.difLabels.push(`Normal`);
1272
+ obj.creatorNames.push(obj.tuning);
1273
+ obj.difficulties.push(0);
1274
+ }
1275
+
1276
+ // 初期速度
1277
+ obj.initSpeeds.push(setVal(difDetails[difpos.Speed], 3.5, C_TYP_FLOAT));
1278
+ });
1279
+ } else {
1280
+ makeWarningWindow(g_msgInfoObj.E_0021);
1281
+ obj.keyLabels = [g_keyObj.initKeyLabel];
1282
+ obj.difLabels = [`Normal`];
1283
+ obj.initSpeeds = [3.5];
1284
+ obj.lifeBorders = [`x`];
1285
+ obj.lifeRecoverys = [6];
1286
+ obj.lifeDamages = [40];
1287
+ obj.lifeInits = [25];
1288
+ obj.creatorNames = [obj.tuning];
1289
+ obj.difficulties = [0];
1290
+ }
1291
+ const keyLists = makeDedupliArray(obj.keyLabels);
1292
+ obj.viewLists = [...Array(obj.keyLabels.length).keys()];
1293
+ obj.keyLists = keyLists.sort((a, b) => parseInt(a) - parseInt(b));
1294
+ obj.undefinedKeyLists = obj.keyLists.filter(key => g_keyObj[`${g_keyObj.defaultProp}${key}_0`] === undefined);
1295
+
1296
+ // 楽曲別のグループ化設定(選曲モードのみ)
1297
+ if (hasVal(_dosObj.musicGroup)) {
1298
+ obj.musicGroups = _dosObj.musicGroup.split(`,`)
1299
+ .map((val, j) => setVal(val, j, C_TYP_NUMBER))
1300
+ .map((val, j) => val < 0 ? j + val : val);
1301
+ for (let k = obj.musicGroups.length; k <= Math.max(...obj.musicNos); k++) {
1302
+ obj.musicGroups[k] = k;
1303
+ }
1304
+ obj.musicIdxList = makeDedupliArray(obj.musicGroups);
1305
+ } else {
1306
+ obj.musicIdxList = [...Array(Math.max(...obj.musicNos) + 1).keys()];
1307
+ }
1308
+
1309
+ // 難易度配色の設定(選曲画面でのみ使用)
1310
+ const normalizeCssColor = _color => {
1311
+ const tmp = document.createElement(`span`);
1312
+ tmp.style.color = ``;
1313
+ tmp.style.color = trimStr(_color ?? ``);
1314
+ return tmp.style.color;
1315
+ };
1316
+ obj.difColorList = [
1317
+ { threshold: Infinity, color: `` }
1318
+ ];
1319
+ if (hasVal(_dosObj.difColor)) {
1320
+ _dosObj.difColor.split(`,`).forEach(val => {
1321
+ const difColorSet = val.split(`/`);
1322
+ obj.difColorList.push({
1323
+ threshold: setIntVal(difColorSet[0]),
1324
+ color: hasVal(difColorSet[1]) ? normalizeCssColor(difColorSet[1]) : ``
1325
+ });
1326
+ })
1327
+ }
1328
+ obj.difColorList.sort((a, b) => a.threshold - b.threshold);
1329
+
1330
+ const sanitizeCustomLink = _link => {
1331
+ try {
1332
+ const raw = trimStr(_link);
1333
+ if (!hasVal(raw)) return undefined;
1334
+ const url = new URL(raw, location.href); // allows relative inputs
1335
+ const allowed = g_isFile ? [`http:`, `https:`, `file:`] : [`http:`, `https:`];
1336
+ return allowed.includes(url.protocol) ? url.href : undefined;
1337
+ } catch {
1338
+ return undefined;
1339
+ }
1340
+ };
1341
+ obj.difCustomLink = [];
1342
+ if (hasVal(_dosObj.difCustomLink)) {
1343
+ splitLF2(_dosObj.difCustomLink).forEach(val => {
1344
+ const commaPos = val.indexOf(`,`);
1345
+ if (commaPos < 0) return;
1346
+ const idxStr = trimStr(val.slice(0, commaPos));
1347
+ const linkStr = val.slice(commaPos + 1);
1348
+ const idx = setIntVal(idxStr, -1);
1349
+ if (!Number.isFinite(idx) || idx < 0 || idx >= obj.difLabels.length) return;
1350
+ const safeHref = sanitizeCustomLink(linkStr);
1351
+ if (safeHref !== undefined) {
1352
+ obj.difCustomLink[idx] = safeHref;
1353
+ }
1354
+ });
1355
+ }
1356
+
1357
+ // 譜面変更セレクターの利用有無
1358
+ obj.difSelectorUse = getDifSelectorUse(_dosObj.difSelectorUse, obj.viewLists);
1359
+
1360
+ // 初期速度の設定
1361
+ g_stateObj.speed = obj.initSpeeds[g_stateObj.scoreId];
1362
+ g_settings.speedNum = roundZero(g_settings.speeds.findIndex(speed => speed === g_stateObj.speed));
1363
+
1364
+ // グラデーションのデフォルト中間色を設定
1365
+ divRoot.appendChild(createDivCss2Label(`dummyLabel`, ``));
1366
+ obj.baseBrightFlg = setBoolVal(_dosObj.baseBright, checkLightOrDark(colorNameToCode(window.getComputedStyle(dummyLabel, ``).color)));
1367
+ const intermediateColor = obj.baseBrightFlg ? `#111111` : `#eeeeee`;
1368
+
1369
+ // 矢印の色変化を常時グラデーションさせる設定
1370
+ obj.defaultColorgrd = [false, intermediateColor];
1371
+ if (hasVal(_dosObj.defaultColorgrd)) {
1372
+ obj.defaultColorgrd = _dosObj.defaultColorgrd.split(`,`);
1373
+ obj.defaultColorgrd[0] = setBoolVal(obj.defaultColorgrd[0]);
1374
+ obj.defaultColorgrd[1] = obj.defaultColorgrd[1] ?? intermediateColor;
1375
+ }
1376
+ g_rankObj.rankColorAllPerfect = intermediateColor;
1377
+
1378
+ // カラーコードのゼロパディング有無設定
1379
+ obj.colorCdPaddingUse = setBoolVal(_dosObj.colorCdPaddingUse);
1380
+
1381
+ // 最大ライフ
1382
+ obj.maxLifeVal = setVal(_dosObj.maxLifeVal, C_VAL_MAXLIFE, C_TYP_FLOAT);
1383
+ if (obj.maxLifeVal <= 0) {
1384
+ obj.maxLifeVal = C_VAL_MAXLIFE;
1385
+ makeWarningWindow(g_msgInfoObj.E_0042.split(`{0}`).join(`maxLifeVal`));
1386
+ }
1387
+
1388
+ // ゲージ初期設定(最大ライフ反映)
1389
+ g_gaugeOptionObj.defaultList.forEach(type => {
1390
+ const pos = g_gaugeOptionObj[`dmg${toCapitalize(type)}`].findIndex(val => val === C_LFE_MAXLIFE);
1391
+ g_gaugeOptionObj[`dmg${toCapitalize(type)}`][pos] = obj.maxLifeVal;
1392
+ });
1393
+
1394
+ // フリーズアローのデフォルト色セットの利用有無 (true: 使用, false: 矢印色を優先してセット)
1395
+ obj.defaultFrzColorUse = setBoolVal(_dosObj.defaultFrzColorUse ?? g_presetObj.frzColors, true);
1396
+
1397
+ // 矢印色変化に対応してフリーズアロー色を追随する範囲の設定
1398
+ // (defaultFrzColorUse=false時のみ)
1399
+ obj.frzScopeFromArrowColors = [];
1400
+
1401
+ if (!obj.defaultFrzColorUse) {
1402
+ const tmpFrzScope = [];
1403
+
1404
+ if (hasVal(_dosObj.frzScopeFromAC)) {
1405
+ tmpFrzScope.push(..._dosObj.frzScopeFromAC.split(`,`));
1406
+ } else if (g_presetObj.frzScopeFromAC !== undefined) {
1407
+ tmpFrzScope.push(...g_presetObj.frzScopeFromAC);
1408
+ }
1409
+ tmpFrzScope.filter(type => [`Normal`, `Hit`].includes(type))
1410
+ .forEach(data => obj.frzScopeFromArrowColors.push(data));
1411
+ }
1412
+
1413
+ // 初期色情報
1414
+ const baseColor = (obj.baseBrightFlg ? `light` : `dark`);
1415
+ Object.assign(g_dfColorObj, g_dfColorBaseObj[baseColor]);
1416
+ Object.keys(g_dfColorObj).forEach(key => obj[key] = g_dfColorObj[key].concat());
1417
+ obj.frzColorDefault = [];
1418
+
1419
+ // ダミー用初期矢印色
1420
+ obj.setDummyColor = [`#777777`, `#444444`, `#777777`, `#444444`, `#777777`];
1421
+ obj.dfColorgrdSet = {
1422
+ '': obj.defaultColorgrd,
1423
+ 'Type0': [!obj.defaultColorgrd[0], obj.defaultColorgrd[1]],
1424
+ };
1425
+
1426
+ // カスタムゲージ設定(共通設定ファイル)
1427
+ addGaugeFulls(g_gaugeOptionObj.survival);
1428
+ addGaugeFulls(g_gaugeOptionObj.border);
1429
+
1430
+ if (g_presetObj.gaugeList !== undefined) {
1431
+ Object.keys(g_presetObj.gaugeList).forEach(key => {
1432
+ g_gaugeOptionObj.customDefault.push(key);
1433
+ g_gaugeOptionObj.varCustomDefault.push(boolToSwitch(g_presetObj.gaugeList[key] === `V`));
1434
+ });
1435
+ g_gaugeOptionObj.custom = g_gaugeOptionObj.customDefault.concat();
1436
+ g_gaugeOptionObj.varCustom = g_gaugeOptionObj.varCustomDefault.concat();
1437
+ addGaugeFulls(g_gaugeOptionObj.customDefault);
1438
+ }
1439
+
1440
+ // カスタムゲージ設定、初期色設定(譜面ヘッダー)の譜面別設定
1441
+ Object.assign(obj, resetBaseColorList(obj, _dosObj));
1442
+ for (let j = 0; j < obj.difLabels.length; j++) {
1443
+ Object.assign(g_gaugeOptionObj, resetCustomGauge(_dosObj, { scoreId: j }));
1444
+ Object.assign(obj, resetBaseColorList(obj, _dosObj, { scoreId: j }));
1445
+ }
1446
+
1447
+ // ダミー譜面の設定
1448
+ if (hasVal(_dosObj.dummyId)) {
1449
+ obj.dummyScoreNos = _dosObj.dummyId.split(`$`);
1450
+ }
1451
+
1452
+ // 無音のフレーム数
1453
+ obj.blankFrameDefs = [200];
1454
+ if (isNaN(parseFloat(_dosObj.blankFrame))) {
1455
+ } else {
1456
+ obj.blankFrameDefs = splitLF2(_dosObj.blankFrame).map(val => parseInt(val));
1457
+ }
1458
+ obj.blankFrame = obj.blankFrameDefs[0];
1459
+ obj.blankFrameDef = obj.blankFrameDefs[0];
1460
+
1461
+ // 開始フレーム数(0以外の場合はフェードインスタート)、終了フレーム数
1462
+ [`startFrame`, `endFrame`].filter(tmpParam => hasVal(_dosObj[tmpParam]))
1463
+ .forEach(param => obj[param] = splitLF2(_dosObj[param]).map(frame => transTimerToFrame(frame)));
1464
+
1465
+ // フェードアウトフレーム数(譜面別)
1466
+ if (hasVal(_dosObj.fadeFrame)) {
1467
+ const fadeFrames = splitLF2(_dosObj.fadeFrame);
1468
+ obj.fadeFrame = [];
1469
+ fadeFrames.forEach((fadeInfo, j) => {
1470
+ obj.fadeFrame[j] = fadeInfo.split(`,`);
1471
+ obj.fadeFrame[j][0] = transTimerToFrame(obj.fadeFrame[j][0]);
1472
+ });
1473
+ }
1474
+
1475
+ // タイミング調整
1476
+ obj.adjustment = (hasVal(_dosObj.adjustment) ? _dosObj.adjustment.split(`$`) : [0]);
1477
+
1478
+ // 再生速度
1479
+ obj.playbackRate = setVal(_dosObj.playbackRate, 1, C_TYP_FLOAT);
1480
+ if (obj.playbackRate <= 0) {
1481
+ obj.playbackRate = 1;
1482
+ makeWarningWindow(g_msgInfoObj.E_0042.split(`{0}`).join(`playbackRate`));
1483
+ }
1484
+
1485
+ // プレイサイズ(X方向, Y方向)
1486
+ obj.playingWidth = setIntVal(_dosObj.playingWidth, g_presetObj.playingWidth ?? `default`);
1487
+ const tmpPlayingHeight = setIntVal(_dosObj.playingHeight, g_presetObj.playingHeight ?? g_sHeight);
1488
+ obj.playingHeight = Math.max(obj.heightVariable ?
1489
+ setIntVal(getQueryParamVal(`h`) - (g_sHeight - tmpPlayingHeight), tmpPlayingHeight) : tmpPlayingHeight, 400);
1490
+
1491
+ // プレイ左上位置(X座標, Y座標)
1492
+ obj.playingX = setIntVal(_dosObj.playingX, g_presetObj.playingX ?? 0);
1493
+ obj.playingY = setIntVal(_dosObj.playingY, g_presetObj.playingY ?? 0);
1494
+
1495
+ // ステップゾーン位置
1496
+ g_posObj.stepY = setVal(_dosObj.stepY, C_STEP_Y, C_TYP_FLOAT);
1497
+ g_posObj.stepYR = setVal(_dosObj.stepYR, C_STEP_YR, C_TYP_FLOAT);
1498
+ g_posObj.stepDiffY = g_posObj.stepY - C_STEP_Y;
1499
+ g_posObj.distY = obj.playingHeight - C_STEP_Y + g_posObj.stepYR;
1500
+ g_posObj.reverseStepY = g_posObj.distY - g_posObj.stepY - g_posObj.stepDiffY - C_ARW_WIDTH;
1501
+ g_posObj.arrowHeight = obj.playingHeight + g_posObj.stepYR - g_posObj.stepDiffY * 2;
1502
+ obj.bottomWordSetFlg = setBoolVal(_dosObj.bottomWordSet);
1503
+
1504
+ // ウィンドウサイズ(高さ)とステップゾーン位置の組み合わせで基準速度を変更
1505
+ obj.baseSpeed = 1 + ((g_posObj.distY - (g_posObj.stepY - C_STEP_Y) * 2) / (500 - C_STEP_Y) - 1) * 0.85;
1506
+
1507
+ // 矢印・フリーズアロー判定位置補正
1508
+ g_diffObj.arrowJdgX = setVal(_dosObj.arrowJdgX, 0, C_TYP_FLOAT);
1509
+ g_diffObj.arrowJdgY = setVal(_dosObj.arrowJdgY, 0, C_TYP_FLOAT);
1510
+ g_diffObj.frzJdgX = setVal(_dosObj.frzJdgX, 0, C_TYP_FLOAT);
1511
+ g_diffObj.frzJdgY = setVal(_dosObj.frzJdgY, 0, C_TYP_FLOAT);
1512
+ g_diffInitObj.arrowJdgX = g_diffObj.arrowJdgX;
1513
+ g_diffInitObj.arrowJdgY = g_diffObj.arrowJdgY;
1514
+ g_diffInitObj.frzJdgX = g_diffObj.frzJdgX;
1515
+ g_diffInitObj.frzJdgY = g_diffObj.frzJdgY;
1516
+
1517
+ // ショートカット表示位置補正
1518
+ g_diffObj.shortcutX = setVal(_dosObj.shortcutX, 0, C_TYP_FLOAT);
1519
+ g_diffObj.shortcutY = setVal(_dosObj.shortcutY, 0, C_TYP_FLOAT);
1520
+ g_diffInitObj.shortcutX = g_diffObj.shortcutX;
1521
+ g_diffInitObj.shortcutY = g_diffObj.shortcutY;
1522
+
1523
+ if (Object.keys(g_diffObj).some(key => g_localStorage[key] !== undefined)) {
1524
+ Object.keys(g_diffObj).forEach(key =>
1525
+ g_diffObj[key] = setIntVal(g_localStorage[key], g_diffObj[key])
1526
+ );
1527
+ }
1528
+
1529
+ // musicフォルダ設定
1530
+ obj.musicFolder = _dosObj.musicFolder ?? (g_remoteFlg ? `${C_MRK_CURRENT_DIRECTORY}../music` : `music`);
1531
+
1532
+ // 楽曲URL
1533
+ if (hasVal(_dosObj.musicUrl)) {
1534
+ const musicUrls = splitLF2(_dosObj.musicUrl);
1535
+ obj.musicUrls = [], obj.musicStarts = [], obj.musicEnds = [];
1536
+ musicUrls.forEach((val, j) => {
1537
+ const musicUrlPair = val.split(`,`);
1538
+ obj.musicUrls[j] = musicUrlPair[0] || ``;
1539
+ if (musicUrlPair[1] !== undefined) {
1540
+ const musicBGMTime = musicUrlPair[1].split(`-`).map(str => str.trim());
1541
+ obj.musicStarts[j] = Math.floor(transTimerToFrame(musicBGMTime[0] ?? 0) / g_fps);
1542
+ obj.musicEnds[j] = musicBGMTime[1] !== undefined ?
1543
+ Math.floor((transTimerToFrame(musicBGMTime[1] ?? 0)) / g_fps) :
1544
+ Math.floor((transTimerToFrame(musicBGMTime[0] ?? 0) + transTimerToFrame(`0:20`)) / g_fps);
1545
+ } else {
1546
+ obj.musicStarts[j] = 0;
1547
+ obj.musicEnds[j] = 20;
1548
+ }
1549
+ });
1550
+ } else {
1551
+ makeWarningWindow(g_msgInfoObj.E_0031);
1552
+ }
1553
+
1554
+ // ハッシュタグ
1555
+ obj.hashTag = _dosObj.hashTag ?? ``;
1556
+
1557
+ // 読込対象の画像を指定(rel:preload)と同じ
1558
+ obj.preloadImages = [];
1559
+ if (hasVal(_dosObj.preloadImages)) {
1560
+ obj.preloadImages = _dosObj.preloadImages.split(`,`).filter(image => hasVal(image)).map(preloadImage => preloadImage);
1561
+ }
1562
+
1563
+ // 初期表示する部分キーの設定
1564
+ obj.keyGroupOrder = [];
1565
+ _dosObj.keyGroupOrder?.split(`$`).forEach((val, j) => {
1566
+ if (val !== ``) {
1567
+ obj.keyGroupOrder[j] = val.split(`,`);
1568
+ }
1569
+ });
1570
+
1571
+ // 縦伸縮率の設定
1572
+ const stretchYRate = [];
1573
+ _dosObj.stretchYRate?.split(`$`).forEach((val, j) => {
1574
+ stretchYRate[j] = hasVal(val) ? setVal(val, 1, C_TYP_FLOAT) : 1;
1575
+ });
1576
+ obj.stretchYRate = makeBaseArray(stretchYRate, obj.difLabels.length, 1);
1577
+ // 最終演出表示有無(noneで無効化)
1578
+ obj.finishView = _dosObj.finishView ?? ``;
1579
+
1580
+ // 更新日
1581
+ obj.releaseDate = _dosObj.releaseDate ?? ``;
1582
+
1583
+ // デフォルトReady/リザルト表示の遅延時間設定
1584
+ [`ready`, `result`].forEach(objName =>
1585
+ obj[`${objName}DelayFrame`] = setIntVal(_dosObj[`${objName}DelayFrame`]));
1586
+
1587
+ // デフォルトReady表示のアニメーション時間設定
1588
+ obj.readyAnimationFrame = setIntVal(_dosObj.readyAnimationFrame, 150);
1589
+
1590
+ // デフォルトReady表示のアニメーション名
1591
+ obj.readyAnimationName = _dosObj.readyAnimationName ?? `leftToRightFade`;
1592
+
1593
+ // デフォルトReady表示の先頭文字色
1594
+ obj.readyColor = _dosObj.readyColor ?? ``;
1595
+
1596
+ // デフォルトReady表示を上書きするテキスト
1597
+ obj.readyHtml = _dosObj.readyHtml ?? ``;
1598
+
1599
+ // デフォルト曲名表示のフォントサイズ
1600
+ obj.titlesize = getHeader(_dosObj, ...getHname(`titleSize`)) ?? ``;
1601
+
1602
+ // デフォルト曲名表示のフォント名
1603
+ // (使用例: |titlefont=Century,Meiryo UI|)
1604
+ obj.titlefonts = g_titleLists.defaultFonts.concat();
1605
+ getHeader(_dosObj, ...getHname(`titleFont`))?.split(`$`).forEach((font, j) => obj.titlefonts[j] = `'${(font.replaceAll(`,`, `', '`))}'`);
1606
+ if (obj.titlefonts[1] === undefined) {
1607
+ obj.titlefonts[1] = obj.titlefonts[0];
1608
+ }
1609
+
1610
+ // デフォルト曲名表示, 背景矢印のグラデーション指定css
1611
+ [`titlegrd`, `titleArrowgrd`].forEach(_name => {
1612
+ const objName = `${_name.toLowerCase()}`;
1613
+ obj[`${objName}s`] = [];
1614
+ const tmpTitlegrd = getHeader(_dosObj, ...getHname(_name))?.replaceAll(`,`, `:`);
1615
+ if (hasVal(tmpTitlegrd)) {
1616
+ obj[`${objName}s`] = tmpTitlegrd.split(`$`);
1617
+ obj[`${objName}`] = obj[`${objName}s`][0] ?? ``;
1618
+ }
1619
+ });
1620
+
1621
+ // デフォルト曲名表示の表示位置調整
1622
+ obj.titlepos = [[0, 0], [0, 0]];
1623
+ getHeader(_dosObj, ...getHname(`titlePos`))?.split(`$`).forEach((pos, j) => obj.titlepos[j] = pos.split(`,`).map(x => parseFloat(x)));
1624
+
1625
+ // タイトル文字のアニメーション設定
1626
+ obj.titleAnimationName = [`leftToRight`];
1627
+ obj.titleAnimationDuration = [1.5];
1628
+ obj.titleAnimationDelay = [0];
1629
+ obj.titleAnimationTimingFunction = [`ease`];
1630
+ obj.titleAnimationClass = [``];
1631
+
1632
+ getHeader(_dosObj, ...getHname(`titleAnimation`))?.split(`$`).forEach((pos, j) => {
1633
+ const titleAnimation = pos.split(`,`);
1634
+ obj.titleAnimationName[j] = setVal(titleAnimation[0], obj.titleAnimationName[0]);
1635
+ obj.titleAnimationDuration[j] = setVal(titleAnimation[1] / g_fps, obj.titleAnimationDuration[0], C_TYP_FLOAT);
1636
+ obj.titleAnimationDelay[j] = setVal(titleAnimation[2] / g_fps, obj.titleAnimationDelay[0], C_TYP_FLOAT);
1637
+ obj.titleAnimationTimingFunction[j] = setVal(titleAnimation[3], obj.titleAnimationName[3]);
1638
+ });
1639
+ getHeader(_dosObj, ...getHname(`titleAnimationClass`))?.split(`$`).forEach((animationClass, j) =>
1640
+ obj.titleAnimationClass[j] = animationClass ?? ``);
1641
+
1642
+ if (obj.titleAnimationName.length === 1) {
1643
+ g_titleLists.animation.forEach(pattern =>
1644
+ obj[`titleAnimation${pattern}`][1] = obj[`titleAnimation${pattern}`][0]);
1645
+ }
1646
+ if (obj.titleAnimationClass.length === 1) {
1647
+ obj.titleAnimationClass[1] = obj.titleAnimationClass[0];
1648
+ }
1649
+
1650
+ // デフォルト曲名表示の複数行時の縦間隔
1651
+ obj.titlelineheight = setIntVal(getHeader(_dosObj, ...getHname(`titleLineHeight`)), ``);
1652
+
1653
+ // フリーズアローの始点で通常矢印の判定を行うか(dotさんソース方式)
1654
+ obj.frzStartjdgUse = setBoolVal(_dosObj.frzStartjdgUse ?? g_presetObj.frzStartjdgUse);
1655
+
1656
+ // 空押し判定の設定
1657
+ // excessiveUses : 譜面毎の空押し有効化設定
1658
+ // excessiveJdgUses: 譜面毎の空押し初期設定
1659
+ obj.excessiveUses = [];
1660
+ obj.excessiveJdgUses = [];
1661
+ splitLF2(_dosObj.excessiveUse)?.forEach(val => {
1662
+ const tmpVal = val.split(`,`);
1663
+ obj.excessiveUses.push(setBoolVal(tmpVal[0]));
1664
+ obj.excessiveJdgUses.push(setVal(tmpVal[1], C_FLG_OFF, C_TYP_SWITCH) === C_FLG_ON);
1665
+ });
1666
+ if ((obj.excessiveUses?.length || 0) < obj.difLabels.length) {
1667
+ obj.excessiveUses = makeBaseArray(obj.excessiveUses, obj.difLabels.length,
1668
+ setBoolVal(obj.excessiveUses?.[0] ?? _dosObj.excessiveUse ?? g_presetObj.excessiveUse, true));
1669
+ obj.excessiveJdgUses = makeBaseArray(obj.excessiveJdgUses, obj.difLabels.length,
1670
+ setBoolVal(obj.excessiveJdgUses?.[0] ?? g_presetObj.excessiveJdgUse ?? false));
1671
+ }
1672
+
1673
+ // excessiveJdgUseが有効な場合は全譜面に対して強制的に上書き
1674
+ if (_dosObj.excessiveJdgUse !== undefined) {
1675
+ const excessiveJdg = setBoolVal(_dosObj.excessiveJdgUse);
1676
+ if (excessiveJdg) {
1677
+ obj.excessiveJdgUses = obj.excessiveJdgUses.map(val => true);
1678
+ }
1679
+ }
1680
+ obj.excessiveJdgUse = obj.excessiveJdgUses[0];
1681
+ g_stateObj.excessive = boolToSwitch(obj.excessiveJdgUse);
1682
+ g_settings.excessiveNum = Number(obj.excessiveJdgUse);
1683
+
1684
+ // 譜面名に制作者名を付加するかどうかのフラグ(選曲用に初期値を退避)
1685
+ obj.makerView = setBoolVal(_dosObj.makerView);
1686
+ obj.makerViewOrg = obj.makerView;
1687
+
1688
+ // shuffleUse=group 時のみshuffle用配列を組み替える
1689
+ if (_dosObj.shuffleUse === `group`) {
1690
+ _dosObj.shuffleUse = true;
1691
+ g_settings.shuffles = g_settings.shuffles.filter(val => !val.endsWith(`+`));
1692
+ }
1693
+
1694
+ // オプション利用可否設定
1695
+ g_canDisabledSettings.forEach(option =>
1696
+ obj[`${option}Use`] = setBoolVal(_dosObj[`${option}Use`] ?? g_presetObj.settingUse?.[option], true));
1697
+
1698
+ let interlockingErrorFlg = false;
1699
+ g_displays.forEach((option, j) => {
1700
+
1701
+ // Display使用可否設定を分解 |displayUse=false,ON|
1702
+ const displayTempUse = _dosObj[`${option}Use`] ?? g_presetObj.settingUse?.[option] ?? `true`;
1703
+ const displayUse = displayTempUse?.split(`,`) ?? [true, C_FLG_ON];
1704
+
1705
+ // displayUse -> ボタンの有効/無効, displaySet -> ボタンの初期値(ON/OFF)
1706
+ obj[`${option}Use`] = setBoolVal(displayUse[0], true);
1707
+ obj[`${option}Set`] = setVal(displayUse.length > 1 ? displayUse[1] :
1708
+ boolToSwitch(obj[`${option}Use`]), ``, C_TYP_SWITCH);
1709
+ g_stateObj[`d_${option.toLowerCase()}`] = setVal(obj[`${option}Set`], C_FLG_ON, C_TYP_SWITCH);
1710
+ obj[`${option}ChainOFF`] = _dosObj[`${option}ChainOFF`]?.split(`,`) ?? [];
1711
+
1712
+ // Displayのデフォルト設定で、双方向に設定されている場合は設定をブロック
1713
+ g_displays.filter((option2, k) =>
1714
+ j > k && (obj[`${option}ChainOFF`].includes(option2) && obj[`${option2}ChainOFF`].includes(option)))
1715
+ .forEach(() => {
1716
+ interlockingErrorFlg = true;
1717
+ makeWarningWindow(g_msgInfoObj.E_0051);
1718
+ });
1719
+ if (!interlockingErrorFlg && obj[`${option}ChainOFF`].includes(option)) {
1720
+ interlockingErrorFlg = true;
1721
+ makeWarningWindow(g_msgInfoObj.E_0051);
1722
+ }
1723
+ });
1724
+
1725
+ if (!interlockingErrorFlg) {
1726
+ g_displays.forEach(option =>
1727
+ obj[`${option}ChainOFF`].forEach(defaultOption => {
1728
+ g_stateObj[`d_${defaultOption.toLowerCase()}`] = C_FLG_OFF;
1729
+ interlockingButton(obj, defaultOption, C_FLG_OFF, C_FLG_ON);
1730
+ }));
1731
+ }
1732
+ obj.arrowEffectUseOrg = obj.arrowEffectUse;
1733
+ obj.arrowEffectSetFlg = obj.arrowEffectSet === C_FLG_ON;
1734
+
1735
+ // ローカルストレージに保存済みのColorType設定からDisplayのColor設定を反映
1736
+ if (g_localStorage.colorType !== undefined) {
1737
+ g_colorType = g_keycons.colorTypes.concat(g_keycons.colorSelf).includes(g_localStorage.colorType)
1738
+ ? g_localStorage.colorType : `Default`;
1739
+ if (obj.colorUse) {
1740
+ g_stateObj.d_color = boolToSwitch(g_keycons.colorDefTypes.includes(g_colorType));
1741
+ }
1742
+ }
1743
+
1744
+ // 別キーパターンの使用有無
1745
+ obj.transKeyUse = setBoolVal(_dosObj.transKeyUse, true);
1746
+
1747
+ // タイトル画面用・背景/マスクデータの分解 (下記すべてで1セット、改行区切り)
1748
+ // [フレーム数,階層,背景パス,class(CSSで別定義),X,Y,width,height,opacity,animationName,animationDuration]
1749
+ g_animationData.forEach(sprite => {
1750
+ obj[`${sprite}TitleData`] = [];
1751
+ obj[`${sprite}TitleMaxDepth`] = -1;
1752
+
1753
+ const dataList = [_dosObj[`${sprite}title${g_localeObj.val}_data`], _dosObj[`${sprite}title_data`]];
1754
+ const data = dataList.find((v) => v !== undefined);
1755
+ if (hasVal(data)) {
1756
+ [obj[`${sprite}TitleData`], obj[`${sprite}TitleMaxDepth`]] = g_animationFunc.make[sprite](data);
1757
+ }
1758
+ });
1759
+
1760
+ // 結果画面用のマスク透過設定
1761
+ obj.masktitleButton = setBoolVal(_dosObj.masktitleButton);
1762
+
1763
+ // 結果画面用のマスク透過設定
1764
+ obj.maskresultButton = setBoolVal(_dosObj.maskresultButton);
1765
+
1766
+ // リザルトモーションをDisplay:BackgroundのON/OFFと連動させるかどうかの設定
1767
+ obj.resultMotionSet = setBoolVal(_dosObj.resultMotionSet, true);
1768
+
1769
+ // 譜面明細の使用可否
1770
+ const tmpDetails = getHeader(_dosObj, `scoreDetailUse`, `chartDetailUse`)?.split(`,`).filter(val => hasVal(val) && val !== `false`)
1771
+ .map(val => replaceStr(val, g_settings.scoreDetailTrans));
1772
+ g_settings.scoreDetails = g_settings.scoreDetailDefs.filter(val => tmpDetails?.includes(val) || tmpDetails === undefined);
1773
+
1774
+ g_stateObj.scoreDetail = g_settings.scoreDetails[0] || ``;
1775
+ g_settings.scoreDetailCursors = g_settings.scoreDetails.map(val => `lnk${val}G`);
1776
+ g_settings.scoreDetailCursorsOrg = g_settings.scoreDetailCursors.concat();
1777
+ g_settings.scoreDetailCursors.push(`btnGraphB`);
1778
+ [`option`, `difSelector`, `scoreDetail`].forEach(page => g_shortcutObj[page].KeyQ.id = g_settings.scoreDetailCursors[0]);
1779
+ g_shortcutObj.scoreDetail.ArrowDown.id = g_settings.scoreDetailCursorsOrg[nextPos(0, 1, g_settings.scoreDetailCursorsOrg.length)];
1780
+ g_shortcutObj.scoreDetail.ArrowUp.id = g_settings.scoreDetailCursorsOrg[nextPos(0, -1, g_settings.scoreDetailCursorsOrg.length)];
1781
+
1782
+ // 判定位置をBackgroundのON/OFFと連動してリセットする設定
1783
+ obj.jdgPosReset = setBoolVal(_dosObj.jdgPosReset, true);
1784
+
1785
+ // タイトル表示用コメント
1786
+ const newlineTag = setBoolVal(_dosObj.commentAutoBr, true) ? `<br>` : ``;
1787
+ const tmpComment = (_dosObj[`commentVal${g_localeObj.val}`] ?? _dosObj.commentVal ?? ``).split(`\r\n`).join(`\n`);
1788
+ obj.commentVal = tmpComment.split(`\n`).join(newlineTag);
1789
+
1790
+ const maxMusicNo = Math.max(...obj.musicNos);
1791
+ for (let j = 0; j <= maxMusicNo; j++) {
1792
+ obj[`commentVal${j}`] = (_dosObj[`commentVal${j}`] || ``).split(`\n`)
1793
+ .filter((val, k) => k !== 0 || val !== ``).join(`<br>`);
1794
+ }
1795
+
1796
+ // コメントの外部化設定
1797
+ obj.commentExternal = setBoolVal(_dosObj.commentExternal);
1798
+
1799
+ // Reverse時の歌詞の自動反転制御
1800
+ obj.wordAutoReverse = _dosObj.wordAutoReverse ?? g_presetObj.wordAutoReverse ?? C_DIS_AUTO;
1801
+
1802
+ // プレイ中クレジットを表示しないエリアのサイズ(X方向)
1803
+ obj.customViewWidth = setVal(_dosObj.customViewWidth ?? _dosObj.customCreditWidth, 0, C_TYP_FLOAT);
1804
+
1805
+ // ショートカットキーが既定値ではない場合の左右の拡張エリアのサイズ
1806
+ if (hasVal(_dosObj.scArea)) {
1807
+ const tmp = _dosObj.scArea.split(`,`);
1808
+ obj.scAreaWidth = setVal(tmp[0], 0, C_TYP_FLOAT);
1809
+ obj.playingLayout = tmp[1] !== `left`;
1810
+ } else {
1811
+ obj.scAreaWidth = g_presetObj.scAreaWidth ?? 0;
1812
+ obj.playingLayout = g_presetObj.playingLayout ?? true;
1813
+ }
1814
+
1815
+ // ジャストフレームの設定 (ローカル/デバッグ時: 0フレーム, 通常時: 1フレーム以内)
1816
+ obj.justFrames = g_isDebug ? 0 : 1;
1817
+
1818
+ // リザルトデータのカスタマイズ
1819
+ obj.resultFormat = escapeHtmlForEnabledTag(_dosObj.resultFormat ?? g_presetObj.resultFormat ?? g_templateObj.resultFormatDf);
1820
+
1821
+ // リザルト画像データのカスタム設定
1822
+ obj.resultValsView = _dosObj.resultValsView?.split(`,`) ?? g_presetObj.resultValsView ?? Array.from(Object.keys(g_presetObj.resultVals ?? {}));
1823
+
1824
+ // フェードイン時にそれ以前のデータを蓄積しない種別(word, back, mask)を指定
1825
+ obj.unStockCategories = (_dosObj.unStockCategory ?? ``).split(`,`);
1826
+ if (g_presetObj.unStockCategories !== undefined) {
1827
+ obj.unStockCategories = makeDedupliArray(obj.unStockCategories, g_presetObj.unStockCategories);
1828
+ }
1829
+ g_fadeinStockList = g_fadeinStockList.filter(cg => obj.unStockCategories.indexOf(cg) === -1);
1830
+
1831
+ // フェードイン時にそれ以前のデータを蓄積しないパターンを指定
1832
+ if (g_presetObj.stockForceDelList !== undefined) {
1833
+ Object.assign(g_stockForceDelList, g_presetObj.stockForceDelList);
1834
+ }
1835
+ g_fadeinStockList.filter(type => hasVal(_dosObj[`${type}StockForceDel`]))
1836
+ .forEach(type => g_stockForceDelList[type] = makeDedupliArray(g_stockForceDelList[type], _dosObj[`${type}StockForceDel`].split(`,`)));
1837
+
1838
+ return obj;
1839
+ };
1840
+
1841
+ /**
1842
+ * 譜面リスト作成有無の状態を取得
1843
+ * @param {boolean} _headerFlg
1844
+ * @param {number[]} _viewLists
1845
+ * @returns {boolean}
1846
+ */
1847
+ const getDifSelectorUse = (_headerFlg, _viewLists = g_headerObj.viewLists) => setBoolVal(_headerFlg, _viewLists.length > 5);
1848
+
1849
+ /**
1850
+ * カラーセットの格納
1851
+ * @param {string} object._from コピー元矢印カラーセット(の譜面番号)
1852
+ * @param {string} object._to コピー先矢印のカラーセット(の譜面番号)
1853
+ * @param {object} object._fromObj コピー元オブジェクト
1854
+ * @param {object} object._toObj コピー先オブジェクト
1855
+ */
1856
+ const resetColorType = ({ _from = ``, _to = ``, _fromObj = g_headerObj, _toObj = g_headerObj } = {}) => {
1857
+ _toObj[`setColor${_to}`] = structuredClone(_fromObj[`setColor${_from}`]);
1858
+ _toObj[`setShadowColor${_to}`] = structuredClone(_fromObj[`setShadowColor${_from}`]);
1859
+ _toObj[`frzColor${_to}`] = structuredClone(_fromObj[`frzColor${_from}`]);
1860
+ _toObj[`frzShadowColor${_to}`] = structuredClone(_fromObj[`frzShadowColor${_from}`]);
1861
+ };
1862
+
1863
+ /**
1864
+ * 配列に対象がいない場合、配列の先頭にその対象を追加
1865
+ * @param {string[]|number[]} _arr 検索対象の配列
1866
+ * @param {string|number} _target 検索対象
1867
+ * @returns {string[]|number[]}
1868
+ */
1869
+ const addValtoArray = (_arr, _target) => {
1870
+ if (!_arr.includes(_target)) {
1871
+ _arr.unshift(_target);
1872
+ }
1873
+ return _arr;
1874
+ };
1875
+
1876
+ /**
1877
+ * 曲名(1行)の取得
1878
+ * @param {string} _musicName
1879
+ * @returns {string}
1880
+ */
1881
+ const getMusicNameSimple = _musicName => replaceStr(_musicName, g_escapeStr.musicNameSimple);
1882
+
1883
+ /**
1884
+ * 曲名(複数行)の取得
1885
+ * @param {string} _musicName
1886
+ * @returns {string[]}
1887
+ */
1888
+ const getMusicNameMultiLine = _musicName => {
1889
+ const tmpName = replaceStr(_musicName, g_escapeStr.musicNameMultiLine).split(`<br>`);
1890
+ return tmpName.length === 1 ? [tmpName[0], ``] : tmpName;
1891
+ };
1892
+
1893
+ /**
1894
+ * 画像セットの入れ替え処理
1895
+ * @param {object} _imgType
1896
+ * @param {string} _imgType.name
1897
+ * @param {string} _imgType.extension
1898
+ * @param {string} _imgType.remoteDir
1899
+ * @param {boolean} _initFlg
1900
+ */
1901
+ const updateImgType = (_imgType, _initFlg = false) => {
1902
+ if (_initFlg) {
1903
+ const baseDir = (_imgType.name === `` ? `` : `${_imgType.name}/`);
1904
+ C_IMG_TITLE_ARROW = `../img/${baseDir}arrow.${_imgType.extension}`;
1905
+ }
1906
+ resetImgs(_imgType.name, _imgType.extension);
1907
+ reloadImgObj();
1908
+ const orgImgObj = structuredClone(g_imgObj);
1909
+ Object.keys(g_imgObj).forEach(key => g_imgObj[key] = `${g_rootPath}${orgImgObj[key]}`);
1910
+
1911
+ // リモート時は作品ページ側にある画像を優先し、リモートに存在するもののみリモートから取得する
1912
+ // titleArrowについては他のImgTypeから取得するため、remoteDir属性には依存させない
1913
+ if (g_remoteFlg) {
1914
+ Object.keys(g_imgObj).forEach(key => g_imgObj[key] = `${g_workPath}${orgImgObj[key]}`);
1915
+ if (_imgType.remoteDir !== `` && hasRemoteDomain(_imgType.remoteDir)) {
1916
+ g_defaultSets.imgList.filter(val => val !== `titleArrow`)
1917
+ .forEach(key => g_imgObj[key] = `${_imgType.remoteDir}img/${orgImgObj[key]}`);
1918
+ } else if (g_defaultSets.imgType.findIndex(val => val === _imgType.name) >= 0) {
1919
+ g_defaultSets.imgList.forEach(key => g_imgObj[key] = `${g_rootPath}${orgImgObj[key]}`);
1920
+ }
1921
+ }
1922
+ if (_imgType.extension === undefined && g_presetObj.overrideExtension !== undefined) {
1923
+ Object.keys(g_imgObj).forEach(key => g_imgObj[key] = `${g_imgObj[key].slice(0, -3)}${g_presetObj.overrideExtension}`);
1924
+ }
1925
+ if (!g_isFile) {
1926
+ g_imgInitList.forEach(img => preloadFile(`image`, g_imgObj[img]));
1927
+ }
1928
+ };
1929
+
1930
+ /**
1931
+ * ゲージ設定リストへの追加
1932
+ * @param {object} _obj
1933
+ */
1934
+ const addGaugeFulls = _obj => _obj.map(key => g_gaugeOptionObj.customFulls[key] = false);
1935
+
1936
+ /**
1937
+ * 矢印・フリーズアロー色のデータ変換
1938
+ * @param {object} _baseObj
1939
+ * @param {object} _dosObj
1940
+ * @param {string} [object.scoreId='']
1941
+ * @param {boolean} [object.scoreLockFlg=g_stateObj.scoreLockFlg]
1942
+ * @returns {object} ※Object.assign(obj, resetBaseColorList(...))の形で呼び出しが必要
1943
+ */
1944
+ const resetBaseColorList = (_baseObj, _dosObj, { scoreId = ``, scoreLockFlg = g_stateObj.scoreLockFlg } = {}) => {
1945
+
1946
+ const obj = {};
1947
+ const idHeader = setScoreIdHeader(scoreId, scoreLockFlg, scoreId !== ``);
1948
+ const getRefData = (_header, _dataName) => {
1949
+ const data = _dosObj[`${_header}${_dataName}`];
1950
+ return data?.startsWith(_header) ? _dosObj[data] : data;
1951
+ }
1952
+
1953
+ [``, `Shadow`].forEach(pattern => {
1954
+ const _arrowCommon = `set${pattern}Color`;
1955
+ const _frzCommon = `frz${pattern}Color`;
1956
+
1957
+ const _name = `${_arrowCommon}${idHeader}`;
1958
+ const _frzName = `${_frzCommon}${idHeader}`;
1959
+ const _arrowInit = `${_arrowCommon}Init`;
1960
+ const _frzInit = `${_frzCommon}Init`;
1961
+
1962
+ const arrowColorTxt = getRefData(_arrowCommon, idHeader) || _dosObj[_arrowCommon];
1963
+ const frzColorTxt = getRefData(_frzCommon, idHeader) || _dosObj[_frzCommon];
1964
+
1965
+ // 矢印色
1966
+ Object.keys(_baseObj.dfColorgrdSet).forEach(type => {
1967
+ [obj[`${_name}${type}`], obj[`${_name}Str${type}`], obj[`${_name}Org${type}`]] =
1968
+ setColorList(arrowColorTxt, _baseObj[_arrowInit], _baseObj[_arrowInit].length, {
1969
+ _defaultColorgrd: _baseObj.dfColorgrdSet[type],
1970
+ _colorCdPaddingUse: _baseObj.colorCdPaddingUse,
1971
+ _shadowFlg: pattern === `Shadow`,
1972
+ });
1973
+
1974
+ obj[`${_frzName}${type}`] = [];
1975
+ obj[`${_frzName}Str${type}`] = [];
1976
+ obj[`${_frzName}Org${type}`] = [];
1977
+ });
1978
+
1979
+ // フリーズアロー色
1980
+ const tmpFrzColors = (frzColorTxt !== undefined ? splitLF2(frzColorTxt) : []);
1981
+ const firstFrzColors = tmpFrzColors[0]?.split(`,`) ?? [];
1982
+
1983
+ for (let j = 0; j < _baseObj.setColorInit.length; j++) {
1984
+
1985
+ // デフォルト配列の作成(1番目の要素をベースに、フリーズアロー初期セット or 矢印色からデータを補完)
1986
+ const currentFrzColors = [];
1987
+ const baseLength = firstFrzColors.length === 0 || _baseObj.defaultFrzColorUse ?
1988
+ _baseObj[_frzInit].length : firstFrzColors.length;
1989
+ for (let k = 0; k < baseLength; k++) {
1990
+ currentFrzColors[k] = setVal(firstFrzColors[k],
1991
+ _baseObj.defaultFrzColorUse ? _baseObj[_frzInit][k] : obj[`${_name}Str`][j]);
1992
+ }
1993
+
1994
+ Object.keys(_baseObj.dfColorgrdSet).forEach(type =>
1995
+ [obj[`${_frzName}${type}`][j], obj[`${_frzName}Str${type}`][j], obj[`${_frzName}Org${type}`][j]] =
1996
+ setColorList(tmpFrzColors[j], currentFrzColors, _baseObj[_frzInit].length, {
1997
+ _defaultColorgrd: _baseObj.dfColorgrdSet[type],
1998
+ _colorCdPaddingUse: _baseObj.colorCdPaddingUse,
1999
+ _defaultFrzColorUse: _baseObj.defaultFrzColorUse,
2000
+ _objType: `frz`,
2001
+ _shadowFlg: pattern === `Shadow`,
2002
+ }));
2003
+ }
2004
+
2005
+ obj[`${_name}Default`] = obj[_name].concat();
2006
+ obj[`${_frzName}Default`] = obj[_frzName].concat();
2007
+ });
2008
+
2009
+ return obj;
2010
+ };
2011
+
2012
+ /**
2013
+ * 矢印・フリーズアロー色のデータ展開
2014
+ * @param {string} _data
2015
+ * @param {string[]} _colorInit
2016
+ * @param {number} _colorInitLength
2017
+ * @param {string[]} [object._defaultColorgrd=g_headerObj.defaultColorgrd]
2018
+ * @param {boolean} [object._colorCdPaddingUse=false]
2019
+ * @param {boolean} [object._defaultFrzColorUse=true]
2020
+ * @param {string} [object._objType='normal']
2021
+ * @param {boolean} [object._shadowFlg=false]
2022
+ * @returns {string[][]}
2023
+ */
2024
+ const setColorList = (_data, _colorInit, _colorInitLength,
2025
+ { _defaultColorgrd = g_headerObj.defaultColorgrd, _colorCdPaddingUse = false,
2026
+ _defaultFrzColorUse = true, _objType = `normal`, _shadowFlg = false } = {}) => {
2027
+
2028
+ // グラデーション文字列 #ffff99:#9999ff@linear-gradient
2029
+ let colorStr = [];
2030
+
2031
+ // カラーコード抽出用 #ffff99 - Ready文字、背景矢印のデフォルト色で使用
2032
+ let colorOrg = [];
2033
+
2034
+ // グラデーション適用後文字列 linear-gradient(to right, #ffff99, #9999ff)
2035
+ let colorList = [];
2036
+
2037
+ // 譜面側で指定されているデータを配列に変換
2038
+ if (hasVal(_data)) {
2039
+ colorList = _data.split(`,`);
2040
+ colorStr = colorList.concat();
2041
+
2042
+ // データ補完処理
2043
+ const defaultLength = colorStr.length;
2044
+ if (_objType === `frz` && _defaultFrzColorUse) {
2045
+ // デフォルト配列に満たない・足りない部分はデフォルト配列で穴埋め
2046
+ for (let j = 0; j < _colorInitLength; j++) {
2047
+ if (!hasVal(colorStr[j])) {
2048
+ colorStr[j] = _colorInit[j];
2049
+ }
2050
+ }
2051
+ } else {
2052
+ // デフォルト配列長をループさせて格納
2053
+ for (let j = 0; j < _colorInitLength; j++) {
2054
+ colorStr[j] = colorStr[j % defaultLength];
2055
+ }
2056
+ }
2057
+ colorList = colorStr.concat();
2058
+
2059
+ for (let j = 0; j < colorList.length; j++) {
2060
+ const tmpSetColorOrg = colorStr[j].replaceAll(`0x`, `#`).split(`:`);
2061
+ const hasColor = tmpSetColorOrg.some(tmpColorOrg => {
2062
+ if (hasVal(tmpColorOrg) && (isColorCd(tmpColorOrg) || !hasAnglePointInfo(tmpColorOrg) || tmpColorOrg === `Default`)) {
2063
+ colorOrg[j] = colorCdPadding(_colorCdPaddingUse, colorToHex(tmpColorOrg));
2064
+ return true;
2065
+ }
2066
+ });
2067
+ if (!hasColor) {
2068
+ colorOrg[j] = _colorInit[j];
2069
+ }
2070
+ colorList[j] = makeColorGradation(colorStr[j] === `` ? _colorInit[j] : colorStr[j], {
2071
+ _defaultColorgrd, _colorCdPaddingUse, _objType, _shadowFlg,
2072
+ });
2073
+ }
2074
+
2075
+ } else {
2076
+
2077
+ // 未定義の場合は指定されたデフォルト配列(_colorInit)で再定義
2078
+ colorStr = _colorInit.concat();
2079
+ colorOrg = _colorInit.concat();
2080
+ colorList = _colorInit.map(colorStr => makeColorGradation(colorStr, {
2081
+ _defaultColorgrd, _colorCdPaddingUse, _shadowFlg,
2082
+ }));
2083
+ }
2084
+
2085
+ return [colorList, colorStr, colorOrg];
2086
+ };
2087
+
2088
+ /**
2089
+ * 複合カスタムゲージの定義設定
2090
+ * |customGauge=_Original::F::Original,_Normal::V::Normal,Escape::V|
2091
+ * @param {object} _dosObj
2092
+ * @param {string} [object.scoreId=0]
2093
+ * @returns {object} ※Object.assign(obj, resetCustomGauge(...))の形で呼び出しが必要
2094
+ */
2095
+ const resetCustomGauge = (_dosObj, { scoreId = 0 } = {}) => {
2096
+
2097
+ const obj = {};
2098
+ const scoreIdHeader = setScoreIdHeader(scoreId, g_stateObj.scoreLockFlg, false);
2099
+ const dosCustomGauge = _dosObj[`customGauge${scoreIdHeader}`];
2100
+ if (hasVal(dosCustomGauge)) {
2101
+ if (g_gaugeOptionObj.defaultPlusList.includes(dosCustomGauge)) {
2102
+ obj[`custom${scoreId}`] = g_gaugeOptionObj[dosCustomGauge].concat();
2103
+ obj[`varCustom${scoreId}`] = g_gaugeOptionObj[`var${toCapitalize(dosCustomGauge)}`].concat();
2104
+ if (g_gaugeOptionObj.defaultList.includes(dosCustomGauge)) {
2105
+ obj[`defaultGauge${scoreId}`] = dosCustomGauge;
2106
+ obj[`typeCustom${scoreId}`] = g_gaugeOptionObj[`type${toCapitalize(dosCustomGauge)}`].concat();
2107
+ }
2108
+ } else {
2109
+ const customGauges = dosCustomGauge.split(`,`);
2110
+
2111
+ obj[`custom${scoreId}`] = [];
2112
+ obj[`varCustom${scoreId}`] = [];
2113
+
2114
+ for (let j = 0; j < customGauges.length; j++) {
2115
+ const customGaugeSets = customGauges[j].split(`::`);
2116
+ obj[`custom${scoreId}`][j] = customGaugeSets[0];
2117
+ obj[`varCustom${scoreId}`][j] = boolToSwitch(customGaugeSets[1] === `V`);
2118
+ if (hasVal(customGaugeSets[2])) {
2119
+ g_lblNameObj[`u_${customGaugeSets[0]}`] = customGaugeSets[2];
2120
+ }
2121
+ }
2122
+ if (scoreId === 0) {
2123
+ obj.custom = obj.custom0.concat();
2124
+ obj.varCustom = obj.varCustom0.concat();
2125
+ }
2126
+ addGaugeFulls(obj[`custom${scoreId}`]);
2127
+ }
2128
+ }
2129
+ return obj;
2130
+ };
2131
+
2132
+ /**
2133
+ * ゲージ別個別設定の取得
2134
+ * @param {object} _dosObj
2135
+ * @param {string} _name
2136
+ * @param {number} _difLength
2137
+ * @param {string} [object.scoreId=0]
2138
+ */
2139
+ const getGaugeSetting = (_dosObj, _name, _difLength, { scoreId = 0 } = {}) => {
2140
+
2141
+ const obj = {
2142
+ lifeBorders: [],
2143
+ lifeRecoverys: [],
2144
+ lifeDamages: [],
2145
+ lifeInits: []
2146
+ };
2147
+ /** ゲージ設定再作成フラグ */
2148
+ let gaugeCreateFlg = false;
2149
+
2150
+ /** ゲージ設定上書きフラグ */
2151
+ const gaugeUpdateFlg = g_stateObj.scoreLockFlg && scoreId > 0;
2152
+
2153
+ /**
2154
+ * ゲージ別個別配列への値格納
2155
+ * この時点では各種ゲージ設定は文字列のまま。setGauge関数にて数式に変換される
2156
+ * @param {number} _scoreId
2157
+ * @param {string[]} _gaugeDetails
2158
+ * @returns {boolean}
2159
+ */
2160
+ const setGaugeDetails = (_scoreId, _gaugeDetails) => {
2161
+
2162
+ obj.lifeBorders[_scoreId] = _gaugeDetails[0] === `x` ? `x` : _gaugeDetails[0];
2163
+ obj.lifeRecoverys[_scoreId] = _gaugeDetails[1];
2164
+ obj.lifeDamages[_scoreId] = _gaugeDetails[2];
2165
+ obj.lifeInits[_scoreId] = _gaugeDetails[3];
2166
+
2167
+ if (gaugeUpdateFlg && hasVal(g_gaugeOptionObj[`gauge${_name}s`])) {
2168
+ // ゲージ上書き時は_gaugeDetails(obj)の値を優先し、デフォルト値で穴埋めする
2169
+ Object.keys(obj).forEach(key => g_gaugeOptionObj[`gauge${_name}s`][key] =
2170
+ fillMissingArrayElem(g_gaugeOptionObj[`gauge${_name}s`][key] || [], obj[key]));
2171
+ return false;
2172
+ }
2173
+ return true;
2174
+ };
2175
+
2176
+ /**
2177
+ * gaugeNormal2, gaugeEasy2などの個別設定があった場合にその値から配列を作成
2178
+ * @param {number} _scoreId
2179
+ * @param {number[]} _defaultGaugeList
2180
+ * @returns {number[]}
2181
+ */
2182
+ const getGaugeDetailList = (_scoreId, _defaultGaugeList) => {
2183
+ if (_scoreId > 0) {
2184
+ const idHeader = setScoreIdHeader(_scoreId, g_stateObj.scoreLockFlg, false);
2185
+ const dosId = (idHeader || 0) - 1;
2186
+ const headerName = `gauge${_name}${idHeader}`;
2187
+ if (hasVal(_dosObj[headerName])) {
2188
+ const gauges = splitLF2(_dosObj[headerName]);
2189
+ return (gauges[dosId] || gauges[0])?.split(`,`);
2190
+ }
2191
+ }
2192
+ return _defaultGaugeList;
2193
+ };
2194
+
2195
+ if (hasVal(_dosObj[`gauge${_name}`])) {
2196
+
2197
+ const gauges = splitLF2(_dosObj[`gauge${_name}`]);
2198
+ if (gaugeUpdateFlg) {
2199
+ gaugeCreateFlg = setGaugeDetails(scoreId, (gauges[scoreId] || gauges[0])?.split(`,`));
2200
+ } else {
2201
+ for (let j = 0; j < _difLength; j++) {
2202
+ gaugeCreateFlg = setGaugeDetails(j, getGaugeDetailList(j, (gauges[j] || gauges[0]).split(`,`)));
2203
+ }
2204
+ }
2205
+
2206
+ } else if (g_presetObj.gaugeCustom?.[_name] !== undefined) {
2207
+
2208
+ const gaugeDetails = [
2209
+ g_presetObj.gaugeCustom[_name].Border, g_presetObj.gaugeCustom[_name].Recovery,
2210
+ g_presetObj.gaugeCustom[_name].Damage, g_presetObj.gaugeCustom[_name].Init,
2211
+ ];
2212
+ if (gaugeUpdateFlg) {
2213
+ gaugeCreateFlg = setGaugeDetails(scoreId, gaugeDetails);
2214
+ } else {
2215
+ for (let j = 0; j < _difLength; j++) {
2216
+ gaugeCreateFlg = setGaugeDetails(j, getGaugeDetailList(j, gaugeDetails));
2217
+ }
2218
+ }
2219
+ }
2220
+ if (gaugeCreateFlg) {
2221
+ g_gaugeOptionObj[`gauge${_name}s`] = obj;
2222
+ }
2223
+ };
2224
+
2225
+ /**
2226
+ * キー名の取得
2227
+ * @param {string} _key
2228
+ * @returns {string} キー名
2229
+ */
2230
+ const getKeyName = _key => unEscapeHtml(escapeHtml(g_keyObj[`keyName${_key}`]?.[0] ?? _key));
2231
+
2232
+ /**
2233
+ * キー単位名の取得
2234
+ * @param {string} _key
2235
+ * @returns {string} キー単位名(デフォルト: key)
2236
+ */
2237
+ const getKeyUnitName = _key => unEscapeHtml(escapeHtml(g_keyObj[`keyName${_key}`]?.[1] ?? `key`));
2238
+
2239
+ /**
2240
+ * シャッフル名の取得
2241
+ * @returns {string}
2242
+ */
2243
+ const getShuffleName = () => {
2244
+ const orgShuffleFlg = getOrgShuffleFlg();
2245
+ return `${getStgDetailName(g_stateObj.shuffle)}${!orgShuffleFlg && !g_stateObj.shuffle.endsWith(`+`) ? getStgDetailName('(S)') : ''}`;
2246
+ };
2247
+
2248
+ /**
2249
+ * シャッフルカスタムフラグの取得
2250
+ * @returns {boolean}
2251
+ */
2252
+ const getOrgShuffleFlg = () => {
2253
+ const keyCtrlPtn = `${g_keyObj.currentKey}_${g_keyObj.currentPtn}`;
2254
+ return g_keyObj[`shuffle${keyCtrlPtn}`].filter((shuffleGr, j) => shuffleGr !== g_keyObj[`shuffle${keyCtrlPtn}_0d`][j]).length === 0;
2255
+ };
2256
+
2257
+ /**
2258
+ * 別キーモード時の表示名の取得
2259
+ * @param {boolean} _spaceFlg
2260
+ * @returns {string} 別キー名
2261
+ */
2262
+ const getTransKeyName = (_spaceFlg = false) => hasVal(g_keyObj[`transKey${g_keyObj.currentKey}_${g_keyObj.currentPtn}`])
2263
+ ? (_spaceFlg ? ` ` : ``) + `(${g_keyObj[`transKey${g_keyObj.currentKey}_${g_keyObj.currentPtn}`]})` : ``;
2264
+
2265
+ /**
2266
+ * ハイスコア定義を行う際のストレージキー名の取得
2267
+ * @param {string} _key
2268
+ * @param {string} _transName
2269
+ * @param {string} _assistFlg
2270
+ * @param {string} _mirrorName
2271
+ * @param {string} _scoreId
2272
+ * @returns {string}
2273
+ */
2274
+ const getStorageKeyName = (_key, _transName, _assistFlg, _mirrorName, _scoreId) => {
2275
+ let scoreName = `${_key}${_transName}${getStgDetailName('k-')}${g_headerObj.difLabels[_scoreId]}${_assistFlg}${_mirrorName}`;
2276
+ if (g_headerObj.makerView) {
2277
+ scoreName += `-${g_headerObj.creatorNames[_scoreId]}`;
2278
+ }
2279
+ return scoreName;
2280
+ };
2281
+
2282
+ /**
2283
+ * KeyBoardEvent.code の値をCW Edition用のキーコードに変換
2284
+ * 簡略指定ができるように、以下の記述を許容
2285
+ * 例) KeyD -> D, ArrowDown -> Down, AltLeft -> Alt
2286
+ * @param {string} _kCdN
2287
+ * @returns {number}
2288
+ */
2289
+ const getKeyCtrlVal = _kCdN => {
2290
+ const convVal = Object.keys(g_kCdN).findIndex(val =>
2291
+ [_kCdN, `Key${_kCdN}`, `Arrow${_kCdN}`].includes(g_kCdN[val]) || _kCdN === replaceStr(g_kCdN[val], g_escapeStr.keyCtrlName));
2292
+ return convVal !== -1 ? convVal : parseInt(_kCdN, 10);
2293
+ };
2294
+
2295
+ /**
2296
+ * 一時的な追加キーの設定
2297
+ * - keyExtraListの指定がない場合は、_dosObj.keyCtrlXに合致するXを追加キーとして追加
2298
+ * @param {object} _dosObj
2299
+ * @param {string[]} object.keyExtraList
2300
+ * @returns {string[]}
2301
+ */
2302
+ const keysConvert = (_dosObj, { keyExtraList = _dosObj.keyExtraList?.split(`,`) } = {}) => {
2303
+
2304
+ if (keyExtraList === undefined) {
2305
+ keyExtraList = [];
2306
+ Object.keys(_dosObj).filter(val => val.startsWith(g_keyObj.defaultProp))
2307
+ .forEach(keyName => keyExtraList.push(keyName.slice(g_keyObj.defaultProp.length)));
2308
+
2309
+ if (keyExtraList.length === 0) {
2310
+ return [];
2311
+ }
2312
+ }
2313
+
2314
+ const existParam = (_data, _paramName) => !hasVal(_data) && g_keyObj[_paramName] !== undefined;
2315
+ const toString = _str => _str;
2316
+ const toInt = _num => isNaN(parseInt(_num)) ? _num : parseInt(_num);
2317
+ const toFloat = _num => isNaN(parseFloat(_num)) ? _num : parseFloat(_num);
2318
+ const toKeyCtrlArray = _str =>
2319
+ makeBaseArray(_str.split(`/`).map(n => getKeyCtrlVal(n)), g_keyObj.minKeyCtrlNum, 0);
2320
+ const toSplitArrayStr = _str => _str.split(`/`).map(n => n);
2321
+
2322
+ // 略記記法を元の文字列に復元後、配列に変換 (1...3,5...7 -> 1,2,3,5,6,7)
2323
+ const toOriginalArray = (_val, _func) => _val?.split(`,`).map(n => _func(n)).join(`,`).split(`,`);
2324
+
2325
+ /**
2326
+ * 略記記法を元の文字列に変換 (1...5 -> 1,2,3,4,5 / 3...+4 -> 3,4,5,6,7)
2327
+ * @param {string} _str
2328
+ * @returns {string}
2329
+ */
2330
+ const toFloatStr = _str => {
2331
+ const nums = _str?.split(`...`);
2332
+ const bottomMark = nums[0].startsWith(`b`) ? `b` : ``;
2333
+ const [startN, endN] = [parseFloat(bottomMark === `b` ? nums[0].slice(1) : nums[0]), parseFloat(nums[1])];
2334
+
2335
+ if (nums.length === 2 && !isNaN(startN) && !isNaN(endN)) {
2336
+ const endN2 = nums[1].startsWith(`+`) ? startN + endN : endN;
2337
+ const arr = [];
2338
+ for (let k = startN; k <= endN2; k++) {
2339
+ arr.push(`${bottomMark}${k}`);
2340
+ }
2341
+ return arr.join(`,`);
2342
+ } else {
2343
+ return _str;
2344
+ }
2345
+ };
2346
+
2347
+ /**
2348
+ * 略記記法を元の文字列に変換 (1@:5 -> 1,1,1,1,1 / onigiri!giko!c@:2 -> onigiri,giko,c,onigiri,giko,c)
2349
+ * @param {string} _str
2350
+ * @returns {string}
2351
+ */
2352
+ const toSameValStr = _str => {
2353
+ const nums = _str?.split(`@:`);
2354
+ const groupStr = toFloatStr(nums[0]).split(`!`).join(`,`);
2355
+ return nums.length === 2 && !isNaN(parseInt(nums[1])) ?
2356
+ fillArray(Math.floor(parseInt(nums[1])), groupStr).join(`,`) : groupStr;
2357
+ };
2358
+
2359
+ /**
2360
+ * キーパターン(相対パターン)をキーパターン(実際のパターン番号)に変換
2361
+ * 例) 12_(0) -> 12_4
2362
+ * それ以外の文字列が来た場合は、そのままの値を戻す
2363
+ * @param {string} _str
2364
+ * @returns {string}
2365
+ */
2366
+ const getKeyPtnName = _str => {
2367
+ const regex = /\((\d+)\)/;
2368
+ const checkStr = _str.match(regex);
2369
+ if (checkStr !== null) {
2370
+ return _str.replace(regex, (match, p) => `${parseInt(p, 10) + setIntVal(g_keyObj.dfPtnNum)}`);
2371
+ }
2372
+ return _str;
2373
+ };
2374
+
2375
+ /**
2376
+ * divMaxX, posXの下段補完処理
2377
+ * ・divXの1番目の指定があるとき、その値を元に下段の位置を補完
2378
+ * 例) |div11x=7,b6|pos11x=0,1,2,3,4,5,6,b0,b1,b5,b6|
2379
+ * -> |div11x=7,13|pos11x=0,1,2,3,4,5,6,7,8,12,13|
2380
+ * @param {number} _num
2381
+ * @param {number} _divNum
2382
+ * @returns {number}
2383
+ */
2384
+ const getKeyPosNum = (_num, _divNum = 0) => {
2385
+ if (!hasVal(_num) || (!_num.startsWith(`b`) && isNaN(parseFloat(_num)))) {
2386
+ return _num;
2387
+ }
2388
+ return _num.startsWith(`b`) ? parseFloat(_num.slice(1)) + _divNum : parseFloat(_num);
2389
+ }
2390
+
2391
+ /**
2392
+ * キーパターンの略名から実際のデータへ展開
2393
+ * - charaX の場合に限り、a>5_0 の形式を aleft, adown, aup, aright, aspace に変換する
2394
+ * @param {string} _str
2395
+ * @param {string} _name
2396
+ * @param {Function} _convFunc
2397
+ * @returns {string[]|number[]}
2398
+ */
2399
+ const expandKeyPtn = (_str, _name, _convFunc) => {
2400
+ const pos = _str.indexOf(`>`);
2401
+ const expandData = _ptnstr => structuredClone(g_keyObj[`${_name}${getKeyPtnName(_ptnstr)}`]) ?? [_convFunc(_ptnstr)];
2402
+
2403
+ if (pos > 0 && _name === `chara`) {
2404
+ const [header, ptn] = [_str.substring(0, pos), _str.substring(pos + 1)];
2405
+ return expandData(ptn)?.map(n => `${header}${n}`);
2406
+ } else {
2407
+ return expandData(_str);
2408
+ }
2409
+ };
2410
+
2411
+ /**
2412
+ * 新キー用複合パラメータ
2413
+ * @param {string} _key キー数
2414
+ * @param {string} _name 名前
2415
+ * @param {Function} _convFunc マッピング関数
2416
+ * @param {string} object.errCd エラーコード
2417
+ * @param {boolean} object.baseCopyFlg コピー配列の準備可否
2418
+ * @param {Function} object.loopFunc パターン別に処理する個別関数
2419
+ * @returns {number} 最小パターン数
2420
+ */
2421
+ const newKeyMultiParam = (_key, _name, _convFunc, { errCd = ``, baseCopyFlg = false, loopFunc = () => true } = {}) => {
2422
+ let tmpMinPatterns = 1;
2423
+ const keyheader = _name + _key;
2424
+ const dfPtn = setIntVal(g_keyObj.dfPtnNum);
2425
+
2426
+ if (hasVal(_dosObj[keyheader])) {
2427
+ const tmpArray = splitLF2(_dosObj[keyheader]);
2428
+ tmpMinPatterns = tmpArray.length;
2429
+ for (let k = 0; k < tmpMinPatterns; k++) {
2430
+ if (existParam(tmpArray[k], `${keyheader}_${k + dfPtn}`)) {
2431
+ continue;
2432
+ }
2433
+ // |keyCtrl9j=Tab,7_0,Enter| -> |keyCtrl9j=Tab,S,D,F,Space,J,K,L,Enter| のように補完
2434
+ // |pos9j=0..4,6..9| -> |pos9j=0,1,2,3,4,6,7,8,9|
2435
+ g_keyObj[`${keyheader}_${k + dfPtn}`] =
2436
+ toOriginalArray(tmpArray[k], toSameValStr).map(n => expandKeyPtn(n, _name, _convFunc)).flat();
2437
+ if (baseCopyFlg) {
2438
+ g_keyObj[`${keyheader}_${k + dfPtn}d`] = structuredClone(g_keyObj[`${keyheader}_${k + dfPtn}`]);
2439
+ }
2440
+ loopFunc(k, keyheader);
2441
+ }
2442
+
2443
+ } else if (errCd !== `` && g_keyObj[`${keyheader}_0`] === undefined) {
2444
+ makeWarningWindow(g_msgInfoObj[errCd].split(`{0}`).join(_key));
2445
+ }
2446
+ return tmpMinPatterns;
2447
+ };
2448
+
2449
+ /**
2450
+ * 新キー用複合パラメータ(特殊)
2451
+ * @param {string} _key キー数
2452
+ * @param {string} _name 名前
2453
+ */
2454
+ const newKeyTripleParam = (_key, _name) => {
2455
+ const keyheader = _name + _key;
2456
+ const dfPtn = setIntVal(g_keyObj.dfPtnNum);
2457
+
2458
+ if (hasVal(_dosObj[keyheader])) {
2459
+ splitLF2(_dosObj[keyheader])?.forEach((tmpParam, k) => {
2460
+ if (existParam(tmpParam, `${keyheader}_${k + dfPtn}`)) {
2461
+ return;
2462
+ }
2463
+
2464
+ let ptnCnt = 0;
2465
+ tmpParam.split(`/`).forEach(list => {
2466
+
2467
+ const keyPtn = getKeyPtnName(list);
2468
+ if (list === ``) {
2469
+ // 空指定の場合は一律同じグループへ割り当て
2470
+ g_keyObj[`${keyheader}_${k + dfPtn}_${ptnCnt}`] = fillArray(g_keyObj[`${g_keyObj.defaultProp}${_key}_${k + dfPtn}`].length);
2471
+
2472
+ } else if (g_keyObj[`${_name}${keyPtn}_0`] !== undefined) {
2473
+ // 他のキーパターン (例: |shuffle8i=8_0| ) を直接指定した場合、該当があれば既存パターンからコピー
2474
+ // 既存パターンが複数ある場合、全てコピーする
2475
+ let m = 0;
2476
+ while (g_keyObj[`${_name}${keyPtn}_${m}`] !== undefined) {
2477
+ g_keyObj[`${keyheader}_${k + dfPtn}_${ptnCnt}`] = structuredClone(g_keyObj[`${_name}${keyPtn}_${m}`]);
2478
+ m++;
2479
+ ptnCnt++;
2480
+ }
2481
+ } else {
2482
+ // 通常の指定方法 (例: |shuffle8i=1,1,1,2,0,0,0,0/1,1,1,1,0,0,0,0| )の場合の取り込み
2483
+ // 部分的にキーパターン指定があった場合は既存パターンを展開 (例: |shuffle9j=2,7_0_0,2|)
2484
+ g_keyObj[`${keyheader}_${k + dfPtn}_${ptnCnt}`] =
2485
+ makeBaseArray(toOriginalArray(list, toSameValStr).map(n =>
2486
+ expandKeyPtn(n, _name, _str => isNaN(parseInt(_str)) ? _str : parseInt(_str, 10))
2487
+ ).flat(), g_keyObj[`${g_keyObj.defaultProp}${_key}_${k + dfPtn}`].length, 0);
2488
+ ptnCnt++;
2489
+ }
2490
+ });
2491
+ g_keyObj[`${keyheader}_${k + dfPtn}`] = structuredClone(g_keyObj[`${keyheader}_${k + dfPtn}_0`]);
2492
+ });
2493
+
2494
+ } else if (g_keyObj[`${keyheader}_${dfPtn}_0`] === undefined) {
2495
+ // 特に指定が無い場合はkeyCtrlX_Yの配列長で決定
2496
+ for (let k = 0; k < g_keyObj.minPatterns; k++) {
2497
+ const ptnName = `${_key}_${k + dfPtn}`;
2498
+ g_keyObj[`${_name}${ptnName}_0`] = fillArray(g_keyObj[`${g_keyObj.defaultProp}${ptnName}`].length);
2499
+ g_keyObj[`${_name}${ptnName}`] = structuredClone(g_keyObj[`${_name}${ptnName}_0`]);
2500
+ }
2501
+ }
2502
+ };
2503
+
2504
+ /**
2505
+ * 新キー用単一パラメータ
2506
+ * @param {string} _key キー数
2507
+ * @param {string} _name 名前
2508
+ * @param {string} _type float, number, string, boolean
2509
+ * @param {string} _defaultVal
2510
+ */
2511
+ const newKeySingleParam = (_key, _name, _type, _defaultVal) => {
2512
+ const keyheader = _name + _key;
2513
+ const dfPtn = setIntVal(g_keyObj.dfPtnNum);
2514
+ if (_dosObj[keyheader] !== undefined) {
2515
+ const tmps = _dosObj[keyheader].split(`$`);
2516
+ for (let k = 0; k < tmps.length; k++) {
2517
+ g_keyObj[`${keyheader}_${k + dfPtn}`] = setVal(g_keyObj[`${_name}${getKeyPtnName(tmps[k])}`],
2518
+ tmps[k].indexOf(`_`) !== -1 ? _defaultVal : setVal(tmps[k], ``, _type));
2519
+ }
2520
+ for (let k = tmps.length; k < g_keyObj.minPatterns; k++) {
2521
+ g_keyObj[`${keyheader}_${k + dfPtn}`] = g_keyObj[`${keyheader}_0`];
2522
+ }
2523
+ }
2524
+ };
2525
+
2526
+ /**
2527
+ * 新キー用複合パラメータ(パターン設定用)
2528
+ * @param {string} _key キー数
2529
+ * @param {string} _name 名前
2530
+ * @param {string} _pairName 詳細設定する変数名
2531
+ * @param {string} _defaultName パラメータの初期値
2532
+ * @param {number} _defaultVal パラメータの初期値の場合の一律設定値(colorX_Yの配列幅に対して設定値で埋める)
2533
+ */
2534
+ const newKeyPairParam = (_key, _name, _pairName, _defaultName = ``, _defaultVal = 0) => {
2535
+ const keyheader = _name + _key;
2536
+ const dfPtn = setIntVal(g_keyObj.dfPtnNum);
2537
+
2538
+ splitLF2(_dosObj[keyheader])?.forEach((tmpParam, k) => {
2539
+ const pairName = `${_pairName}${_key}_${k + dfPtn}`;
2540
+ if (!hasVal(tmpParam)) {
2541
+ return;
2542
+ }
2543
+ g_keyObj[pairName] = {};
2544
+
2545
+ // デフォルト項目がある場合は先に定義
2546
+ if (_defaultName !== ``) {
2547
+ g_keyObj[pairName][_defaultName] = fillArray(g_keyObj[`${g_keyObj.defaultProp}${_key}_${k + dfPtn}`].length, _defaultVal);
2548
+ }
2549
+ tmpParam.split(`/`).forEach(pairs => {
2550
+ const keyPtn = getKeyPtnName(pairs);
2551
+ if (pairs === ``) {
2552
+ } else if (g_keyObj[`${_pairName}${keyPtn}`] !== undefined) {
2553
+ // 他のキーパターン指定時、該当があればプロパティを全コピー
2554
+ Object.assign(g_keyObj[pairName], g_keyObj[`${_pairName}${keyPtn}`]);
2555
+ } else {
2556
+ // 通常の指定方法(例:|scroll8i=Cross::1,1,1,-,-,-,1,1/Split::1,1,1,1,-,-,-,-|)から取り込み
2557
+ // 部分的にキーパターン指定があった場合は既存パターンを展開 (例: |scroll9j=Cross::1,7_0,1|)
2558
+ const tmpParamPair = pairs.split(`::`);
2559
+ g_keyObj[pairName][tmpParamPair[0]] =
2560
+ makeBaseArray(toOriginalArray(tmpParamPair[1], toSameValStr)?.map(n =>
2561
+ structuredClone(g_keyObj[`${_pairName}${getKeyPtnName(n)}`]?.[tmpParamPair[0]]) ??
2562
+ [n === `-` ? -1 : parseInt(n, 10)]
2563
+ ).flat(), g_keyObj[`${g_keyObj.defaultProp}${_key}_${k + dfPtn}`].length, _defaultVal);
2564
+ }
2565
+ });
2566
+ });
2567
+ };
2568
+
2569
+ // 対象キー毎に処理
2570
+ keyExtraList.forEach(newKey => {
2571
+ g_keyObj.minPatterns = 1;
2572
+ g_keyObj.dfPtnNum = 0;
2573
+
2574
+ try {
2575
+
2576
+ // キーパターンの追記 (appendX)
2577
+ if (setBoolVal(_dosObj[`append${newKey}`])) {
2578
+ for (let j = 0; ; j++) {
2579
+ if (g_keyObj[`${g_keyObj.defaultProp}${newKey}_${j}`] === undefined) {
2580
+ break;
2581
+ }
2582
+ g_keyObj.dfPtnNum++;
2583
+ }
2584
+ }
2585
+ const dfPtnNum = g_keyObj.dfPtnNum;
2586
+
2587
+ // キーの名前 (keyNameX)
2588
+ g_keyObj[`keyName${newKey}`] = _dosObj[`keyName${newKey}`]?.split(`,`) ?? [newKey, `key`];
2589
+
2590
+ // キーの最小横幅 (minWidthX)
2591
+ g_keyObj[`minWidth${newKey}`] = _dosObj[`minWidth${newKey}`] ?? g_keyObj[`minWidth${newKey}`] ?? g_keyObj.minWidthDefault;
2592
+
2593
+ // 移動ロック (movLockX)
2594
+ g_keyObj[`movLock${newKey}`] = setBoolVal(_dosObj[`movLock${newKey}`] ?? g_keyObj[`movLock${newKey}`], false);
2595
+
2596
+ // 位置マニュアル化 (initManualX)
2597
+ g_keyObj[`initManual${newKey}`] = setBoolVal(_dosObj[`initManual${newKey}`] ?? g_keyObj[`initManual${newKey}`], false);
2598
+
2599
+ // カスタムキーの説明ページ(keyHelpJaX / keyHelpEnX)
2600
+ Object.keys(g_lang_lblNameObj).forEach(lang =>
2601
+ g_lang_lblNameObj[lang][`keyHelp${newKey}`] = _dosObj[`keyHelp${lang}${newKey}`] ?? _dosObj[`keyHelp${newKey}`] ?? ``);
2602
+
2603
+ // キーコンフィグ (keyCtrlX_Y)
2604
+ g_keyObj.minPatterns = newKeyMultiParam(newKey, `keyCtrl`, toKeyCtrlArray, {
2605
+ errCd: `E_0104`, baseCopyFlg: true,
2606
+ });
2607
+
2608
+ // 読込変数の接頭辞 (charaX_Y)
2609
+ newKeyMultiParam(newKey, `chara`, toString);
2610
+
2611
+ // 矢印色パターン (colorX_Y)
2612
+ newKeyTripleParam(newKey, `color`);
2613
+
2614
+ // 矢印の回転量指定、キャラクタパターン (stepRtnX_Y)
2615
+ newKeyTripleParam(newKey, `stepRtn`);
2616
+
2617
+ // 各キーの区切り位置 (divX_Y)
2618
+ _dosObj[`div${newKey}`]?.split(`$`).forEach((tmpDiv, k) => {
2619
+ const tmpDivPtn = tmpDiv.split(`,`);
2620
+ const ptnName = `${newKey}_${k + dfPtnNum}`;
2621
+
2622
+ if (g_keyObj[`div${tmpDivPtn[0]}`] !== undefined) {
2623
+ // 既定キーパターンが指定された場合、存在すればその値を適用
2624
+ g_keyObj[`div${ptnName}`] = g_keyObj[`div${tmpDivPtn[0]}`];
2625
+ g_keyObj[`divMax${ptnName}`] = setVal(g_keyObj[`divMax${tmpDivPtn[0]}`], undefined, C_TYP_FLOAT);
2626
+ } else if (!hasVal(tmpDivPtn[0]) && setIntVal(g_keyObj[`div${ptnName}`], -1) !== -1) {
2627
+ // カスタムキー側のdivXが未定義だが、すでに初期設定で定義済みの場合はスキップ
2628
+ return;
2629
+ } else {
2630
+ // それ以外の場合は指定された値を適用(未指定時はその後で指定)
2631
+ g_keyObj[`div${ptnName}`] = setVal(tmpDivPtn[0], undefined, C_TYP_NUMBER);
2632
+ g_keyObj[`divMax${ptnName}`] = setVal(getKeyPosNum(tmpDivPtn[1], g_keyObj[`div${ptnName}`]), undefined, C_TYP_FLOAT);
2633
+ }
2634
+ });
2635
+
2636
+ // ステップゾーン位置 (posX_Y)
2637
+ newKeyMultiParam(newKey, `pos`, toFloat, {
2638
+ loopFunc: (k, keyheader) => {
2639
+ g_keyObj[`${keyheader}_${k + dfPtnNum}`].forEach((val, j) =>
2640
+ g_keyObj[`${keyheader}_${k + dfPtnNum}`][j] = getKeyPosNum(String(val), g_keyObj[`div${newKey}_${k + dfPtnNum}`]));
2641
+ },
2642
+ });
2643
+
2644
+ // charaX_Y, posX_Y, keyGroupX_Y, divX_Y, divMaxX_Yが未指定の場合はkeyCtrlX_Yを元に適用
2645
+ for (let k = 0; k < g_keyObj.minPatterns; k++) {
2646
+ setKeyDfVal(`${newKey}_${k + dfPtnNum}`);
2647
+ }
2648
+
2649
+ // ステップゾーン間隔 (blankX_Y)
2650
+ newKeySingleParam(newKey, `blank`, C_TYP_FLOAT, g_keyObj.blank_def);
2651
+
2652
+ // 矢印群の倍率 (scaleX_Y)
2653
+ newKeySingleParam(newKey, `scale`, C_TYP_FLOAT, g_keyObj.scale_def);
2654
+
2655
+ // プレイ中ショートカット:リトライ (keyRetryX_Y)
2656
+ newKeySingleParam(newKey, `keyRetry`, C_TYP_STRING, C_KEY_RETRY);
2657
+
2658
+ // プレイ中ショートカット:タイトルバック (keyTitleBackX_Y)
2659
+ newKeySingleParam(newKey, `keyTitleBack`, C_TYP_STRING, C_KEY_TITLEBACK);
2660
+
2661
+ // プレイ中ショートカット:タイトルバック (keyPauseX_Y)
2662
+ newKeySingleParam(newKey, `keyPause`, C_TYP_STRING, C_KEY_PAUSE);
2663
+
2664
+ // 別キーフラグ (transKeyX_Y)
2665
+ newKeySingleParam(newKey, `transKey`, C_TYP_STRING, ``);
2666
+
2667
+ // フラットモード (flatModeX_Y)
2668
+ newKeySingleParam(newKey, `flatMode`, C_TYP_BOOLEAN, false);
2669
+
2670
+ // シャッフルグループ (shuffleX_Y)
2671
+ newKeyTripleParam(newKey, `shuffle`);
2672
+
2673
+ // キーグループ (keyGroupX_Y)
2674
+ newKeyMultiParam(newKey, `keyGroup`, toSplitArrayStr);
2675
+
2676
+ // キーグループの表示制御 (keyGroupOrderX_Y)
2677
+ newKeyMultiParam(newKey, `keyGroupOrder`, toString);
2678
+
2679
+ // スクロールパターン (scrollX_Y)
2680
+ // |scroll(newKey)=Cross::1,1,-1,-1,-1,1,1/Split::1,1,1,-1,-1,-1,-1$...|
2681
+ newKeyPairParam(newKey, `scroll`, `scrollDir`, C_FLG_HYPHEN, 1);
2682
+
2683
+ // アシストパターン (assistX_Y)
2684
+ // |assist(newKey)=Onigiri::0,0,0,0,0,1/AA::0,0,0,1,1,1$...|
2685
+ newKeyPairParam(newKey, `assist`, `assistPos`);
2686
+
2687
+ // レーンごとの割当レイヤーグループ (layerGroupX_Y)
2688
+ newKeyMultiParam(newKey, `layerGroup`, toInt);
2689
+
2690
+ // レイヤーごとのアニメーション情報 (layerTransX_Y)
2691
+ if (hasVal(_dosObj[`layerTrans${newKey}`])) {
2692
+ _dosObj[`layerTrans${newKey}`] = _dosObj[`layerTrans${newKey}`]?.replaceAll(`,`, `___`);
2693
+ newKeyMultiParam(newKey, `layerTrans`, toSplitArrayStr, {
2694
+ loopFunc: (k, keyheader) => {
2695
+ g_keyObj[`${keyheader}_${k + dfPtnNum}`][0] = g_keyObj[`${keyheader}_${k + dfPtnNum}`]?.[0]?.map(val => val.replaceAll(`___`, `,`));
2696
+ },
2697
+ });
2698
+ }
2699
+ // カスタムキーで定義されたtransKeyPtnを補完
2700
+ completeTransKeyPtn([newKey]);
2701
+
2702
+ // keyRetry, keyTitleBack, keyPauseのキー名をキーコードに変換
2703
+ const keyTypePatterns = Object.keys(g_keyObj).filter(val =>
2704
+ val.startsWith(`keyRetry${newKey}`) || val.startsWith(`keyTitleBack${newKey}`) || val.startsWith(`keyPause${newKey}`));
2705
+ keyTypePatterns.forEach(name => g_keyObj[name] = getKeyCtrlVal(g_keyObj[name]));
2706
+ } catch (e) {
2707
+ g_headerObj.undefinedKeyListFinal.push(newKey);
2708
+ console.warn(`Error in key pattern conversion: ${newKey}`, e);
2709
+ }
2710
+ });
2711
+
2712
+ return keyExtraList;
2713
+ };
2714
+
2715
+ /**
2716
+ * キーパターンのデフォルト値設定
2717
+ * @param {string} _ptnName
2718
+ */
2719
+ const setKeyDfVal = _ptnName => {
2720
+ const baseLength = g_keyObj[`${g_keyObj.defaultProp}${_ptnName}`].length;
2721
+ g_keyObj[`chara${_ptnName}`] = padArray(g_keyObj[`chara${_ptnName}`], [...Array(baseLength).keys()].map(i => `${i + 1}a`));
2722
+ g_keyObj[`pos${_ptnName}`] = padArray(g_keyObj[`pos${_ptnName}`], [...Array(baseLength).keys()].map(i => i));
2723
+ g_keyObj[`keyGroup${_ptnName}`] = padArray(g_keyObj[`keyGroup${_ptnName}`], fillArray(baseLength, [`0`]));
2724
+
2725
+ if (g_keyObj[`div${_ptnName}`] === undefined) {
2726
+ g_keyObj[`div${_ptnName}`] = Math.max(...g_keyObj[`pos${_ptnName}`]) + 1;
2727
+ }
2728
+ if (g_keyObj[`divMax${_ptnName}`] === undefined) {
2729
+ g_keyObj[`divMax${_ptnName}`] = Math.max(...g_keyObj[`pos${_ptnName}`]) + 1;
2730
+ }
2731
+ };
2732
+
2733
+ /**
2734
+ * 背景・マスク用画像の描画
2735
+ * @param {object} _obj
2736
+ * @param {string} _obj.path 画像のパス
2737
+ * @param {string} _obj.class 画像を装飾するCSSクラス名
2738
+ * @param {string} _obj.left 画像の位置(x座標)
2739
+ * @param {string} _obj.top 画像の位置(y座標)
2740
+ * @param {number} _obj.width 画像の幅
2741
+ * @param {string} _obj.height 画像の高さ (他との共用項目のため、stringで受ける)
2742
+ * @param {string} _obj.animationName アニメーション名
2743
+ * @param {string} _obj.animationDuration アニメーションを動かす間隔(秒)
2744
+ * @param {number} _obj.opacity 画像の不透明度
2745
+ * @returns {string}
2746
+ */
2747
+ const makeSpriteImage = _obj => {
2748
+ let tmpInnerHTML = `<img src=${_obj.path} class="${_obj.class}" style="position:absolute;left:${wUnit(_obj.left)};top:${wUnit(_obj.top)}`;
2749
+ if (_obj.width > 0) {
2750
+ tmpInnerHTML += `;width:${wUnit(_obj.width)}`;
2751
+ }
2752
+ if (setIntVal(_obj.height) > 0) {
2753
+ tmpInnerHTML += `;height:${wUnit(_obj.height)}`;
2754
+ }
2755
+ tmpInnerHTML += `;animation-name:${_obj.animationName};animation-duration:${_obj.animationDuration}s;opacity:${_obj.opacity}">`;
2756
+ return tmpInnerHTML;
2757
+ };
2758
+
2759
+ /**
2760
+ * 背景・マスク用テキストの描画
2761
+ * @param {object} _obj
2762
+ * @param {string} _obj.path テキスト本体
2763
+ * @param {string} _obj.class テキストを装飾するCSSクラス名
2764
+ * @param {string} _obj.left テキストの位置(x座標)
2765
+ * @param {string} _obj.top テキストの位置(y座標)
2766
+ * @param {number} _obj.width テキストのフォントサイズ (font-size)
2767
+ * @param {string} _obj.height テキストの色 (color)
2768
+ * @param {string} _obj.animationName アニメーション名
2769
+ * @param {string} _obj.animationDuration アニメーションを動かす間隔(秒)
2770
+ * @param {number} _obj.opacity テキストの不透明度
2771
+ * @returns {string}
2772
+ */
2773
+ const makeSpriteText = _obj => {
2774
+ let tmpInnerHTML = `<span class="${_obj.class}" style="display:inline-block;position:absolute;left:${wUnit(_obj.left)};top:${wUnit(_obj.top)}`;
2775
+
2776
+ // この場合のwidthは font-size と解釈する
2777
+ if (_obj.width > 0) {
2778
+ tmpInnerHTML += `;font-size:${wUnit(_obj.width)}`;
2779
+ }
2780
+
2781
+ // この場合のheightは color と解釈する
2782
+ if (_obj.height !== ``) {
2783
+ tmpInnerHTML += `;color:${_obj.height}`;
2784
+ }
2785
+ tmpInnerHTML += `;animation-name:${_obj.animationName};animation-duration:${_obj.animationDuration}s;opacity:${_obj.opacity}">${_obj.path}</span>`;
2786
+ return tmpInnerHTML;
2787
+ };
2788
+
2789
+ /**
2790
+ * 多重配列の存在をチェックし、
2791
+ * 存在しない場合は作成、存在する場合は重複を避けて配列を新規作成
2792
+ * @param {any[][]} _obj
2793
+ * @returns [多重配列(初期化済),配列初期化済数]
2794
+ */
2795
+ const checkDuplicatedObjects = _obj => {
2796
+ let dataCnts = 0;
2797
+ if (_obj === undefined) {
2798
+ _obj = [];
2799
+ _obj[0] = [];
2800
+ } else {
2801
+ for (let m = 1; ; m++) {
2802
+ if (_obj[m] === undefined) {
2803
+ _obj[m] = [];
2804
+ dataCnts = m;
2805
+ break;
2806
+ }
2807
+ }
2808
+ }
2809
+ return [_obj, dataCnts];
2810
+ };
2811
+
2812
+ /**
2813
+ * 多層スプライトデータの作成処理
2814
+ * @param {string} _data
2815
+ * @param {Function} _calcFrame
2816
+ * @returns [多層スプライトデータ, 最大深度]
2817
+ */
2818
+ const makeSpriteData = (_data, _calcFrame = _frame => _frame) => {
2819
+
2820
+ const spriteData = [];
2821
+ let maxDepth = -1;
2822
+
2823
+ splitLF(_data).filter(data => hasVal(data)).forEach(tmpData => {
2824
+ const tmpSpriteData = tmpData.split(`,`).map(val => trimStr(val));
2825
+
2826
+ // 深度が"-"の場合はスキップ
2827
+ if (tmpSpriteData[1] === undefined || tmpSpriteData[1] === `-` ||
2828
+ (tmpSpriteData[1] === `` && ![`[loop]`, `[jump]`].includes(tmpSpriteData[2]))) {
2829
+ return;
2830
+ }
2831
+
2832
+ // 値チェックとエスケープ処理
2833
+ const tmpFrame = setIntVal(tmpSpriteData[0], -1) === 0 ? 0 :
2834
+ roundZero(_calcFrame(setVal(tmpSpriteData[0], 200, C_TYP_CALC)));
2835
+ const tmpDepth = (tmpSpriteData[1] === C_FLG_ALL ? C_FLG_ALL : setVal(tmpSpriteData[1], 0, C_TYP_CALC));
2836
+ if (tmpDepth !== C_FLG_ALL && tmpDepth > maxDepth) {
2837
+ maxDepth = tmpDepth;
2838
+ }
2839
+
2840
+ const colorObjFlg = tmpSpriteData[2]?.startsWith(`[c]`) || false;
2841
+ const transformFlg = tmpSpriteData[2]?.startsWith(`[t]`) || false;
2842
+ const tmpObj = {
2843
+ path: escapeHtml(tmpSpriteData[2] ?? ``, g_escapeStr.escapeCode), // 画像パス or テキスト
2844
+ class: escapeHtml(tmpSpriteData[3] ?? ``), // CSSクラス
2845
+ left: transformFlg
2846
+ ? setVal(tmpSpriteData[4], 1000, C_TYP_NUMBER) // [t]のみtransformのPriority
2847
+ : setVal(tmpSpriteData[4], `0`).includes(`{`)
2848
+ ? `${setVal(tmpSpriteData[4], 0)}`
2849
+ : `{${setVal(tmpSpriteData[4], 0)}}`, // X座標
2850
+ top: setVal(tmpSpriteData[5], `0`).includes(`{`)
2851
+ ? `${setVal(tmpSpriteData[5], 0)}`
2852
+ : `{${setVal(tmpSpriteData[5], 0)}}`, // Y座標
2853
+ width: `${setIntVal(tmpSpriteData[6])}`, // spanタグの場合は font-size
2854
+ height: `${escapeHtml(tmpSpriteData[7] ?? ``)}`, // spanタグの場合は color(文字列可)
2855
+ opacity: setVal(tmpSpriteData[8], 1, C_TYP_FLOAT),
2856
+ animationName: escapeHtml(setVal(tmpSpriteData[9], C_DIS_NONE)),
2857
+ animationDuration: setIntVal(tmpSpriteData[10]) / g_fps,
2858
+ };
2859
+ if (setVal(tmpSpriteData[11], g_presetObj.animationFillMode) !== undefined) {
2860
+ tmpObj.animationFillMode = setVal(tmpSpriteData[11], g_presetObj.animationFillMode);
2861
+ }
2862
+ tmpObj.path = preloadImgFile(tmpObj.path, { syncBackPath: g_headerObj.syncBackPath });
2863
+
2864
+ let dataCnts = 0;
2865
+ [spriteData[tmpFrame], dataCnts] =
2866
+ checkDuplicatedObjects(spriteData[tmpFrame]);
2867
+
2868
+ const emptyPatterns = [`[loop]`, `[jump]`];
2869
+ const spriteFrameData = spriteData[tmpFrame][dataCnts] = {
2870
+ depth: tmpDepth,
2871
+ };
2872
+
2873
+ if (colorObjFlg) {
2874
+ // [c]始まりの場合、カラーオブジェクト用の作成準備を行う
2875
+ const data = tmpObj.path.slice(`[c]`.length).split(`/`);
2876
+ let objPart = data[0];
2877
+ if (!isNaN(parseInt(data[0]))) {
2878
+ const keyCtrlPtn = `${g_keyObj.currentKey}_${g_keyObj.currentPtn}`;
2879
+ objPart = g_keyObj[`stepRtn${keyCtrlPtn}`][data[0]];
2880
+ spriteFrameData.transform = parseInt(data[0]);
2881
+ }
2882
+ spriteFrameData.colorObjInfo = {
2883
+ x: tmpObj.left, y: tmpObj.top, w: tmpObj.width, h: tmpObj.height,
2884
+ rotate: setVal(objPart, `0`), opacity: tmpObj.opacity,
2885
+ background: makeColorGradation(setVal(data[1], `#ffffff`), { _defaultColorgrd: false }),
2886
+ animationName: tmpObj.animationName,
2887
+ animationDuration: `${tmpObj.animationDuration}s`,
2888
+ };
2889
+ spriteFrameData.colorObjId = `${tmpFrame}_${dataCnts}`;
2890
+ spriteFrameData.colorObjClass = setVal(tmpObj.class, undefined);
2891
+ if (tmpObj.animationFillMode !== undefined) {
2892
+ spriteFrameData.colorObjInfo.animationFillMode = tmpObj.animationFillMode;
2893
+ }
2894
+ } else if (transformFlg) {
2895
+ // [t]始まりの場合、レイヤーに対してtransformを掛ける準備を行う
2896
+ const transformData = tmpObj.path.slice(`[t]`.length);
2897
+ spriteFrameData.transform = transformData || ``;
2898
+ spriteFrameData.transformId = tmpObj.class;
2899
+ spriteFrameData.transPriority = tmpObj.left;
2900
+
2901
+ } else if (tmpObj.path === ``) {
2902
+ spriteFrameData.command = ``;
2903
+ } else if (emptyPatterns.includes(tmpObj.path)) {
2904
+ // ループ、フレームジャンプの場合の処理
2905
+ spriteFrameData.command = tmpObj.path;
2906
+ spriteFrameData.jumpFrame = tmpObj.class;
2907
+ spriteFrameData.maxLoop = tmpObj.left;
2908
+ spriteFrameData.htmlText = ``;
2909
+ } else {
2910
+ // それ以外の画像、テキストの場合
2911
+ spriteFrameData.animationName = tmpObj.animationName;
2912
+ spriteFrameData.htmlText = (checkImage(tmpObj.path) ? makeSpriteImage(tmpObj) : makeSpriteText(tmpObj));
2913
+ }
2914
+ });
2915
+
2916
+ return [spriteData, maxDepth];
2917
+ };
2918
+
2919
+ /**
2920
+ * スタイル変更データの作成処理
2921
+ * @param {string} _data
2922
+ * @param {Function} _calcFrame
2923
+ * @returns [多層スプライトデータ, 1(固定)]
2924
+ */
2925
+ const makeStyleData = (_data, _calcFrame = _frame => _frame) => {
2926
+ const spriteData = [];
2927
+ splitLF(_data).filter(data => hasVal(data)).forEach(tmpData => {
2928
+ const tmpSpriteData = tmpData.split(`,`).map(val => trimStr(val));
2929
+
2930
+ // カスタムプロパティの名称(--始まり)で無い場合はコメントと見做してスキップ
2931
+ if (tmpSpriteData.length <= 1 || !tmpSpriteData[1].startsWith(`--`)) {
2932
+ return;
2933
+ }
2934
+ const tmpFrame = setIntVal(tmpSpriteData[0], -1) === 0 ? 0 :
2935
+ roundZero(_calcFrame(setVal(tmpSpriteData[0], 200, C_TYP_CALC)));
2936
+
2937
+ let dataCnts = 0;
2938
+ [spriteData[tmpFrame], dataCnts] = checkDuplicatedObjects(spriteData[tmpFrame]);
2939
+ spriteData[tmpFrame][dataCnts] = {
2940
+ depth: tmpSpriteData[1],
2941
+ styleData: getCssCustomProperty(tmpSpriteData[1], tmpSpriteData[2]),
2942
+ };
2943
+ });
2944
+ return [spriteData, 1];
2945
+ };
2946
+
2947
+ /**
2948
+ * 画像ファイルかどうかをチェック
2949
+ * @param {string} _str
2950
+ * @returns {boolean}
2951
+ */
2952
+ const checkImage = _str => listMatching(_str, g_imgExtensions, { prefix: `[.]`, suffix: `$` });
2953
+
2954
+ /**
2955
+ * back/masktitle(result)において、ジャンプ先のフレーム数を取得
2956
+ * @param {string} _frames ジャンプ先のフレーム数情報。コロン指定でジャンプ先を確率で分岐 (ex. 300:1500:1500)
2957
+ * @returns {number}
2958
+ */
2959
+ const getSpriteJumpFrame = _frames => {
2960
+ const jumpFrames = _frames.split(`:`);
2961
+ const jumpCnt = Math.floor(Math.random() * jumpFrames.length);
2962
+ return setIntVal(Number(jumpFrames[jumpCnt]) - 1);
2963
+ };
2964
+
2965
+ /**
2966
+ * 背景・マスクモーションの表示(共通処理)
2967
+ * @param {object} _spriteData
2968
+ * @param {string} _name
2969
+ * @param {boolean} [_condition=true]
2970
+ */
2971
+ const drawBaseSpriteData = (_spriteData, _name, _condition = true) => {
2972
+ const baseSprite = document.getElementById(`${_name}Sprite${_spriteData.depth}`);
2973
+ if (_spriteData.command === ``) {
2974
+ if (_spriteData.depth === C_FLG_ALL) {
2975
+ for (let j = 0; j <= g_scoreObj[`${_name}MaxDepth`]; j++) {
2976
+ document.getElementById(`${_name}Sprite${j}`).textContent = ``;
2977
+ }
2978
+ } else {
2979
+ baseSprite.textContent = ``;
2980
+ }
2981
+ } else {
2982
+ if (_condition) {
2983
+ if (_spriteData.colorObjInfo === undefined && _spriteData.transform === undefined) {
2984
+ baseSprite.innerHTML = convertStrToVal(_spriteData.htmlText);
2985
+ return;
2986
+ }
2987
+ if (_spriteData.colorObjInfo !== undefined) {
2988
+ const colorObjClass = _spriteData.colorObjClass?.split(`/`) ?? [];
2989
+ const id = `${_name}${_spriteData.depth}${_spriteData.colorObjId}`;
2990
+ [`x`, `y`, `w`, `h`].forEach(val => _spriteData.colorObjInfo[val] = convertStrToVal(_spriteData.colorObjInfo[val]));
2991
+ baseSprite.appendChild(
2992
+ createColorObject2(id, _spriteData.colorObjInfo, ...colorObjClass)
2993
+ );
2994
+ }
2995
+ if (_spriteData.transform !== undefined) {
2996
+ const targetId = `${_name}Sprite${_spriteData.depth}`;
2997
+
2998
+ if (!isNaN(parseInt(_spriteData.transform))) {
2999
+ // PlayWindow由来のtransformを継承(別transformId)
3000
+ const transformId = `${_name}${_spriteData.depth}PlayWindow`;
3001
+ const transformData = getTransform(`mainSprite`, `playWindow`);
3002
+
3003
+ if (hasVal(transformData)) {
3004
+ if (transformData !== getTransform(targetId, transformId)) {
3005
+ addTransform(targetId, transformId, transformData, g_transPriority.playWindow);
3006
+ }
3007
+ } else {
3008
+ delTransform(targetId, transformId);
3009
+ }
3010
+ } else {
3011
+ // 明示指定のtransformを適用
3012
+ const transformId = _spriteData.transformId || `${_name}${_spriteData.depth}`;
3013
+
3014
+ if (hasVal(_spriteData.transform)) {
3015
+ addTransform(targetId, transformId, _spriteData.transform, _spriteData.transPriority);
3016
+ } else {
3017
+ delTransform(targetId, transformId);
3018
+ }
3019
+ }
3020
+ }
3021
+ }
3022
+ }
3023
+ };
3024
+
3025
+ /**
3026
+ * 背景・マスクモーションの表示(タイトル・リザルト用)
3027
+ * @param {number} _frame
3028
+ * @param {string} _displayName title / result
3029
+ * @param {string} _depthName back / mask
3030
+ * @returns {number}
3031
+ */
3032
+ const drawSpriteData = (_frame, _displayName, _depthName) => {
3033
+
3034
+ const spriteName = `${_depthName}${toCapitalize(_displayName)}`;
3035
+ const tmpObjs = g_headerObj[`${spriteName}Data`][_frame];
3036
+
3037
+ for (let j = 0; j < tmpObjs.length; j++) {
3038
+ const tmpObj = tmpObjs[j];
3039
+ drawBaseSpriteData(tmpObj, spriteName, ![`[loop]`, `[jump]`].includes(tmpObj.command));
3040
+ if (tmpObj.command === `[loop]`) {
3041
+ // キーワード指定:ループ
3042
+ // 指定フレーム(class)へ移動する
3043
+ g_scoreObj[`${spriteName}LoopCount`]++;
3044
+ return getSpriteJumpFrame(tmpObj.jumpFrame);
3045
+
3046
+ } else if (tmpObj.command === `[jump]`) {
3047
+ // キーワード指定:フレームジャンプ
3048
+ // 指定回数以上のループ(maxLoop)があれば指定フレーム(jumpFrame)へ移動する
3049
+ if (g_scoreObj[`${spriteName}LoopCount`] >= Number(tmpObj.maxLoop)) {
3050
+ g_scoreObj[`${spriteName}LoopCount`] = 0;
3051
+ return getSpriteJumpFrame(tmpObj.jumpFrame);
3052
+ }
3053
+ }
3054
+ }
3055
+ return _frame;
3056
+ };
3057
+
3058
+ /**
3059
+ * 背景・マスクモーションの表示
3060
+ * @param {number} _frame
3061
+ * @param {string} _depthName
3062
+ */
3063
+ const drawMainSpriteData = (_frame, _depthName) =>
3064
+ g_scoreObj[`${_depthName}Data`][_frame].forEach(tmpObj => drawBaseSpriteData(tmpObj, _depthName));
3065
+
3066
+ /**
3067
+ * スタイル切替
3068
+ * @param {number} _frame
3069
+ * @param {string} _displayName
3070
+ * @returns {number}
3071
+ */
3072
+ const drawStyleData = (_frame, _displayName) => {
3073
+ g_headerObj[`style${toCapitalize(_displayName)}Data`][_frame].forEach(tmpObj =>
3074
+ document.documentElement.style.setProperty(tmpObj.depth, tmpObj.styleData));
3075
+
3076
+ return _frame;
3077
+ };
3078
+
3079
+ const drawMainStyleData = (_frame) =>
3080
+ g_scoreObj.styleData[_frame].forEach(tmpObj =>
3081
+ document.documentElement.style.setProperty(tmpObj.depth, tmpObj.styleData));
3082
+
3083
+ /**
3084
+ * タイトル・リザルトモーションの描画
3085
+ * @param {string} _displayName
3086
+ */
3087
+ const drawTitleResultMotion = _displayName =>
3088
+ g_animationData.forEach(sprite => {
3089
+ const spriteName = `${sprite}${toCapitalize(_displayName)}`;
3090
+ if (g_headerObj[`${spriteName}Data`][g_scoreObj[`${spriteName}FrameNum`]] !== undefined) {
3091
+ g_scoreObj[`${spriteName}FrameNum`] = g_animationFunc.draw[sprite](g_scoreObj[`${spriteName}FrameNum`], _displayName, sprite);
3092
+ }
3093
+ });