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.
- package/js/danoni_main.js +479 -16549
- package/js/lib/danoni_constants.js +2 -1
- package/js/lib/dataLoader.js +2837 -0
- package/js/lib/dosConverter.js +3093 -0
- package/js/lib/keyconfig.js +1932 -0
- package/js/lib/mainWindow.js +2626 -0
- package/js/lib/result.js +979 -0
- package/js/lib/settings.js +3426 -0
- package/js/lib/title.js +1274 -0
- package/package.json +1 -1
|
@@ -0,0 +1,2837 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dancing☆Onigiri (CW Edition)
|
|
3
|
+
* 譜面及び楽曲データの読込・変換処理
|
|
4
|
+
* - ページ: loading, loadingIos
|
|
5
|
+
*
|
|
6
|
+
* Source by tickle
|
|
7
|
+
* Created : 2026/09/13
|
|
8
|
+
* Revised :
|
|
9
|
+
*
|
|
10
|
+
* https://github.com/cwtickle/danoniplus
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/*-----------------------------------------------------------*/
|
|
14
|
+
/* Scene : LOADING [strawberry] */
|
|
15
|
+
/*-----------------------------------------------------------*/
|
|
16
|
+
|
|
17
|
+
const loadMusic = async () => {
|
|
18
|
+
|
|
19
|
+
clearWindow();
|
|
20
|
+
pauseBGM();
|
|
21
|
+
g_currentPage = `loading`;
|
|
22
|
+
|
|
23
|
+
const musicUrl = getMusicUrl(g_stateObj.scoreId);
|
|
24
|
+
const url = getFullMusicUrl(musicUrl);
|
|
25
|
+
g_headerObj.musicUrl = musicUrl;
|
|
26
|
+
g_musicEncodedFlg = listMatching(musicUrl, [`.js`, `.txt`], { suffix: `$` });
|
|
27
|
+
|
|
28
|
+
// Now Loadingを表示
|
|
29
|
+
const lblLoading = getLoadingLabel();
|
|
30
|
+
divRoot.appendChild(lblLoading);
|
|
31
|
+
|
|
32
|
+
// 音源準備Promise(ローカル/オンライン双方を吸収)
|
|
33
|
+
const audioReadyPromise = loadAndSetupAudio(url, lblLoading);
|
|
34
|
+
|
|
35
|
+
// 譜面データ読込・変換処理Promise(g_audioに依存しない部分)
|
|
36
|
+
const chartReadyPromise = loadChartFile().then(() => prepareScoreData());
|
|
37
|
+
|
|
38
|
+
// 両方の完了を待つ
|
|
39
|
+
let loadSucceeded = true;
|
|
40
|
+
try {
|
|
41
|
+
await Promise.all([audioReadyPromise, chartReadyPromise]);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn(`Loading error: ${e}`);
|
|
44
|
+
loadSucceeded = false;
|
|
45
|
+
} finally {
|
|
46
|
+
deleteDiv(divRoot, `lblLoading`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (loadSucceeded) {
|
|
50
|
+
// 初回プレイ時に出力デバイスの起動待ちでずれるのを防ぐため、
|
|
51
|
+
// メイン画面へ移行する前にAudioContextを起動させておく
|
|
52
|
+
await warmUpAudioContext();
|
|
53
|
+
mainInit(); // 音源・譜面変換双方の完了後にまとめて呼ぶ
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 音源の取得とセットアップ
|
|
59
|
+
* - エンコード形式(base64)か通常の音声ファイルかを判定し、それぞれの準備処理に振り分ける
|
|
60
|
+
* - iOSの場合はユーザー操作(ジェスチャー)を待ってから再生準備を行う(readyToStart経由)
|
|
61
|
+
* - キャッシュヒット時はダウンロード自体を行わないよう、取得処理はsetupWebAudioへ
|
|
62
|
+
* コールバックとして渡し、キャッシュミス時にのみ評価されるようにしている
|
|
63
|
+
* @param {string} _url 音源の取得元URL
|
|
64
|
+
* @param {HTMLDivElement} _lblLoading ローディング表示用のDiv要素
|
|
65
|
+
* @returns {Promise<void>} 再生準備(canplaythrough相当)が完了したら解決するPromise
|
|
66
|
+
*/
|
|
67
|
+
const loadAndSetupAudio = async (_url, _lblLoading) => {
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* iOSの場合はユーザー操作(ジェスチャー)を待ってから_funcを実行する
|
|
71
|
+
* - AudioContextの制約上、iOSはジェスチャーを起点にしないと音声再生が許可されないため
|
|
72
|
+
* @param {() => Promise<void>} _func 実行する音源準備処理
|
|
73
|
+
* @returns {Promise<void>}
|
|
74
|
+
*/
|
|
75
|
+
const readyToStart = _func => {
|
|
76
|
+
if (!g_isIos) {
|
|
77
|
+
return _func(); // 通常環境はそのまま実行するだけ
|
|
78
|
+
}
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
g_currentPage = `loadingIos`;
|
|
81
|
+
_lblLoading.textContent = `Click to Start!`;
|
|
82
|
+
divRoot.appendChild(makePlayButton(evt => {
|
|
83
|
+
getSharedAudioContext().resume();
|
|
84
|
+
g_currentPage = `loading`;
|
|
85
|
+
resetKeyControl();
|
|
86
|
+
divRoot.removeChild(evt.target);
|
|
87
|
+
_func().then(resolve).catch(reject);
|
|
88
|
+
}));
|
|
89
|
+
setShortcutEvent(g_currentPage);
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 音源準備処理そのものを、条件に応じて組み立てる
|
|
94
|
+
const setupAudioFunc = (() => {
|
|
95
|
+
|
|
96
|
+
// エンコードなし & ローカル実行: Audio要素で直接再生
|
|
97
|
+
if (!g_musicEncodedFlg && g_isFile) {
|
|
98
|
+
return () => {
|
|
99
|
+
g_audio = new Audio();
|
|
100
|
+
g_audio.src = _url;
|
|
101
|
+
return musicAfterLoaded();
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// エンコードなし & オンライン: WebAudioAPI経由(URLからfetch)
|
|
106
|
+
if (!g_musicEncodedFlg) {
|
|
107
|
+
return () => setupWebAudio(async () => {
|
|
108
|
+
const blobUrl = await fetchMusicBlobUrl(_url, _lblLoading);
|
|
109
|
+
try {
|
|
110
|
+
const response = await fetch(blobUrl);
|
|
111
|
+
return await response.arrayBuffer();
|
|
112
|
+
} finally {
|
|
113
|
+
URL.revokeObjectURL(blobUrl);
|
|
114
|
+
}
|
|
115
|
+
}, _url);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// エンコードあり: スクリプト読込・musicInit実行を経てWebAudioAPI準備
|
|
119
|
+
return () => setupWebAudio(async () => {
|
|
120
|
+
const scriptSrc = g_isFile ? _url : await fetchMusicBlobUrl(_url, _lblLoading);
|
|
121
|
+
try {
|
|
122
|
+
await loadScript2(scriptSrc);
|
|
123
|
+
} finally {
|
|
124
|
+
if (!g_isFile) {
|
|
125
|
+
URL.revokeObjectURL(scriptSrc);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (typeof musicInit !== C_TYP_FUNCTION) {
|
|
129
|
+
makeWarningWindow(g_msgInfoObj.E_0031, { backBtnUse: true });
|
|
130
|
+
throw new Error(`musicInit is not defined`);
|
|
131
|
+
}
|
|
132
|
+
musicInit();
|
|
133
|
+
return base64ToUint8Array(g_musicdata).buffer;
|
|
134
|
+
}, _url);
|
|
135
|
+
})();
|
|
136
|
+
|
|
137
|
+
return readyToStart(setupAudioFunc);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* XHRによる音源ファイルのダウンロード(Promise化)
|
|
142
|
+
* - ダウンロードして Blob URL を返す
|
|
143
|
+
* @param {string} _url 音源ファイルのURL
|
|
144
|
+
* @param {HTMLDivElement} _lblLoading ローディング表示用のDiv要素
|
|
145
|
+
* @returns {Promise<string>} Blob URL
|
|
146
|
+
*/
|
|
147
|
+
const fetchMusicBlobUrl = (_url, _lblLoading) => new Promise((resolve, reject) => {
|
|
148
|
+
const request = new XMLHttpRequest();
|
|
149
|
+
request.open(`GET`, _url, true);
|
|
150
|
+
request.responseType = `blob`;
|
|
151
|
+
|
|
152
|
+
const STALL_TIMEOUT_MS = 30000; // 30秒間、進捗がなければ停滞とみなす
|
|
153
|
+
let stallTimer = null;
|
|
154
|
+
|
|
155
|
+
// 停滞タイマーをリセット
|
|
156
|
+
const resetStallTimer = () => {
|
|
157
|
+
g_timerHandler.clearTimeout(stallTimer);
|
|
158
|
+
stallTimer = g_timerHandler.setTimeout(() => {
|
|
159
|
+
request.abort();
|
|
160
|
+
makeWarningWindow(g_msgInfoObj.E_0033, { backBtnUse: true });
|
|
161
|
+
reject(new Error(`stalled`));
|
|
162
|
+
}, STALL_TIMEOUT_MS);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// 読み込み完了時
|
|
166
|
+
request.addEventListener(`load`, () => {
|
|
167
|
+
g_timerHandler.clearTimeout(stallTimer);
|
|
168
|
+
if (request.status >= 200 && request.status < 300) {
|
|
169
|
+
const blobUrl = URL.createObjectURL(request.response);
|
|
170
|
+
createEmptySprite(divRoot, `loader`, g_windowObj.loader);
|
|
171
|
+
_lblLoading.textContent = g_lblNameObj.pleaseWait;
|
|
172
|
+
resolve(blobUrl);
|
|
173
|
+
} else {
|
|
174
|
+
makeWarningWindow(`${g_msgInfoObj.E_0041.split('{0}').join(getFullPath(_url))}<br>(${request.status} ${request.statusText})`, { backBtnUse: true });
|
|
175
|
+
reject(new Error(`HTTP ${request.status}`));
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// 進捗時
|
|
180
|
+
request.addEventListener(`progress`, _event => {
|
|
181
|
+
resetStallTimer(); // 進捗があるたびにタイマーをリセット
|
|
182
|
+
const lblLoadingElem = document.getElementById(`lblLoading`);
|
|
183
|
+
if (lblLoadingElem === null) return; // 並列処理で先に削除されている場合の防御
|
|
184
|
+
|
|
185
|
+
if (_event.lengthComputable) {
|
|
186
|
+
const rate = _event.loaded / _event.total;
|
|
187
|
+
createEmptySprite(divRoot, `loader`, { y: g_sHeight - 10, h: 10, w: g_sWidth * rate, backgroundColor: `#eeeeee` });
|
|
188
|
+
lblLoadingElem.textContent = `${g_lblNameObj.nowLoading} ${Math.floor(rate * 100)}%`;
|
|
189
|
+
} else {
|
|
190
|
+
lblLoadingElem.textContent = `${g_lblNameObj.nowLoading} ${_event.loaded}Bytes`;
|
|
191
|
+
}
|
|
192
|
+
// ユーザカスタムイベント
|
|
193
|
+
safeExecuteCustomHooks(`g_customJsObj.progress`, g_customJsObj.progress, _event);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
request.addEventListener(`error`, () => {
|
|
197
|
+
g_timerHandler.clearTimeout(stallTimer);
|
|
198
|
+
makeWarningWindow(g_msgInfoObj.E_0034, { backBtnUse: true });
|
|
199
|
+
reject(new Error(`network error`));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
resetStallTimer(); // 初回(最初のprogressが来るまで)のタイマーも開始
|
|
203
|
+
request.send();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* WebAudioAPIによる音源再生の準備(共通処理)
|
|
208
|
+
* - AudioPlayerを生成し、canplaythrough/errorの発火待ちを開始した上で、
|
|
209
|
+
* ArrayBufferを取得してデコードする(キャッシュヒット時はデコードをスキップ)
|
|
210
|
+
* @param {() => Promise<ArrayBuffer>} _fetchArrayBuffer 未キャッシュの場合にArrayBufferを取得する関数
|
|
211
|
+
* @param {string} [_cacheKey] AudioBufferキャッシュ照合用のキー(省略時はキャッシュを使わない)
|
|
212
|
+
* @returns {Promise<void>} 再生準備(canplaythrough相当)が完了したら解決するPromise
|
|
213
|
+
*/
|
|
214
|
+
const setupWebAudio = async (_fetchArrayBuffer, _cacheKey) => {
|
|
215
|
+
g_audio = new AudioPlayer();
|
|
216
|
+
const loadedPromise = musicAfterLoaded(); // canplaythrough/errorの発火をここで待つ
|
|
217
|
+
|
|
218
|
+
const cachedBuffer = _cacheKey ? getAudioBufferFromCache(_cacheKey) : undefined;
|
|
219
|
+
if (cachedBuffer !== undefined) {
|
|
220
|
+
g_audio.setBuffer(cachedBuffer);
|
|
221
|
+
} else {
|
|
222
|
+
const arrayBuffer = await _fetchArrayBuffer();
|
|
223
|
+
await g_audio.init(arrayBuffer);
|
|
224
|
+
if (_cacheKey) {
|
|
225
|
+
cacheAudioBuffer(_cacheKey, g_audio.getBuffer());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return loadedPromise;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const g_audioBufferCache = new Map();
|
|
232
|
+
const AUDIO_CACHE_MAX = 5;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* デコード済みAudioBufferのキャッシュからの取得
|
|
236
|
+
* - Mapは挿入順を保持するのみでアクセス順を保持しないため、
|
|
237
|
+
* ヒット時にエントリを一度削除して再挿入し、最新として扱う(LRU方式)
|
|
238
|
+
* @param {string} _key キャッシュキー(楽曲の実URL)
|
|
239
|
+
* @returns {AudioBuffer|undefined} キャッシュされたAudioBuffer。存在しない場合はundefined
|
|
240
|
+
*/
|
|
241
|
+
const getAudioBufferFromCache = (_key) => {
|
|
242
|
+
if (!g_audioBufferCache.has(_key)) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
const buffer = g_audioBufferCache.get(_key);
|
|
246
|
+
g_audioBufferCache.delete(_key);
|
|
247
|
+
g_audioBufferCache.set(_key, buffer); // 末尾(最新)に再挿入
|
|
248
|
+
return buffer;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* デコード済みAudioBufferのキャッシュへの登録
|
|
253
|
+
* - 直近 AUDIO_CACHE_MAX 件を保持するLRU方式。上限を超えた場合は最も古いエントリから破棄する
|
|
254
|
+
* @param {string} _key キャッシュキー(楽曲の実URL)
|
|
255
|
+
* @param {AudioBuffer} _buffer デコード済みのAudioBuffer
|
|
256
|
+
*/
|
|
257
|
+
const cacheAudioBuffer = (_key, _buffer) => {
|
|
258
|
+
g_audioBufferCache.set(_key, _buffer);
|
|
259
|
+
if (g_audioBufferCache.size > AUDIO_CACHE_MAX) {
|
|
260
|
+
g_audioBufferCache.delete(g_audioBufferCache.keys().next().value); // 古い順に破棄
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* base64文字列をUint8Arrayに変換
|
|
266
|
+
* - Uint8Array.from(atob(str), callback)によるコールバック呼び出し形式は
|
|
267
|
+
* 大容量データで変換オーバーヘッドが大きいため、forループによる直接代入で高速化している
|
|
268
|
+
* @param {string} _base64Str base64エンコードされた文字列
|
|
269
|
+
* @returns {Uint8Array} 変換後のバイト配列
|
|
270
|
+
*/
|
|
271
|
+
const base64ToUint8Array = (_base64Str) => {
|
|
272
|
+
const binaryStr = atob(_base64Str);
|
|
273
|
+
const len = binaryStr.length;
|
|
274
|
+
const array = new Uint8Array(len);
|
|
275
|
+
for (let i = 0; i < len; i++) {
|
|
276
|
+
array[i] = binaryStr.charCodeAt(i);
|
|
277
|
+
}
|
|
278
|
+
return array;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 音源の再生準備完了を待つ
|
|
283
|
+
* - g_audio が Audio要素の場合は canplaythrough/error イベントの発火を待つ
|
|
284
|
+
* - g_audio が AudioPlayer の場合、readyState が既に4(デコード済み、キャッシュヒット時など)であれば即時解決する
|
|
285
|
+
* - canplaythrough/error のどちらが発火しても、もう一方のリスナーも確実に削除する(cleanup)
|
|
286
|
+
* @returns {Promise<void>} 再生準備が完了したら解決し、読込エラー時は例外を投げて拒否するPromise
|
|
287
|
+
*/
|
|
288
|
+
const musicAfterLoaded = () => new Promise((resolve, reject) => {
|
|
289
|
+
g_audio.load();
|
|
290
|
+
|
|
291
|
+
if (g_audio.readyState === 4) {
|
|
292
|
+
resolve();
|
|
293
|
+
} else {
|
|
294
|
+
const onCanPlay = () => { cleanup(); resolve(); };
|
|
295
|
+
const onError = () => {
|
|
296
|
+
cleanup();
|
|
297
|
+
makeWarningWindow(g_msgInfoObj.E_0041.split(`{0}`).join(g_audio.src), { backBtnUse: true });
|
|
298
|
+
reject(new Error(`audio load error`));
|
|
299
|
+
};
|
|
300
|
+
const cleanup = () => {
|
|
301
|
+
g_audio.removeEventListener(`canplaythrough`, onCanPlay, false);
|
|
302
|
+
g_audio.removeEventListener(`error`, onError, false);
|
|
303
|
+
};
|
|
304
|
+
g_audio.addEventListener(`canplaythrough`, onCanPlay, false);
|
|
305
|
+
g_audio.addEventListener(`error`, onError, false);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 譜面データの変換処理
|
|
311
|
+
* - 音源データの状態に依存しない部分のみを担う(g_audio非参照)
|
|
312
|
+
* - loadMusic経由(並行フロー)、executeRetry経由(曲中リトライ)の両方から呼ばれる
|
|
313
|
+
*/
|
|
314
|
+
const prepareScoreData = () => {
|
|
315
|
+
|
|
316
|
+
const tkObj = getKeyInfo();
|
|
317
|
+
const [keyCtrlPtn, keyNum] = [tkObj.keyCtrlPtn, tkObj.keyNum];
|
|
318
|
+
g_headerObj.blankFrameDef = setVal(g_headerObj.blankFrameDefs[g_stateObj.scoreId], g_headerObj.blankFrameDefs[0]);
|
|
319
|
+
g_headerObj.blankFrame = g_headerObj.blankFrameDef;
|
|
320
|
+
|
|
321
|
+
// ユーザカスタムイベント
|
|
322
|
+
safeExecuteCustomHooks(`g_customJsObj.preloading`, g_customJsObj.preloading);
|
|
323
|
+
safeExecuteCustomHooks(`g_skinJsObj.preloading`, g_skinJsObj.preloading);
|
|
324
|
+
|
|
325
|
+
let dummyIdHeader = ``;
|
|
326
|
+
if (g_stateObj.dummyId !== ``) {
|
|
327
|
+
if (g_stateObj.dummyId === 0 || g_stateObj.dummyId === 1) {
|
|
328
|
+
dummyIdHeader = ``;
|
|
329
|
+
} else {
|
|
330
|
+
dummyIdHeader = g_stateObj.dummyId;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
g_scoreObj = scoreConvert(g_rootObj, g_stateObj.scoreId, 0, dummyIdHeader);
|
|
334
|
+
|
|
335
|
+
// Motionオプション適用時の矢印別の速度を取得(配列形式)
|
|
336
|
+
g_workObj.motionOnFrames = setMotionOnFrame();
|
|
337
|
+
g_workObj.motionReverseFlg = g_workObj.motionOnFrames.filter(val => g_stateObj.speed + val < 0).length > 0;
|
|
338
|
+
|
|
339
|
+
// 矢印描画時間の引き伸ばし設定(個別加速がある場合かつ逆走を伴うモーションは初期倍速によりさらに引き伸ばしを行う)
|
|
340
|
+
// ただし、速度による引き伸ばし(boostFactor)の上限は描画の関係で2倍までとする
|
|
341
|
+
let maxBoost = 1;
|
|
342
|
+
for (let j = 0; j < g_scoreObj.boostData.length; j += 2) {
|
|
343
|
+
maxBoost = Math.max(maxBoost, g_scoreObj.boostData[j + 1]);
|
|
344
|
+
}
|
|
345
|
+
const boostFactor = 1 + (
|
|
346
|
+
(g_settings.motionDistRates[g_settings.motionNum] === 1 || !g_workObj.motionReverseFlg)
|
|
347
|
+
? 0
|
|
348
|
+
: (
|
|
349
|
+
g_stateObj.speed * g_headerObj.baseSpeed - g_settings.motionBoostFactorMinSpd > 0 && maxBoost > 1
|
|
350
|
+
? Math.min(maxBoost / 4, 1) : 0
|
|
351
|
+
)
|
|
352
|
+
);
|
|
353
|
+
g_scoreObj.distY = Math.max(
|
|
354
|
+
g_headerObj.stretchYRate[g_stateObj.scoreId], g_settings.motionDistRates[g_settings.motionNum] * boostFactor
|
|
355
|
+
) * g_posObj.distY;
|
|
356
|
+
|
|
357
|
+
// 最終フレーム数の取得
|
|
358
|
+
let lastFrame = getLastFrame(g_scoreObj) + g_headerObj.blankFrame;
|
|
359
|
+
|
|
360
|
+
// 最初の矢印データがあるフレーム数を取得
|
|
361
|
+
let firstArrowFrame = getFirstArrowFrame(g_scoreObj);
|
|
362
|
+
|
|
363
|
+
// 開始フレーム数の取得(フェードイン加味)
|
|
364
|
+
g_scoreObj.frameNum = getStartFrame(lastFrame, g_stateObj.fadein);
|
|
365
|
+
|
|
366
|
+
// フレームごとの速度を取得(配列形式)
|
|
367
|
+
let speedOnFrame = setSpeedOnFrame(g_scoreObj.speedData, lastFrame);
|
|
368
|
+
|
|
369
|
+
// 最初のフレームで出現する矢印が、ステップゾーンに到達するまでのフレーム数を取得
|
|
370
|
+
const firstFrame = (g_scoreObj.frameNum === 0 ? 0 : g_scoreObj.frameNum + g_headerObj.blankFrame);
|
|
371
|
+
let arrivalFrame = getFirstArrivalFrame(firstFrame, speedOnFrame);
|
|
372
|
+
|
|
373
|
+
// キーパターン(デフォルト)に対応する矢印番号を格納
|
|
374
|
+
convertReplaceNums();
|
|
375
|
+
|
|
376
|
+
const setData = (_data, _minLength = 1) => hasArrayList(_data, _minLength) ? _data.concat() : [];
|
|
377
|
+
|
|
378
|
+
// フレーム・曲開始位置調整
|
|
379
|
+
let preblankFrame = 0;
|
|
380
|
+
if (g_scoreObj.frameNum === 0) {
|
|
381
|
+
if (firstArrowFrame - g_limitObj.adjustment < arrivalFrame) {
|
|
382
|
+
preblankFrame = arrivalFrame - firstArrowFrame + g_limitObj.adjustment;
|
|
383
|
+
|
|
384
|
+
// 譜面データの再読み込み
|
|
385
|
+
const noteExistObj = {
|
|
386
|
+
arrow: true,
|
|
387
|
+
frz: true,
|
|
388
|
+
dummyArrow: g_stateObj.shuffle === C_FLG_OFF,
|
|
389
|
+
dummyFrz: g_stateObj.shuffle === C_FLG_OFF,
|
|
390
|
+
};
|
|
391
|
+
const tmpObj = scoreConvert(g_rootObj, g_stateObj.scoreId, preblankFrame, dummyIdHeader);
|
|
392
|
+
for (let j = 0; j < keyNum; j++) {
|
|
393
|
+
Object.keys(noteExistObj).filter(name => tmpObj[`${name}Data`][j] !== undefined && noteExistObj[name])
|
|
394
|
+
.forEach(name => g_scoreObj[`${name}Data`][j] = structuredClone(tmpObj[`${name}Data`][j]));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
Object.keys(g_dataMinObj).forEach(dataType =>
|
|
398
|
+
g_scoreObj[`${dataType}Data`] = setData(tmpObj[`${dataType}Data`], g_dataMinObj[dataType]));
|
|
399
|
+
|
|
400
|
+
lastFrame += preblankFrame;
|
|
401
|
+
firstArrowFrame += preblankFrame;
|
|
402
|
+
speedOnFrame = setSpeedOnFrame(g_scoreObj.speedData, lastFrame);
|
|
403
|
+
arrivalFrame = getFirstArrivalFrame(firstFrame, speedOnFrame);
|
|
404
|
+
g_headerObj.blankFrame += preblankFrame;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
g_scoreObj.baseFrame = g_scoreObj.frameNum - g_stateObj.intAdjustment;
|
|
408
|
+
|
|
409
|
+
// シャッフルグループ未定義の場合
|
|
410
|
+
if (g_keyObj[`shuffle${keyCtrlPtn}`] === undefined) {
|
|
411
|
+
g_keyObj[`shuffle${keyCtrlPtn}`] = fillArray(keyNum);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// シャッフルグループを扱いやすくする
|
|
415
|
+
// [0, 0, 0, 1, 0, 0, 0] -> [[0, 1, 2, 4, 5, 6], [3]]
|
|
416
|
+
g_workObj.shuffleGroupMap = {};
|
|
417
|
+
g_keyObj[`shuffle${keyCtrlPtn}`].forEach((_val, _i) =>
|
|
418
|
+
g_workObj.shuffleGroupMap[_val]?.push(_i) || (g_workObj.shuffleGroupMap[_val] = [_i]));
|
|
419
|
+
|
|
420
|
+
// Mirror,Random,S-Randomの適用
|
|
421
|
+
g_shuffleFunc.get(g_stateObj.shuffle)(keyNum, Object.values(g_workObj.shuffleGroupMap));
|
|
422
|
+
|
|
423
|
+
// アシスト用の配列があれば、ダミーデータで上書き
|
|
424
|
+
if (typeof g_keyObj[`assistPos${keyCtrlPtn}`] === C_TYP_OBJECT &&
|
|
425
|
+
!g_autoPlaysBase.includes(g_stateObj.autoPlay)) {
|
|
426
|
+
const assistArray = g_keyObj[`assistPos${keyCtrlPtn}`][g_stateObj.autoPlay];
|
|
427
|
+
for (let j = 0; j < keyNum; j++) {
|
|
428
|
+
if (assistArray[j] === 1) {
|
|
429
|
+
g_scoreObj.dummyArrowData[j] = g_scoreObj.arrowData[j].concat();
|
|
430
|
+
g_scoreObj.arrowData[j] = [];
|
|
431
|
+
g_scoreObj.dummyFrzData[j] = g_scoreObj.frzData[j].concat();
|
|
432
|
+
g_scoreObj.frzData[j] = [];
|
|
433
|
+
} else {
|
|
434
|
+
g_scoreObj.dummyArrowData[j] = [];
|
|
435
|
+
g_scoreObj.dummyFrzData[j] = [];
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 矢印・フリーズアロー数をカウント
|
|
441
|
+
g_allArrow = 0;
|
|
442
|
+
g_allFrz = 0;
|
|
443
|
+
g_scoreObj.arrowData.forEach(data => g_allArrow += data.length);
|
|
444
|
+
g_scoreObj.frzData.forEach(data => g_allFrz += Math.floor(data.length / 2) * 2);
|
|
445
|
+
|
|
446
|
+
// ライフ回復・ダメージ量の計算
|
|
447
|
+
// フリーズ始点でも通常判定させる場合は総矢印数を水増しする
|
|
448
|
+
if (g_headerObj.frzStartjdgUse) {
|
|
449
|
+
g_allArrow += g_allFrz / 2;
|
|
450
|
+
}
|
|
451
|
+
g_fullArrows = g_allArrow + g_allFrz / 2;
|
|
452
|
+
|
|
453
|
+
calcLifeVals(g_fullArrows);
|
|
454
|
+
|
|
455
|
+
// 矢印・フリーズアロー・速度/色変化格納処理
|
|
456
|
+
pushArrows(g_scoreObj, speedOnFrame, arrivalFrame);
|
|
457
|
+
|
|
458
|
+
// メインに入る前の最終初期化処理
|
|
459
|
+
getArrowSettings();
|
|
460
|
+
|
|
461
|
+
// ユーザカスタムイベント
|
|
462
|
+
safeExecuteCustomHooks(`g_customJsObj.loading`, g_customJsObj.loading);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* 譜面番号の取得
|
|
467
|
+
* @param {number} _scoreId
|
|
468
|
+
* @param {boolean} _scoreLockFlg
|
|
469
|
+
* @param {boolean} _useOne 1譜面目指定有無フラグ (初期色に関する箇所のみ指定)
|
|
470
|
+
* @returns {number|string}
|
|
471
|
+
*/
|
|
472
|
+
const setScoreIdHeader = (_scoreId = 0, _scoreLockFlg = false, _useOne = false) => {
|
|
473
|
+
if (!_scoreLockFlg && _scoreId > 0) {
|
|
474
|
+
return Number(_scoreId) + 1;
|
|
475
|
+
} else if (_scoreLockFlg && g_headerObj.scoreNos?.[_scoreId] > 1) {
|
|
476
|
+
return g_headerObj.scoreNos[_scoreId];
|
|
477
|
+
}
|
|
478
|
+
return _useOne ? 1 : ``;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* 譜面ファイル番号の取得
|
|
483
|
+
* @param {number} [_scoreId=0]
|
|
484
|
+
* @param {boolean} [_scoreLockFlg=false]
|
|
485
|
+
* @returns {number|string}
|
|
486
|
+
*/
|
|
487
|
+
const setDosIdHeader = (_scoreId = 0, _scoreLockFlg = false) => {
|
|
488
|
+
if (_scoreLockFlg && g_headerObj.dosNos?.[_scoreId] > 0) {
|
|
489
|
+
return g_headerObj.dosNos?.[_scoreId] > 1 ? g_headerObj.dosNos[_scoreId] : ``;
|
|
490
|
+
} else if (_scoreId > 0) {
|
|
491
|
+
return Number(_scoreId) + 1;
|
|
492
|
+
}
|
|
493
|
+
return ``;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Mirror,Randomの適用
|
|
498
|
+
* @param {number} _keyNum
|
|
499
|
+
* @param {number[][]} _shuffleGroup
|
|
500
|
+
* @param {number[][]} _style
|
|
501
|
+
*/
|
|
502
|
+
const applyShuffle = (_keyNum, _shuffleGroup, _style) => {
|
|
503
|
+
// 並べ替え用の配列を作成
|
|
504
|
+
// index[i]番目のキーの譜面がi番目のキーに流れるようになります
|
|
505
|
+
const index = [...Array(_keyNum).keys()];
|
|
506
|
+
for (let i = 0; i < _shuffleGroup.length; i++) {
|
|
507
|
+
for (let j = 0; j < _shuffleGroup[i].length; j++) {
|
|
508
|
+
index[_shuffleGroup[i][j]] = _style[i][j];
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// indexに従って並べ替え
|
|
513
|
+
g_typeLists.arrow.forEach(type => {
|
|
514
|
+
const tmpData = structuredClone(g_scoreObj[`${type}Data`]);
|
|
515
|
+
for (let i = 0; i < _keyNum; i++) {
|
|
516
|
+
g_scoreObj[`${type}Data`][i] = tmpData[index[i]] || [];
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* X-Mirror作成用の入れ替え関数
|
|
523
|
+
* グループが4の倍数のとき、4n+1, 4n+2のみ入れ替える
|
|
524
|
+
* @param {number[][]} _style
|
|
525
|
+
* @param {number[]} _group
|
|
526
|
+
* @param {number} _i
|
|
527
|
+
* @param {number} _divideNum
|
|
528
|
+
* @returns {number[][]}
|
|
529
|
+
*/
|
|
530
|
+
const swapGroupNums = (_style, _group, _i, _divideNum) => {
|
|
531
|
+
if (_group.length % _divideNum === 0) {
|
|
532
|
+
for (let k = 0; k < _group.length / _divideNum; k++) {
|
|
533
|
+
const swap1 = Math.floor(_divideNum * (k + 1 / 2) - 1);
|
|
534
|
+
const swap2 = Math.ceil(_divideNum * (k + 1 / 2));
|
|
535
|
+
[_style[_i][swap1], _style[_i][swap2]] = [_style[_i][swap2], _style[_i][swap1]];
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return _style;
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Mirrorの適用
|
|
543
|
+
* @param {number} _keyNum
|
|
544
|
+
* @param {number[][]} _shuffleGroup
|
|
545
|
+
* @param {boolean} [_swapFlg=false]
|
|
546
|
+
*/
|
|
547
|
+
const applyMirror = (_keyNum, _shuffleGroup, _swapFlg = false) => {
|
|
548
|
+
|
|
549
|
+
// シャッフルグループごとにミラー
|
|
550
|
+
const style = structuredClone(_shuffleGroup).map(_group => _group.reverse());
|
|
551
|
+
const mirStyle = structuredClone(style);
|
|
552
|
+
|
|
553
|
+
if (_swapFlg) {
|
|
554
|
+
style.forEach((group, i) => {
|
|
555
|
+
g_settings.swapPattern.forEach(val => {
|
|
556
|
+
swapGroupNums(style, group, i, val);
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
let swapUseFlg = false;
|
|
560
|
+
style.forEach((_group, j) => {
|
|
561
|
+
_group.forEach((val, k) => {
|
|
562
|
+
if (style[j][k] !== mirStyle[j][k]) {
|
|
563
|
+
swapUseFlg = true;
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
if (!swapUseFlg) {
|
|
569
|
+
g_stateObj.shuffle = `Mirror`;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
applyShuffle(_keyNum, _shuffleGroup, style);
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Turningの適用
|
|
577
|
+
* @param {number} _keyNum
|
|
578
|
+
* @param {number[][]} _shuffleGroup
|
|
579
|
+
*/
|
|
580
|
+
const applyTurning = (_keyNum, _shuffleGroup) => {
|
|
581
|
+
const mirrorOrNot = _array => Math.random() >= 0.5 ? _array.reverse() : _array;
|
|
582
|
+
const style = structuredClone(_shuffleGroup).map(_group => {
|
|
583
|
+
const startNum = Math.floor(Math.random() * (_group.length - 1)) + 1;
|
|
584
|
+
return mirrorOrNot(makeDedupliArray(_group.slice(startNum), _group.slice(0, startNum)));
|
|
585
|
+
});
|
|
586
|
+
applyShuffle(_keyNum, _shuffleGroup, style);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Randomの適用
|
|
591
|
+
* @param {number} _keyNum
|
|
592
|
+
* @param {number[][]} _shuffleGroup
|
|
593
|
+
*/
|
|
594
|
+
const applyRandom = (_keyNum, _shuffleGroup) => {
|
|
595
|
+
// シャッフルグループごとにシャッフル(Fisher-Yates)
|
|
596
|
+
const style = structuredClone(_shuffleGroup).map(_group => {
|
|
597
|
+
for (let i = _group.length - 1; i > 0; i--) {
|
|
598
|
+
const random = Math.floor(Math.random() * (i + 1));
|
|
599
|
+
[_group[i], _group[random]] = [_group[random], _group[i]];
|
|
600
|
+
}
|
|
601
|
+
return _group;
|
|
602
|
+
});
|
|
603
|
+
applyShuffle(_keyNum, _shuffleGroup, style);
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* S-Randomの適用
|
|
608
|
+
* @param {number} _keyNum
|
|
609
|
+
* @param {number[][]} _shuffleGroup
|
|
610
|
+
* @param {string} _arrowHeader
|
|
611
|
+
* @param {string} _frzHeader
|
|
612
|
+
*/
|
|
613
|
+
const applySRandom = (_keyNum, _shuffleGroup, _arrowHeader, _frzHeader) => {
|
|
614
|
+
|
|
615
|
+
const tmpArrowData = [...Array(_keyNum)].map(() => []);
|
|
616
|
+
const tmpFrzData = [...Array(_keyNum)].map(() => []);
|
|
617
|
+
const scatterFrame = 10;
|
|
618
|
+
|
|
619
|
+
// シャッフルグループごとに処理
|
|
620
|
+
_shuffleGroup.forEach(_group => {
|
|
621
|
+
// 全フリーズを開始フレーム順に並べる
|
|
622
|
+
const allFreezeArrows = [];
|
|
623
|
+
_group.forEach(_key => {
|
|
624
|
+
const frzData = g_scoreObj[`${_frzHeader}Data`][_key] || [];
|
|
625
|
+
for (let i = 0; i < frzData.length; i += 2) {
|
|
626
|
+
allFreezeArrows.push({ begin: frzData[i], end: frzData[i + 1] });
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
allFreezeArrows.sort((_a, _b) => _a.begin - _b.begin);
|
|
630
|
+
|
|
631
|
+
// 重ならないようにフリーズを配置
|
|
632
|
+
allFreezeArrows.forEach(_freeze => {
|
|
633
|
+
// 置ける場所を検索
|
|
634
|
+
const freeSpacesFlat = _group.filter(
|
|
635
|
+
_key => tmpFrzData[_key].find(_other => _freeze.begin - scatterFrame <= _other.end + scatterFrame) === undefined
|
|
636
|
+
);
|
|
637
|
+
const freeSpaces = _group.filter(
|
|
638
|
+
_key => tmpFrzData[_key].find(_other => _freeze.begin <= _other.end) === undefined
|
|
639
|
+
);
|
|
640
|
+
let currentFreeSpaces = freeSpaces;
|
|
641
|
+
if (g_stateObj.shuffle.startsWith(`Scatter`)) {
|
|
642
|
+
currentFreeSpaces = freeSpacesFlat.length > 0 ? freeSpacesFlat : freeSpaces;
|
|
643
|
+
}
|
|
644
|
+
// ランダムに配置
|
|
645
|
+
const random = Math.floor(Math.random() * currentFreeSpaces.length);
|
|
646
|
+
tmpFrzData[currentFreeSpaces[random]].push(_freeze);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// 通常矢印の配置
|
|
650
|
+
const allArrows = _group.map(_key => g_scoreObj[`${_arrowHeader}Data`][_key]).flat();
|
|
651
|
+
allArrows.sort((_a, _b) => _a - _b);
|
|
652
|
+
let prev2Num = 0, prevNum = 0, sameFlg = true;
|
|
653
|
+
allArrows.forEach(_arrow => {
|
|
654
|
+
|
|
655
|
+
// 直前の矢印のフレーム数を取得
|
|
656
|
+
sameFlg = true;
|
|
657
|
+
if (prev2Num !== _arrow && prevNum !== _arrow) {
|
|
658
|
+
prev2Num = prevNum;
|
|
659
|
+
prevNum = _arrow;
|
|
660
|
+
sameFlg = false;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const getFreeSpaces = ({ scatterFrame = 0, frzFlg = false, prevFlg = false } = {}) =>
|
|
664
|
+
_group.filter(_key =>
|
|
665
|
+
// 通常矢印と重ならない
|
|
666
|
+
tmpArrowData[_key].find(_other => _arrow >= _other - scatterFrame && _arrow <= _other + scatterFrame) === undefined
|
|
667
|
+
// フリーズと重ならない
|
|
668
|
+
&& (!frzFlg || tmpFrzData[_key].find(_freeze => _arrow >= _freeze.begin - scatterFrame && _arrow <= _freeze.end + scatterFrame) === undefined)
|
|
669
|
+
// 直前の矢印と重ならない
|
|
670
|
+
&& (!prevFlg || tmpArrowData[_key].find(_other => prev2Num === _other) === undefined)
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
// 置ける場所を検索
|
|
674
|
+
const freeSpacesFlat = getFreeSpaces({ scatterFrame, frzFlg: true, prevFlg: true });
|
|
675
|
+
const freeSpaces = getFreeSpaces({ frzFlg: true });
|
|
676
|
+
const freeSpacesAlt = getFreeSpaces();
|
|
677
|
+
|
|
678
|
+
// ランダムに配置
|
|
679
|
+
let currentFreeSpaces = freeSpaces.length > 0 ? freeSpaces : freeSpacesAlt;
|
|
680
|
+
if (g_stateObj.shuffle.startsWith(`Scatter`)) {
|
|
681
|
+
currentFreeSpaces = freeSpacesFlat.length > 0 && !sameFlg ? freeSpacesFlat : currentFreeSpaces;
|
|
682
|
+
}
|
|
683
|
+
const random = Math.floor(Math.random() * currentFreeSpaces.length);
|
|
684
|
+
tmpArrowData[currentFreeSpaces[random]].push(_arrow);
|
|
685
|
+
})
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
g_scoreObj[`${_arrowHeader}Data`] = tmpArrowData;
|
|
689
|
+
g_scoreObj[`${_frzHeader}Data`] = tmpFrzData.map(_freezes =>
|
|
690
|
+
_freezes.map(_freeze => [_freeze.begin, _freeze.end]).flat()
|
|
691
|
+
);
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* 譜面データの分解
|
|
696
|
+
* @param {object} _dosObj
|
|
697
|
+
* @param {number} _scoreId 譜面番号
|
|
698
|
+
* @param {number} _preblankFrame 補完フレーム数
|
|
699
|
+
* @param {string} [_dummyNo] ダミー用譜面番号添え字
|
|
700
|
+
* @param {string} [_keyCtrlPtn] 選択キー及びパターン
|
|
701
|
+
* @param {boolean} [_scoreAnalyzeFlg=false] 譜面詳細データのために必要分で読込を中断
|
|
702
|
+
* @returns {object} 矢印・フリーズアロー・速度/色変化データの格納オブジェクト
|
|
703
|
+
*/
|
|
704
|
+
const scoreConvert = (_dosObj, _scoreId, _preblankFrame, _dummyNo = ``,
|
|
705
|
+
_keyCtrlPtn = `${g_keyObj.currentKey}_${g_keyObj.currentPtn}`, _scoreAnalyzeFlg = false) => {
|
|
706
|
+
|
|
707
|
+
// 矢印群の格納先
|
|
708
|
+
const obj = {};
|
|
709
|
+
|
|
710
|
+
const scoreIdHeader = setScoreIdHeader(_scoreId, g_stateObj.scoreLockFlg, false);
|
|
711
|
+
const keyNum = g_keyObj[`${g_keyObj.defaultProp}${_keyCtrlPtn}`].length;
|
|
712
|
+
obj.arrowData = [];
|
|
713
|
+
obj.frzData = [];
|
|
714
|
+
obj.dummyArrowData = [];
|
|
715
|
+
obj.dummyFrzData = [];
|
|
716
|
+
|
|
717
|
+
// realAdjustment: 全体, intAdjustment: 整数値のみ(切り捨て、ファイル時のみ四捨五入), decimalAdjustment: 小数値のみ
|
|
718
|
+
const headerAdjustment = parseFloat(g_headerObj.adjustment[g_stateObj.scoreId] || g_headerObj.adjustment[0]);
|
|
719
|
+
g_stateObj.realAdjustment = (parseFloat(g_stateObj.adjustment) + headerAdjustment) / g_headerObj.playbackRate + _preblankFrame;
|
|
720
|
+
g_stateObj.intAdjustment = isLocalMusicFile(g_stateObj.scoreId) ?
|
|
721
|
+
Math.round(g_stateObj.realAdjustment) : Math.floor(g_stateObj.realAdjustment);
|
|
722
|
+
g_stateObj.decimalAdjustment = g_stateObj.realAdjustment - g_stateObj.intAdjustment;
|
|
723
|
+
|
|
724
|
+
const blankFrame = g_headerObj.blankFrame;
|
|
725
|
+
const calcFrame = _frame => Math.round((parseFloat(_frame) - blankFrame) / g_headerObj.playbackRate + blankFrame + g_stateObj.intAdjustment);
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* 矢印データの格納
|
|
729
|
+
* @param {string} _data
|
|
730
|
+
* @returns {number[]}
|
|
731
|
+
*/
|
|
732
|
+
const storeArrowData = _data => hasVal(_data) ?
|
|
733
|
+
splitLF(_data)?.join(``).split(`,`).filter(data => !isNaN(parseFloat(data))).map(data => calcFrame(data)).sort((_a, _b) => _a - _b) : [];
|
|
734
|
+
|
|
735
|
+
for (let j = 0; j < keyNum; j++) {
|
|
736
|
+
|
|
737
|
+
// 矢印データの分解
|
|
738
|
+
const arrowName = g_keyObj[`chara${_keyCtrlPtn}`][j];
|
|
739
|
+
obj.arrowData[j] = storeArrowData(_dosObj[`${arrowName}${scoreIdHeader}_data`]);
|
|
740
|
+
|
|
741
|
+
if (g_stateObj.dummyId !== ``) {
|
|
742
|
+
obj.dummyArrowData[j] = storeArrowData(_dosObj[`${arrowName}${_dummyNo}_data`]);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// 矢印名からフリーズアロー名への変換
|
|
746
|
+
let frzName = replaceStr(g_keyObj[`chara${_keyCtrlPtn}`][j], g_escapeStr.frzName);
|
|
747
|
+
if (frzName.indexOf(`frz`) === -1 && frzName.indexOf(`foni`) === -1) {
|
|
748
|
+
frzName = frzName.replaceAll(frzName, `frz${toCapitalize(frzName)}`);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// フリーズアローデータの分解 (2つで1セット)
|
|
752
|
+
obj.frzData[j] = storeArrowData(_dosObj[`${frzName}${scoreIdHeader}_data`]);
|
|
753
|
+
|
|
754
|
+
if (g_stateObj.dummyId !== ``) {
|
|
755
|
+
obj.dummyFrzData[j] = storeArrowData(_dosObj[`${frzName}${_dummyNo}_data`]);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* 速度変化データの分解・格納(フレーム数, 矢印番号)
|
|
761
|
+
* @param {string} _header
|
|
762
|
+
* @param {number} _scoreNo
|
|
763
|
+
* @param {string} _footer
|
|
764
|
+
* @returns {number[]}
|
|
765
|
+
*/
|
|
766
|
+
const setSpeedData = (_header, _scoreNo, _footer = `_data`) => {
|
|
767
|
+
const dosSpeedData = getRefData(_header, `${_scoreNo}${_footer}`);
|
|
768
|
+
const speedData = [];
|
|
769
|
+
|
|
770
|
+
if (hasVal(dosSpeedData) && g_stateObj.d_velocity !== C_FLG_OFF) {
|
|
771
|
+
const tmpArrayData = splitLF(dosSpeedData);
|
|
772
|
+
|
|
773
|
+
tmpArrayData.filter(data => hasVal(data)).forEach(tmpData => {
|
|
774
|
+
const tmpSpeedData = tmpData.split(`,`);
|
|
775
|
+
for (let k = 0; k < tmpSpeedData.length; k += 2) {
|
|
776
|
+
if (!hasVal(tmpSpeedData[k])) {
|
|
777
|
+
continue;
|
|
778
|
+
} else if (tmpSpeedData[k + 1] === `-`) {
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
const frame = calcFrame(setVal(tmpSpeedData[k], ``, C_TYP_CALC));
|
|
782
|
+
const speed = setVal(tmpSpeedData[k + 1], 1, C_TYP_CALC);
|
|
783
|
+
|
|
784
|
+
speedData.push([frame, speed]);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
return speedData.sort((_a, _b) => _a[0] - _b[0]).flat();
|
|
788
|
+
}
|
|
789
|
+
return [];
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* 個別・全体色変化データをマージして整列し、単純配列として返却
|
|
794
|
+
* @param {string} _header
|
|
795
|
+
* @returns {any[]}
|
|
796
|
+
*/
|
|
797
|
+
const mergeColorData = (_header = ``) => {
|
|
798
|
+
if (obj[`color${_header}Data`] === undefined) return [];
|
|
799
|
+
const tmpArr = obj[`color${_header}Data`].concat(obj[`acolor${_header}Data`]);
|
|
800
|
+
delete obj[`acolor${_header}Data`];
|
|
801
|
+
return tmpArr.sort((_a, _b) => _a[0] - _b[0]).flat();
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* 色変化データの分解・格納(フレーム数, 矢印番号)
|
|
806
|
+
* 後で個別・全体色変化をマージするため、二次元配列として返却
|
|
807
|
+
* @param {string} _header
|
|
808
|
+
* @param {number} _scoreNo
|
|
809
|
+
* @returns {any[][]}
|
|
810
|
+
*/
|
|
811
|
+
const setColorData = (_header, _scoreNo) => {
|
|
812
|
+
const dosColorData = getRefData(_header, `${_scoreNo}_data`);
|
|
813
|
+
const colorData = [];
|
|
814
|
+
const allFlg = (_header.charAt(0) === `a`);
|
|
815
|
+
|
|
816
|
+
if (hasVal(dosColorData) && g_stateObj.d_color === C_FLG_ON) {
|
|
817
|
+
const tmpArrayData = splitLF(dosColorData);
|
|
818
|
+
|
|
819
|
+
tmpArrayData.filter(data => hasVal(data)).forEach(tmpData => {
|
|
820
|
+
const tmpColorData = tmpData.split(`,`);
|
|
821
|
+
for (let k = 0; k < tmpColorData.length; k += 3) {
|
|
822
|
+
if (!hasVal(tmpColorData[k])) {
|
|
823
|
+
continue;
|
|
824
|
+
} else if (tmpColorData[k + 1] === `-`) {
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
const frame = calcFrame(setVal(tmpColorData[k], ``, C_TYP_CALC));
|
|
828
|
+
const colorNum = setVal(tmpColorData[k + 1], 0, C_TYP_CALC);
|
|
829
|
+
const colorCd = tmpColorData[k + 2];
|
|
830
|
+
|
|
831
|
+
// フレーム数、色番号、カラーコード、全体色変化フラグをセットとして配列化
|
|
832
|
+
colorData.push([frame, colorNum, colorCd, allFlg]);
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
return colorData.sort((_a, _b) => _a[0] - _b[0]);
|
|
836
|
+
}
|
|
837
|
+
return [];
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* 色変化データの分解・格納(新形式)
|
|
842
|
+
* - 個別・全体色変化を一体で管理するため通常の配列で返却
|
|
843
|
+
* @param {string} _header
|
|
844
|
+
* @param {number} _scoreNo
|
|
845
|
+
* @returns {any[][]}
|
|
846
|
+
*/
|
|
847
|
+
const setColor2Data = (_header, _scoreNo) => {
|
|
848
|
+
const dosColorData = getRefData(_header, `${_scoreNo}_data`);
|
|
849
|
+
const colorData = {
|
|
850
|
+
Arrow: [], ArrowShadow: [], Normal: [], NormalBar: [], NormalShadow: [],
|
|
851
|
+
Hit: [], HitBar: [], HitShadow: [],
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
if (hasVal(dosColorData) && g_stateObj.d_color === C_FLG_ON) {
|
|
855
|
+
|
|
856
|
+
splitLF(dosColorData).filter(data => hasVal(data)).forEach(tmpData => {
|
|
857
|
+
const tmpColorData = tmpData.split(`,`).map(val => trimStr(val));
|
|
858
|
+
if (!hasVal(tmpColorData[0]) || tmpColorData[1] === `-`) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const frame = calcFrame(setVal(tmpColorData[0], ``, C_TYP_CALC));
|
|
862
|
+
const colorCd = tmpColorData[2];
|
|
863
|
+
|
|
864
|
+
// 色変化対象の取得
|
|
865
|
+
const pos = tmpColorData[1]?.indexOf(`:`);
|
|
866
|
+
const patternStr = pos > 0 ? [trimStr(tmpColorData[1].substring(0, pos)), trimStr(tmpColorData[1].substring(pos + 1))]
|
|
867
|
+
: [tmpColorData[1]];
|
|
868
|
+
const patterns = replaceStr(trimStr(patternStr[1] || `Arrow`), g_escapeStr.colorPatternName).split(`/`);
|
|
869
|
+
|
|
870
|
+
// 矢印番号の組み立て
|
|
871
|
+
const colorVals = [];
|
|
872
|
+
replaceStr(patternStr[0], g_escapeStr.targetPatternName)?.split(`/`)?.forEach(val => {
|
|
873
|
+
if (val.indexOf(`...`) > 0) {
|
|
874
|
+
// 範囲指定表記の補完 例. 0...3 -> 0/1/2/3
|
|
875
|
+
const [valMin, valMax] = [val.split(`...`)[0], val.split(`...`)[1]].map(val => setIntVal(val));
|
|
876
|
+
for (let k = valMin; k <= valMax; k++) {
|
|
877
|
+
colorVals.push(String(k));
|
|
878
|
+
}
|
|
879
|
+
} else {
|
|
880
|
+
colorVals.push(val);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
// フレーム数、色番号、カラーコード、全体色変化フラグ、変更対象をセットとして配列化し、色変化対象ごとのプロパティへ追加
|
|
885
|
+
patterns.forEach(pattern => {
|
|
886
|
+
try {
|
|
887
|
+
colorVals.forEach(val => colorData[pattern].push([frame, val, colorCd, hasVal(tmpColorData[3]), pattern]));
|
|
888
|
+
} catch (error) {
|
|
889
|
+
makeWarningWindow(g_msgInfoObj.E_0201.split(`{0}`).join(pattern));
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
// 色変化対象ごとにフレーム数をキーにソートしてフラット化
|
|
894
|
+
Object.keys(colorData).forEach(pattern =>
|
|
895
|
+
colorData[pattern] = colorData[pattern].sort((_a, _b) => _a[0] - _b[0]).flat());
|
|
896
|
+
}
|
|
897
|
+
return colorData;
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* 矢印モーションデータの分解・格納(フレーム数, 矢印番号)
|
|
902
|
+
* @param {string} _header
|
|
903
|
+
* @param {number} _scoreNo
|
|
904
|
+
* @returns {any[]}
|
|
905
|
+
*/
|
|
906
|
+
const setCssMotionData = (_header, _scoreNo) => {
|
|
907
|
+
const dosCssMotionData = getRefData(`${_header}Motion`, `${_scoreNo}_data`) || _dosObj[`${_header}Motion_data`];
|
|
908
|
+
const cssMotionData = [];
|
|
909
|
+
|
|
910
|
+
if (hasVal(dosCssMotionData) && g_stateObj.d_arroweffect === C_FLG_ON) {
|
|
911
|
+
splitLF(dosCssMotionData).filter(data => hasVal(data)).forEach(tmpData => {
|
|
912
|
+
const tmpcssMotionData = tmpData.split(`,`);
|
|
913
|
+
if (isNaN(parseInt(tmpcssMotionData[0]))) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const frame = calcFrame(tmpcssMotionData[0]);
|
|
917
|
+
const arrowNum = parseFloat(tmpcssMotionData[1]);
|
|
918
|
+
const styleUp = (tmpcssMotionData[2] === `none` ? `` : tmpcssMotionData[2]);
|
|
919
|
+
const styleDown = (tmpcssMotionData[3] === `none` ? `` : setVal(tmpcssMotionData[3], styleUp));
|
|
920
|
+
const movLock = setBoolVal(tmpcssMotionData[4] === `movLock`, false);
|
|
921
|
+
const initManual = setBoolVal(tmpcssMotionData[5] === `initManual`, false);
|
|
922
|
+
|
|
923
|
+
cssMotionData.push([frame, arrowNum, styleUp, styleDown, movLock, initManual]);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// 個別のモーションデータが存在する場合、Effect設定を自動リセット
|
|
927
|
+
if (cssMotionData.length > 0) {
|
|
928
|
+
g_stateObj.effect = C_FLG_OFF;
|
|
929
|
+
g_settings.effectNum = 0;
|
|
930
|
+
}
|
|
931
|
+
return cssMotionData.sort((_a, _b) => _a[0] - _b[0]).flat();
|
|
932
|
+
}
|
|
933
|
+
return [];
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* スクロール変化データの分解
|
|
938
|
+
* @param {number} _scoreNo
|
|
939
|
+
* @returns {number[]}
|
|
940
|
+
*/
|
|
941
|
+
const setScrollchData = (_scoreNo) => {
|
|
942
|
+
const dosScrollchData = getRefData(`scrollch`, `${_scoreNo}_data`) || _dosObj.scrollch_data;
|
|
943
|
+
const scrollchData = [];
|
|
944
|
+
let maxLayerGroup = 0;
|
|
945
|
+
|
|
946
|
+
if (hasVal(dosScrollchData)) {
|
|
947
|
+
splitLF(dosScrollchData).filter(data => hasVal(data)).forEach(tmpData => {
|
|
948
|
+
const tmpScrollchData = tmpData.split(`,`);
|
|
949
|
+
if (isNaN(parseInt(tmpScrollchData[0]))) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const frame = calcFrame(tmpScrollchData[0]);
|
|
953
|
+
const arrowNum = parseFloat(tmpScrollchData[1]);
|
|
954
|
+
const scrollDir = parseFloat(tmpScrollchData[2] ?? `1`);
|
|
955
|
+
const layerGroup = parseFloat(tmpScrollchData[3] ?? `-1`);
|
|
956
|
+
let layerTrans = tmpScrollchData[4] ?? ``;
|
|
957
|
+
maxLayerGroup = Math.max(maxLayerGroup, layerGroup);
|
|
958
|
+
if (g_stateObj.reverse === C_FLG_ON) {
|
|
959
|
+
layerTrans = invertSpecificTransforms(layerTrans, [`translateX`]);
|
|
960
|
+
}
|
|
961
|
+
if (g_stateObj.swapping === `Mirror+`) {
|
|
962
|
+
layerTrans = invertSpecificTransforms(layerTrans, [`translateY`]);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
scrollchData.push([frame, arrowNum, frame, scrollDir, layerGroup, layerTrans]);
|
|
966
|
+
});
|
|
967
|
+
g_stateObj.layerNumDf = Math.max((maxLayerGroup + 1) * 2, 2);
|
|
968
|
+
return scrollchData.sort((_a, _b) => _a[0] - _b[0]).flat();
|
|
969
|
+
}
|
|
970
|
+
return [];
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* 譜面データに別の関連名が含まれていた場合、関連名の変数を返す
|
|
975
|
+
* 例) |backA2_data=back_data| -> back_dataで定義された値を使用
|
|
976
|
+
* @param {string} _header
|
|
977
|
+
* @param {string} _dataName
|
|
978
|
+
* @returns {string|undefined}
|
|
979
|
+
*/
|
|
980
|
+
const getRefData = (_header, _dataName) => {
|
|
981
|
+
const data = _dosObj[`${_header}${_dataName}`];
|
|
982
|
+
let dataStr = data;
|
|
983
|
+
splitLF(data)?.filter(val => val?.startsWith(_header) && _dosObj[val] !== undefined)
|
|
984
|
+
.forEach(val => dataStr = dataStr.replace(val, _dosObj[val]));
|
|
985
|
+
return dataStr;
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* 譜面データの優先順配列パターンの取得
|
|
990
|
+
* @param {string} _type
|
|
991
|
+
* @param {number} _scoreNo
|
|
992
|
+
* @returns {string[]}
|
|
993
|
+
*/
|
|
994
|
+
const getPriorityVal = (_type, _scoreNo) => [
|
|
995
|
+
`${_type}${g_localeObj.val}${_scoreNo}_data`,
|
|
996
|
+
`${_type}${g_localeObj.val}_data`,
|
|
997
|
+
`${_type}${_scoreNo}_data`,
|
|
998
|
+
`${_type}_data`
|
|
999
|
+
];
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* 歌詞表示、背景・マスクデータの優先順取得
|
|
1003
|
+
* @param {string[]} _defaultHeaders
|
|
1004
|
+
* @returns {string[]}
|
|
1005
|
+
*/
|
|
1006
|
+
const getPriorityHeader = (_defaultHeaders = []) => {
|
|
1007
|
+
if (_defaultHeaders.length > 0) {
|
|
1008
|
+
return makeDedupliArray(_defaultHeaders);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const list = [];
|
|
1012
|
+
const ptnName = `${(g_keyObj.currentPtn === -1 ? g_keyObj.storagePtn : g_keyObj.currentPtn) + 1}`;
|
|
1013
|
+
const keyName = setVal(g_keyObj[`transKey${_keyCtrlPtn}`], g_keyObj.currentKey);
|
|
1014
|
+
let type = ``;
|
|
1015
|
+
if (g_stateObj.scroll !== C_FLG_HYPHEN) {
|
|
1016
|
+
type = `Alt`;
|
|
1017
|
+
} else if (g_stateObj.reverse === C_FLG_ON) {
|
|
1018
|
+
type = `Rev`;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
[g_stateObj.scroll, type, ``].forEach(header => {
|
|
1022
|
+
list.push(`${header}[${ptnName}]`, `${header}<${keyName}>`);
|
|
1023
|
+
if (hasVal(g_keyObj[`transKey${_keyCtrlPtn}`])) {
|
|
1024
|
+
list.push(`${header}A`);
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
list.push(g_stateObj.scroll, type, ``);
|
|
1028
|
+
|
|
1029
|
+
return makeDedupliArray(list);
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* 歌詞データの分解
|
|
1034
|
+
* @param {string} _scoreNo
|
|
1035
|
+
* @returns {string[][]}
|
|
1036
|
+
*/
|
|
1037
|
+
const makeWordData = _scoreNo => {
|
|
1038
|
+
const wordDataList = [];
|
|
1039
|
+
const wordTargets = [];
|
|
1040
|
+
let wordReverseFlg = false;
|
|
1041
|
+
const divideCnt = getKeyInfo().divideCnt;
|
|
1042
|
+
const addDataList = (_type = ``) => wordTargets.push(...getPriorityVal(_type, _scoreNo));
|
|
1043
|
+
getPriorityHeader().forEach(val => addDataList(val));
|
|
1044
|
+
makeDedupliArray(wordTargets).forEach(val => wordDataList.push(getRefData(`word`, val)));
|
|
1045
|
+
|
|
1046
|
+
if (g_stateObj.reverse === C_FLG_ON) {
|
|
1047
|
+
let wordTarget = ``;
|
|
1048
|
+
for (let val of makeDedupliArray(wordTargets)) {
|
|
1049
|
+
if (getRefData(`word`, val) !== undefined) {
|
|
1050
|
+
wordTarget = val;
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// wordRev_dataが指定されている場合はそのままの位置を採用
|
|
1056
|
+
// word_dataのみ指定されている場合、下記ルールに従って設定
|
|
1057
|
+
if (!wordTarget.includes(`Rev`) && g_stateObj.scroll === C_FLG_HYPHEN) {
|
|
1058
|
+
// Reverse時の歌詞の自動反転制御設定
|
|
1059
|
+
if (g_headerObj.wordAutoReverse !== C_DIS_AUTO) {
|
|
1060
|
+
wordReverseFlg = g_headerObj.wordAutoReverse === C_FLG_ON;
|
|
1061
|
+
} else if (keyNum === divideCnt + 1) {
|
|
1062
|
+
wordReverseFlg = true;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const inputWordData = wordDataList.find((v) => v !== undefined);
|
|
1068
|
+
return (inputWordData !== undefined ? makeSpriteWordData(inputWordData, wordReverseFlg) : [[], -1]);
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* 多層歌詞データの格納処理
|
|
1073
|
+
* @param {object} _data
|
|
1074
|
+
* @param {boolean} _reverseFlg
|
|
1075
|
+
* @returns
|
|
1076
|
+
*/
|
|
1077
|
+
const makeSpriteWordData = (_data, _reverseFlg = false) => {
|
|
1078
|
+
const wordData = [];
|
|
1079
|
+
let wordMaxDepth = -1;
|
|
1080
|
+
let wordReverseFlg = _reverseFlg;
|
|
1081
|
+
const tmpArrayData = splitLF(_data);
|
|
1082
|
+
|
|
1083
|
+
if (g_headerObj.wordAutoReverse === C_DIS_AUTO) {
|
|
1084
|
+
tmpArrayData.filter(data => hasVal(data) && data?.indexOf(`<br>`) !== -1).forEach(() => wordReverseFlg = false);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
tmpArrayData.filter(data => hasVal(data)).forEach(tmpData => {
|
|
1088
|
+
const tmpWordData = tmpData.split(`,`).map(val => trimStr(val));
|
|
1089
|
+
for (let k = 0; k < tmpWordData.length; k += 3) {
|
|
1090
|
+
if (!hasVal(tmpWordData[k])) {
|
|
1091
|
+
continue;
|
|
1092
|
+
} else if (tmpWordData[k + 1] === `-`) {
|
|
1093
|
+
break;
|
|
1094
|
+
}
|
|
1095
|
+
tmpWordData[k] = calcFrame(setVal(tmpWordData[k], ``, C_TYP_CALC));
|
|
1096
|
+
tmpWordData[k + 1] = setVal(tmpWordData[k + 1], 0, C_TYP_CALC);
|
|
1097
|
+
tmpWordData[k + 1] = Math.floor(tmpWordData[k + 1] / 2) * 2 +
|
|
1098
|
+
(tmpWordData[k + 1] + Number(wordReverseFlg)) % 2;
|
|
1099
|
+
|
|
1100
|
+
if (tmpWordData[k + 1] > wordMaxDepth) {
|
|
1101
|
+
wordMaxDepth = tmpWordData[k + 1];
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// 同一行数で数字が取得できるまでは歌詞表示と見做して結合
|
|
1105
|
+
let m = 3;
|
|
1106
|
+
while (hasVal(tmpWordData[m]) && isNaN(parseInt(tmpWordData[m])) && m < tmpWordData.length) {
|
|
1107
|
+
tmpWordData[k + 2] += `,${tmpWordData[k + m]}`;
|
|
1108
|
+
tmpWordData.splice(k + m, 1);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// 歌詞表示データの格納
|
|
1112
|
+
let dataCnts = 0;
|
|
1113
|
+
[wordData[tmpWordData[k]], dataCnts] =
|
|
1114
|
+
checkDuplicatedObjects(wordData[tmpWordData[k]]);
|
|
1115
|
+
|
|
1116
|
+
if (tmpWordData.length > 3 && tmpWordData.length < 6) {
|
|
1117
|
+
tmpWordData[3] = setIntVal(tmpWordData[3], C_WOD_FRAME);
|
|
1118
|
+
wordData[tmpWordData[0]][dataCnts].push(tmpWordData[1],
|
|
1119
|
+
escapeHtmlForEnabledTag(tmpWordData[2]), tmpWordData[3]);
|
|
1120
|
+
break;
|
|
1121
|
+
} else {
|
|
1122
|
+
wordData[tmpWordData[k]][dataCnts].push(tmpWordData[k + 1],
|
|
1123
|
+
escapeHtmlForEnabledTag(tmpWordData[k + 2] ?? ``));
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
return [wordData, wordMaxDepth];
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* 背景・マスク、スキン変更データの分解
|
|
1133
|
+
* @param {string} _header
|
|
1134
|
+
* @param {string} _scoreNo 譜面番号
|
|
1135
|
+
* @param {string[]} [object.resultTypes] リザルトモーションの種類 (result, failedB, failedS)
|
|
1136
|
+
* @returns {any[][]}
|
|
1137
|
+
*/
|
|
1138
|
+
const makeBackgroundData = (_header, _scoreNo, { resultTypes = [] } = {}) => {
|
|
1139
|
+
const dataList = [];
|
|
1140
|
+
const animationTargets = [];
|
|
1141
|
+
const calcFrameFunc = resultTypes.length > 0 ? undefined : calcFrame;
|
|
1142
|
+
const addDataList = (_type = ``) => animationTargets.push(...getPriorityVal(_type, _scoreNo));
|
|
1143
|
+
getPriorityHeader(resultTypes).forEach(val => addDataList(val));
|
|
1144
|
+
makeDedupliArray(animationTargets).forEach(val => dataList.push(getRefData(_header, val)));
|
|
1145
|
+
|
|
1146
|
+
const data = dataList.find((v) => v !== undefined);
|
|
1147
|
+
return (data !== undefined ? g_animationFunc.make[_header](data, calcFrameFunc) : [[], -1]);
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// 速度変化データの分解 (2つで1セット)
|
|
1151
|
+
let speedFooter = ``;
|
|
1152
|
+
if (hasVal(_dosObj[`speed${scoreIdHeader}_data`])) {
|
|
1153
|
+
speedFooter = `_data`;
|
|
1154
|
+
}
|
|
1155
|
+
if (hasVal(_dosObj[`speed${scoreIdHeader}_change`])) {
|
|
1156
|
+
speedFooter = `_change`;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// 速度変化(個別・全体)の分解 (2つで1セット, セット毎の改行区切り可)
|
|
1160
|
+
obj.boostData = setSpeedData(`boost`, scoreIdHeader);
|
|
1161
|
+
obj.speedData = setSpeedData(`speed`, scoreIdHeader, speedFooter);
|
|
1162
|
+
|
|
1163
|
+
// 色変化(個別・全体)の分解 (3つで1セット, セット毎の改行区切り可)
|
|
1164
|
+
g_typeLists.color.forEach(sprite => {
|
|
1165
|
+
obj[`${sprite}Data`] = setColorData(sprite, scoreIdHeader);
|
|
1166
|
+
if (g_stateObj.dummyId !== ``) {
|
|
1167
|
+
obj[`${sprite}DummyData`] = setColorData(sprite, _dummyNo);
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
// 色変化(新形式)の分解(3~4つで1セット, セット毎の改行区切り)
|
|
1171
|
+
obj.ncolorData = setColor2Data(`ncolor`, scoreIdHeader);
|
|
1172
|
+
if (g_stateObj.dummyId !== ``) {
|
|
1173
|
+
obj.ncolorDummyData = setColor2Data(`ncolor`, _dummyNo);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
if (_scoreAnalyzeFlg) {
|
|
1177
|
+
return obj;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
obj.colorData = mergeColorData();
|
|
1181
|
+
obj.dummyColorData = mergeColorData(`Dummy`);
|
|
1182
|
+
|
|
1183
|
+
// レイヤー数の初期値設定(譜面ごとに設定のため)
|
|
1184
|
+
g_stateObj.layerNumDf = 2;
|
|
1185
|
+
|
|
1186
|
+
// 矢印モーション(個別)データの分解(3~4つで1セット, セット毎の改行区切り)
|
|
1187
|
+
obj.arrowCssMotionData = setCssMotionData(`arrow`, scoreIdHeader);
|
|
1188
|
+
obj.frzCssMotionData = setCssMotionData(`frz`, scoreIdHeader);
|
|
1189
|
+
if (g_stateObj.dummyId !== ``) {
|
|
1190
|
+
obj.dummyArrowCssMotionData = setCssMotionData(`arrow`, _dummyNo);
|
|
1191
|
+
obj.dummyFrzCssMotionData = setCssMotionData(`frz`, _dummyNo);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// スクロール変化データの分解
|
|
1195
|
+
obj.scrollchData = setScrollchData(scoreIdHeader);
|
|
1196
|
+
|
|
1197
|
+
// 歌詞データの分解 (3つで1セット, セット毎の改行区切り可)
|
|
1198
|
+
obj.wordData = [];
|
|
1199
|
+
obj.wordMaxDepth = -1;
|
|
1200
|
+
if (g_stateObj.d_background === C_FLG_OFF) {
|
|
1201
|
+
} else {
|
|
1202
|
+
[obj.wordData, obj.wordMaxDepth] = makeWordData(scoreIdHeader);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// 背景・マスク・スキン変更データの分解 (下記すべてで1セット、改行区切り)
|
|
1206
|
+
// - 背景・マスク: [フレーム数, 階層, 背景パス, class(CSSで別定義), X, Y, width, height, opacity, animationName, animationDuration, animationFillMode]
|
|
1207
|
+
// - スキン変更 : [フレーム数, CSSカスタムプロパティ名, 設定内容]
|
|
1208
|
+
g_animationData.forEach(sprite => {
|
|
1209
|
+
obj[`${sprite}Data`] = [];
|
|
1210
|
+
obj[`${sprite}MaxDepth`] = -1;
|
|
1211
|
+
|
|
1212
|
+
if (g_stateObj.d_background === C_FLG_OFF) {
|
|
1213
|
+
} else {
|
|
1214
|
+
[obj[`${sprite}Data`], obj[`${sprite}MaxDepth`]] = makeBackgroundData(sprite, scoreIdHeader);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (g_stateObj.d_background === C_FLG_OFF && g_headerObj.resultMotionSet) {
|
|
1218
|
+
[`Result`, `Failed`].forEach(backName => {
|
|
1219
|
+
g_headerObj[`${backName}Data`] = [];
|
|
1220
|
+
g_headerObj[`${backName}MaxDepth`] = -1;
|
|
1221
|
+
});
|
|
1222
|
+
} else {
|
|
1223
|
+
[g_headerObj[`${sprite}ResultData`], g_headerObj[`${sprite}ResultMaxDepth`]] =
|
|
1224
|
+
makeBackgroundData(sprite, scoreIdHeader, { resultTypes: [`result`] });
|
|
1225
|
+
[g_headerObj[`${sprite}FailedData`], g_headerObj[`${sprite}FailedMaxDepth`]] =
|
|
1226
|
+
makeBackgroundData(sprite, scoreIdHeader, { resultTypes: [`failed${g_stateObj.lifeMode.slice(0, 1)}`, `result`] });
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
// キー変化定義
|
|
1231
|
+
obj.keychFrames = [];
|
|
1232
|
+
obj.keychTarget = [];
|
|
1233
|
+
obj.keychTargetAlpha = [];
|
|
1234
|
+
if (hasVal(getRefData(`keych`, `${scoreIdHeader}_data`))) {
|
|
1235
|
+
const keychdata = splitLF2(getRefData(`keych`, `${scoreIdHeader}_data`), `,`);
|
|
1236
|
+
obj.keychFrames.push(...(keychdata.filter((val, j) => j % 2 === 0)).map(val => val === `0` ? 0 : calcFrame(val)));
|
|
1237
|
+
|
|
1238
|
+
keychdata.filter((val, j) => j % 2 === 1)?.forEach(targets => {
|
|
1239
|
+
const targetKeyList = [], targetKeyAlpha = [];
|
|
1240
|
+
targets?.split(`/`).forEach(target => {
|
|
1241
|
+
targetKeyList.push(trimStr(target?.split(`:`)[0]));
|
|
1242
|
+
targetKeyAlpha.push(trimStr(target?.split(`:`)[1]) || 1);
|
|
1243
|
+
})
|
|
1244
|
+
obj.keychTarget.push(targetKeyList);
|
|
1245
|
+
obj.keychTargetAlpha.push(targetKeyAlpha);
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
obj.keychFrames.unshift(0);
|
|
1249
|
+
obj.keychTarget.unshift([`0`]);
|
|
1250
|
+
obj.keychTargetAlpha.unshift([1]);
|
|
1251
|
+
|
|
1252
|
+
return obj;
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* ライフ回復量・ダメージ量の算出
|
|
1257
|
+
* @param {number} _allArrows
|
|
1258
|
+
*/
|
|
1259
|
+
const calcLifeVals = _allArrows => {
|
|
1260
|
+
|
|
1261
|
+
if (g_stateObj.lifeVariable === C_FLG_ON) {
|
|
1262
|
+
g_workObj.lifeRcv = calcLifeVal(g_stateObj.lifeRcv, _allArrows);
|
|
1263
|
+
g_workObj.lifeDmg = calcLifeVal(g_stateObj.lifeDmg, _allArrows);
|
|
1264
|
+
} else {
|
|
1265
|
+
g_workObj.lifeRcv = g_stateObj.lifeRcv;
|
|
1266
|
+
g_workObj.lifeDmg = g_stateObj.lifeDmg;
|
|
1267
|
+
}
|
|
1268
|
+
g_workObj.lifeBorder = g_headerObj.maxLifeVal * g_stateObj.lifeBorder / 100;
|
|
1269
|
+
g_workObj.lifeInit = g_headerObj.maxLifeVal * g_stateObj.lifeInit / 100;
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* ライフ回復量・ダメージ量の算出
|
|
1274
|
+
* @param {number} _val
|
|
1275
|
+
* @param {number} _allArrows
|
|
1276
|
+
* @returns {number}
|
|
1277
|
+
*/
|
|
1278
|
+
const calcLifeVal = (_val, _allArrows) => _val * g_headerObj.maxLifeVal / _allArrows;
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* 最終フレーム数の取得
|
|
1282
|
+
* @param {object} _dataObj
|
|
1283
|
+
* @param {string} [_keyCtrlPtn]
|
|
1284
|
+
* @returns {number}
|
|
1285
|
+
*/
|
|
1286
|
+
const getLastFrame = (_dataObj, _keyCtrlPtn = `${g_keyObj.currentKey}_${g_keyObj.currentPtn}`) => {
|
|
1287
|
+
|
|
1288
|
+
let tmpLastNum = 0;
|
|
1289
|
+
const keyNum = g_keyObj[`${g_keyObj.defaultProp}${_keyCtrlPtn}`].length;
|
|
1290
|
+
|
|
1291
|
+
for (let j = 0; j < keyNum; j++) {
|
|
1292
|
+
const data = [
|
|
1293
|
+
_dataObj.arrowData[j],
|
|
1294
|
+
_dataObj.frzData[j],
|
|
1295
|
+
_dataObj.dummyArrowData[j],
|
|
1296
|
+
_dataObj.dummyFrzData[j]
|
|
1297
|
+
];
|
|
1298
|
+
|
|
1299
|
+
data.filter(data => hasVal(data)).forEach(_objData => {
|
|
1300
|
+
if (_objData.at(-1) > tmpLastNum) {
|
|
1301
|
+
tmpLastNum = _objData.at(-1);
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
return tmpLastNum;
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* 最初の矢印フレームの取得
|
|
1310
|
+
* @param {object} _dataObj
|
|
1311
|
+
* @param {string} [_keyCtrlPtn]
|
|
1312
|
+
* @returns {number}
|
|
1313
|
+
*/
|
|
1314
|
+
const getFirstArrowFrame = (_dataObj, _keyCtrlPtn = `${g_keyObj.currentKey}_${g_keyObj.currentPtn}`) => {
|
|
1315
|
+
|
|
1316
|
+
let tmpFirstNum = Infinity;
|
|
1317
|
+
const keyNum = g_keyObj[`${g_keyObj.defaultProp}${_keyCtrlPtn}`].length;
|
|
1318
|
+
|
|
1319
|
+
for (let j = 0; j < keyNum; j++) {
|
|
1320
|
+
const data = [
|
|
1321
|
+
_dataObj.arrowData[j],
|
|
1322
|
+
_dataObj.frzData[j],
|
|
1323
|
+
_dataObj.dummyArrowData[j],
|
|
1324
|
+
_dataObj.dummyFrzData[j]
|
|
1325
|
+
];
|
|
1326
|
+
|
|
1327
|
+
data.filter(data => hasVal(data)).forEach(_objData => {
|
|
1328
|
+
if (_objData[0] !== `` && _objData[0] < tmpFirstNum && _objData[0] + g_limitObj.adjustment > 0) {
|
|
1329
|
+
tmpFirstNum = _objData[0];
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
return (tmpFirstNum === Infinity ? 0 : tmpFirstNum);
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* 開始フレームの取得
|
|
1338
|
+
* @param {number} _lastFrame
|
|
1339
|
+
* @param {number} [_fadein=0]
|
|
1340
|
+
* @param {number} [_scoreId=g_scoreObj.scoreId]
|
|
1341
|
+
* @returns {number}
|
|
1342
|
+
*/
|
|
1343
|
+
const getStartFrame = (_lastFrame, _fadein = 0, _scoreId = g_stateObj.scoreId) => {
|
|
1344
|
+
let frameNum = setIntVal(g_headerObj.startFrame?.[_scoreId], setIntVal(g_headerObj.startFrame?.[0], 0));
|
|
1345
|
+
if (_lastFrame >= frameNum) {
|
|
1346
|
+
frameNum = Math.round(_fadein / 100 * (_lastFrame - frameNum)) + frameNum;
|
|
1347
|
+
}
|
|
1348
|
+
return frameNum;
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* 速度補正値の算出
|
|
1353
|
+
* @param {number} _speed
|
|
1354
|
+
* @returns {number}
|
|
1355
|
+
*/
|
|
1356
|
+
const getSpeedFactor = _speed => {
|
|
1357
|
+
if (Math.abs(_speed) === 1) {
|
|
1358
|
+
// ±1 はそのまま返して符号を保持
|
|
1359
|
+
return _speed;
|
|
1360
|
+
}
|
|
1361
|
+
if (g_stateObj.d_velocity === `Extreme`) {
|
|
1362
|
+
// |speed|>1 を強めに、<1 を弱めに
|
|
1363
|
+
return _speed * (Math.abs(_speed) > 1 ? 1.5 : 0.75);
|
|
1364
|
+
}
|
|
1365
|
+
if (g_stateObj.d_velocity === `Soft`) {
|
|
1366
|
+
// 変化幅を緩和(符号は維持)
|
|
1367
|
+
return (1 + Math.abs(_speed)) / 2 * Math.sign(_speed);
|
|
1368
|
+
}
|
|
1369
|
+
return _speed;
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* 各フレームごとの速度を格納
|
|
1374
|
+
* @param {object} _speedData
|
|
1375
|
+
* @param {number} _lastFrame
|
|
1376
|
+
* @returns {number[]}
|
|
1377
|
+
*/
|
|
1378
|
+
const setSpeedOnFrame = (_speedData, _lastFrame) => {
|
|
1379
|
+
|
|
1380
|
+
const speedOnFrame = [];
|
|
1381
|
+
let currentSpeed = g_stateObj.speed * g_headerObj.baseSpeed * 2;
|
|
1382
|
+
|
|
1383
|
+
for (let frm = 0, s = 0; frm <= _lastFrame; frm++) {
|
|
1384
|
+
while (frm >= _speedData?.[s]) {
|
|
1385
|
+
currentSpeed = getSpeedFactor(_speedData[s + 1]) * g_stateObj.speed * g_headerObj.baseSpeed * 2;
|
|
1386
|
+
s += 2;
|
|
1387
|
+
}
|
|
1388
|
+
speedOnFrame[frm] = currentSpeed;
|
|
1389
|
+
}
|
|
1390
|
+
return speedOnFrame;
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Motionオプション適用時の矢印別の速度設定
|
|
1395
|
+
* - 矢印が表示される最大フレーム数を 101フレーム と定義。
|
|
1396
|
+
* @returns {number[]}
|
|
1397
|
+
*/
|
|
1398
|
+
const setMotionOnFrame = () => g_motionFunc.get(g_stateObj.motion)(fillArray(101));
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* Boost用の適用関数
|
|
1402
|
+
* - ステップゾーンに近づくにつれて加速量を大きく/小さくする (16 → 85)
|
|
1403
|
+
* @param {number[]} _frms
|
|
1404
|
+
* @param {number} _spd
|
|
1405
|
+
* @param {number} [_pnFlg=1] 正負(1 もしくは -1)
|
|
1406
|
+
* @returns {number[]}
|
|
1407
|
+
*/
|
|
1408
|
+
const getBoostTrace = (_frms, _spd, _pnFlg = 1) => {
|
|
1409
|
+
for (let j = C_MOTION_STD_POS + 1; j < C_MOTION_STD_POS + 70; j++) {
|
|
1410
|
+
_frms[j] = (C_MOTION_STD_POS + 70 - j) * _pnFlg * _spd / 50;
|
|
1411
|
+
}
|
|
1412
|
+
return _frms;
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Brake用の適用関数
|
|
1417
|
+
* - 初期は+2x、ステップゾーンに近づくにつれて加速量を下げる (20 → 34)
|
|
1418
|
+
* @param {number[]} _frms
|
|
1419
|
+
* @returns {number[]}
|
|
1420
|
+
*/
|
|
1421
|
+
const getBrakeTrace = _frms => {
|
|
1422
|
+
for (let j = C_MOTION_STD_POS + 5; j < C_MOTION_STD_POS + 19; j++) {
|
|
1423
|
+
_frms[j] = (j - 15) * 4 / 14;
|
|
1424
|
+
}
|
|
1425
|
+
for (let j = C_MOTION_STD_POS + 19; j <= g_headerObj.playingHeight / 2; j++) {
|
|
1426
|
+
_frms[j] = 4;
|
|
1427
|
+
}
|
|
1428
|
+
return _frms;
|
|
1429
|
+
};
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* Fountain用の適用関数
|
|
1433
|
+
* - 反対側から出現し、画面中央付近で折り返す。タイミングは初期速度により変化。
|
|
1434
|
+
* @param {number[]} _frms
|
|
1435
|
+
* @param {number} _spd
|
|
1436
|
+
* @returns {number[]}
|
|
1437
|
+
*/
|
|
1438
|
+
const getFountainTrace = (_frms, _spd) => {
|
|
1439
|
+
const minj = C_MOTION_STD_POS + 1;
|
|
1440
|
+
const maxj = C_MOTION_STD_POS + Math.ceil(400 / _spd) + 1;
|
|
1441
|
+
const maxMotionFrm = Math.max(maxj, C_MOTION_STD_POS + g_sHeight / 2);
|
|
1442
|
+
const diff = 50 / (maxj - minj);
|
|
1443
|
+
const factor = 0.5 + _spd / 40;
|
|
1444
|
+
|
|
1445
|
+
for (let j = minj; j < maxMotionFrm; j++) {
|
|
1446
|
+
_frms[j] = Math.floor((10 - (j - C_MOTION_STD_POS - 1) * diff) * factor);
|
|
1447
|
+
}
|
|
1448
|
+
return _frms;
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* 最初のフレームで出現する矢印が、ステップゾーンに到達するまでのフレーム数を取得
|
|
1453
|
+
* @param {number} _startFrame
|
|
1454
|
+
* @param {object} _speedOnFrame
|
|
1455
|
+
* @returns {number}
|
|
1456
|
+
*/
|
|
1457
|
+
const getFirstArrivalFrame = (_startFrame, _speedOnFrame) => {
|
|
1458
|
+
let startY = 0;
|
|
1459
|
+
let frm = _startFrame;
|
|
1460
|
+
|
|
1461
|
+
while (g_scoreObj.distY - startY > 0) {
|
|
1462
|
+
startY += _speedOnFrame[frm];
|
|
1463
|
+
frm++;
|
|
1464
|
+
}
|
|
1465
|
+
return frm;
|
|
1466
|
+
};
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* 矢印・フリーズアロー・速度/色変化格納処理
|
|
1470
|
+
* @param {object} _dataObj
|
|
1471
|
+
* @param {object} _speedOnFrame
|
|
1472
|
+
* @param {number} _firstArrivalFrame
|
|
1473
|
+
*/
|
|
1474
|
+
const pushArrows = (_dataObj, _speedOnFrame, _firstArrivalFrame) => {
|
|
1475
|
+
|
|
1476
|
+
// 矢印・フリーズアロー・速度/色変化用 フレーム別処理配列
|
|
1477
|
+
[``, `Dummy`].forEach(header =>
|
|
1478
|
+
g_typeLists.dataList.forEach(name => g_workObj[`mk${header}${name}`] = []));
|
|
1479
|
+
|
|
1480
|
+
const boostData = [];
|
|
1481
|
+
if (hasArrayList(_dataObj.boostData, 2)) {
|
|
1482
|
+
const _data = _dataObj.boostData.concat();
|
|
1483
|
+
for (let k = 0; k < _data.length; k += 2) {
|
|
1484
|
+
boostData.push({ frame: _data[k], spd: getSpeedFactor(_data[k + 1]) });
|
|
1485
|
+
}
|
|
1486
|
+
if (boostData.length > 0 && boostData[0].frame > 0) {
|
|
1487
|
+
boostData.unshift({ frame: 0, spd: 1 });
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
const getSpdByFrame = _targetFrame => {
|
|
1491
|
+
// targetFrame 以下の frame を持つ要素の中で、最後(最新)のものを見つける
|
|
1492
|
+
const result = boostData.findLast((item) => _targetFrame >= item.frame);
|
|
1493
|
+
|
|
1494
|
+
// 該当するものがない場合(frame: 0 未満など)のフォールバック
|
|
1495
|
+
return result ? result.spd : 1;
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* 矢印・フリーズアローのデータ格納処理
|
|
1500
|
+
* @param {number} _j
|
|
1501
|
+
* @param {number} _k
|
|
1502
|
+
* @param {number[]} _data
|
|
1503
|
+
* @param {number} _startPoint
|
|
1504
|
+
* @param {string} _header
|
|
1505
|
+
* @param {boolean} _frzFlg
|
|
1506
|
+
* @param {number} object.initY
|
|
1507
|
+
* @param {number} object.initBoostY
|
|
1508
|
+
* @param {number} object.arrivalFrame
|
|
1509
|
+
* @param {number} object.motionFrame
|
|
1510
|
+
*/
|
|
1511
|
+
const setNotes = (_j, _k, _data, _startPoint, _header, _frzFlg = false, { initY, initBoostY, arrivalFrame, motionFrame, boostSpd } = {}) => {
|
|
1512
|
+
if (_startPoint >= 0) {
|
|
1513
|
+
const arrowAttrs = {
|
|
1514
|
+
pos: _j, initY, initBoostY, arrivalFrame, motionFrame, boostSpd,
|
|
1515
|
+
get boostDir() {
|
|
1516
|
+
return Math.sign(this.boostSpd);
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
if (g_workObj[`mk${_header}Arrow`][_startPoint] === undefined) {
|
|
1520
|
+
g_workObj[`mk${_header}Arrow`][_startPoint] = [arrowAttrs];
|
|
1521
|
+
} else {
|
|
1522
|
+
let insertFlg = false;
|
|
1523
|
+
for (let m = 0; m < g_workObj[`mk${_header}Arrow`][_startPoint].length; m++) {
|
|
1524
|
+
if (arrowAttrs.arrivalFrame < g_workObj[`mk${_header}Arrow`][_startPoint][m].arrivalFrame) {
|
|
1525
|
+
g_workObj[`mk${_header}Arrow`][_startPoint].splice(m, 0, arrowAttrs);
|
|
1526
|
+
insertFlg = true;
|
|
1527
|
+
break;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
if (!insertFlg) {
|
|
1531
|
+
g_workObj[`mk${_header}Arrow`][_startPoint].push(arrowAttrs);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
if (_frzFlg) {
|
|
1535
|
+
g_workObj[`mk${_header}Length`][_j][_k] = getFrzLength(_speedOnFrame, _data[_k], _data[_k + 1]);
|
|
1536
|
+
}
|
|
1537
|
+
} else if (_frzFlg && g_workObj[`mk${_header}Length`][_j] !== undefined) {
|
|
1538
|
+
g_workObj[`mk${_header}Length`][_j] = structuredClone(g_workObj[`mk${_header}Length`][_j].slice(_k + 2));
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* 矢印・フリーズアローの出現位置計算
|
|
1544
|
+
* @param {number} _j
|
|
1545
|
+
* @param {number[]} _data
|
|
1546
|
+
* @param {string} _header
|
|
1547
|
+
* @param {boolean} _frzFlg
|
|
1548
|
+
*/
|
|
1549
|
+
const calcNotes = (_j, _data, _header = ``, _frzFlg = false) => {
|
|
1550
|
+
if (_data === undefined) {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
const calcInitBoostY = _arrivalFrame => sumData(g_workObj.motionOnFrames.filter((val, j) => j <= _arrivalFrame));
|
|
1555
|
+
const camelHeader = toCapitalize(_header);
|
|
1556
|
+
const setcnt = (_frzFlg ? 2 : 1);
|
|
1557
|
+
if (_frzFlg && _data.length % 2 !== 0) {
|
|
1558
|
+
_data.pop();
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
let spdNext = Infinity;
|
|
1562
|
+
let spdk = (_dataObj.speedData?.length ?? 0) - 2;
|
|
1563
|
+
let spdPrev = _dataObj.speedData?.[spdk] ?? 0;
|
|
1564
|
+
|
|
1565
|
+
// 最後尾のデータから計算して格納
|
|
1566
|
+
const lastk = _data.length - setcnt;
|
|
1567
|
+
let arrowArrivalFrm = _data[lastk];
|
|
1568
|
+
let tmpObj = getArrowStartFrame(arrowArrivalFrm, _speedOnFrame);
|
|
1569
|
+
|
|
1570
|
+
let startPoint = tmpObj.frm;
|
|
1571
|
+
let arrivalFrm = tmpObj.arrivalFrm;
|
|
1572
|
+
let minNotesFrame = startPoint;
|
|
1573
|
+
|
|
1574
|
+
if (_frzFlg) {
|
|
1575
|
+
g_workObj[`mk${camelHeader}Length`][_j] = [];
|
|
1576
|
+
}
|
|
1577
|
+
setNotes(_j, lastk, _data, startPoint, camelHeader, _frzFlg, {
|
|
1578
|
+
initY: tmpObj.startY, initBoostY: calcInitBoostY(tmpObj.motionFrm),
|
|
1579
|
+
arrivalFrame: tmpObj.arrivalFrm, motionFrame: tmpObj.motionFrm, boostSpd: getSpdByFrame(arrowArrivalFrm)
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
// 矢印は1つずつ、フリーズアローは2つで1セット
|
|
1583
|
+
for (let k = lastk - setcnt; k >= 0; k -= setcnt) {
|
|
1584
|
+
arrowArrivalFrm = _data[k];
|
|
1585
|
+
|
|
1586
|
+
if (arrowArrivalFrm < _firstArrivalFrame) {
|
|
1587
|
+
|
|
1588
|
+
// 出現位置が開始前の場合は除外
|
|
1589
|
+
if (_frzFlg && g_workObj[`mk${camelHeader}Length`][_j] !== undefined) {
|
|
1590
|
+
g_workObj[`mk${camelHeader}Length`][_j] = structuredClone(g_workObj[`mk${camelHeader}Length`][_j].slice(k + 2));
|
|
1591
|
+
}
|
|
1592
|
+
break;
|
|
1593
|
+
|
|
1594
|
+
} else if ((arrowArrivalFrm - arrivalFrm > spdPrev)
|
|
1595
|
+
&& arrowArrivalFrm < spdNext) {
|
|
1596
|
+
|
|
1597
|
+
// 最初から最後まで同じスピードのときは前回のデータを流用(ステップゾーン到達フレーム - 移動フレーム)
|
|
1598
|
+
startPoint = arrowArrivalFrm - arrivalFrm;
|
|
1599
|
+
|
|
1600
|
+
} else {
|
|
1601
|
+
|
|
1602
|
+
// 速度変化が間に入るときは再計算
|
|
1603
|
+
while (arrowArrivalFrm < spdPrev) {
|
|
1604
|
+
spdk -= 2;
|
|
1605
|
+
spdNext = spdPrev;
|
|
1606
|
+
spdPrev = _dataObj.speedData[spdk];
|
|
1607
|
+
}
|
|
1608
|
+
tmpObj = getArrowStartFrame(arrowArrivalFrm, _speedOnFrame);
|
|
1609
|
+
startPoint = tmpObj.frm;
|
|
1610
|
+
arrivalFrm = tmpObj.arrivalFrm;
|
|
1611
|
+
|
|
1612
|
+
// --- 逆転検知ロジック ---
|
|
1613
|
+
// 後ろからループしているため、minNotesFrameには「自分より譜面上後ろにあるノーツ」の
|
|
1614
|
+
// 最小生成フレーム(最も早く出現するもの)が入っている。
|
|
1615
|
+
|
|
1616
|
+
// 「自分より後ろのノーツ」の方が、「自分」よりも早く出現する場合、
|
|
1617
|
+
// 配列の順序と出現時間の順序が入れ替わっている(逆転)とみなす。
|
|
1618
|
+
// 逆転している場合は、最小生成フレームまでさらに遡って、出現フレームを再計算する。
|
|
1619
|
+
if (minNotesFrame < startPoint) {
|
|
1620
|
+
tmpObj = getAdjArrowStartFrame({ ...tmpObj, frm: startPoint }, _speedOnFrame, minNotesFrame);
|
|
1621
|
+
startPoint = tmpObj.frm;
|
|
1622
|
+
arrivalFrm = tmpObj.arrivalFrm;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
// 最小値を更新
|
|
1626
|
+
minNotesFrame = Math.min(minNotesFrame, startPoint);
|
|
1627
|
+
|
|
1628
|
+
// 出現タイミングを保存
|
|
1629
|
+
setNotes(_j, k, _data, startPoint, camelHeader, _frzFlg, {
|
|
1630
|
+
initY: tmpObj.startY, initBoostY: calcInitBoostY(tmpObj.motionFrm),
|
|
1631
|
+
arrivalFrame: tmpObj.arrivalFrm, motionFrame: tmpObj.motionFrm, boostSpd: getSpdByFrame(arrowArrivalFrm)
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
for (let j = 0; j < getKeyInfo().keyNum; j++) {
|
|
1637
|
+
|
|
1638
|
+
// 矢印の出現フレーム数計算
|
|
1639
|
+
calcNotes(j, _dataObj.arrowData[j]);
|
|
1640
|
+
calcNotes(j, _dataObj.dummyArrowData[j], `dummy`);
|
|
1641
|
+
|
|
1642
|
+
// フリーズアローの出現フレーム数計算
|
|
1643
|
+
calcNotes(j, _dataObj.frzData[j], `frz`, true);
|
|
1644
|
+
calcNotes(j, _dataObj.dummyFrzData[j], `dummyFrz`, true);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/**
|
|
1648
|
+
* 色変化・モーションデータ・スクロール反転データのタイミング更新
|
|
1649
|
+
* - この関数を使用する場合、配列グループの先頭2つが「フレーム数、矢印番号」となっていないと動作しない
|
|
1650
|
+
* @param {string} _type
|
|
1651
|
+
* @param {string} _header
|
|
1652
|
+
* @param {Function} _setFunc 後続実行関数
|
|
1653
|
+
* @param {number} object._term 1セット当たりのデータ数(デフォルトは後続実行関数の引数の数-1, デフォルト引数・オブジェクト引数除く)
|
|
1654
|
+
* @param {boolean} object._colorFlg 個別色変化フラグ
|
|
1655
|
+
* @param {boolean} object._calcFrameFlg 逆算を無条件で行うかどうかの可否
|
|
1656
|
+
* @param {string} object._propName 色変化種類 (Arrow, ArrowShadow, FrzNormal, FrzNormalBar, ...)
|
|
1657
|
+
*/
|
|
1658
|
+
const calcDataTiming = (_type, _header, _setFunc = () => true,
|
|
1659
|
+
{ _term = _setFunc.length - 1, _colorFlg = false, _calcFrameFlg = false, _propName = `` } = {}) => {
|
|
1660
|
+
|
|
1661
|
+
const camelHeader = _header === `` ? _type : `${_header}${toCapitalize(_type)}`;
|
|
1662
|
+
const baseData = hasVal(_propName) ? _dataObj[`${camelHeader}Data`][_propName] : _dataObj[`${camelHeader}Data`];
|
|
1663
|
+
|
|
1664
|
+
if (!hasArrayList(baseData, _term)) {
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const frontData = [];
|
|
1668
|
+
let minDataFrame = Infinity;
|
|
1669
|
+
for (let k = baseData.length - _term; k >= 0; k -= _term) {
|
|
1670
|
+
const calcFrameFlg = (_colorFlg && !baseData[k + 3]) || _calcFrameFlg;
|
|
1671
|
+
|
|
1672
|
+
if (baseData[k] < g_scoreObj.frameNum) {
|
|
1673
|
+
// フェードイン直前にある色変化・モーションデータ・スクロール反転データを取得して格納
|
|
1674
|
+
if (!hasValInArray(baseData[k + 1], frontData)) {
|
|
1675
|
+
frontData.unshift(baseData.slice(k + 1, k + _term));
|
|
1676
|
+
}
|
|
1677
|
+
} else {
|
|
1678
|
+
if (calcFrameFlg) {
|
|
1679
|
+
let tmpObj = getArrowStartFrame(baseData[k], _speedOnFrame);
|
|
1680
|
+
|
|
1681
|
+
// 到達フレームがより早い(=baseData[k]がより小さい)要素なのに、
|
|
1682
|
+
// より遅い要素より後ろで出現してしまう場合は矢印と同様に補正する
|
|
1683
|
+
if (minDataFrame < tmpObj.frm) {
|
|
1684
|
+
tmpObj = getAdjArrowStartFrame(tmpObj, _speedOnFrame, minDataFrame);
|
|
1685
|
+
}
|
|
1686
|
+
if (tmpObj.frm < g_scoreObj.frameNum) {
|
|
1687
|
+
const diff = g_scoreObj.frameNum - tmpObj.frm;
|
|
1688
|
+
tmpObj.frm = g_scoreObj.frameNum;
|
|
1689
|
+
tmpObj.arrivalFrm -= diff;
|
|
1690
|
+
}
|
|
1691
|
+
minDataFrame = Math.min(minDataFrame, tmpObj.frm);
|
|
1692
|
+
baseData[k] = tmpObj.frm;
|
|
1693
|
+
}
|
|
1694
|
+
_setFunc(toCapitalize(_header), ...baseData.slice(k, k + _term));
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
frontData.forEach(data => _setFunc(toCapitalize(_header), g_scoreObj.frameNum, ...data));
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* 歌詞表示、背景・マスク表示のフェードイン時調整処理
|
|
1702
|
+
* @param {string} _type
|
|
1703
|
+
* @param {object} _data
|
|
1704
|
+
* @returns {object}
|
|
1705
|
+
*/
|
|
1706
|
+
const calcAnimationData = (_type, _data) => {
|
|
1707
|
+
|
|
1708
|
+
const startNum = g_scoreObj.frameNum;
|
|
1709
|
+
const cgArrays = [`word`];
|
|
1710
|
+
|
|
1711
|
+
const isSameDepth = (_j, _k) =>
|
|
1712
|
+
_data[startNum][_j] !== undefined &&
|
|
1713
|
+
_data[startNum][_k] !== undefined &&
|
|
1714
|
+
(cgArrays.includes(_type) ? _data[startNum][_j][0] === _data[startNum][_k][0] :
|
|
1715
|
+
_data[startNum][_j].depth === _data[startNum][_k].depth);
|
|
1716
|
+
|
|
1717
|
+
const isExceptData = {
|
|
1718
|
+
word: (_exceptList, _j) => listMatching(_data[startNum][_j][1] || ``, _exceptList.word),
|
|
1719
|
+
back: (_exceptList, _j) => listMatching(_data[startNum][_j].animationName || ``, _exceptList.back),
|
|
1720
|
+
mask: (_exceptList, _j) => listMatching(_data[startNum][_j].animationName || ``, _exceptList.mask),
|
|
1721
|
+
style: (_exceptList, _j) => listMatching(_data[startNum][_j].depth || ``, _exceptList.style),
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
const getLength = _list =>
|
|
1725
|
+
_list === undefined ? 0 :
|
|
1726
|
+
(cgArrays.includes(_type) ? _list.length : Object.keys(_list).length);
|
|
1727
|
+
|
|
1728
|
+
// フェードイン位置にそれ以前のデータを前追加
|
|
1729
|
+
if (startNum > 0 && _data[startNum] === undefined) {
|
|
1730
|
+
_data[startNum] = [];
|
|
1731
|
+
}
|
|
1732
|
+
for (let j = _data.length - 1; j >= 0; j--) {
|
|
1733
|
+
if (_data[j] !== undefined && j < g_scoreObj.frameNum) {
|
|
1734
|
+
_data[startNum].unshift(..._data[j]);
|
|
1735
|
+
_data[j] = undefined;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// 重複する深度をカット(後方優先)
|
|
1740
|
+
// ただし、除外リストにあるデータは残す
|
|
1741
|
+
for (let j = getLength(_data[startNum]) - 1; j >= 0; j--) {
|
|
1742
|
+
if (_data[startNum][j] !== undefined) {
|
|
1743
|
+
for (let k = j - 1; k >= 0; k--) {
|
|
1744
|
+
if (isSameDepth(j, k) && !isExceptData[_type](g_preloadExceptList, k)) {
|
|
1745
|
+
_data[startNum][k] = undefined;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
// g_stockForceDelList に合致する消去対象データを検索し、削除
|
|
1751
|
+
for (let j = getLength(_data[startNum]) - 1; j >= 0; j--) {
|
|
1752
|
+
if (_data[startNum][j] !== undefined && isExceptData[_type](g_stockForceDelList, j)) {
|
|
1753
|
+
_data[startNum][j] = undefined;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// カットした箇所をリストから削除
|
|
1758
|
+
if (getLength(_data[startNum]) > 0) {
|
|
1759
|
+
_data[startNum] = _data[startNum].filter(list => getLength(list) > 0);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
return _data;
|
|
1763
|
+
};
|
|
1764
|
+
|
|
1765
|
+
// 個別・全体色変化、モーションデータ・スクロール反転データのタイミング更新
|
|
1766
|
+
[``, `dummy`].forEach(type => {
|
|
1767
|
+
calcDataTiming(`color`, type, pushColors, { _term: 4, _colorFlg: true });
|
|
1768
|
+
if (_dataObj[`ncolor${type}Data`] !== undefined) {
|
|
1769
|
+
Object.keys(_dataObj[`ncolor${type}Data`]).forEach(pattern =>
|
|
1770
|
+
calcDataTiming(`ncolor`, type, pushColors, { _term: 5, _colorFlg: true, _propName: pattern }));
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
g_typeLists.arrow.forEach(header =>
|
|
1775
|
+
calcDataTiming(`cssMotion`, header, pushCssMotions, { _calcFrameFlg: true }));
|
|
1776
|
+
|
|
1777
|
+
calcDataTiming(`scrollch`, ``, pushScrollchs, { _calcFrameFlg: true });
|
|
1778
|
+
|
|
1779
|
+
g_fadeinStockList.forEach(type =>
|
|
1780
|
+
_dataObj[`${type}Data`] = calcAnimationData(type, _dataObj[`${type}Data`]));
|
|
1781
|
+
|
|
1782
|
+
|
|
1783
|
+
// 実際に処理させる途中変速配列を作成
|
|
1784
|
+
g_workObj.speedData = [];
|
|
1785
|
+
g_workObj.speedData.push(g_scoreObj.frameNum);
|
|
1786
|
+
g_workObj.speedData.push(_speedOnFrame[g_scoreObj.frameNum]);
|
|
1787
|
+
|
|
1788
|
+
for (let k = 0; k < _dataObj?.speedData.length; k += 2) {
|
|
1789
|
+
if (_dataObj.speedData[k] >= g_scoreObj.frameNum) {
|
|
1790
|
+
g_workObj.speedData.push(_dataObj.speedData[k]);
|
|
1791
|
+
g_workObj.speedData.push(_speedOnFrame[_dataObj.speedData[k]]);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
/**
|
|
1797
|
+
* 出現フレームを指定フレームまで強制的に遡って再計算する(逆転補正用)
|
|
1798
|
+
* @param {object} _obj getArrowStartFrameの戻り値
|
|
1799
|
+
* @param {object} _speedOnFrame
|
|
1800
|
+
* @param {number} _targetFrame
|
|
1801
|
+
* @returns {{ frm: number, startY: number, arrivalFrm: number, motionFrm: number }}
|
|
1802
|
+
*/
|
|
1803
|
+
const getAdjArrowStartFrame = (_obj, _speedOnFrame, _targetFrame) => {
|
|
1804
|
+
while (_obj.frm > _targetFrame) {
|
|
1805
|
+
_obj.startY += _speedOnFrame[_obj.frm - 1];
|
|
1806
|
+
|
|
1807
|
+
if (_speedOnFrame[_obj.frm - 1] !== 0) {
|
|
1808
|
+
_obj.motionFrm++;
|
|
1809
|
+
}
|
|
1810
|
+
_obj.frm--;
|
|
1811
|
+
_obj.arrivalFrm++;
|
|
1812
|
+
}
|
|
1813
|
+
return _obj;
|
|
1814
|
+
};
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* ステップゾーン到達地点から逆算して開始フレームを取得
|
|
1818
|
+
* @param {number} _frame
|
|
1819
|
+
* @param {object} _speedOnFrame
|
|
1820
|
+
* @returns {{ frm: number, startY: number, arrivalFrm: number, motionFrm: number }}
|
|
1821
|
+
*/
|
|
1822
|
+
const getArrowStartFrame = (_frame, _speedOnFrame) => {
|
|
1823
|
+
|
|
1824
|
+
const obj = {
|
|
1825
|
+
frm: _frame,
|
|
1826
|
+
startY: 0,
|
|
1827
|
+
arrivalFrm: 0,
|
|
1828
|
+
motionFrm: C_MOTION_STD_POS
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
while (g_scoreObj.distY - obj.startY > 0) {
|
|
1832
|
+
obj.startY += _speedOnFrame[obj.frm - 1];
|
|
1833
|
+
|
|
1834
|
+
if (_speedOnFrame[obj.frm - 1] !== 0) {
|
|
1835
|
+
obj.motionFrm++;
|
|
1836
|
+
}
|
|
1837
|
+
obj.frm--;
|
|
1838
|
+
obj.arrivalFrm++;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
return obj;
|
|
1842
|
+
};
|
|
1843
|
+
|
|
1844
|
+
/**
|
|
1845
|
+
* 速度を加味したフリーズアローの長さを取得
|
|
1846
|
+
* @param {object} _speedOnFrame
|
|
1847
|
+
* @param {number} _startFrame
|
|
1848
|
+
* @param {number} _endFrame
|
|
1849
|
+
* @returns {number}
|
|
1850
|
+
*/
|
|
1851
|
+
const getFrzLength = (_speedOnFrame, _startFrame, _endFrame) => {
|
|
1852
|
+
let frzLength = 0;
|
|
1853
|
+
|
|
1854
|
+
for (let frm = _startFrame; frm < _endFrame; frm++) {
|
|
1855
|
+
frzLength += _speedOnFrame[frm];
|
|
1856
|
+
}
|
|
1857
|
+
return frzLength;
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
* キーパターン(デフォルト)に対応する矢印番号を格納
|
|
1862
|
+
* - 色変化、矢印・フリーズアローモーション、スクロール変化で
|
|
1863
|
+
* 矢印レーンの番号を実際のキーパターンに対応する番号に置き換える際に使用
|
|
1864
|
+
* - 例: [0, 1, 2, 3, 4] -> [4, 0, 1, 2, 3]
|
|
1865
|
+
*/
|
|
1866
|
+
const convertReplaceNums = () => {
|
|
1867
|
+
const tkObj = getKeyInfo();
|
|
1868
|
+
const baseCharas = g_keyObj[`chara${g_keyObj.currentKey}_0`];
|
|
1869
|
+
const convCharas = g_keyObj[`chara${tkObj.keyCtrlPtn}`];
|
|
1870
|
+
|
|
1871
|
+
g_workObj.replaceNums = [];
|
|
1872
|
+
|
|
1873
|
+
for (let j = 0; j < tkObj.keyNum; j++) {
|
|
1874
|
+
for (let k = 0; k < tkObj.keyNum; k++) {
|
|
1875
|
+
if (baseCharas[j] === convCharas[k]) {
|
|
1876
|
+
g_workObj.replaceNums[j] = k;
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1883
|
+
/**
|
|
1884
|
+
* 色情報の格納
|
|
1885
|
+
* @param {string} _header
|
|
1886
|
+
* @param {number} _frame
|
|
1887
|
+
* @param {number} _val 色番号
|
|
1888
|
+
* @param {string} _colorCd カラーコード
|
|
1889
|
+
* @param {string} _allFlg 全体色変化有無
|
|
1890
|
+
* @param {string} _pattern 色変化種類 (Arrow, ArrowShadow, FrzNormal, FrzNormalBar, ...)
|
|
1891
|
+
*/
|
|
1892
|
+
const pushColors = (_header, _frame, _val, _colorCd, _allFlg, _pattern = ``) => {
|
|
1893
|
+
|
|
1894
|
+
const tkObj = getKeyInfo();
|
|
1895
|
+
const grdFlg = (g_colorType === `Type0` ? !g_headerObj.defaultColorgrd[0] : g_headerObj.defaultColorgrd[0]);
|
|
1896
|
+
const colorCd = makeColorGradation(_colorCd, { _defaultColorgrd: [grdFlg, g_headerObj.defaultColorgrd[1]] });
|
|
1897
|
+
const addAll = Number(_allFlg) * 1000;
|
|
1898
|
+
const allUseTypes = [];
|
|
1899
|
+
|
|
1900
|
+
/**
|
|
1901
|
+
* 全体色変化の有効化(フレーム別)
|
|
1902
|
+
* @param {...any} _types
|
|
1903
|
+
*/
|
|
1904
|
+
const enabledAll = (..._types) => {
|
|
1905
|
+
if (_allFlg) {
|
|
1906
|
+
_types.forEach(type => g_workObj[`mk${type}ColorChangeAll`][_frame] = true);
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1910
|
+
/**
|
|
1911
|
+
* 色変化用配列(フレーム別)へのデータ追加
|
|
1912
|
+
* @param {string} _baseStr
|
|
1913
|
+
* @param {number} _cVal
|
|
1914
|
+
*/
|
|
1915
|
+
const pushColor = (_baseStr, _cVal) => {
|
|
1916
|
+
g_workObj[_baseStr][_frame]?.push(_cVal) || (g_workObj[_baseStr][_frame] = [_cVal]);
|
|
1917
|
+
g_workObj[`${_baseStr}Cd`][_frame]?.push(colorCd) || (g_workObj[`${_baseStr}Cd`][_frame] = [colorCd]);
|
|
1918
|
+
};
|
|
1919
|
+
|
|
1920
|
+
/**
|
|
1921
|
+
* 色変化データ(ncolor_data)の格納処理
|
|
1922
|
+
*/
|
|
1923
|
+
const executePushColors = () => {
|
|
1924
|
+
const baseHeaders = [];
|
|
1925
|
+
if (_pattern.startsWith(`Arrow`)) {
|
|
1926
|
+
baseHeaders.push(`mk${_header}Color${_pattern.slice('Arrow'.length)}`);
|
|
1927
|
+
allUseTypes.push(`Arrow`);
|
|
1928
|
+
|
|
1929
|
+
// フリーズアロー色の追随設定がある場合、対象を追加
|
|
1930
|
+
if (_pattern === `Arrow`) {
|
|
1931
|
+
g_headerObj.frzScopeFromArrowColors.forEach(type =>
|
|
1932
|
+
baseHeaders.push(`mk${_header}FColor${type}`, `mk${_header}FColor${type}Bar`));
|
|
1933
|
+
if (g_headerObj.frzScopeFromArrowColors.length > 0) {
|
|
1934
|
+
allUseTypes.push(`Frz`);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
} else {
|
|
1938
|
+
baseHeaders.push(`mk${_header}FColor${_pattern}`);
|
|
1939
|
+
allUseTypes.push(`Frz`);
|
|
1940
|
+
}
|
|
1941
|
+
// 色変化情報の格納
|
|
1942
|
+
if (_val.startsWith('g')) {
|
|
1943
|
+
// g付きの場合は矢印グループから対象の矢印番号を検索
|
|
1944
|
+
const groupVal = setIntVal(_val.slice(1));
|
|
1945
|
+
for (let j = 0; j < tkObj.keyNum; j++) {
|
|
1946
|
+
if (g_keyObj[`color${tkObj.keyCtrlPtn}`][j] === groupVal) {
|
|
1947
|
+
baseHeaders.forEach(baseHeader => pushColor(baseHeader, j + addAll));
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
} else {
|
|
1951
|
+
baseHeaders.forEach(baseHeader => pushColor(baseHeader, g_workObj.replaceNums[setIntVal(_val)] + addAll));
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
|
|
1955
|
+
/**
|
|
1956
|
+
* 従来の色変化データ派生(color_data, acolor_data)の格納処理
|
|
1957
|
+
*/
|
|
1958
|
+
const executePushColorsOld = () => {
|
|
1959
|
+
|
|
1960
|
+
if (_val < 30 || _val >= 1000) {
|
|
1961
|
+
const baseHeaders = [`mk${_header}Color`];
|
|
1962
|
+
allUseTypes.push(`Arrow`);
|
|
1963
|
+
|
|
1964
|
+
// フリーズアロー色の追随設定がある場合、対象を追加
|
|
1965
|
+
g_headerObj.frzScopeFromArrowColors.forEach(type =>
|
|
1966
|
+
baseHeaders.push(`mk${_header}FColor${type}`, `mk${_header}FColor${type}Bar`));
|
|
1967
|
+
if (g_headerObj.frzScopeFromArrowColors.length > 0) {
|
|
1968
|
+
allUseTypes.push(`Frz`);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// 矢印の色変化 (追随指定時はフリーズアローも色変化)
|
|
1972
|
+
baseHeaders.forEach(baseHeader => {
|
|
1973
|
+
if (_val < 20 || _val >= 1000) {
|
|
1974
|
+
pushColor(baseHeader, g_workObj.replaceNums[_val % 1000] + addAll);
|
|
1975
|
+
} else if (_val >= 20) {
|
|
1976
|
+
const colorNum = _val - 20;
|
|
1977
|
+
for (let j = 0; j < tkObj.keyNum; j++) {
|
|
1978
|
+
if (g_keyObj[`color${tkObj.keyCtrlPtn}`][j] === colorNum) {
|
|
1979
|
+
pushColor(baseHeader, j + addAll);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
|
|
1985
|
+
} else {
|
|
1986
|
+
const baseHeader = `mk${_header}FColor`;
|
|
1987
|
+
allUseTypes.push(`Frz`);
|
|
1988
|
+
|
|
1989
|
+
// フリーズアローの色変化
|
|
1990
|
+
const tmpVals = [];
|
|
1991
|
+
if (_val < 50) {
|
|
1992
|
+
tmpVals.push(_val % 30);
|
|
1993
|
+
} else if (_val < 60) {
|
|
1994
|
+
tmpVals.push((_val % 50) * 2, (_val % 50) * 2 + 1);
|
|
1995
|
+
} else {
|
|
1996
|
+
if (_val === 60) {
|
|
1997
|
+
tmpVals.push(...Array(8).keys());
|
|
1998
|
+
} else {
|
|
1999
|
+
tmpVals.push(...[...Array(8).keys()].map(j => j + 10));
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
tmpVals.forEach(targetj => {
|
|
2003
|
+
|
|
2004
|
+
// targetj=0,2,4,6,8 ⇒ Arrow, 1,3,5,7,9 ⇒ Bar
|
|
2005
|
+
const ctype = (targetj >= 10 ? `Hit` : `Normal`) + (targetj % 2 === 0 ? `` : `Bar`);
|
|
2006
|
+
const colorPos = Math.ceil((targetj % 10 - 1) / 2);
|
|
2007
|
+
|
|
2008
|
+
g_keyObj[`color${tkObj.keyCtrlPtn}`].forEach((cpattern, k) => {
|
|
2009
|
+
if (colorPos === cpattern) {
|
|
2010
|
+
pushColor(baseHeader + ctype, k + addAll);
|
|
2011
|
+
}
|
|
2012
|
+
});
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
hasVal(_pattern) ? executePushColors() : executePushColorsOld();
|
|
2017
|
+
enabledAll(...allUseTypes);
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
/**
|
|
2021
|
+
* CSSモーション情報の格納
|
|
2022
|
+
* @param {string} _header
|
|
2023
|
+
* @param {number} _frame
|
|
2024
|
+
* @param {number} _val
|
|
2025
|
+
* @param {string} _styleName
|
|
2026
|
+
* @param {string} _styleNameRev
|
|
2027
|
+
* @param {boolean|undefined} _movLock
|
|
2028
|
+
* @param {boolean|undefined} _initManual
|
|
2029
|
+
*/
|
|
2030
|
+
const pushCssMotions = (_header, _frame, _val, _styleName, _styleNameRev, _movLock, _initManual) => {
|
|
2031
|
+
|
|
2032
|
+
const camelHeader = toCapitalize(_header);
|
|
2033
|
+
const tkObj = getKeyInfo();
|
|
2034
|
+
|
|
2035
|
+
// 矢印のモーション
|
|
2036
|
+
if (g_workObj[`mk${camelHeader}CssMotion`][_frame] === undefined) {
|
|
2037
|
+
g_workObj[`mk${camelHeader}CssMotion`][_frame] = [];
|
|
2038
|
+
g_workObj[`mk${camelHeader}CssMotionName`][_frame] = [];
|
|
2039
|
+
g_workObj[`mk${camelHeader}MovLock`][_frame] = [];
|
|
2040
|
+
g_workObj[`mk${camelHeader}InitManual`][_frame] = [];
|
|
2041
|
+
}
|
|
2042
|
+
if (_val < 20 || _val >= 1000) {
|
|
2043
|
+
const realVal = g_workObj.replaceNums[_val % 1000];
|
|
2044
|
+
g_workObj[`mk${camelHeader}CssMotion`][_frame].push(realVal);
|
|
2045
|
+
g_workObj[`mk${camelHeader}CssMotionName`][_frame].push(_styleName, _styleNameRev);
|
|
2046
|
+
g_workObj[`mk${camelHeader}MovLock`][_frame].push(_movLock);
|
|
2047
|
+
g_workObj[`mk${camelHeader}InitManual`][_frame].push(_initManual);
|
|
2048
|
+
|
|
2049
|
+
} else {
|
|
2050
|
+
const colorNum = _val - 20;
|
|
2051
|
+
for (let j = 0; j < tkObj.keyNum; j++) {
|
|
2052
|
+
if (g_keyObj[`color${tkObj.keyCtrlPtn}`][j] === colorNum) {
|
|
2053
|
+
g_workObj[`mk${camelHeader}CssMotion`][_frame].push(j);
|
|
2054
|
+
g_workObj[`mk${camelHeader}CssMotionName`][_frame].push(_styleName, _styleNameRev);
|
|
2055
|
+
g_workObj[`mk${camelHeader}MovLock`][_frame].push(_movLock);
|
|
2056
|
+
g_workObj[`mk${camelHeader}InitManual`][_frame].push(_initManual);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
|
|
2062
|
+
/**
|
|
2063
|
+
* スクロール変化情報の格納
|
|
2064
|
+
* @param {string} _header
|
|
2065
|
+
* @param {number} _frameArrow
|
|
2066
|
+
* @param {number} _val
|
|
2067
|
+
* @param {number} _frameStep
|
|
2068
|
+
* @param {number} _scrollDir
|
|
2069
|
+
* @param {number} _layerGroup
|
|
2070
|
+
* @param {number} _layerTrans
|
|
2071
|
+
*/
|
|
2072
|
+
const pushScrollchs = (_header, _frameArrow, _val, _frameStep, _scrollDir, _layerGroup, _layerTrans) => {
|
|
2073
|
+
const tkObj = getKeyInfo();
|
|
2074
|
+
g_stateObj.layerNum = Math.max(g_stateObj.layerNum, (_layerGroup + 1) * 2);
|
|
2075
|
+
|
|
2076
|
+
const frameArrow = Math.max(_frameArrow, g_scoreObj.frameNum);
|
|
2077
|
+
const frameStep = Math.max(_frameStep, g_scoreObj.frameNum);
|
|
2078
|
+
const pushData = (_pattern, _frame, _val) =>
|
|
2079
|
+
g_workObj[`mkScrollch${_pattern}`][_frame]?.push(_val) || (g_workObj[`mkScrollch${_pattern}`][_frame] = [_val]);
|
|
2080
|
+
const pushScrollData = _j => {
|
|
2081
|
+
pushData(`Arrow`, frameArrow, _j);
|
|
2082
|
+
pushData(`ArrowDir`, frameArrow, _scrollDir);
|
|
2083
|
+
pushData(`ArrowLayerGroup`, frameArrow, _layerGroup);
|
|
2084
|
+
pushData(`ArrowLayerTrans`, frameArrow, _layerTrans);
|
|
2085
|
+
pushData(`Step`, frameStep, _j);
|
|
2086
|
+
pushData(`StepDir`, frameStep, _scrollDir);
|
|
2087
|
+
pushData(`StepLayerGroup`, frameStep, _layerGroup);
|
|
2088
|
+
pushData(`StepLayerTrans`, frameStep, _layerTrans);
|
|
2089
|
+
};
|
|
2090
|
+
|
|
2091
|
+
if (_val < 20 || _val >= 1000) {
|
|
2092
|
+
const realVal = g_workObj.replaceNums[_val % 1000];
|
|
2093
|
+
pushScrollData(realVal);
|
|
2094
|
+
|
|
2095
|
+
} else {
|
|
2096
|
+
const colorNum = _val - 20;
|
|
2097
|
+
for (let j = 0; j < tkObj.keyNum; j++) {
|
|
2098
|
+
if (g_keyObj[`color${tkObj.keyCtrlPtn}`][j] === colorNum) {
|
|
2099
|
+
pushScrollData(j);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
|
|
2105
|
+
/**
|
|
2106
|
+
* ホワイトリストに登録されたCSS関数内の数値符号のみを反転する関数
|
|
2107
|
+
* @param {string} cssString
|
|
2108
|
+
* @param {string[]} exceptList 反転対象から除外するCSS関数名のリスト
|
|
2109
|
+
* @returns {string}
|
|
2110
|
+
*/
|
|
2111
|
+
const invertSpecificTransforms = (cssString, exceptList = []) => {
|
|
2112
|
+
// 反転対象にしたいCSS関数名を指定(ホワイトリスト)
|
|
2113
|
+
const baseTargetFunctions = [
|
|
2114
|
+
`rotate`, `rotateX`, `rotateY`, `rotateZ`, `rotate3d`,
|
|
2115
|
+
`translate`, `translateX`, `translateY`, `translateZ`, `translate3d`,
|
|
2116
|
+
`skew`, `skewX`, `skewY`
|
|
2117
|
+
];
|
|
2118
|
+
const targetFunctions = baseTargetFunctions.filter(pattern => !exceptList.includes(pattern));
|
|
2119
|
+
if (targetFunctions.length === 0) {
|
|
2120
|
+
return cssString; // 反転対象がない場合は元の文字列を返す
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// RegExパターンを作成: /(rotate|translate...)\(([^)]+)\)/g
|
|
2124
|
+
const pattern = new RegExp(`\\b(${targetFunctions.join(`|`)})\\(([^)]+)\\)`, `g`);
|
|
2125
|
+
|
|
2126
|
+
return cssString.replace(pattern, (match, funcName, args) => {
|
|
2127
|
+
// 関数の中身(引数)に含まれる数値だけを符号反転
|
|
2128
|
+
const invertedArgs = args.replace(/(-?\d+(?:\.\d+)?)/g, (numStr) => {
|
|
2129
|
+
return numStr.startsWith(`-`) ? numStr.slice(1) : `-` + numStr;
|
|
2130
|
+
});
|
|
2131
|
+
|
|
2132
|
+
return `${funcName}(${invertedArgs})`;
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
/**
|
|
2137
|
+
* メイン画面前の初期化処理
|
|
2138
|
+
*/
|
|
2139
|
+
const getArrowSettings = () => {
|
|
2140
|
+
|
|
2141
|
+
g_attrObj = {};
|
|
2142
|
+
const tkObj = getKeyInfo();
|
|
2143
|
+
const [keyCtrlPtn, keyNum, posMax, divideCnt] =
|
|
2144
|
+
[tkObj.keyCtrlPtn, tkObj.keyNum, tkObj.posMax, tkObj.divideCnt];
|
|
2145
|
+
|
|
2146
|
+
g_keyCopyLists.simpleDef.forEach(header => updateKeyInfo(header, keyCtrlPtn));
|
|
2147
|
+
g_headerObj.tuning = g_headerObj.creatorNames[g_stateObj.scoreId];
|
|
2148
|
+
|
|
2149
|
+
// 各種初期化
|
|
2150
|
+
// g_workObj.frzArrowInitRtnはフリーズアロー(初期表示)としての利用に限定
|
|
2151
|
+
g_workObj.stepX = [];
|
|
2152
|
+
g_workObj.stepX_df = [];
|
|
2153
|
+
g_workObj.scrollDir = [];
|
|
2154
|
+
g_workObj.scrollDirDefault = [];
|
|
2155
|
+
g_workObj.dividePos = [];
|
|
2156
|
+
g_workObj.scale = g_keyObj.scale;
|
|
2157
|
+
g_workObj.stepRtn = structuredClone(g_keyObj[`stepRtn${keyCtrlPtn}`]);
|
|
2158
|
+
g_workObj.stepHitRtn = structuredClone(g_keyObj[`stepRtn${keyCtrlPtn}`]);
|
|
2159
|
+
g_workObj.arrowRtn = structuredClone(g_keyObj[`stepRtn${keyCtrlPtn}`]);
|
|
2160
|
+
g_workObj.frzArrowInitRtn = structuredClone(g_keyObj[`stepRtn${keyCtrlPtn}`]);
|
|
2161
|
+
g_workObj.keyCtrl = structuredClone(g_keyObj[`keyCtrl${keyCtrlPtn}`]);
|
|
2162
|
+
g_workObj.diffList = [];
|
|
2163
|
+
g_workObj.mainEndTime = 0;
|
|
2164
|
+
g_workObj.currentLifeState = ``;
|
|
2165
|
+
g_workObj.layerTrans = [];
|
|
2166
|
+
g_errorCache['g_customJsObj.mainEnterFrame'] = [];
|
|
2167
|
+
|
|
2168
|
+
const rotateBy = (val, delta) => {
|
|
2169
|
+
// numeric
|
|
2170
|
+
const n = Number(val);
|
|
2171
|
+
if (Number.isFinite(n)) return n + delta;
|
|
2172
|
+
|
|
2173
|
+
// "type:deg"
|
|
2174
|
+
const [type, degStr = `0`] = String(val).split(`:`);
|
|
2175
|
+
const deg = Number(degStr);
|
|
2176
|
+
return Number.isFinite(deg) ? `${type}:${deg + delta}` : val;
|
|
2177
|
+
};
|
|
2178
|
+
const changeStepRtn = (_name, _angle) =>
|
|
2179
|
+
g_workObj[_name] = g_workObj[_name].map(v => rotateBy(v, _angle));
|
|
2180
|
+
|
|
2181
|
+
if (g_stateObj.playWindow === `SideScroll`) {
|
|
2182
|
+
if (g_stateObj.rotateEnabled) {
|
|
2183
|
+
const sign = g_stateObj.playWindowType === C_FLG_REVERSE2 ? -1 : 1;
|
|
2184
|
+
changeStepRtn(`stepRtn`, 90 * sign);
|
|
2185
|
+
changeStepRtn(`stepHitRtn`, 90 * sign);
|
|
2186
|
+
changeStepRtn(`arrowRtn`, 90 * sign);
|
|
2187
|
+
changeStepRtn(`frzArrowInitRtn`, 90 * sign);
|
|
2188
|
+
}
|
|
2189
|
+
const div = g_keyObj[`div${keyCtrlPtn}`];
|
|
2190
|
+
const divMax = g_keyObj[`divMax${keyCtrlPtn}`] ?? posMax;
|
|
2191
|
+
const denom = (Math.max(div, divMax - div) + 1) * g_keyObj.blank;
|
|
2192
|
+
if (Number.isFinite(denom) && denom > 0) {
|
|
2193
|
+
g_workObj.scale *= Math.min(g_sHeight / denom, 1);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// CamoufrageType: FrzArrowの場合のみ、フリーズアロー(初期)の矢印を180度回転
|
|
2198
|
+
if (g_stateObj.rotateEnabled && g_stateObj.camoufrageType === `FrzArrow`) {
|
|
2199
|
+
changeStepRtn(`frzArrowInitRtn`, 180);
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
g_workObj.keyGroupMaps = tkObj.keyGroupMaps;
|
|
2203
|
+
g_workObj.keyGroupList = tkObj.keyGroupList;
|
|
2204
|
+
|
|
2205
|
+
if (g_keyObj[`layerTrans${keyCtrlPtn}`]?.[0]) {
|
|
2206
|
+
g_workObj.layerTrans = structuredClone(g_keyObj[`layerTrans${keyCtrlPtn}`][0]);
|
|
2207
|
+
if (g_stateObj.reverse === C_FLG_ON) {
|
|
2208
|
+
// Reverse時はX軸反転のため、X軸の座標変換では符号を変換しない
|
|
2209
|
+
g_workObj.layerTrans = g_workObj.layerTrans.map(val => invertSpecificTransforms(val, [`translateX`]));
|
|
2210
|
+
}
|
|
2211
|
+
if (g_stateObj.swapping === `Mirror+`) {
|
|
2212
|
+
// Swapping: Mirror+ はY軸反転のため、Y軸の座標変換では符号を変換しない
|
|
2213
|
+
g_workObj.layerTrans = g_workObj.layerTrans.map(val => invertSpecificTransforms(val, [`translateY`]));
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
const keyCtrlLen = g_workObj.keyCtrl.length;
|
|
2218
|
+
g_workObj.keyCtrlN = [...Array(keyCtrlLen)].map(() => []);
|
|
2219
|
+
g_workObj.keyHitFlg = [...Array(keyCtrlLen)].map(() => []);
|
|
2220
|
+
for (let j = 0; j < g_workObj.keyCtrl.length; j++) {
|
|
2221
|
+
for (let k = 0; k < g_workObj.keyCtrl[j].length; k++) {
|
|
2222
|
+
g_workObj.keyCtrlN[j][k] = g_kCdN[g_workObj.keyCtrl[j][k]];
|
|
2223
|
+
g_workObj.keyHitFlg[j][k] = false;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
g_typeLists.arrow.forEach(type => g_workObj[`judg${toCapitalize(type)}Cnt`] = fillArray(keyNum, 1));
|
|
2228
|
+
g_workObj.judgFrzHitCnt = fillArray(keyNum, 1);
|
|
2229
|
+
g_judgObj.lockFlgs = fillArray(keyNum, false);
|
|
2230
|
+
|
|
2231
|
+
// 矢印色管理 (個別・全体)
|
|
2232
|
+
const eachOrAll = [``, `All`];
|
|
2233
|
+
eachOrAll.forEach(type => {
|
|
2234
|
+
[`arrow`, `dummyArrow`].forEach(arrowType =>
|
|
2235
|
+
g_typeLists.arrowColor.forEach(objType => g_workObj[`${arrowType}${objType}Colors${type}`] = []));
|
|
2236
|
+
|
|
2237
|
+
[`frz`, `dummyFrz`].forEach(arrowType =>
|
|
2238
|
+
g_typeLists.frzColor.forEach(frzType => g_workObj[`${arrowType}${frzType}Colors${type}`] = []));
|
|
2239
|
+
});
|
|
2240
|
+
|
|
2241
|
+
// モーション管理
|
|
2242
|
+
g_typeLists.arrow.forEach(type => {
|
|
2243
|
+
g_workObj[`${type}CssMotions`] = fillArray(keyNum, ``);
|
|
2244
|
+
g_workObj[`${type}MovLock`] = fillArray(keyNum, ``);
|
|
2245
|
+
g_workObj[`${type}InitManual`] = fillArray(keyNum, ``);
|
|
2246
|
+
});
|
|
2247
|
+
g_workObj.frzArrowCssMotions = fillArray(keyNum, ``);
|
|
2248
|
+
g_workObj.dummyFrzArrowCssMotions = fillArray(keyNum, ``);
|
|
2249
|
+
|
|
2250
|
+
const scrollDirOptions = g_keyObj[`scrollDir${keyCtrlPtn}`]?.[g_stateObj.scroll] ?? fillArray(keyNum, 1);
|
|
2251
|
+
|
|
2252
|
+
g_stateObj.autoAll = boolToSwitch(g_stateObj.autoPlay === C_FLG_ALL);
|
|
2253
|
+
g_workObj.hitPosition = (g_stateObj.autoAll === C_FLG_ON ? 0 : g_stateObj.hitPosition);
|
|
2254
|
+
changeSetColor();
|
|
2255
|
+
|
|
2256
|
+
for (let j = 0; j < keyNum; j++) {
|
|
2257
|
+
|
|
2258
|
+
const posj = g_keyObj[`pos${keyCtrlPtn}`][j];
|
|
2259
|
+
const colorj = g_keyObj[`color${keyCtrlPtn}`][j];
|
|
2260
|
+
let stdPos = posj - ((posj > divideCnt ? posMax : 0) + divideCnt) / 2;
|
|
2261
|
+
|
|
2262
|
+
if (g_stateObj.swapping === `Mirror+`) {
|
|
2263
|
+
g_workObj.stepX_df[j] = g_keyObj.blank * stdPos + (g_headerObj.playingWidth - C_ARW_WIDTH) / 2;
|
|
2264
|
+
stdPos = -stdPos;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
g_workObj.stepX[j] = g_keyObj.blank * stdPos + (g_headerObj.playingWidth - C_ARW_WIDTH) / 2;
|
|
2268
|
+
const baseLayer = g_keyObj[`layerGroup${keyCtrlPtn}`]?.[j] || 0;
|
|
2269
|
+
g_workObj.dividePos[j] = baseLayer * 2 + ((posj <= divideCnt ? 0 : 1) + (scrollDirOptions[j] === 1 ? 0 : 1) + (g_stateObj.reverse === C_FLG_OFF ? 0 : 1)) % 2;
|
|
2270
|
+
g_workObj.scrollDir[j] = (posj <= divideCnt ? 1 : -1) * scrollDirOptions[j] * (g_stateObj.reverse === C_FLG_OFF ? 1 : -1);
|
|
2271
|
+
|
|
2272
|
+
// 個別色設定
|
|
2273
|
+
g_workObj.arrowColors[j] = g_headerObj.setColor[colorj];
|
|
2274
|
+
g_workObj.dummyArrowColors[j] = g_headerObj.setDummyColor[colorj];
|
|
2275
|
+
g_workObj.arrowShadowColors[j] = g_headerObj.setShadowColor[colorj] || ``;
|
|
2276
|
+
g_workObj.dummyArrowShadowColors[j] = g_headerObj.setDummyColor[colorj] || ``;
|
|
2277
|
+
|
|
2278
|
+
g_typeLists.frzColor.forEach((frzType, k) => {
|
|
2279
|
+
g_workObj[`frz${frzType}Colors`][j] = g_headerObj.frzColor[colorj][k] || ``;
|
|
2280
|
+
g_workObj[`dummyFrz${frzType}Colors`][j] =
|
|
2281
|
+
frzType.includes(`Shadow`) ? `` : g_headerObj.setDummyColor[colorj] || ``;
|
|
2282
|
+
});
|
|
2283
|
+
g_workObj.frzNormalShadowColors[j] = g_headerObj.frzShadowColor[colorj][0] || ``;
|
|
2284
|
+
g_workObj.frzHitShadowColors[j] = g_headerObj.frzShadowColor[colorj][1] || ``;
|
|
2285
|
+
|
|
2286
|
+
// 全体色設定
|
|
2287
|
+
g_workObj.arrowColorsAll[j] = ``;
|
|
2288
|
+
g_workObj.dummyArrowColorsAll[j] = ``;
|
|
2289
|
+
g_workObj.arrowShadowColorsAll[j] = ``;
|
|
2290
|
+
g_workObj.dummyArrowShadowColorsAll[j] = ``;
|
|
2291
|
+
|
|
2292
|
+
g_typeLists.frzColor.forEach((frzType, k) => {
|
|
2293
|
+
g_workObj[`frz${frzType}ColorsAll`][j] = ``;
|
|
2294
|
+
g_workObj[`dummyFrz${frzType}ColorsAll`][j] = ``;
|
|
2295
|
+
});
|
|
2296
|
+
g_workObj.frzNormalShadowColorsAll[j] = ``;
|
|
2297
|
+
g_workObj.frzHitShadowColorsAll[j] = ``;
|
|
2298
|
+
|
|
2299
|
+
}
|
|
2300
|
+
g_workObj.orgFlatFlg = g_workObj.dividePos.every(v => v % 2 === g_workObj.dividePos[0] % 2);
|
|
2301
|
+
g_stateObj.layerNumDf = Math.max(g_stateObj.layerNumDf, Math.ceil((Math.max(...g_workObj.dividePos) + 1) / 2) * 2);
|
|
2302
|
+
|
|
2303
|
+
// Swapping設定に応じたステップゾーンの入れ替え
|
|
2304
|
+
// Mirror+のみ事前にオリジナルの位置を設定済みのためスキップする
|
|
2305
|
+
if (g_workObj.stepX_df.length === 0) {
|
|
2306
|
+
g_workObj.stepX_df = structuredClone(g_workObj.stepX);
|
|
2307
|
+
}
|
|
2308
|
+
if (g_settings.swappingSubs.includes(g_stateObj.swapping)) {
|
|
2309
|
+
|
|
2310
|
+
// Swappingにおけるグループ単位での入れ替えでは、上下でステップゾーンが分かれている場合は分離してシャッフルする
|
|
2311
|
+
let _style = structuredClone(Object.values(g_workObj.shuffleGroupMap));
|
|
2312
|
+
const _styleTrans = _style.flatMap(arr => {
|
|
2313
|
+
// g_workObj.devidePosの値ごとにグループ化する
|
|
2314
|
+
const groups = {};
|
|
2315
|
+
|
|
2316
|
+
arr.forEach(n => {
|
|
2317
|
+
const div = g_workObj.dividePos[n];
|
|
2318
|
+
if (!groups[div]) groups[div] = [];
|
|
2319
|
+
groups[div].push(n);
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
// groups は {0:[...], 1:[...], 2:[...]} のような形になるので
|
|
2323
|
+
// これを配列に変換して返す
|
|
2324
|
+
return Object.values(groups);
|
|
2325
|
+
});
|
|
2326
|
+
const _styleTransDf = structuredClone(Object.values(_styleTrans));
|
|
2327
|
+
|
|
2328
|
+
if (g_stateObj.swapping === `Mirror` || g_stateObj.swapping === `OuterSwap`) {
|
|
2329
|
+
_styleTrans.map(_group => _group.reverse());
|
|
2330
|
+
}
|
|
2331
|
+
if (g_stateObj.swapping.endsWith(`Swap`)) {
|
|
2332
|
+
// グループの内側だけ入れ替える
|
|
2333
|
+
_styleTrans.forEach((group, i) => {
|
|
2334
|
+
g_settings.swapPattern.forEach(val => {
|
|
2335
|
+
swapGroupNums(_styleTrans, group, i, val);
|
|
2336
|
+
});
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
// オリジナルと同一の場合、設定をOFFに戻す
|
|
2340
|
+
if (JSON.stringify(_styleTransDf) === JSON.stringify(_styleTrans)) {
|
|
2341
|
+
g_stateObj.swapping = C_FLG_OFF;
|
|
2342
|
+
g_settings.swappingNum = 0;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// 入れ替えた結果に合わせてX座標位置を入れ替える
|
|
2346
|
+
_styleTrans.forEach((_group, _i) => {
|
|
2347
|
+
_group.forEach((_val, _j) => {
|
|
2348
|
+
g_workObj.stepX[_group[_j]] = g_workObj.stepX_df[_styleTransDf[_i][_j]];
|
|
2349
|
+
});
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
// StepArea(Default, Halfway以外)によるレイヤー移動
|
|
2354
|
+
// ずらした位置に表示するため、レイヤーを倍化して倍化した先に割り当てる
|
|
2355
|
+
const assignLayer = _func => {
|
|
2356
|
+
for (let j = 0; j < keyNum; j++) {
|
|
2357
|
+
g_workObj.dividePos[j] = (_func(j) ? 0 : 1) * g_stateObj.layerNumDf + g_workObj.dividePos[j];
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
if (g_stateObj.stepArea === `X-Flower` || (g_stateObj.stepArea.includes(`Mismatched`) && g_workObj.orgFlatFlg)) {
|
|
2361
|
+
assignLayer(j => g_workObj.stepX[j] < (g_headerObj.playingWidth - C_ARW_WIDTH) / 2);
|
|
2362
|
+
} else if (g_stateObj.stepArea === `Alt-Crossing`) {
|
|
2363
|
+
assignLayer(j => Math.round(g_keyObj[`pos${keyCtrlPtn}`][j]) % 2 === 0);
|
|
2364
|
+
}
|
|
2365
|
+
if (g_stateObj.stepArea === `2Step`) {
|
|
2366
|
+
for (let j = 0; j < keyNum; j++) {
|
|
2367
|
+
if (g_workObj.orgFlatFlg && g_workObj.stepX[j] >= (g_headerObj.playingWidth - C_ARW_WIDTH) / 2) {
|
|
2368
|
+
g_workObj.dividePos[j] = Math.floor(g_workObj.dividePos[j] / 2) * 2 + (g_workObj.dividePos[j] + 1) % 2;
|
|
2369
|
+
g_workObj.scrollDir[j] *= -1;
|
|
2370
|
+
}
|
|
2371
|
+
if (g_workObj.dividePos[j] % 2 === (Number(g_stateObj.reverse === C_FLG_ON) + 1) % 2) {
|
|
2372
|
+
g_workObj.dividePos[j] = g_stateObj.layerNumDf + g_workObj.dividePos[j] + Number(g_stateObj.reverse === C_FLG_ON ? 1 : -1);
|
|
2373
|
+
g_workObj.scrollDir[j] *= -1;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
g_workObj.scrollDirDefault = g_workObj.scrollDir.concat();
|
|
2378
|
+
g_workObj.dividePosDefault = g_workObj.dividePos.concat();
|
|
2379
|
+
g_stateObj.layerNum = g_stateObj.layerNumDf * (g_settings.stepAreaLayers.includes(g_stateObj.stepArea) ? 2 : 1);
|
|
2380
|
+
|
|
2381
|
+
// g_workObjの不要なプロパティを削除
|
|
2382
|
+
if (g_stateObj.dummyId === `` && g_autoPlaysBase.includes(g_stateObj.autoPlay)) {
|
|
2383
|
+
Object.keys(g_workObj).filter(key => key.startsWith(`dummy`) || key.startsWith(`mkDummy`))
|
|
2384
|
+
.forEach(key => delete g_workObj[key]);
|
|
2385
|
+
}
|
|
2386
|
+
const targetColorKeys = [`mkColor`, `mkColorShadow`];
|
|
2387
|
+
const usedColorKeys = [];
|
|
2388
|
+
targetColorKeys.push(...g_typeLists.frzColor.map(type => `mkFColor${type}`));
|
|
2389
|
+
targetColorKeys.forEach(key => {
|
|
2390
|
+
if (g_workObj[key].length === 0) {
|
|
2391
|
+
delete g_workObj[key];
|
|
2392
|
+
delete g_workObj[`${key}Cd`];
|
|
2393
|
+
} else {
|
|
2394
|
+
usedColorKeys.push(key);
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
[`Arrow`, `Step`].forEach(type => {
|
|
2398
|
+
[``, `Dir`, `LayerGroup`, `LayerTrans`].forEach(type2 => {
|
|
2399
|
+
if (g_workObj[`mkScrollch${type}${type2}`].length === 0) {
|
|
2400
|
+
delete g_workObj[`mkScrollch${type}${type2}`];
|
|
2401
|
+
}
|
|
2402
|
+
});
|
|
2403
|
+
});
|
|
2404
|
+
[`Arrow`, `Frz`].forEach(type => {
|
|
2405
|
+
if (g_workObj[`mk${type}ColorChangeAll`].length === 0) {
|
|
2406
|
+
delete g_workObj[`mk${type}ColorChangeAll`];
|
|
2407
|
+
}
|
|
2408
|
+
[``, `Name`].forEach(type2 => {
|
|
2409
|
+
if (g_workObj[`mk${type}CssMotion${type2}`].length === 0) {
|
|
2410
|
+
delete g_workObj[`mk${type}CssMotion${type2}`];
|
|
2411
|
+
}
|
|
2412
|
+
});
|
|
2413
|
+
});
|
|
2414
|
+
|
|
2415
|
+
// 初期位置、ライフ設定の初期化
|
|
2416
|
+
Object.keys(g_resultObj).forEach(judgeCnt => g_resultObj[judgeCnt] = 0);
|
|
2417
|
+
g_resultObj.spState = ``;
|
|
2418
|
+
g_resultObj.gaugeTransition = [];
|
|
2419
|
+
|
|
2420
|
+
g_displays.forEach(_disp => {
|
|
2421
|
+
const lowerDisp = _disp.toLowerCase();
|
|
2422
|
+
g_workObj[`${lowerDisp}Disp`] = (g_stateObj[`d_${lowerDisp}`] === C_FLG_OFF ? C_DIS_NONE : C_DIS_INHERIT);
|
|
2423
|
+
});
|
|
2424
|
+
g_workObj.judgmentDisp = g_stateObj.d_judgment !== C_FLG_ON ? C_DIS_NONE : C_DIS_INHERIT;
|
|
2425
|
+
g_workObj.fastslowDisp = g_stateObj.d_judgment === C_FLG_OFF ? C_DIS_NONE : C_DIS_INHERIT;
|
|
2426
|
+
|
|
2427
|
+
g_workObj.lifeVal = Math.floor(g_workObj.lifeInit * 100) / 100;
|
|
2428
|
+
g_workObj.arrowReturnVal = 0;
|
|
2429
|
+
g_gameOverFlg = false;
|
|
2430
|
+
g_finishFlg = true;
|
|
2431
|
+
g_workObj.nonDefaultSc = g_headerObj.keyRetry !== C_KEY_RETRY || g_headerObj.keyTitleBack !== C_KEY_TITLEBACK || g_headerObj.keyPause !== C_KEY_PAUSE;
|
|
2432
|
+
if (g_headerObj.scAreaWidth === 0 && (
|
|
2433
|
+
g_headerObj.keyRetry !== g_headerObj.keyRetryDef2 ||
|
|
2434
|
+
g_headerObj.keyTitleBack !== g_headerObj.keyTitleBackDef2 ||
|
|
2435
|
+
g_headerObj.keyPause !== g_headerObj.keyPauseDef2
|
|
2436
|
+
)) {
|
|
2437
|
+
g_workObj.nonDefaultSc = false;
|
|
2438
|
+
}
|
|
2439
|
+
if (g_diffObj.shortcutX !== 0 || g_diffObj.shortcutY !== 0) {
|
|
2440
|
+
g_workObj.nonDefaultSc = true;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
g_workObj.backX = (g_workObj.nonDefaultSc && g_headerObj.playingLayout ? g_headerObj.scAreaWidth : 0);
|
|
2444
|
+
g_workObj.playingX = g_headerObj.playingX + g_workObj.backX;
|
|
2445
|
+
|
|
2446
|
+
// FrzReturnの初期化
|
|
2447
|
+
g_workObj.frzReturnFlg = false;
|
|
2448
|
+
g_workObj.frzReturnSeq = g_frzReturnSeqFunc.get(g_stateObj.frzReturnType)();
|
|
2449
|
+
if (g_workObj.frzReturnTimerId) {
|
|
2450
|
+
g_timerHandler.clearTimeout(g_workObj.frzReturnTimerId);
|
|
2451
|
+
g_workObj.frzReturnTimerId = null;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
// Camoufrageの設定
|
|
2455
|
+
if (g_stateObj.camoufrage !== C_FLG_OFF) {
|
|
2456
|
+
|
|
2457
|
+
const eachOrAll = [``, `All`];
|
|
2458
|
+
const keyNum = g_keyObj[`chara${g_keyObj.currentKey}_${g_keyObj.currentPtn}`].length;
|
|
2459
|
+
|
|
2460
|
+
// 位置変更用の配列を作成
|
|
2461
|
+
const randArray = [...Array(keyNum).keys()].map(_i => _i);
|
|
2462
|
+
let _i = randArray.length;
|
|
2463
|
+
while (_i) {
|
|
2464
|
+
const _j = Math.floor(Math.random() * _i--);
|
|
2465
|
+
[randArray[_i], randArray[_j]] = [randArray[_j], randArray[_i]];
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
// 位置変更用の配列に従い、初期矢印・初期色の位置変更
|
|
2469
|
+
const getSwapArray = (_array) => {
|
|
2470
|
+
const _copiedArray = structuredClone(_array);
|
|
2471
|
+
return _array.map((_val, _i) => _array[_i] = _copiedArray[randArray[_i]]);
|
|
2472
|
+
};
|
|
2473
|
+
|
|
2474
|
+
// 矢印ヒット時に元の矢印がわかるようにするため、あえて g_workObj.stepHitRtn はそのままにする
|
|
2475
|
+
if (g_stateObj.camoufrage.includes(`Step`)) {
|
|
2476
|
+
g_workObj.stepRtn = getSwapArray(g_workObj.stepRtn);
|
|
2477
|
+
}
|
|
2478
|
+
if (g_stateObj.camoufrage.includes(`Arrow`)) {
|
|
2479
|
+
g_workObj.arrowRtn = getSwapArray(g_workObj.arrowRtn);
|
|
2480
|
+
g_workObj.frzArrowInitRtn = getSwapArray(g_workObj.frzArrowInitRtn);
|
|
2481
|
+
}
|
|
2482
|
+
if (g_stateObj.camoufrage.includes(`Color`)) {
|
|
2483
|
+
eachOrAll.forEach(type => {
|
|
2484
|
+
// ダミー矢印は対象外
|
|
2485
|
+
g_workObj[`arrowColors${type}`] = getSwapArray(g_workObj[`arrowColors${type}`]);
|
|
2486
|
+
g_workObj[`arrowShadowColors${type}`] = getSwapArray(g_workObj[`arrowShadowColors${type}`]);
|
|
2487
|
+
|
|
2488
|
+
g_typeLists.frzColor.forEach(frzType => {
|
|
2489
|
+
g_workObj[`frz${frzType}Colors${type}`] = getSwapArray(g_workObj[`frz${frzType}Colors${type}`]);
|
|
2490
|
+
});
|
|
2491
|
+
g_workObj[`frzNormalShadowColors${type}`] = getSwapArray(g_workObj[`frzNormalShadowColors${type}`]);
|
|
2492
|
+
g_workObj[`frzHitShadowColors${type}`] = getSwapArray(g_workObj[`frzHitShadowColors${type}`]);
|
|
2493
|
+
});
|
|
2494
|
+
|
|
2495
|
+
// 位置変更用の配列に従い、個別・全体色変化の位置変更
|
|
2496
|
+
const getSwapList = (_array) => {
|
|
2497
|
+
const _copiedArray = structuredClone(_array);
|
|
2498
|
+
return _array.map((_val, _i) => _array[_i] = randArray[_copiedArray[_i]]);
|
|
2499
|
+
};
|
|
2500
|
+
usedColorKeys.filter(type => g_workObj[type] !== undefined).forEach(type => {
|
|
2501
|
+
for (let j = 0; j < g_workObj[type].length; j++) {
|
|
2502
|
+
if (g_workObj[type][j] === undefined) {
|
|
2503
|
+
continue;
|
|
2504
|
+
}
|
|
2505
|
+
g_workObj[type][j] = getSwapList(g_workObj[type][j]);
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// Shaking: Drunkでの画面揺れ設定 (X方向、Y方向、移動軸条件、回転軸条件)
|
|
2512
|
+
g_workObj.drunkXFlg = false;
|
|
2513
|
+
g_workObj.drunkYFlg = false;
|
|
2514
|
+
g_workObj.drunkAxisFlg = false;
|
|
2515
|
+
g_workObj.drunkRotateFlg = false;
|
|
2516
|
+
|
|
2517
|
+
// AppearanceFilterの可視範囲設定
|
|
2518
|
+
g_workObj.aprFilterCnt = 0;
|
|
2519
|
+
|
|
2520
|
+
// キー別の移動ロック制御、初期位置のマニュアル可否設定の初期値
|
|
2521
|
+
// 矢印・フリーズアローモーションよりも優先される
|
|
2522
|
+
g_workObj.movLockEnabled = g_keyObj[`movLock${g_keyObj.currentKey}`] === true;
|
|
2523
|
+
g_workObj.initManualEnabled = g_keyObj[`initManual${g_keyObj.currentKey}`] === true;
|
|
2524
|
+
|
|
2525
|
+
if (g_stateObj.dataSaveFlg) {
|
|
2526
|
+
// ローカルストレージへAdjustment, HitPosition, Volume設定を保存
|
|
2527
|
+
// 変更が確定した時点で表示用のキャッシュを解放
|
|
2528
|
+
g_storeSettings.forEach(setting => g_localStorage[setting] = g_stateObj[setting]);
|
|
2529
|
+
viewKeyStorage.cache = new Map();
|
|
2530
|
+
localStorage.setItem(g_localStorageUrl, JSON.stringify(g_localStorage));
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
// リバース、キーコンフィグなどをローカルストレージへ保存(Data Save: ON かつ別キーモードで無い場合)
|
|
2534
|
+
if (g_stateObj.dataSaveFlg && !hasVal(g_keyObj[`transKey${keyCtrlPtn}`])) {
|
|
2535
|
+
|
|
2536
|
+
// 次回キーコンフィグ画面へ戻ったとき、保存済みキーコンフィグ設定が表示されるようにする
|
|
2537
|
+
g_keyObj.prevKey = `Dummy`;
|
|
2538
|
+
|
|
2539
|
+
// ローカルストレージへcolorType設定を保存
|
|
2540
|
+
g_localStorage.colorType = g_colorType;
|
|
2541
|
+
|
|
2542
|
+
let storageObj = g_localKeyStorage;
|
|
2543
|
+
let addKey = ``;
|
|
2544
|
+
|
|
2545
|
+
// リバース、キーコンフィグの保存(キー別)
|
|
2546
|
+
if (g_stateObj.extraKeyFlg) {
|
|
2547
|
+
storageObj = g_localStorage;
|
|
2548
|
+
addKey = g_keyObj.currentKey;
|
|
2549
|
+
}
|
|
2550
|
+
if (g_headerObj.reverseUse) {
|
|
2551
|
+
storageObj[`reverse${addKey}`] = g_stateObj.reverse;
|
|
2552
|
+
}
|
|
2553
|
+
storageObj[`keyCtrl${addKey}`] = setKeyCtrl(g_localKeyStorage, keyNum, keyCtrlPtn);
|
|
2554
|
+
if (g_keyObj.currentPtn !== -1) {
|
|
2555
|
+
storageObj[`keyCtrlPtn${addKey}`] = g_keyObj.currentPtn;
|
|
2556
|
+
g_keyObj[`keyCtrl${keyCtrlPtn}`] = structuredClone(g_keyObj[`keyCtrl${keyCtrlPtn}d`]);
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// カラーセットの保存(キー別)
|
|
2560
|
+
if (!g_keycons.colorDefTypes.includes(g_colorType)) {
|
|
2561
|
+
|
|
2562
|
+
resetColorType({ _toObj: storageObj, _to: addKey });
|
|
2563
|
+
resetColorType({ _from: g_colorType, _to: g_colorType, _fromObj: g_dfColorObj });
|
|
2564
|
+
|
|
2565
|
+
g_colorType = g_keycons.colorSelf;
|
|
2566
|
+
g_localStorage.colorType = g_keycons.colorSelf;
|
|
2567
|
+
g_keycons.colorTypes = addValtoArray(g_keycons.colorTypes, g_keycons.colorSelf);
|
|
2568
|
+
resetColorType({ _to: g_keycons.colorSelf });
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
g_keycons.groups.forEach(type => {
|
|
2572
|
+
const groupNum = g_keycons[`${type}GroupNum`];
|
|
2573
|
+
storageObj[`${type}${addKey}`] = structuredClone(g_keyObj[`${type}${keyCtrlPtn}_${groupNum}`]);
|
|
2574
|
+
g_keyObj[`${type}${g_keyObj.currentKey}_-1_${groupNum}`] = structuredClone(g_keyObj[`${type}${keyCtrlPtn}_${groupNum}d`]);
|
|
2575
|
+
g_keyObj[`${type}${keyCtrlPtn}_${groupNum}`] = structuredClone(g_keyObj[`${type}${keyCtrlPtn}_${groupNum}d`]);
|
|
2576
|
+
|
|
2577
|
+
// 古いキーデータの削除 (互換用)
|
|
2578
|
+
if (storageObj[`${type}${g_keyObj.currentKey}_-1_-1`] !== undefined) {
|
|
2579
|
+
delete storageObj[`${type}${g_keyObj.currentKey}_-1_-1`];
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2583
|
+
if (!g_stateObj.extraKeyFlg) {
|
|
2584
|
+
localStorage.setItem(`danonicw-${g_keyObj.currentKey}k`, JSON.stringify(g_localKeyStorage));
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
localStorage.setItem(g_localStorageUrl, JSON.stringify(g_localStorage));
|
|
2588
|
+
g_canLoadDifInfoFlg = true;
|
|
2589
|
+
|
|
2590
|
+
} else {
|
|
2591
|
+
// データ未保存 もしくは 別キーモード時はキーコンフィグ設定を初期化しない
|
|
2592
|
+
g_keyObj.prevKey = g_keyObj.currentKey;
|
|
2593
|
+
g_canLoadDifInfoFlg = false;
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
|
|
2597
|
+
/**
|
|
2598
|
+
* キーコンフィグ保存処理
|
|
2599
|
+
* @param {object} _localStorage 保存先のローカルストレージ名
|
|
2600
|
+
* @param {number} _keyNum
|
|
2601
|
+
* @param {string} _keyCtrlPtn
|
|
2602
|
+
* @returns {any[][]}
|
|
2603
|
+
*/
|
|
2604
|
+
const setKeyCtrl = (_localStorage, _keyNum, _keyCtrlPtn) => {
|
|
2605
|
+
const localPtn = `${g_keyObj.currentKey}_-1`;
|
|
2606
|
+
const keyCtrl = [...Array(_keyNum)].map(() => []);
|
|
2607
|
+
for (let j = 0; j < _keyNum; j++) {
|
|
2608
|
+
for (let k = 0; k < g_keyObj[`keyCtrl${_keyCtrlPtn}`][j].length; k++) {
|
|
2609
|
+
keyCtrl[j][k] = g_keyObj[`keyCtrl${_keyCtrlPtn}`][j][k];
|
|
2610
|
+
}
|
|
2611
|
+
if (g_keyObj[`keyCtrl${localPtn}`] !== undefined) {
|
|
2612
|
+
if (g_keyObj[`keyCtrl${_keyCtrlPtn}`][j].length < g_keyObj[`keyCtrl${localPtn}`][j].length) {
|
|
2613
|
+
for (let k = g_keyObj[`keyCtrl${_keyCtrlPtn}`][j].length; k < g_keyObj[`keyCtrl${localPtn}`][j].length; k++) {
|
|
2614
|
+
keyCtrl[j][k] = undefined;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
return keyCtrl;
|
|
2620
|
+
};
|
|
2621
|
+
|
|
2622
|
+
/*-----------------------------------------------------------*/
|
|
2623
|
+
/* その他の共通設定 */
|
|
2624
|
+
/*-----------------------------------------------------------*/
|
|
2625
|
+
|
|
2626
|
+
// WebAudioAPIでAudio要素風に再生するクラス
|
|
2627
|
+
class AudioPlayer {
|
|
2628
|
+
constructor() {
|
|
2629
|
+
this._context = getSharedAudioContext();
|
|
2630
|
+
this._gain = this._context.createGain();
|
|
2631
|
+
this._gain.connect(this._context.destination);
|
|
2632
|
+
this._startTime = 0;
|
|
2633
|
+
this._scheduledTime = 0;
|
|
2634
|
+
this._fadeinPosition = 0;
|
|
2635
|
+
this._eventListeners = {};
|
|
2636
|
+
this.playbackRate = 1;
|
|
2637
|
+
this._muted = false;
|
|
2638
|
+
this._savedVolume = 1;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
async init(_arrayBuffer) {
|
|
2642
|
+
this._arrayBuffer = _arrayBuffer;
|
|
2643
|
+
await this._context.decodeAudioData(this._arrayBuffer, _buffer => {
|
|
2644
|
+
this._duration = _buffer.duration;
|
|
2645
|
+
this._buffer = _buffer;
|
|
2646
|
+
});
|
|
2647
|
+
this._eventListeners[`canplaythrough`]?.forEach(_listener => _listener());
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
/**
|
|
2651
|
+
* 再生処理
|
|
2652
|
+
* @param {number} _adjustmentTime
|
|
2653
|
+
* - 実際の再生開始時間は、scheduleLead + _adjustmentTime から開始される
|
|
2654
|
+
* - ただしゲーム内での経過時間計算は _adjustmentTime を基準に行う
|
|
2655
|
+
* - scheduleLead は安定した再生タイミングを確保するための内部マージン
|
|
2656
|
+
*/
|
|
2657
|
+
play(_adjustmentTime = 0) {
|
|
2658
|
+
// AudioContextの時計は1回だけ読み、以降はその値を使い回す
|
|
2659
|
+
// - currentTimeはレンダークォンタム単位でしか進まないため、複数回読むと
|
|
2660
|
+
// 予約時刻と論理開始時刻が最大1クォンタム分ずれる
|
|
2661
|
+
const ctxNow = this._context.currentTime;
|
|
2662
|
+
|
|
2663
|
+
this._source = this._context.createBufferSource();
|
|
2664
|
+
this._source.buffer = this._buffer;
|
|
2665
|
+
this._source.playbackRate.value = this.playbackRate;
|
|
2666
|
+
this._source.connect(this._gain);
|
|
2667
|
+
|
|
2668
|
+
// 実際の予約時刻(内部スケジューリング用のマージンを含む)
|
|
2669
|
+
const startAt = ctxNow + g_scheduleLead + _adjustmentTime;
|
|
2670
|
+
this._source.start(startAt, this._fadeinPosition);
|
|
2671
|
+
|
|
2672
|
+
// ゲーム側の論理的開始時刻(g_scheduleLead を含めない)
|
|
2673
|
+
this._startTime = ctxNow + _adjustmentTime;
|
|
2674
|
+
|
|
2675
|
+
// 実際に音が鳴り始めるAudioContext上の時刻(フレーム同期の基準)
|
|
2676
|
+
this._scheduledTime = startAt;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
pause() {
|
|
2680
|
+
if (this._source) {
|
|
2681
|
+
this._source.stop(0);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
close() {
|
|
2686
|
+
if (this._source) {
|
|
2687
|
+
this._source.stop(0);
|
|
2688
|
+
this._source.disconnect(this._gain);
|
|
2689
|
+
this._source = null;
|
|
2690
|
+
}
|
|
2691
|
+
if (this._gain) {
|
|
2692
|
+
this._gain.disconnect();
|
|
2693
|
+
this._gain = null;
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
get elapsedTime() {
|
|
2698
|
+
return (this._context.currentTime - this._scheduledTime) * this.playbackRate + g_scheduleLead + this._fadeinPosition;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
/** AudioContextの現在時刻(秒) */
|
|
2702
|
+
get contextTime() {
|
|
2703
|
+
return this._context.currentTime;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
/** 実際に音が鳴り始めるAudioContext上の時刻(秒) */
|
|
2707
|
+
get scheduledTime() {
|
|
2708
|
+
return this._scheduledTime;
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
/** 音が出力デバイスから実際に出るまでの遅延(秒) */
|
|
2712
|
+
get outputLatency() {
|
|
2713
|
+
return this._context.outputLatency || this._context.baseLatency || 0;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
/** AudioContextの状態(running / suspended / closed) */
|
|
2717
|
+
get contextState() {
|
|
2718
|
+
return this._context.state;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
set currentTime(_currentTime) {
|
|
2722
|
+
this._fadeinPosition = _currentTime;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
get volume() {
|
|
2726
|
+
// ミュート中でも設定されている音量を返す
|
|
2727
|
+
return this._muted ? this._savedVolume : this._gain.gain.value;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
set volume(_volume) {
|
|
2731
|
+
if (this._muted) {
|
|
2732
|
+
// ミュート中でも音量設定は保存
|
|
2733
|
+
this._savedVolume = _volume;
|
|
2734
|
+
} else {
|
|
2735
|
+
this._gain.gain.value = _volume;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
get duration() {
|
|
2740
|
+
return this._duration;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
get readyState() {
|
|
2744
|
+
if (this._duration) {
|
|
2745
|
+
return 4;
|
|
2746
|
+
} else {
|
|
2747
|
+
return 0;
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
get muted() {
|
|
2752
|
+
return this._muted;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
set muted(_muted) {
|
|
2756
|
+
if (this._muted === _muted) {
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
this._muted = _muted;
|
|
2760
|
+
|
|
2761
|
+
if (_muted) {
|
|
2762
|
+
// ミュート時:現在の音量を保存してゲインを0に
|
|
2763
|
+
this._savedVolume = this._gain.gain.value;
|
|
2764
|
+
this._gain.gain.value = 0;
|
|
2765
|
+
} else {
|
|
2766
|
+
// ミュート解除時:保存した音量を復元
|
|
2767
|
+
this._gain.gain.value = this._savedVolume;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
addEventListener(_type, _listener) {
|
|
2772
|
+
this._eventListeners[_type]?.push(_listener) || (this._eventListeners[_type] = [_listener]);
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
removeEventListener(_type, _listener) {
|
|
2776
|
+
if (this._eventListeners[_type] === undefined) {
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
this._eventListeners[_type] = this._eventListeners[_type].filter(_element => _element !== _listener);
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
getBuffer() {
|
|
2783
|
+
return this._buffer;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
setBuffer(_buffer) {
|
|
2787
|
+
this._buffer = _buffer;
|
|
2788
|
+
this._duration = _buffer.duration;
|
|
2789
|
+
this._eventListeners[`canplaythrough`]?.forEach(_listener => _listener());
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
load() { }
|
|
2793
|
+
dispatchEvent() { }
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
// グローバルで1つだけ保持(遅延生成)
|
|
2797
|
+
const getSharedAudioContext = () => {
|
|
2798
|
+
if (!g_sharedAudioContext) {
|
|
2799
|
+
g_sharedAudioContext = new AudioContext();
|
|
2800
|
+
}
|
|
2801
|
+
// タブのバックグラウンド化等でsuspendedになることがあるため念のためresume
|
|
2802
|
+
if (g_sharedAudioContext.state === `suspended`) {
|
|
2803
|
+
g_sharedAudioContext.resume();
|
|
2804
|
+
}
|
|
2805
|
+
return g_sharedAudioContext;
|
|
2806
|
+
};
|
|
2807
|
+
|
|
2808
|
+
/**
|
|
2809
|
+
* AudioContextのウォームアップ
|
|
2810
|
+
* - 生成直後・resume直後のAudioContextは出力デバイスの起動待ちのため、
|
|
2811
|
+
* しばらく currentTime が進まない(環境により数十〜数百ms)
|
|
2812
|
+
* - この状態で音源をスケジュールすると、起動に要した時間がそのまま
|
|
2813
|
+
* 音源と譜面のずれになるため、無音を1回鳴らして時計が動き出すまで待つ
|
|
2814
|
+
* @returns {Promise<void>}
|
|
2815
|
+
*/
|
|
2816
|
+
const warmUpAudioContext = async () => {
|
|
2817
|
+
const ctx = getSharedAudioContext();
|
|
2818
|
+
if (ctx.state !== `running`) {
|
|
2819
|
+
await ctx.resume().catch(() => { });
|
|
2820
|
+
}
|
|
2821
|
+
if (ctx.state !== `running`) {
|
|
2822
|
+
return; // ジェスチャー未取得等でresumeできない場合は何もしない
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
// 無音を1サンプルだけ鳴らして出力デバイスを起動させる
|
|
2826
|
+
const source = ctx.createBufferSource();
|
|
2827
|
+
source.buffer = ctx.createBuffer(1, 1, ctx.sampleRate);
|
|
2828
|
+
source.connect(ctx.destination);
|
|
2829
|
+
source.start();
|
|
2830
|
+
|
|
2831
|
+
// currentTimeが実際に進み始めるまで待機(最大500ms)
|
|
2832
|
+
const baseTime = ctx.currentTime;
|
|
2833
|
+
const limitTime = performance.now() + 500;
|
|
2834
|
+
while (ctx.currentTime === baseTime && performance.now() < limitTime) {
|
|
2835
|
+
await new Promise(resolve => g_timerHandler.setTimeout(resolve, 10));
|
|
2836
|
+
}
|
|
2837
|
+
};
|